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

Using a VT_RECORD VARIANT in Managed Code Part 3

1. Introduction.

1.1 Starting from this installment (part 3) of this series of articles, I shall be using the VariantStructGeneric structure (first defined in Defining a VARIANT Structure in Managed Code Part 2) to demonstrate how to work with VT_RECORD VARIANTs in managed code.

1.2 The VariantStructGeneric structure is a complete replica of the unmanaged VARIANT structure and it can be used inside managed code easily.

1.3 We shall begin our study of using VariantStructGeneric to contain a VT_RECORD VARIANT by demonstrating how to use this structure to pass a VT_RECORD VARIANT from managed code to unmanaged code.

1.4 Throughout this part, the ManagedUDT structure (defined in part 1) will be used as the UDT to be passed to unmanaged code.

1.5 The DisplayContentsOfRecordVariant() API that we have met in part 1 will be used as the recipient of the VARIANT. The DisplayContentsOfRecordVariant2() API will also be used albeit a new C# DllImport declaration for it will be defined.

2. Passing a VT_RECORD VARIANT from Managed Code to Unmanaged Code using a Pointer to a VariantStructGeneric.

2.1 The following is a general guideline on how this is done :

  • A ManagedUDT structure must be instantiated of course.
  • An unmanaged version of the ManagedUDT structure must then be created with values fully replicated from those of the managed ManagedUDT structure.
  • This is because it is the unmanaged structure that needs to be pointed to by the VariantStructGeneric and subsequently passed to the unmanaged code.
  • Besides the unmanaged version of the ManagedUDT structure, a VT_RECORD VARIANT will need to contain a pointer to an IRecordInfo interface that is associated with the UDT.
  • The obtaining of this IRecordInfo interface pointer is a 2-step process :
  • First obtain the ITypeInfo interface pointer for ManagedUDT.
  • Next obtain the IRecordInfo interface associated with the ITypeInfo.
  • Note that both interface pointers must eventually be Release()’d.
  • The beauty of using a VariantStructGeneric structure is that it can be used just like a VARIANT in unmanaged code (e.g. C++).
  • A good practice in the usage of VARIANTs is to always call VariantClear() on it when we have finished using it.

2.2 The code below demonstrates the above concepts :

[DllImport("oleaut32.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
static extern UInt32 VariantClear(IntPtr pvarg);

[DllImport("oleaut32.dll", CallingConvention = CallingConvention.StdCall, PreserveSig = false)]
public static extern IntPtr GetRecordInfoFromTypeInfo(IntPtr pTypeInfo);

// Display_ManagedUDT_Using_Ptr_To_VariantStructGeneric()
// displays a managed structure (type ManagedUDT)
// using the VariantStructGeneric structure which
// serves as the managed version of the VARIANT
// structure.
private static void Display_ManagedUDT_Using_Ptr_To_VariantStructGeneric()
{
    // Allocate a ManagedUDT structure and fill its member fields.
    ManagedUDT managed_udt = new ManagedUDT();
    managed_udt.m_str01 = "Hello World";
    managed_udt.m_int01 = 100;

    // Note that the managed structure cannot by itself be passed
    // to unmanaged code via a VARIANT. It must be represented by
    // an unmanaged counterpart.
    //
    // To allocate space for the unmanaged version of the ManagedUDT
    // structure, we must always use the Marshal.AllocCoTaskMem()
    // method. This is in line with COM protocol.
    //
    IntPtr pmanaged_udt = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(ManagedUDT)));
    // Convert the managed ManagedUDT structure into
    // its unmanaged counterpart by using Marshal.StructureToPtr().
    Marshal.StructureToPtr(managed_udt, pmanaged_udt, false);

    // The obtaining of the IRecordInfo interface pointer for ManagedUDT
    // is a 2-step process :
    //
    // 1. First obtain the ITypeInfo interface pointer for ManagedUDT.
    // 2. Next obtain the IRecordInfo interface associated with the ITypeInfo.
    //
    // Note that both interface pointers must eventually be Release()'d.
    IntPtr pITypeInfoForManagedUDT = Marshal.GetITypeInfoForType(typeof(ManagedUDT));
    IntPtr pIRecordInfoForManagedUDT = GetRecordInfoFromTypeInfo(pITypeInfoForManagedUDT);

    // Instantiate a VariantStructGeneric structure and fill its fields.
    VariantStructGeneric Variant = new VariantStructGeneric();
    Variant.variant_part.vt = (ushort)(VarEnum.VT_RECORD);
    Variant.variant_part.variant_union.record_data.pRecInfo = pIRecordInfoForManagedUDT;
    Variant.variant_part.variant_union.record_data.pvRecord = pmanaged_udt;

    // Allocate a GCHandle so that we can pin the
    // Variant Structure in memory.
    GCHandle gch = GCHandle.Alloc(Variant, GCHandleType.Pinned);
    // Use the GCHandle.AddrOfPinnedObject() method
    // to obtain a pointer to the (pinned) Variant Structure.
    IntPtr pVariant = gch.AddrOfPinnedObject();

    // Call the DisplayContentsOfRecordVariant() API.
    DisplayContentsOfRecordVariant(pVariant);

    // Call VariantClear() on the VARIANT.
    // This will free the unmanaged counterpart
    // of the ManagedUDT structure first allocated
    // using Marshal.AllocCoTaskMem().
    //
    // Hence after calling VariantClear(),
    // we must not free the unmanaged ManagedUDT
    // structure via Marshal.FreeCoTaskMem().
    //
    // Note that all fields of this structure
    // which are pointer-based (e.g. BSTRs)
    // will be cleared by VariantClear().
    //
    // Hence using VariantClear() is a better
    // option to calling Marshal.FreeCoTaskMem()
    // because Marshal.FreeCoTaskMem() will not
    // free any internal pointer-based fields.
    VariantClear(pVariant);
    // Call GCHandle.Free() to unpin the VARIANT
    // structure.
    gch.Free();

    // We must Release() the ITypeInfo interface
    // pointer for the ManagedUDT structure.
    Marshal.Release(pITypeInfoForManagedUDT);
    pITypeInfoForManagedUDT = IntPtr.Zero;

    // We must Release() the IRecordInfo interface
    // pointer for the ManagedUDT structure.
    Marshal.Release(pIRecordInfoForManagedUDT);
    pIRecordInfoForManagedUDT = IntPtr.Zero;

    // We must not free the memory space
    // previously allocated for the
    // ManagedUDT structure by calling
    // code like the following :
    //
    // Marshal.FreeCoTaskMem(pmanaged_udt);
    // pmanaged_udt = IntPtr.Zero;
    //
    // This is because VariantClear()
    // would have freed this space for us.
}

Note the following pertinent points about the code above :

  • The above function uses the GetRecordInfoFromTypeInfo() API exported from oleaut32.dll. A C# declaration for it is listed in the code sample.
  • We prepare the unmanaged version of the ManagedUDT structure by using Marshal.AllocCoTaskMem() to allocate space for it and then using Marshal.StructureToPtr() to transcribe the field values from the ManagedUDT structure to the unmanaged one.
  • As mentioned in point 2.1, to obtain the IRecordInfo interface pointer for the ManagedUDT structure, we must first obtain the ITypeInfo interface pointer associated with ManagedUDT. This is obtained by using the Marshal.GetITypeInfoForType() function.
  • Through Marshal.GetITypeInfoForType(), the CLR will internally create and return a COM-visible managed object that implements ITypeInfo.
  • We then use the GetRecordInfoFromTypeInfo() API on the ITypeInfo interface pointer to obtain the IRecordInfo interface pointer associated with ManagedUDT.
  • An instance of the VariantStructGeneric structure must be created of course and its values set accordingly.
  • We then allocate a GCHandle to pin the VariantStructGeneric structure Variant in memory. This is possible because the VariantStructGeneric structure contains only blittable members.
  • After the DisplayContentsOfRecordVariant() API has been called, the contents of the VariantStructGeneric structure must be cleared. This is best done using the VariantClear() API.
  • Calling VariantClear() will free all the fields of the structure which are references to other objects (e.g. BSTRs and SAFEARRAYs).
  • This is possible because the IRecordInfo object associated with the UDT internally contains metadata on the fields of the UDT. The freeing of object references is done by using IRecordInfo::RecordClear().
  • After performing this important step, VariantClear() will free the memory space occupied by the unmanaged ManagedUDT structure that was first allocated using Marshal.AllocCoTaskMem().
  • Note that this is done using Marshal.FreeCoTaskMem(). This is why when we first allocate the memory for the unmanaged ManagedUDT structure, we must use Marshal.AllocCoTaskMem().
  • Note that after calling VariantClear(), we must no longer free the unmanaged ManagedUDT structure via Marshal.FreeCoTaskMem(). This is already done by VariantClear().

2.3 This function will output the following console printout :

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

3. Passing a VT_RECORD VARIANT from Managed Code to Unmanaged Code using a  VariantStructGeneric Structure Directly.

3.1 Not all APIs take a VARIANT structure as a pointer. Some may take a VARIANT structure directly. An example is the DisplayContentsOfRecordVariant2() API which is documented in part 2.

3.2 In this section, I will demonstrate how to call DisplayContentsOfRecordVariant2(), passing a direct VariantStructGeneric structure.

3.3 In part 2, I have listed a C# declaration for DisplayContentsOfRecordVariant2() as follows :

[DllImport("TestDLL.dll", CallingConvention = CallingConvention.StdCall)]
private static extern void DisplayContentsOfRecordVariant2([In] [MarshalAs(UnmanagedType.Struct)] Object var);

This is not suitable for our purposes because we will not want to wrap the VariantStructGeneric structure in a System.Object. Instead, we want to pass the VariantStructGeneric structure directly as a parameter.

3.4 A suitable C# declaration for DisplayContentsOfRecordVariant2() is listed below :

[DllImport("TestDLL.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "DisplayContentsOfRecordVariant2")]
private static extern void DisplayContentsOfRecordVariant3([In] [MarshalAs(UnmanagedType.Struct)] VariantStructGeneric var);

This declaration will uses the same DisplayContentsOfRecordVariant2() API albeit it is called via an alias “DisplayContentsOfRecordVariant3()” which takes an entirely different parameter.

The MarshalAsAttribute with argument UnmanagedType.Struct used on the parameter is a good way to indicate that the counterpart parameter for the API is actually a VARIANT.

3.5 The code listing below demonstrates how to call DisplayContentsOfRecordVariant3() in C# :

// Display_ManagedUDT_Using_Direct_VariantStructGeneric()
// displays a managed structure (type ManagedUDT)
// using the VariantStructGeneric structure which
// serves as the managed version of the VARIANT
// structure.
//
// The VariantStructGeneric structure is passed
// directly to the API and not via a pointer.
private static void Display_ManagedUDT_Using_Direct_VariantStructGeneric()
{
    // Allocate a ManagedUDT structure and fill its member fields.
    ManagedUDT managed_udt = new ManagedUDT();
    managed_udt.m_str01 = "Hello World";
    managed_udt.m_int01 = 100;

    // Note that the managed structure cannot by itself be passed
    // to unmanaged code via a VARIANT. It must be represented by
    // an unmanaged counterpart.
    //
    // To allocate space for the unmanaged version of the ManagedUDT
    // structure, we must always use the Marshal.AllocCoTaskMem()
    // method. This is in line with COM protocol.
    //
    IntPtr pmanaged_udt = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(ManagedUDT)));
    // Convert the managed ManagedUDT structure into
    // its unmanaged counterpart by using Marshal.StructureToPtr().
    Marshal.StructureToPtr(managed_udt, pmanaged_udt, false);

    // The obtaining of the IRecordInfo interface pointer for ManagedUDT
    // is a 2-step process :
    //
    // 1. First obtain the ITypeInfo interface pointer for ManagedUDT.
    // 2. Next obtain the IRecordInfo interface associated with the ITypeInfo.
    //
    // Note that both interface pointers must eventually be Release()'d.
    IntPtr pITypeInfoForManagedUDT = Marshal.GetITypeInfoForType(typeof(ManagedUDT));
    IntPtr pIRecordInfoForManagedUDT = GetRecordInfoFromTypeInfo(pITypeInfoForManagedUDT);

    // Instantiate a VariantStructGeneric structure and fill its fields.
    VariantStructGeneric Variant = new VariantStructGeneric();
    Variant.variant_part.vt = (ushort)(VarEnum.VT_RECORD);
    Variant.variant_part.variant_union.record_data.pRecInfo = pIRecordInfoForManagedUDT;
    Variant.variant_part.variant_union.record_data.pvRecord = pmanaged_udt;

    // Call the DisplayContentsOfRecordVariant3() API
    // passing the VariantStructGeneric structure
    // directly.
    DisplayContentsOfRecordVariant3(Variant);

    // Allocate a GCHandle so that we can pin the
    // Variant Structure in memory.
    // This is done so that we can call VariantClear()
    // on the Variant structure.
    GCHandle gch = GCHandle.Alloc(Variant, GCHandleType.Pinned);
    // Use the GCHandle.AddrOfPinnedObject() method
    // to obtain a pointer to the (pinned) Variant Structure.
    IntPtr pVariant = gch.AddrOfPinnedObject();

    // Call VariantClear() on the Variant Structure.
    // This will free the unmanaged counterpart
    // of the ManagedUDT structure first allocated
    // using Marshal.AllocCoTaskMem().
    //
    // Hence after calling VariantClear(),
    // we must not free the unmanaged ManagedUDT
    // structure via Marshal.FreeCoTaskMem().
    //
    // Note that all fields of this structure
    // which are pointer-based (e.g. BSTRs)
    // will be cleared by VariantClear().
    //
    // Hence using VariantClear() is a better
    // option to calling Marshal.FreeCoTaskMem()
    // because Marshal.FreeCoTaskMem() will not
    // free any internal pointer-based fields.
    VariantClear(pVariant);
    // Call GCHandle.Free() to unpin the VARIANT
    // structure.
    gch.Free();

    // We must Release() the ITypeInfo interface
    // pointer for the ManagedUDT structure.
    Marshal.Release(pITypeInfoForManagedUDT);
    pITypeInfoForManagedUDT = IntPtr.Zero;

    // We must Release() the IRecordInfo interface
    // pointer for the ManagedUDT structure.
    Marshal.Release(pIRecordInfoForManagedUDT);
    pIRecordInfoForManagedUDT = IntPtr.Zero;

    // We must not free the memory space
    // previously allocated for the
    // ManagedUDT structure by calling
    // code like the following :
    //
    // Marshal.FreeCoTaskMem(pmanaged_udt);
    // pmanaged_udt = IntPtr.Zero;
    //
    // This is because VariantClear()
    // would have freed this space for us.
}

This function is very similar to the Display_ManagedUDT_Using_Ptr_To_VariantStructGeneric() function listed in section 2. Note the following significant points about this function :

  • As we already know, this time the external API (i.e. DisplayContentsOfRecordVariant3()) is called with Variant passed directly instead of as a pointer.
  • Note that we still use the services of the GCHandle class to pin the VariantStructGeneric structure Variant in memory. This is done so that we can call VariantClear() on it.
  • As usual, Marshal.Release() must be called on the ITypeInfo and IRecordInfo interface pointers associated with the ManagedUDT structure.
  • Also, since VariantClear() has already been called to free not only the unmanaged ManagedUDT structure pointed to inside the VariantStructGeneric but also its object reference fields, we must not call Marshal.FreeCoTaskMem() on the unmanaged structure.

4. In Conclusion.

4.1 This part has been a good start to our use of the VariantStructGeneric structure to manage a VT_RECORD VARIANT.

4.2 In part 4, I shall demonstrate how to use VariantStructGeneric to return a VT_RECORD VARIANT from an unmanaged API to managed code.

4.3 Return to Part 2.

4.4 Proceed to Part 4.

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 29, 2011

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