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

Using a VT_RECORD VARIANT in Managed Code Part 4

1. Introduction.

1.1 In part 3, I started to use the VariantStructGeneric structure to work with VT_RECORD VARIANTs in managed code.

1.2 We can see that the VariantStructGeneric structure completely replicates the unmanaged VARIANT structure and its use in managed code is intuitive for those who are familiar with VARIANTs.

1.3 In part 3, I demonstrated code to pass a VT_RECORD VariantStructGeneric structure as an “in” parameter to an unmanaged API both as a direct VARIANT structure and as a pointer to a VARIANT structure.

1.4 Here in part 4 I shall demonstrate how to receive a VT_RECORD VariantStructGeneric structure as an “out” parameter from an unmanaged API.

2. Re-use of the ManagedUDT Structure.

2.1 We shall be re-using the ManagedUDT structure that we have built in part 1. However here from part 4 onwards we shall be defining this structure in a class library of its own.

2.2 Doing so will enable us to export the class library to COM as a type library. This is done via TLBEXP.EXE. The type library will contain a UDT definition of the ManagedUDT structure.

2.3 Why do we need this type library ? It is because here in part 4, we shall be creating a VT_RECORD VARIANT from inside unmanaged C++ code and so it would need access to the IRecordInfo interface associated with the ManagedUDT structure.

2.4 When we have the definition of ManagedUDT in a COM type library, our unmanaged code will be able to access its IRecordInfo interface to create a VT_RECORD VARIANT.

2.5 I have named the class library project ManagedUDTTypes and it produces ManagedUDTTypes.dll as the output class library. The following is the full listing of the ManagedUDT structure used to build this class library :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace ManagedUDTTypes
{
    // 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;
    }
}

2.6 I then used TLBEXP.EXE on the class library to produce the ManagedUDTTypes.tlb type library :

tlbexp .\bin\debug\ManagedUDTTypes.dll /out:.\bin\debug\ManagedUDTTypes.tlb

2.7 Once we have this type library, it can be #imported into a C++ project. This will be demonstrated in the next section.

3. The Unmanaged API that returns a VT_RECORD VARIANT as an “Out” Parameter.

3.1 The following is a full code listing for the GetUDTVariant() API. This listing also includes the codes for a helper function named GetIRecordType() :

#include "stdafx.h"

#import "ManagedUDTTypes.tlb"
using namespace ManagedUDTTypes;

HRESULT GetIRecordType
(
  LPCTSTR lpszTypeLibraryPath,  // Path to type library that contains definition of a User-Defined Type (UDT).
  REFGUID refguid,  // GUID of UDT.
  IRecordInfo** ppIRecordInfoReceiver  // Receiver of IRecordInfo that encapsulates information of the UDT.
)
{
  _bstr_t bstTypeLibraryPath = lpszTypeLibraryPath;
  ITypeLib* pTypeLib = NULL;
  ITypeInfo* pTypeInfo = NULL;
  HRESULT hrRet = S_OK;

  *ppIRecordInfoReceiver = NULL;  // Initialize receiver.

  hrRet = LoadTypeLib((const OLECHAR FAR*)bstTypeLibraryPath, &pTypeLib);

  if (SUCCEEDED(hrRet))
  {
    if (pTypeLib)
    {
	  hrRet = pTypeLib -> GetTypeInfoOfGuid(refguid, &pTypeInfo);

	  pTypeLib->Release();
	  pTypeLib = NULL;
    }

    if (pTypeInfo)
    {
      hrRet = GetRecordInfoFromTypeInfo(pTypeInfo, ppIRecordInfoReceiver);

	  pTypeInfo->Release();

	  pTypeInfo = NULL;
    }
  }

  return hrRet;
}

// GetUDTVariant() takes a pointer to a VARIANT as parameter
// and will fill it with values that makes it a VT_RECORD
// VARIANT.
HRESULT __stdcall GetUDTVariant(/*[out]*/ VARIANT* pVarReceiver)
{
  // Initialize the receiver.
  VariantInit(pVarReceiver);

  // Obtain the IRecordInfo associated with ManagedUDT.
  IRecordInfo* pIRecordInfo = NULL;

  HRESULT hrRet = GetIRecordType
  (
    "ManagedUDTTypes.tlb",
    __uuidof(ManagedUDT),
    &pIRecordInfo
  );

  // If unable to obtain the IRecordInfo interface
  // pointer, return immediately.
  if (!SUCCEEDED(hrRet))
  {
    return hrRet;
  }

  ManagedUDT* pManagedUDT = (ManagedUDT*)::CoTaskMemAlloc(sizeof(ManagedUDT));

  pManagedUDT -> m_str01 = ::SysAllocString(L"String from GetUDTVariant() API.");
  pManagedUDT -> m_int01 = 100;

  V_VT(pVarReceiver) = VT_RECORD;
  V_RECORDINFO(pVarReceiver) = pIRecordInfo;
  // Remember to AddRef() on pIRecordInfo
  // because we are now returning it to
  // the caller via pVarReceiver.
  pIRecordInfo -> AddRef();
  V_RECORD(pVarReceiver) = pManagedUDT;

  // Perform a final Release() on pIRecordInfo.
  pIRecordInfo -> Release();
  pIRecordInfo = NULL;

  return S_OK;
}

3.2 The following are pertinent points about this code listing in general :

  • This code imports “ManagedUDTTypes.tlb” which we created using TLBEXP.EXE in section 2.
  • This exposes the ManagedUDT structure to the C++ code in the .TLH file generated by the VC++ compiler :
#pragma pack(push, 1)

struct __declspec(uuid("bbfe1092-a90c-4b6d-b279-cba28b9eddfa"))
ManagedUDT
{
    BSTR m_str01;
    long m_int01;
};

#pragma pack(pop)
  • This definition also allows the C++ code to easily reference the GUID of the ManagedUDT structure via the uuidof() operator.

3.3 Note the following significant points concerning the GetIRecordType() helper function :

  • This function loads a specified type library file via the LoadTypeLib() API.
  • LoadTypeLib() returns a pointer to the ITypeLib interface which is associated with the type library.
  • This function then uses the GetTypeInfoOfGuid() method of the ITypeLib interface to obtain a pointer to an ITypeInfo interface which is associated with the GUID of the ManagedUDT structure.
  • Finally, GetRecordInfoFromTypeInfo() is called to obtain a pointer to an IRecordInfo interface which is associated with the ITypeInfo.
  • This IRecordInfo interface pointer is returned to the caller.

3.4 The following are important points concerning the GetUDTVariant() API :

  • The mission of the GetUDTVariant() API is to take a pointer to a VARIANT as parameter and then fill it up so as to return it as a VT_RECORD VARIANT.
  • It first uses the GetIRecordType() helper function to obtain the IRecordInfo interface which is associated with the ManagedUDT structure. If the helper function fails, GetUDTVariant() will return with an error code.
  • Next, a ManagedUDT structure is created via CoTaskMemAlloc(). It is a COM protocol to always allocate a UDT using CoTaskMemAlloc(). This is because when the UDT is later freed via IRecordInfo::RecordDestroy(), CoTaskMemFree() will be used for the freeing process.
  • The fields of the structure are then filled.
  • The variant type of the returning VARIANT is set to VT_RECORD.
  • The IRecordInfo interface pointer is set accordingly. Note well that we must call AddRef() on the pointer to the IRecordInfo interface set in the VARIANT. This is because the interface pointer is a returned item and so its reference count must be incremented.
  • The pointer to the UDT record is also set in the VARIANT.

3.5 The following is a C# declaration for GetUDTVariant() :

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

Note the following :

  • The parameter is defined as of type VariantStructGeneric. We do not need to use the IntPtr type and later convert the out pointer into a VariantStructGeneric structure using Marshal.PtrToStructure(). A direct use of the VariantStructGeneric is possible.
  • The parameter is also marked with the MarshalAsAttribute with the UnmanagedType.Struct argument. This indicates that the counterpart parameter type on the unmanaged side is of type VARIANT.
  • Since the parameter is marked as “out”, this indicates to the interop marshaler to pass a pointer to a VariantStructGeneric as the parameter.
  • This perfectly fits the signature of the GetUDTVariant() API.

4. Receiving a VARIANT from GetUDTVariant() as an “Out” Parameter.

4.1 The C# client code that will call GetUDTVariant() must reference the ManagedUDTTypes class library now that the ManagedUDT structure is now defined in a class library of its own.

4.2 The following C# method demonstrates a call to GetUDTVariant() :

using ManagedUDTTypes;

private static void CallGetUDTVariant()
{
    VariantStructGeneric varReceiver;

    GetUDTVariant(out varReceiver);

    // Confirm that varReceiver is of type VT_RECORD.
    if (varReceiver.variant_part.vt == (ushort)(VarEnum.VT_RECORD))
    {
        Console.WriteLine("varReceiver.variant_part.vt == (VarEnum.VT_RECORD).");
    }
    else
    {
        Console.WriteLine("varReceiver.variant_part.vt != (VarEnum.VT_RECORD).");
    }

    // varReceiver.variant_part.variant_union.record_data.pvRecord points
    // to the unmanaged ManagedUDT structure which was first allocated
    // via CoTaskMemAlloc() inside the GetUDTVariant() API.
    //
    // We now convert this unmanaged structure into a managed version.
    ManagedUDT managed_udt = (ManagedUDT)Marshal.PtrToStructure
        (
          varReceiver.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(varReceiver, 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 CoTaskMemAlloc() which was done in the
    // GetUDTVariant() API.
    //
    // 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();
}

4.3 Note the following important points related to the CallGetUDTVariant() C# function :

  • A VariantStructGeneric structure varReceiver is defined but not initialized.
  • The GetUDTVariant() API is called with varReceiver passed as an out parameter.
  • varReceiver is then tested to check whether it is of type VT_RECORD.
  • The varReceiver.variant_part.variant_union.record_data.pvRecord points to the unmanaged ManagedUDT structure which was first allocated by CoTaskMemAlloc() inside the GetUDTVariant() API.
  • CallGetUDTVariant() then converts this unmanaged structure into its managed counterpart. This is done by using Marshal.PtrToStructure().
  • The field values of the managed ManagedUDT structure are then displayed.
  • Then, we use the GCHandle class to pin varReceiver in memory. This is done so that we can call VariantClear() on varReceiver.
  • The call to VariantClear() on the pointer to varReceiver will free the unmanaged ManagedUDT structure. All fields of this structure which are references to other objects (e.g. BSTRs, SAFEARRAYs) will be cleared as a result of calling VariantClear().
  • VariantClear() will also call Release() on the IRecordInfo interface pointer pointed to by varReceiver.variant_part.variant_union.record_data.pRecInfo.

4.4 The following console output will be displayed by this function :

varReceiver.variant_part.vt == (VarEnum.VT_RECORD).
managed_udt.m_str01 : String from GetUDTVariant() API.
managed_udt.m_int01 : 100

5. In Conclusion.

5.1 This article is an important landmark because finally we have found a way to return a VT_RECORD VARIANT from unmanaged code to managed code.

5.2 Previous standard coding techniques, e.g. using System.Object or using IntPtr together with Marshal.GetObjectForNativeVariant(), do not work with a returned VT_RECORD VARIANT.

5.3 In part 5, I shall demonstrate how to pass a VARIANT to and from unmanaged code (i.e. as a reference parameter).

5.4 Return to Part 3.

5.5 Proceed to Part 5.

 

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 3 « limbioliong - October 2, 2011

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