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

Marshaling a SAFEARRAY of Managed Structures by COM Interop Part 2

1. Introduction.

1.1 In part 1 of this series of blogs, we studied how to pass a SAFEARRAY of UDTs (which originate from managed code) to an unmanaged client.

1.2 In this part 2, we shall be looking at how to pass a SAFEARRAY of UDTs from an unmanaged COM client application to a managed COM server.

2. The Managed Structure, COM Visible Interface and Managed Implementation.

2.1 We shall be using the same managed structure that we have built up since part 1 :

[ComVisible(true)]
[Guid("B4A16864-42FF-48ea-973B-E0BE5922719E")]
public struct TestStruct
{
  [MarshalAs(UnmanagedType.I4)]
  public Int32 m_integer;
  [MarshalAs(UnmanagedType.R8)]
  public double m_double;
  [MarshalAs(UnmanagedType.BStr)]
  public string m_string;
}

2.2 The above managed structure produced the following IDL declaration :

typedef
[
  uuid(B4A16864-42FF-48ea-973B-E0BE5922719E),
  version(1.0)
]
struct TestStruct
{
  long m_integer;
  double m_double;
  BSTR m_string;
} TestStruct;

2.3 We shall also be using the same COM-visible managed interface that we saw in part 1 :

[ComVisible(true)]
[Guid("FE37FC87-1E1B-4262-B720-1E6503B3E964")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ITestInterface
{
  void GetTestStructArray([Out] out TestStruct[] test_struct_array);
  void SetTestStructArray([In] TestStruct[] test_struct_array);
  void ReferenceTestStructArray([In][Out] ref TestStruct[] test_struct_array);
}

2.4 The above C# interface produced the following COM interface in IDL :

[
  odl,
  uuid(FE37FC87-1E1B-4262-B720-1E6503B3E964),
  version(1.0),
  oleautomation
]
interface ITestInterface : IUnknown
{
  HRESULT _stdcall GetTestStructArray([out] SAFEARRAY(TestStruct)* test_struct_array);
  HRESULT _stdcall SetTestStructArray([in] SAFEARRAY(TestStruct) test_struct_array);
  HRESULT _stdcall ReferenceTestStructArray([in, out] SAFEARRAY(TestStruct)* test_struct_array);
}

2.5 In addition, the managed implementation of ITestInterface, i.e. TestClass, will be used again.

3. Receiving a SAFEARRAY of TestStruct via COM Interop.

3.1 With reference to ITestInterface, the relevant COM method to use is :

void SetTestStructArray([In] TestStruct[] test_struct_array);

3.2 The following TestClass method is an implementation :

public void SetTestStructArray([In] TestStruct[] test_struct_array)
{
  for (int i = 0; i < test_struct_array.Length; i++)
  {
    Console.WriteLine("test_struct_array[{0:D}].m_integer : {1:D}", i, test_struct_array[i].m_integer);
    Console.WriteLine("test_struct_array[{0:D}].m_double  : {1:F}", i, test_struct_array[i].m_double);
    Console.WriteLine("test_struct_array[{0:D}].m_string  : {1:S}", i, test_struct_array[i].m_string);
  }
}

The basic idea is for an unmanaged C++ client to generate a SAFEARRAY of TestStruct structures and then call this method from an instance of the “TestClass” class in order to display the members of each structure contained in the array.

The following describes what happens at a low-level :

  • The incoming unmanaged SAFEARRAY is used to create managed array “test_struct_array”.
  • Since the SAFEARRAY type is well documented, the interop marshaler is fully able to access the array data using either SAFEARRAY APIs or by using the fields of the SAFEARRAY structure directly.
  • The managed array creation process is done with various methods of the Marshal class (one of them is very likely Marshal.PtrToStructure()).

3.3 We shall use the same sample C++ code that creates an instance of the C# TestClass class as was listed in part 1 :

#include "stdafx.h"
#import "..\TestCSCOMServer01\bin\Debug\TestCSCOMServer01.tlb" raw_interface_only no_implementation
using namespace TestCSCOMServer01;

void SetTestStructArray(/*[in]*/ ITestInterfacePtr& spITestInterface);

int _tmain(int argc, _TCHAR* argv[])
{
	::CoInitialize(NULL);

	if (1)
	{
		ITestInterfacePtr spITestInterface = NULL;

		spITestInterface.CreateInstance(__uuidof(TestClass));

		SetTestStructArray(spITestInterface);
	}

	::CoUninitialize();

	return 0;
}

The following is a sample C++ function which creates a SAFEARRAY of TestStruct structures and then passes it to the SetTestStructArray() method :

void SetTestStructArray(/*[in]*/ ITestInterfacePtr& spITestInterface)
{
	IRecordInfoPtr spIRecordInfo = NULL;
	HRESULT hr;

	// Get the IRecordInfo object associated with TestStruct.
	// This is obtainable from the registered type library
	// of the Managed COM Server.
	hr = GetRecordInfoFromGuids
	(
		__uuidof(__TestCSCOMServer01),  // The GUID of the type library containing the UDT. Get this from the .TLH file.
		1,  // The major version number of the type library of the UDT.
		0,  //  The minor version number of the type library of the UDT.
		0,  // The locale ID of the caller.
		__uuidof(TestStruct),  // The GUID of the typeinfo that describes the UDT. Get this from the .TLH file.
		&spIRecordInfo
	);

	if (spIRecordInfo == NULL)
	{
		// If we are unable to obtain the IRecordInfo object
		// return immediately.
		return;
	}

	SAFEARRAYBOUND	safearraybound;

	memset(&safearraybound, 0, sizeof(safearraybound));
	safearraybound.cElements = 5; // The SAFEARRAY is to contain 5 elements.
	safearraybound.lLbound = 0; // The lowerbound is 0.

	SAFEARRAY* pSafeArrayOfTestStruct = SafeArrayCreateEx
	(
		VT_RECORD, // Create a SAFEARRAY of VT_RECORD types.
		1, // this SAFEARRAY is 1-dimensional.
		&safearraybound, // The SAFEARRAY has lower bound 0 and will contain 5 elements.
		(PVOID)spIRecordInfo
	);

	if (pSafeArrayOfTestStruct)
	{
		// Insert structures into the SAFEARRAY.
		for (int i = 0; i < 5; i++)
                  {
                    long rgIndices[1];
                    char szString[256];
                    TestStruct value;

                    memset(&value, 0, sizeof(value));
                    value.m_integer = i;
                    value.m_double = 0.123 + (double)i;
                    sprintf(szString, "Hello World [%d]", i);
                    value.m_string = _bstr_t(szString).copy();

                    // SafeArrayPutElement() will insert
                    // a COPY of the structure currently
                    // being inserted.
                    //
                    // Hence it is important that when "value"
                    // is no longer required, its members be cleared.
                    // This is especially so for members which
                    // point to allocated memory (e.g. BSTRs).
                    rgIndices[0] = i;
                    SafeArrayPutElement
                    (
                      pSafeArrayOfTestStruct,
                      rgIndices,
                      (void*)&value
                    ); 			

                    // We clear the members of "value"
                    // which includes the "m_string" BSTR member
                    // for which it will call SysFreeString().
                    spIRecordInfo -> RecordClear((PVOID)&value);
		}
	}

	// Call the SetTestStructArray() interface method.
	// The pointer to the SAFEARRAY is passed as an
	// "in" (read-only) parameter.
	spITestInterface -> SetTestStructArray(pSafeArrayOfTestStruct);
	// This client application is still the owner of
	// the SAFEARRAY and so the onus is on this client
	// app to free the memory via SafeArrayDestroy().
         ::SafeArrayDestroy(pSafeArrayOfTestStruct);
	pSafeArrayOfTestStruct = NULL;
}

Note the following pertinent points about this function :

  • In order to create a SAFEARRAY of TestStruct UDTs, the C++ client needs to acquire the IRecordInfo object associated with the UDT. This is acquired via the GetRecordInfoFromGuids() API.
  • The GetRecordInfoFromGuids() API requires the GUID of the type library which contains the UDT as well as the GUID of the typeinfo that describes the UDT. Both GUIDs can be read from the .TLH file which is generated by the Visual C++ compiler when we #import the type library of the managed COM server.
  • To create a SAFEARRAY of UDTs, we may use the SafeArrayCreateEx() API. This API requires a pointer to the IRecordInfo object associated with the UDT.
  • Another API that may be used to set an IRecordInfo pointer to a SAFEARRAY is SafeArraySetRecordInfo(). In fact, the Interop Marshaler uses this API when it creates a SAFEARRAY based on an array of managed structs.
  • To insert UDTs into a SAFEARRAY, we used the SafeArrayPutElement() API. Note that SafeArrayPutElement() will insert a COPY of the structure currently being inserted. This is known as copy-semantics.
  • Hence it is important that when the inserted UDT is no longer required, its members be cleared. This is especially so for members which point to allocated memory (e.g. BSTRs).
  • To do this, either manually free members which are associated with allocated memory, or call the IRecordInfo::RecordClear() method.
  • Note that when the SetTestStructArray() method is called, the pointer to the SAFEARRAY is passed as an “in” (read-only) parameter. The method is not expected to make any modifications to the SAFEARRAY.
  • This client application is still the owner of the SAFEARRAY and so the onus is on this client app to free the memory via SafeArrayDestroy().

 3.4 When the C++ client code is run, the following output will be generated :

test_struct_array[0].m_integer : 0
test_struct_array[0].m_double  : 0.12
test_struct_array[0].m_string  : Hello World [0]
test_struct_array[1].m_integer : 1
test_struct_array[1].m_double  : 1.12
test_struct_array[1].m_string  : Hello World [1]
test_struct_array[2].m_integer : 2
test_struct_array[2].m_double  : 2.12
test_struct_array[2].m_string  : Hello World [2]
test_struct_array[3].m_integer : 3
test_struct_array[3].m_double  : 3.12
test_struct_array[3].m_string  : Hello World [3]
test_struct_array[4].m_integer : 4
test_struct_array[4].m_double  : 4.12
test_struct_array[4].m_string  : Hello World [4]

4. In Conclusion.

4.1 In this part 2, we studied how to generate a SAFEARRAY of UDTs from unmanaged code. This is possible because the managed COM server has been registered previously and a type library is available.

4.2 It is the type library that contains information on a UDT and it is this information that allows for the creation of an associated IRecordInfo object.

4.3 In the next part, we shall study how a SAFEARRAY of UDTs may be passed 2 ways (as an “in” and “out” or by-reference parameter).

About these ads

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

2 thoughts on “Marshaling a SAFEARRAY of Managed Structures by COM Interop Part 2

  1. Thank you very much for the clear, detailed and thorough explanation and examples. This is easily the best I’ve come across in my research that clearly explains everything. You’ve saved me a lot of time and I’m grateful!

    Posted by xeilok | September 9, 2014, 2:20 am

Trackbacks/Pingbacks

  1. Pingback: Marshaling a SAFEARRAY of Managed Structures by COM Interop | Jisku.com - Developers Network - September 21, 2012

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

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: