//
you're reading...
.NET/COM/ActiveX Interop, COM, COM Structures

Interoping COM Structures.

1. Introduction.

1.1 COM structures, or User-Defined Types (UDTs) are very useful constructs. Their interoperability in managed code, however, is not perfect and there are situations in which their use is not possible.

1.2 This blog examines various scenarios in which UDTs are used between COM and managed code (specifically C#). We will also analyze the success or failure of these scenarios.

2. Test UDT, Interface and C# Client Code.

2.1 Throughout this blog, we shall use the following UDT for testing and analysis :

typedef [uuid(E3FC6839-20D7-45de-B6C3-F512A11BB4CC), version(1.0)]
struct TestStructure01
{
  int int_value;
  BSTR bstr_value;
} TestStructure01;

2.2 This structure is contained within an IDL “library” statement :

[
  uuid(78AC6426-49BA-4822-BA2F-917E820C85E8),
  version(1.0),
  helpstring("TestCOMServer01 1.0 Type Library")
]
library TestCOMServer01Lib
{
  importlib("stdole2.tlb");

  typedef [uuid(E3FC6839-20D7-45de-B6C3-F512A11BB4CC), version(1.0)]
  struct TestStructure01
  {
    int	int_value;
    BSTR bstr_value;
  } TestStructure01;	

  ...
  ...
  ...
};

If it is not fully contained within the “library” statement, it must at least be referenced from inside it. This is important because otherwise the structure will not be contained within the type library generated from the IDL. This will result in TestStructure01 not being recognized by the client C# code.

2.3 We will also build on a test interface ITestCOMClass01 and implementation CTestCOMClass01 which will be used throughout this blog.

[
  object,
  uuid(65CB3D85-D8C7-46ED-BE05-A61B9CA97CF7),
  dual,
  nonextensible,
  helpstring("ITestCOMClass01 Interface"),
  pointer_default(unique)
]
interface ITestCOMClass01 : IDispatch
{
  ...
  ...
  ...
};

This interface and implementation will be embellished with methods that serve as test codes for various usage scenarios of TestStructure01. A managed console client application (written in C#) will be used throughout this blog for testing.

2.4 The COM code listed above will be created using ATL and will be compiled into TestCOMServer01.dll. The managed client application is created using a C# project which compiles into CSTestClient01.exe.

3. Scenario 1 : Passing a UDT to COM via an [in] VARIANT.

3.1 For this, we define the following method for ITestCOMClass01 :

interface ITestCOMClass01 : IDispatch
{
  [id(1), helpstring("method TestMethod01")] HRESULT TestMethod01([in] VARIANT var);
};

3.2 The following is a sample implementation :

STDMETHODIMP CTestCOMClass01::TestMethod01(VARIANT var)
{
	// TODO: Add your implementation code here

	// Check to see if the incoming VARIANT is a VT_RECORD
	// VARIANT.
	if (V_VT(&var) != VT_RECORD)
	{
		// If not exit immediately.
		return E_FAIL;
	}
	// Obtain the IRecordInfo object associated with
	// the UDT.
	IRecordInfoPtr spIRecordInfo = V_RECORDINFO(&var);

	// Check the GUID of the IRecordInfo object.
	// This must be "E3FC6839-20D7-45de-B6C3-F512A11BB4CC".
	GUID guid;
	spIRecordInfo -> GetGuid(&guid);
	// If GUIDs do not match, exit.
	if (guid != GUID_TestStructure01)
	{
		return E_FAIL;
	}

	// All have gone well so far. We now display the field values of
	// TestStructure01.
	TestStructure01* ptest_structure_01 = (TestStructure01*)V_RECORD(&var);

	if (ptest_structure_01)
	{
	  printf ("TestStructure01.int_value  : [%d].\r\n", ptest_structure_01 -> int_value);
	  printf ("TestStructure01.bstr_value : [%S].\r\n", ptest_structure_01 -> bstr_value);
	}

	// "var" is an "in" parameter and so it remains owned by the calling
	// code. This method must treat it as "read-only" and must not free
	// its memory.

	return S_OK;
}

3.3 The following is a sample C# client code that calls TestMethod01() :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TestCOMServer01Lib;

namespace CSTestClient01
{
    class Program
    {
        static void CallTestMethod01(TestCOMClass01Class test_com_class_01)
        {
            // Define a TestStructure01 structure.
            TestStructure01 test_structure_01 = new TestStructure01();

            // Fill it with values.
            test_structure_01.int_value = 100;
            test_structure_01.bstr_value = "CallTestMethod01";

            // Convert it into an object type
            // which is the managed version of
            // a COM VARIANT.
            object obj = (object)test_structure_01;

            // Call the TestMethod01() method.
            test_com_class_01.TestMethod01(obj);
        }

        static void Main(string[] args)
        {
            TestCOMClass01Class test_com_class_01 = new TestCOMClass01Class();

            CallTestMethod01(test_com_class_01);
        }
    }
}

3.4 The result : successful. The “TestStructure01” structure is successfully inserted into a managed “object” which is then passed an an “in” parameter to “TestMethod01()”.

3.5 The following will be the output displayed by the program :

TestStructure01.int_value  : [100].
TestStructure01.bstr_value : [CallTestMethod01].

4. Scenario 2 : Receiving a UDT from COM via an [out] VARIANT.

4.1 For this, we define the following method for ITestCOMClass01 :

[id(2), helpstring("method TestMethod02")] HRESULT TestMethod02([out] VARIANT* pvarReceiver);

4.2 The following is a sample implementation :

STDMETHODIMP CTestCOMClass01::TestMethod02(VARIANT* pvarReceiver)
{
  // TODO: Add your implementation code here

  // Allocate a TestStructure01 in memory.
  TestStructure01* pTestStructure01 = (TestStructure01*)::CoTaskMemAlloc(sizeof(TestStructure01));
  // Assign values to the structure.
  pTestStructure01 -> int_value = 100;
  pTestStructure01 -> bstr_value = ::SysAllocString(L"CTestCOMClass01::TestMethod02()");

  // Initialize the VARIANT receiver.
  VariantInit(pvarReceiver);
  V_VT(pvarReceiver) = VT_RECORD;
  V_RECORD(pvarReceiver) = pTestStructure01;

  // Get hold of the IRecordInfo object associated
  // with TestStructure01.
  TCHAR szModuleFileName[_MAX_PATH];

  GetModuleFileName
  (
    GetModuleHandle(L"TestCOMServer01.dll"),
    szModuleFileName,
    _MAX_PATH
  );

  IRecordInfoPtr spIRecordInfo = NULL;

  GetIRecordType
  (
    (LPCTSTR)szModuleFileName,
    GUID_TestStructure01,
    &spIRecordInfo
  );

  // Insert a pointer to the IRecordInfo object
  // into the VARIANT receiver.
  if (spIRecordInfo)
  {
    V_RECORDINFO(pvarReceiver) = (IRecordInfo*)spIRecordInfo;
    V_RECORDINFO(pvarReceiver) -> AddRef();
  }	

  return S_OK;
}

4.3 The following is a sample C# client code that calls TestMethod01() :

static void CallTestMethod02(TestCOMClass01Class test_com_class_01)
{
  object obj;

  test_com_class_01.TestMethod02(out obj);

  TestStructure01 test_structure_01 = (TestStructure01)obj;
}

4.4 The result : unsuccessful. Control was able to reach the TestMethod02() C++ method. However, when the method returned and time came for the interop marshaler to convert the contents of the returned “out” VARIANT into a managed object, an error message that read :

An unhandled exception of type ‘System.ArgumentException’ occurred in CSTestClient01.exe

Additional information: The specified record cannot be mapped to a managed value class.

is displayed. The returned VT_RECORD VARIANT could not be mapped into a managed “TestStructure01” structure.

4.5 This is very strange indeed. I personally found it intriguing that “TestStructure01” could be successfully marshaled from a managed object into an unmanaged VT_RECORD VARIANT complete with an IRecordInfo object (section 3) but the reverse, being marshaled from a VT_RECORD VARIANT to a managed object, was not possible.

4.6 Exchanging structures between components using the VARIANT is a very common and useful practice. I shall research into ways to overcome this problem and will update in a future blog.

5. Scenario 3 : Exchanging a UDT with COM via a Structure Directly.

5.1 For this, we define the following method for ITestCOMClass01 :

[id(3), helpstring("method TestMethod03")] HRESULT TestMethod03([in,out] TestStructure01* pTestStructure01);

I have defined the TestStructure01 pointer parameter as an [in, out] parameter so that, once shown to be successful, we know that the basic technique will work where the parameter is declared as [in] only and where it is declared as [out] only.

Note that for [in] parameters, the caller side allocates memory for the parameter (if this is required) and it is the caller that frees this memory (if necessary).

For [out] parameters, it is the callee that allocates memory for the parameter (if this is required) which is to be returned and it is the caller that frees this memory (if this is required).

For [in, out] parameters, it is the caller that allocates memory for the parameter (if this is required). The callee is then at liberty to free this memory and re-allocate new memory for the parameter to be returned. Eventually, however, it is the caller that must free this memory.

5.2 The following is a sample implementation :

STDMETHODIMP CTestCOMClass01::TestMethod03(TestStructure01* pTestStructure01)
{
  // TODO: Add your implementation code here
  if (pTestStructure01 == NULL)
  {
    return E_POINTER;
  }

  // Assign values to the structure.
  (pTestStructure01 -> int_value) += 1;  // Increment "int_value" field by 1.
  // First free the existing string.
  ::SysFreeString(pTestStructure01 -> bstr_value);
  // Then reassign "bstr_value" field.
  pTestStructure01 -> bstr_value = ::SysAllocString(L"CTestCOMClass01::TestMethod03()");

  return S_OK;
}

This sample implementation will increment the “int_value” field by 1 and re-assign the “bstr_value” field. The method is also at liberty to free the entire TestStructure01 structure pointed to by pTestStructure01 and then re-allocating an entire new structure.

5.3 The following is a sample C# client code that calls TestMethod03() :

static void CallTestMethod03(TestCOMClass01Class test_com_class_01)
{
  // Define a TestStructure01 structure.
  TestStructure01 test_structure_01 = new TestStructure01();

  // Fill it with values.
  test_structure_01.int_value = 100;
  test_structure_01.bstr_value = "CallTestMethod03";

  Console.WriteLine("Data before call to TestMethod03() :");
  Console.WriteLine("test_structure_01.int_value  : [{0:D}]", test_structure_01.int_value);
  Console.WriteLine("test_structure_01.bstr_value : [{0:S}]", test_structure_01.bstr_value);

  // Call the TestMethod03() method.
  test_com_class_01.TestMethod03(ref test_structure_01);

  Console.WriteLine("Data after call to TestMethod03() :");
  Console.WriteLine("test_structure_01.int_value  : [{0:D}]", test_structure_01.int_value);
  Console.WriteLine("test_structure_01.bstr_value : [{0:S}]", test_structure_01.bstr_value);
}

5.4 The result : successful. The “TestStructure01” structure is successfully passed to “TestMethod03()” as a reference parameter. The output of the program is displayed below :

Data before call to TestMethod03() :
test_structure_01.int_value  : [100]
test_structure_01.bstr_value : [CallTestMethod03]
Data after call to TestMethod03() :
test_structure_01.int_value  : [101]
test_structure_01.bstr_value : [CTestCOMClass01::TestMethod03()]

5.5 Listed below are pertinent points about this particular call scenario :

  • Notice that before CallTestMethod03() called TestCOMClass01Class.TestMethod03(), a TestStructure01 structure was allocated and its field values initialized.
  • This is because the TestStructure01 parameter is both an “in” and an “out” parameter. Being an “in” parameter entails that TestStructure01 be first initialized before it is passed to the target method.
  • Now when the target method (i.e. TestMethod03()) is called, an unmanaged TestStructure01 structure is allocated by the interop marshaler (using Marshal.AllocCoTaskMem()) and then values for its fields are filled.
  • The interop marshaler obtains information about the unmanaged type of each field from the interop assembly which is automatically generated by the IDE when TestCOMServer01.dll was imported. Using ILDASM.EXE, we can examine how the TestStructure01 is imported :
.class public sequential ansi sealed beforefieldinit TestCOMServer01Lib.TestStructure01
       extends [mscorlib]System.ValueType
{
  .pack 4
  .size 0
  .custom instance void [mscorlib]System.Runtime.InteropServices.GuidAttribute::.ctor(string)
   = ( 01 00 24 45 33 46 43 36 38 33 39 2D 32 30 44 37   // ..$E3FC6839-20D7
       2D 34 35 44 45 2D 42 36 43 33 2D 46 35 31 32 41   // -45DE-B6C3-F512A
       31 31 42 42 34 43 43 00 00 )                      // 11BB4CC..
  .field public int32 int_value
  .field public  marshal( bstr) string bstr_value
} // end of class TestCOMServer01Lib.TestStructure01
  • From the above listing, we can see that the “int_value” field is marshaled as int32 (a 32-bit integral value). The “bstr_value” field is marshaled as a BSTR. The interop marshaler uses this information to perform the transformation of the managed TestStructure01 into its unmanaged counterpart.
  • Inside the TestMethod03() method, both the “int_value” and the “bstr_value” fields are modified. The “bstr_value” field, being a BSTR, must first be freed using the ::SysFreeString() API. Thereafter, a new BSTR is allocated using ::SysAllocString().
  • As mentioned elsewhere in this blog, the TestMethod03() code may freely de-allocate the entire TestStructure01 and then re-allocate a new one in its place. This is perfectly fine as long as the “pTestStructure01” parameter is made to point to the newly allocated structure.
  • If a new structure is indeed to be allocated, it must be allocated using ::CoTaskMemAlloc(). The old one must be freed using ::CoTaskMemFree().
  • When TestMethod03() returns, the unmanaged TestStructure01 is converted into its managed counterpart. Then the interop marshaler will free the unmanaged TestStructure01 using Marshal.FreeCoTaskMem().

6. In Conclusion.

6.1 I certainly hope the reader has benefitted from this blog. It is still unclear to me why it is not possible to return a UDT to managed code via an object whereas it certainly was possible to pass a UDT to unmanaged code via a VARIANT from an object.

6.2 I shall research into ways to workaround this problem. Will update once I have more information. Stay tuned.

 

Advertisements

About Lim Bio Liong

I've been in software development for nearly 20 years specializing in C , COM and C#. It's truly an exicting time we live in, with so much resources at our disposal to gain and share knowledge. I hope my blog will serve a small part in this global knowledge sharing network. For many years now I've been deeply involved with C development work. However since circa 2010, my current work has required me to use more and more on C# with a particular focus on COM interop. I've also written several articles for CodeProject. However, in recent years I've concentrated my time more on helping others in the MSDN forums. Please feel free to leave a comment whenever you have any constructive criticism over any of my blog posts.

Discussion

3 thoughts on “Interoping COM Structures.

  1. This is a great article. How do you pass array of structures from managed to unmanaged? Appreciate your help.

    Posted by Reddy | September 18, 2012, 12:43 am

Trackbacks/Pingbacks

  1. Pingback: Using VARIANTs in Managed Code Part 2 « limbioliong - September 25, 2011

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: