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

Using a VT_RECORD VARIANT in Managed Code Part 1

1. Introduction.

1.1 I have previously expounded in-depth on the usage of VARIANTs in managed code (please see the Using VARIANTs in Managed Code series of articles).

1.2 I have decided to dedicate a special series on VARIANTs that contain User-Defined Types (UDTs) because these deserve special attention.

1.3 VARIANTs that contain UDTs (also known as a VT_RECORD VARIANT), can be passed to unmanaged code rather easily. However, they cannot be returned from unmanaged code in general.

1.4 In this new series of blogs I will demonstrate how to use such VARIANTs in managed code and how to exchange these with unmanaged code.

1.5 In parts 1 and 2, I setup the basic code constructs that expedite the passing of a VT_RECORD VARIANT one-way from managed code to unmanaged code. In later parts, I shall expound on the way to receive a VT_RECORD VARIANT from unmanaged code.

1.6 Throughout the entire series of blogs, the unmanaged code that I shall be using will be written in C++.

2. The Managed Structure Exposed as a COM UDT.

2.1 Throughout most parts of this series of blogs, I shall be using the following managed structure which will be exposed as a COM UDT :

// ManagedUDT must be exposed as a public structure.
// Hence it must be declared at the namespace scope
// or declared as part of a public class.
[ComVisible(true)]
[Guid("BBFE1092-A90C-4b6d-B279-CBA28B9EDDFA")]
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ManagedUDT
{
    [MarshalAs(UnmanagedType.BStr)]
    public string m_str01;
    public Int32 m_int01;
}

Note the following pertinent points about the managed structure above in relation to being exposed as a COM UDT :

  • It must be a public structure which means that it must be declared at the namespace scope or declared as part of a public class.
  • It must be attributed with the ComVisibleAttribute (with an argument of true).
  • It must be given a GUID (via the GuidAttribute).
  • The StructLayoutAttribute is nenecssary whenever a managed structure is to be exposed to unmanaged code whether or not it is to be exposed as a COM UDT.

2.2 To be usable in unmanaged code, this structure must be defined in unmanaged code. The following is the definition of ManagedUDT in C++ :

#pragma pack(1)
struct ManagedUDT
{
  BSTR m_str01;
  int m_int01;
};

3. API that Displays the Contents of a VT_RECORD VARIANT.

3.1 The following is a listing of an unmanaged function that displays the contents of a VT_RECORD VARIANT :

__declspec(dllexport) void __stdcall DisplayContentsOfRecordVariant(/*[in]*/ const VARIANT* pVariant)
{
	if (V_VT(pVariant) != VT_RECORD)
	{
		return;
	}

	IRecordInfo* pIRecordInfo = V_RECORDINFO(pVariant);
	ManagedUDT* pManagedUDT = (ManagedUDT*)(V_RECORD(pVariant));

	// Display information on the IRecordInfo object assocoated
	// with the ManagedUDT structure.
	if (pIRecordInfo)
	{
		// Obtain the name of the UDT.
		// This should be "ManagedUDT".
		BSTR bstrRecordName = NULL;
		pIRecordInfo -> GetName(&bstrRecordName);
		if (bstrRecordName)
		{
		  printf ("Record Name : [%S].\r\n", bstrRecordName);
		  ::SysFreeString(bstrRecordName);
		  bstrRecordName = NULL;
		}

		// Obtain the GUID associated with the UDT.
		// This should be the one declared via the
		// GuidAttribute for the ManagedUDT structure,
		// i.e. "BBFE1092-A90C-4b6d-B279-CBA28B9EDDFA".
		GUID guid;
		pIRecordInfo -> GetGuid(&guid);

		// Display the GUID as a string.
		LPOLESTR lpwzGuid = NULL;
		StringFromIID
		(
		  guid,
		  &lpwzGuid
		);

		if (lpwzGuid)
		{
		  printf ("Record GUID : [%S].\r\n", lpwzGuid);
		  ::CoTaskMemFree((LPVOID)lpwzGuid);
		  lpwzGuid = NULL;
		}
	}

	// Display the field values of the ManagedUDT structure
	// contained inside the input VARIANT.
	if (pManagedUDT)
	{
	  printf ("pManagedUDT -> m_str01 : [%S].\r\n", pManagedUDT -> m_str01);
	  printf ("pManagedUDT -> m_int01 : [%d].\r\n", pManagedUDT -> m_int01);
	}
}

This API displays information on the IRecordInfo associated with the UDT and also displays the field values of the UDT contained inside the input VARIANT.

3.2 The C# declaration for this API is listed below :

[DllImport("TestDLL.dll", CallingConvention = CallingConvention.StdCall)]
private static extern void DisplayContentsOfRecordVariant([In] IntPtr pVariant);

3.3 We shall be using the above API quite frequently throughout this series of blogs.

4. Passing a VT_RECORD VARIANT to an Unmanaged API via an IntPtr.

4.1 In this section, I shall demonstrate how to marshal ManagedUDT to the DisplayContentsOfRecordVariant() API using a native VARIANT which has been allocated manually using Marshal.AllocHGlobal() and initialized via Marshal.GetNativeVariantForObject().

4.2 The C# code listed below demonstrates this :

const Int32 SizeOfNativeVariant = 16;

// This function creates a ManagedUDT structure,
// sets its field values and then displays it
// via DisplayContentsOfRecordVariant() which
// takes an IntPtr as parameter.
private static void DisplayRecordVariantViaNativeVariant()
{
    // Instantiate a ManagedUDT structure
    // and fill its fields.
    ManagedUDT managed_udt = new ManagedUDT();
    managed_udt.m_str01 = "Hello World";
    managed_udt.m_int01 = 100;

    // Allocate in unmanaged memory a block of space
    // the size of a native VARIANT.
    IntPtr pVariant = Marshal.AllocHGlobal(SizeOfNativeVariant);

    // Construct a native VARIANT that contains
    // the ManagedUDT UDT.
    Marshal.GetNativeVariantForObject(managed_udt, pVariant);

    // Call the API.
    DisplayContentsOfRecordVariant(pVariant);

    // Clear the contents of the unmanaged ManagedUDT
    // structure contained inside the native VARIANT.
    VariantClear(pVariant);
    // Free the space occuppied by the native VARIANT.
    Marshal.FreeHGlobal(pVariant);
    pVariant = IntPtr.Zero;
}

4.3 This code will produce the following console output :

Record Name : [ManagedUDT].
Record GUID : [{BBFE1092-A90C-4B6D-B279-CBA28B9EDDFA}].
pManagedUDT -> m_str01 : [Hello World].
pManagedUDT -> m_int01 : [100].

4.4 The following are pertinent points regarding the DisplayRecordVariantViaNativeVariant() C# function :

  • The code first instantiates a ManagedUDT structure managed_udt and then fills it with values.
  • Next, using Marshal.AllocHGlobal(), the function allocates memory space in unmanaged memory the size of a native VARIANT.
  • Then, using Marshal.GetNativeVariantForObject(), the ManagedUDT structure managed_udt is transfomed into a VT_RECORD VARIANT.
  • Marshal.GetNativeVariantForObject() achieves this by allocating space (in managed memory) large enough to hold an unmanaged version of a ManagedUDT structure (as listed in point 2.2).
  • The MarshalAsAttribute which was assigned to the m_str01 string field indicates to the interop marshaler to create a BSTR for the counterpart m_str01 string field in the unmanaged structure.
  • This unmanaged version of the ManagedUDT structure is the UDT that will be pointed to by the __tagBRECORD.pvRecord field.
  • Next, Marshal.GetNativeVariantForObject() will have to create an IRecordInfo object to be associated with the UDT.
  • The obtaining of the IRecordInfo interface pointer for ManagedUDT is a 2-step process :
  • First the interface for the ITypeInfo object for the ManagedUDT structure must be obtained. This is accomplished using Marshal.GetITypeInfoForType().
  • Next, the interface for the IRecordInfo object associated with the ITypeInfo is to be obtained. This is accomplished using the GetRecordInfoFromTypeInfo() API.
  • Note that both interface pointers must eventually be Release()’d.
  • At this point, both the UDT and its associated IRecordInfo interface pointer are obtained and Marshal.GetNativeVariantForObject() successfully returns a VT_RECORD VARIANT.
  • The VARIANT is then passed to the DisplayContentsOfRecordVariant() API as a pointer.
  • When this API completes, VariantClear() is called. VariantClear() will call IRecordInfo::RecordClear() on the UDT which will free the contents of its fields (including fields which are references to memory e.g. BSTR, SAFEARRAY, etc). Hence, the BSTR field m_str01 will be freed.
  • Additionally, the memory space occuppied by the UDT itself will be freed by VariantClear().
  • Calling VariantClear() will clear the UDT and free its contents but this is not enough. We also need to clear the memory occuppied by the VARIANT that we have first allocated using Marshal.AllocHGlobal().

5. In Conclusion.

5.1 So far so good. We have successfully passed a native VT_RECORD VARIANT from managed code to unmanaged code.

5.2 In part 2, I shall demonstrate a very simple, albeit indirect, way to pass a VT_RECORD VARIANT from managed code to unmanaged code.

5.3 Proceed to Part 2.

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

Trackbacks/Pingbacks

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

  2. Pingback: Using a VT_RECORD VARIANT in Managed Code Part 3 « limbioliong - September 29, 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: