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

Marshaling a SAFEARRAY of Managed Structures by COM Interop Part 3

1. Introduction.

1.1 In this final part of the series of blogs, we will look at the most interesting aspect of marshaling a SAFEARRAY of managed structures by COM interop : passing a SAFEARRAY of UDTs as an “in” and “out” (reference) parameter.

1.2 A SAFEARRAY of UDTs marshaled in this way may be modified by the COM server and this is what I aim to demonstrate in this final installment.

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

2.1 We shall once again 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 This 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. Passing a SAFEARRAY of TestStruct Parameter by Reference via COM Interop.

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

void ReferenceTestStructArray([In][Out] ref TestStruct[] test_struct_array);

3.2 The following TestClass method is an implementation :

public void ReferenceTestStructArray([In][Out] ref TestStruct[] test_struct_array)
{
  int i;
  // First change the "m_string" member of each existing element.
  for (i = 0; i < test_struct_array.Length; i++)
  {
    test_struct_array[i].m_integer = i;
    test_struct_array[i].m_double = 0.123 + (double)i;
    test_struct_array[i].m_string = string.Format("Hello Unmanaged World {0:D}", i);
  }

  int iOriginalSize = test_struct_array.Length;
  // Increase the number of elements in the array by 2 more.
  Array.Resize(ref test_struct_array, iOriginalSize + 2);
  // Assign values into each element.
  for (i = iOriginalSize; i < test_struct_array.Length; i++)
  {
    test_struct_array[i].m_integer = i;
    test_struct_array[i].m_double = 0.123 + (double)i;
    test_struct_array[i].m_string = string.Format("Hello Unmanaged World {0:D}", i);
  }
}

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. This method will modify the “m_string” member of each TestStruct element. It will also add 2 new elements into the array for dramatic effect. This addition is done using the Marshal.Resize() static method.

This method is really a combination of the GetTestStructArray() and SetTestStructArray() methods that we met earlier in parts 1 and 2. Hence at low-level, we should expect a combination of the 2 activities that result from passing a SAFEARRAY as an “in” and “out” parameter.

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()).
  • All activities of the managed array do not affect the in-bound SAFEARRAY parameter.
  • When the method returns, the original in-bound SAFEARRAY parameter is destroyed using SafeArrayDestroy().
  • A new SAFEARRAY is then created using SafeArrayAllocDescriptorEx().
  • Since the contents of the SAFEARRAY are UDTs, the interop marshaler will create an IRecordInfo object that is to be associated with the TestStruct structure in unmanaged code. This is done with the GetRecordInfoFromTypeInfo() API which is likely called internally by the Marshal.GetITypeInfoForType() method.
  • The interop marshaler then inserts the IRecordInfo object into the SAFEARRAY via the SafeArraySetRecordInfo() API.
  • Memory for the array of UDTs is then allocated using SafeArrayAllocData().
  • After that, fresh UDTs are generated from the most current data of the managed test_struct_array array and inserted into this array data memory.
  • The SAFEARRAY is then returned to the client.
  • Note that because a new array will be internally allocated (by SafeArrayAllocDescriptorEx()), it cannot be guarenteed that the original SAFEARRAY which was allocated by the client before the ReferenceTestStructArray() call be re-used. Indeed, before the new SAFEARRAY is created, the original one is destroyed by the SafeArrayDestroy() API.

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 ReferenceTestStructArray(/*[in]*/ ITestInterfacePtr& spITestInterface);

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

	if (1)
	{
		ITestInterfacePtr spITestInterface = NULL;

		spITestInterface.CreateInstance(__uuidof(TestClass));

		ReferenceTestStructArray(spITestInterface);
	}

	::CoUninitialize();

	return 0;
}

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

void ReferenceTestStructArray(/*[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 ReferenceTestStructArray() interface method.
	// A pointer to a pointer to the SAFEARRAY is passed
	// as an "in" and "out" (reference) parameter.
	spITestInterface -> ReferenceTestStructArray(&pSafeArrayOfTestStruct);

	// After ReferenceTestStructArray() has been called,
	// the returned SAFEARRAY may be changed in size.
	// Individual elements may also have been changed.
	// We now display each element of the array.

	// Obtain bounds information of the SAFEARRAY.
	long lLbound = 0;
	long lUbound = 0;

	SafeArrayGetLBound(pSafeArrayOfTestStruct, 1, &lLbound);
	SafeArrayGetUBound(pSafeArrayOfTestStruct, 1, &lUbound);
	long lDimSize = lUbound - lLbound + 1;		

	// Obtain a copy of each structure contained
	// in the SAFEARRAY and display the values of
         // its members.
	for (int i = 0; i < lDimSize; i++)
         {
           long rgIndices[1];
           TestStruct value;

           // Note that we must set all fields
           // inside value to zero.
           // This is important because
           // SafeArrayGetElement() will first
           // clear all members of value.
           // For the "m_string" BSTR member,
           // it will call the ::SysFreeString()
           // API. Now if "m_string" contains
           // random data, it will result in
           // a crash. If it is NULL, it is OK.
           memset(&value, 0, sizeof(value));
           // SafeArrayGetElement() will return
           // a copy of the relevant element in
           // the structure. 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;
           SafeArrayGetElement
           (
             pSafeArrayOfTestStruct,
             rgIndices,
             (void FAR*)&value
           );
           printf ("TestStruct[%d].m_integer : [%d]\r\n", i, value.m_integer);
           printf ("TestStruct[%d].m_double  : [%f]\r\n", i, value.m_double);
           printf ("TestStruct[%d].m_string  : [%S]\r\n", i, value.m_string);
           // We clear the members of "value"
           // which includes the "m_string" BSTR member.
           spIRecordInfo -> RecordClear((PVOID)&value);
	}

	// 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;
}

3.4 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 ReferenceTestStructArray() method is called, the address of the pointer to the SAFEARRAY is passed as an “in” and “out” (i.e. reference) parameter. The method is at liberty to free this SAFEARRAY and then re-allocate a new one to return to the client.
  • The method, however, is not the owner of the memory of the original in-bound SAFEARRAY even though it may re-allocate it. The client application is still the owner of the SAFEARRAY and so the onus is on this client app to finally free the memory via SafeArrayDestroy() after the ReferenceTestStructArray() method call.

3.5 Now before the ReferenceTestStructArray() method call, the contents of the SAFEARRAY is as follows :

pSafeArrayOfTestStruct[0].m_integer : [0]
pSafeArrayOfTestStruct[0].m_double  : [0.123000]
pSafeArrayOfTestStruct[0].m_string  : [Hello World 0]
pSafeArrayOfTestStruct[1].m_integer : [1]
pSafeArrayOfTestStruct[1].m_double  : [1.123000]
pSafeArrayOfTestStruct[1].m_string  : [Hello World 1]
pSafeArrayOfTestStruct[2].m_integer : [2]
pSafeArrayOfTestStruct[2].m_double  : [2.123000]
pSafeArrayOfTestStruct[2].m_string  : [Hello World 2]
pSafeArrayOfTestStruct[3].m_integer : [3]
pSafeArrayOfTestStruct[3].m_double  : [3.123000]
pSafeArrayOfTestStruct[3].m_string  : [Hello World 3]
pSafeArrayOfTestStruct[4].m_integer : [4]
pSafeArrayOfTestStruct[4].m_double  : [4.123000]
pSafeArrayOfTestStruct[4].m_string  : [Hello World 4]

3.6 After the method call, the following will be the contents of the SAFEARRAY :

pSafeArrayOfTestStruct[0].m_integer : [0]
pSafeArrayOfTestStruct[0].m_double  : [0.123000]
pSafeArrayOfTestStruct[0].m_string  : [Hello Unmanaged World 0]
pSafeArrayOfTestStruct[1].m_integer : [1]
pSafeArrayOfTestStruct[1].m_double  : [1.123000]
pSafeArrayOfTestStruct[1].m_string  : [Hello Unmanaged World 1]
pSafeArrayOfTestStruct[2].m_integer : [2]
pSafeArrayOfTestStruct[2].m_double  : [2.123000]
pSafeArrayOfTestStruct[2].m_string  : [Hello Unmanaged World 2]
pSafeArrayOfTestStruct[3].m_integer : [3]
pSafeArrayOfTestStruct[3].m_double  : [3.123000]
pSafeArrayOfTestStruct[3].m_string  : [Hello Unmanaged World 3]
pSafeArrayOfTestStruct[4].m_integer : [4]
pSafeArrayOfTestStruct[4].m_double  : [4.123000]
pSafeArrayOfTestStruct[4].m_string  : [Hello Unmanaged World 4]
pSafeArrayOfTestStruct[5].m_integer : [5] 
pSafeArrayOfTestStruct[5].m_double : [5.123000] 
pSafeArrayOfTestStruct[5].m_string : [Hello Unmanaged World 5] 
pSafeArrayOfTestStruct[6].m_integer : [6] 
pSafeArrayOfTestStruct[6].m_double : [6.123000] 
pSafeArrayOfTestStruct[6].m_string : [Hello Unmanaged World 6]

Notice that the “m_string” members of the original structure elements have been changed. In addition, new array elements have been added.

4. In Conclusion.

4.1 We have thus completed our in-depth study of how to exchange SAFEARRAYs of UDTs between a managed COM server and an unmanaged client.

4.2 In the context of COM, we have the advantage of the presence of an associated type library which stores type information for the UDT.

4.3 In another series of blogs I shall return to discuss the exchange of SAFEARRAYs of UDTs which originate from managed code which are not exposed as COM servers.

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

No comments yet.

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: