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

Using a VT_RECORD VARIANT in Managed Code Part 5

1. Introduction.

1.1 In part 4, I demonstrated how to receive a VT_RECORD VariantStructGeneric structure from an unmanaged API as an “out” parameter.

1.2 Here in part 5, I shall demonstrate how to pass a VT_RECORD VariantStructGeneric to and from an unmanaged API as both and “in” and “out” parameter (i.e. by reference parameter).

1.3 A structure will be passed to an external API via a VARIANT. The API will display the field values of the structure. Thereafter, the API will assign new values into the fields and return it to the caller.

1.4 In this part 5, I shall be re-using the ManagedUDT structure that defined in its own class library back in part 4.

2. The Unmanaged API that takes a VT_RECORD VARIANT as an “in” and “Out” Parameter.

2.1 The following is a full code listing for the ReferenceUDTVariant() API that will take a reference paramater to a VARIANT :

// ReferenceUDTVariant() takes a pointer to a VARIANT as parameter,
// display its contents, clears the contents and then fills it with
// new VT_RECORD data.
HRESULT __stdcall ReferenceUDTVariant(/*[in, out]*/ VARIANT* pVarReceiver)
{
  if (V_VT(pVarReceiver) != VT_RECORD)
  {
    return E_FAIL;
  }

  // Extract the ManagedUDT from the VARIANT.
  ManagedUDT* pManagedUDT = (ManagedUDT*)V_RECORD(pVarReceiver);
  // Display its field values.
  printf ("pManagedUDT -> m_str01 : %S.\r\n", pManagedUDT -> m_str01);
  printf ("pManagedUDT -> m_int01 : %d.\r\n", pManagedUDT -> m_int01);

  // Obtain the IRecordInfo interface pointer associated
  // with the record.
  IRecordInfo* pIRecordInfo = V_RECORDINFO(pVarReceiver);
  // Clear the record fields without freeing the memory
  // occupied by the record.
  pIRecordInfo -> RecordClear((LPVOID)pManagedUDT);

  // Allocate new values into pManagedUDT.
  pManagedUDT -> m_str01 = ::SysAllocString(L"String returned GetUDTVariant() API.");
  pManagedUDT -> m_int01 = 200;

  // No need to re-arrange pVarReceiver because
  // the IRecordInfo interface pointer is still
  // the same one to be used.
  //
  // We also do not need to Release() or AddRef()
  // it.
  //
  // Also it is the same ManagedUDT record used.
  // Only that its fields have been modified.

  return S_OK;
}

2.2 The following are important points to note about this API :

  • It first checks to see if the incoming VARIANT is of type VT_RECORD. If not, it returns with E_FAIL.
  • It then extracts a pointer to the (unmanaged) ManagedUDT structure from the VARIANT and displays its field values.
  • This is done to confirm that correct values have been received from the managed code.
  • Next, it obtains the IRecordInfo interface pointer associated the record and uses its RecordClear() method to clear the fields of the record without de-allocating the memory space used by the record itself. The m_str01 field (a BSTR) in particular, will be freed as a result.
  • The ReferenceUDTVariant() function will then use the same pointer to the unmanaged ManagedUDT and fill its fields with new values.
  • The function then returns.

2.3 This API is declared in C# as follows :

[DllImport("TestDLL.dll", CallingConvention = CallingConvention.StdCall)]
private static extern UInt32 ReferenceUDTVariant([In][Out][MarshalAs(UnmanagedType.Struct)] ref VariantStructGeneric varReceiver);

3. Passing a VARIANT to and from ReferenceUDTVariant() as an “in” and “Out” Parameter.

3.1 The following C# method demonstrates a call to ReferenceUDTVariant() :

[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);

private static void CallReferenceUDTVariant()
{
    // Allocate a ManagedUDT structure and fill its member fields.
    ManagedUDT managed_udt = new ManagedUDT();
    managed_udt.m_str01 = "String from CallReferenceUDTVariant() C# function.";
    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;

    ReferenceUDTVariant(ref Variant);

    // We now display the field values of the record data contained
    // inside Variant. To do this, we first convert this record
    // (an unmanaged version of the ManagedUDT structure) into its
    // managed counterpart.
    //
    // Note that we can use the same managed_udt struct that we
    // have already declared.
    managed_udt = (ManagedUDT)Marshal.PtrToStructure
        (
          Variant.variant_part.variant_union.record_data.pvRecord,
          typeof(ManagedUDT)
        );

    Console.WriteLine("managed_udt.m_str01 : {0:S}", managed_udt.m_str01);
    Console.WriteLine("managed_udt.m_int01 : {0:D}", managed_udt.m_int01);

    // Allocate a GCHandle so that we can pin the
    // Variant Structure varReceiver 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() which was done
    // in this function.
    //
    // Note that all fields of this structure
    // which are pointer-based (e.g. BSTRs)
    // will be cleared by VariantClear().
    //
    // VariantClear() will also Release() the
    // IRecordInfo interface pointer pointed to by
    // varReceiver.variant_part.variant_union.record_data.pRecInfo.
    VariantClear(pVariant);
    pVariant = IntPtr.Zero;
    // 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.
}

3.2 Note the following important points related to the CallReferenceUDTVariant() C# function :

  • A ManagedUDT structure must be allocated and filled with field values of course.
  • Note that the managed structure cannot by itself be passed to unmanaged code contained inside a VARIANT. It must be represented by an unmanaged counterpart.
  • This unmanaged version of the ManagedUDT structure must be allocated dynamically in memory.
  • To allocate space for this structure, we must always use the Marshal.AllocCoTaskMem() method. This is in line with COM protocol.
  • Note that even though we called Marshal.SizeOf(typeof(ManagedUDT)) with ManagedUDT as the type to obtain the size for, it is actually the size of an unmanaged version of ManagedUDT that will be obtained. This is done with the help of the definitions of the types of the structure members as well as any MarshalAsAttributes attached to them.
  • Thereafter, Marshal.StructureToPtr() is used to convert the managed ManagedUDT structure into its unmanaged counterpart.
  • After that, we obtain the IRecordInfo interface pointer which is associated with the ManagedUDT structure. This is done by a 2-step process :
  • 1. First obtain the ITypeInfo interface pointer for ManagedUDT. This is done by using Marshal.GetITypeInfoForType().
  • 2. Next obtain the IRecordInfo interface associated with the ITypeInfo. This is done by calling the GetRecordInfoFromTypeInfo() API.
  • Note that both interface pointers must eventually be Release()’d by calling Marshal.Release().
  • We then instantiate a VariantStructGeneric structure and fill its fields with appropriate VT_RECORD data.
  • The ReferenceUDTVariant() API is then called. This API will display the field values of the unmanaged ManagedUDT structure and then fill it with new values (with the previous ones cleared first).
  • We then convert the unmanaged structure (contained inside the VariantStructGeneric structure) into the managed ManagedUDT structure by calling Marshal.PtrToStructure().
  • Note that we can use the same managed_udt structure to receive the new value.
  • We then display the new field values of the managed_udt structure.
  • After that, we use GCHandle to pin Variant in memory and obtain its memory location.
  • This is done in order to call VariantClear() on Variant.
  • Note that VariantClear() will not only free all member fields of the unmanaged ManagedUDT structure which are references to other memory locations (e.g. BSTRs), it will also call Release() on the IRecordInfo interface associated with the record. VariantClear() will also free up the memory used by the unmanaged version of the ManagedUDT structure.
  • We will also need to call Marshal.Release() to release the reference count of the ITypeInfo interface as well as the IRecordInfo interface, both of which are associated with the ManagedUDT structure.

3.3 The following will be the console output when the CallReferenceUDTVariant() is run :

pManagedUDT -> m_str01 : String from CallReferenceUDTVariant() C# function..
pManagedUDT -> m_int01 : 100.
managed_udt.m_str01 : String returned GetUDTVariant() API.
managed_udt.m_int01 : 200

4. In Conclusion.

4.1 And there we have it. A full 5 part treatise on the use of VT_RECORD VARIANTs in managed code. I hope the reader will benefit tremendously from this series of articles.

4.2 It is also my hope that the reader will realize that many programming constructs can be shared between managed and unmanaged code. Once the low-level mechanisms are understood clearly, many things are possible. Even a complicated structure like the VARIANT can be aptly represented in managed code.

4.3 Return 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 4 « limbioliong - October 10, 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: