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

Using VARIANTs in Managed Code Part 1

1. Introduction.

1.1 The VARIANT type is a commonly used structure in unmanaged COM development.

1.2 A VARIANT is meant to be a generic container of a wide variety of COM types (in fact, every type recognized in the sub-set of the COM sub-system known as OLE automation).

1.3 Its counterpart in the managed world is the System.Object type which is also a generic container of all managed types.

1.4 This blog is an improved write-up which supercedes the earlier C# Interop : How to return a VARIANT from an Unmanaged Function.

1.5 In this blog, I aim to demonstrate how to receive VARIANTs from unmanaged APIs. Two techniques (one low-level and the other high-level) are explored and some tips are provided for determining the underlying variant type.

1.6 For demonstrative purposes, we shall use C++ to implement the unmanaged API and use C# for coding the managed application.

2. Technique 1 : Returning a VARIANT from an Unmanaged API to Managed Code via a Pointer.

2.1 The first technique that we shall study is a low-level one. Basically, the VARIANT is returned as a VARIANT pointer and the equivalent C# declaration for the API must indicate a return type of IntPtr. For example :

__declspec(dllexport) VARIANT* __stdcall MyFunction();

The C# declaration for MyFunction() would be something like :

[DllImport(@"MyDLL.dll", CallingConvention = CallingConvention.StdCall)]
static extern IntPtr MyFunction();

2.2 In the C++ implementation of MyFunction(), you must use the CoTaskMemAlloc() API or the GlobalAlloc() API to allocate the memory for the VARIANT. A pointer to this VARIANT is then returned. The following is a sample C++ implementation for MyFunction() :

__declspec(dllexport) VARIANT* __stdcall MyFunction()
{
  VARIANT* pvarRet = (VARIANT*)CoTaskMemAlloc(sizeof(VARIANT));

  VariantInit(pvarRet);
  V_VT(pvarRet) = VT_BSTR;
  V_BSTR(pvarRet) = ::SysAllocString(L"MyFunction() return string.");

  return pvarRet;
}

The above function allocates memory for a VARIANT struct and then set it to contain a BSTR.

2.3 Note that on the C# side, the returned IntPtr will point permanently to the VARIANT. However, C# has no way to use this IntPtr meaningfully unless the VARIANT that it points to is first “converted” to a managed object.

2.4 Note my use of quotations for the word converted. In actual fact, the VARIANT behind the IntPtr is not converted but rather a brand new managed object is created from it.

2.5 To do this, we use the Marshal.GetObjectForNativeVariant() function to instantiate a managed object that serves as the managed representation of the data contained inside the returned VARIANT :

IntPtr pVariant = MyFunction();
object objRet = Marshal.GetObjectForNativeVariant(pVariant);

Hence, based on the C++ code listed in point 2.2 above, “objRet” will contain a managed string.

2.6 Now note that MyFunction() returns a pointer to a VARIANT to the C# client code. This means that the C# client code owns the memory of the VARIANT that is returned via the pointer. As such, the onus is on the client code to ensure that this memory is eventually freed (note that freeing this memory is necessary because it is unmanaged and is thus not subject to garbage collection).

On this note, there are some important memory release issues associated with the use of VARIANTs whether it be within managed or unmanaged code. These are :

  • Always use the VariantClear() Win32 API to clear the data contained inside the returned VARIANT. The returned VARIANT may contain data that has itself been memory-allocated (e.g. BSTR, SAFEARRAY, etc). If so, VariantClear() will release this memory. Or the returned VARIANT may internally contain a pointer to a COM object. If so, VariantClear() must be used to perform an appropriate Release(). Sampe code for this is provided later below.
  • The VARIANT pointed to by the IntPtr must also be memory released. That is, the VARIANT struct itself, which was previously memory-allocated in the C++ code, must be freed. For this, we must use the Marshal.FreeCoTaskMem() function if CoTaskMemAlloc() was used in the C++ implementation for MyFunction() and Marshal.FreeHGlobal() if GlobalAlloc() was used.

2.7 Hence a sample complete C# code that uses MyFunction() should be something like the following :

[DllImport(@"MyDLL.dll", CallingConvention = CallingConvention.StdCall)]
static extern IntPtr MyFunction();

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

private static void UseMyFunction()
{
  IntPtr pVariant = MyFunction();
  object objRet = Marshal.GetObjectForNativeVariant(pVariant);

  VariantClear(pVariant);
  Marshal.FreeCoTaskMem(pVariant);
  pVariant = IntPtr.Zero;
}

3. Technique 2 : Returning a VARIANT from an Unmanaged API with Direct Managed Object Conversion.

3.1 The example given in section 2 required pretty low-level work to be done by the C# coder.

3.2 Beside the call to the MyFunction() API, UseMyFunction() has to call Marshal.GetObjectForNativeVariant() to create a managed object from the returned IntPtr. After that, it has to take charge of clearing the VARIANT (via VariantClear()) followed by clearing the memory occuppied by the VARIANT itself via Marshal.FreeCoTaskMem().

3.3 An alternative to this low-level approach is to use the services of the interop marshaler to perform the object conversion and the memory clearing of the returned VARIANT.

3.4 Let’s say we have the following C++ API :

__declspec(dllexport) void __stdcall MyFunctionReturnObject(/*[out]*/ VARIANT* pVariantReceiver)
{
  VariantInit(pVariantReceiver);
  V_VT(pVariantReceiver) = VT_BSTR;
  V_BSTR(pVariantReceiver) = ::SysAllocString(L"MyFunction() return string.");
}

3.5 The C# declaration for MyFunctionReturnObject() would be something like :

[DllImport(@"MyDLL.dll", CallingConvention = CallingConvention.StdCall)]
static extern void MyFunctionReturnObject([Out] [MarshalAs(UnmanagedType.Struct)] out object pVariantAsObjectReceiver);

Note the use of the MarshalAsAttribute : the UnmanagedType.Struct argument indicates that on the unmanaged side of the API call, a VARIANT is expected. And since the parameter is declared to be an “out” parameter, the VARIANT must be expressed as a pointer.

3.6 Here is a sample call to the MyFunctionReturnObject() API :

private static void MyFunctionReturnObject()
{
  object obj = null;

  MyFunctionReturnObject(out obj);

  Console.WriteLine("Object type : {0:S}.", obj.GetType().ToString());
  Console.WriteLine("Object value : {0:S}.", obj.ToString());
}

Note how simple the call is. Under the covers, the interop marshaler will perform the following :

  • Allocate a buffer large enough for a VARIANT structure. This structure need not be allocated from unmanaged memory. It may also be taken from some temporary working buffer that the interop marshaler has already acquired.
  • A pointer to this buffer is then passed to the MyFunctionReturnObject() API.
  • The API performs its task of initializing the pointer to what it perceives as a VARIANT and assigns to it a new BSTR.
  • When the API returns, the interop marshaler will note that the VARIANT contains a BSTR.
  • It will construct a managed string using the value of the BSTR and then free this BSTR.
  • The new managed string will then be boxed inside a System.Object.
  • The buffer that was used to contain the unmanaged VARIANT will then be recovered. Note that if this buffer was from some pre-acquired temporary working memory, the space that was used for the VARIANT is simply marked for re-use.

3.7 The MyFunctionReturnObject() C# function will produce the following expected console output :

Object type : System.String.
Object value : MyFunction() return string..

4. An Important Limitation.

4.1 Note an important limitation. It is not possible to return a VT_RECORD VARIANT to managed code.

4.2 If Marshal.GetObjectForNativeVariant() was called on a returned IntPtr that points to such a VARIANT, an ArgumentException will be thrown.

4.3 Hence it is not possible to return a VARIANT that contains a User-Defined Type (i.e. a structure).

5. The Managed Object is Independent of the VARIANT.

5.1 Note that once a managed object has been created from a VARIANT, the managed object is totally independent of the VARIANT from which it was constructed.

5.2 The data inside the VARIANT is merely used to create the equivalent managed object. And the data inside the VARIANT is and will always be unmanaged.

6. Determining the Variant Type of a VARIANT.

6.1 The ability to detect the Variant Type of a VARIANT can be a useful coding construct.

6.2 Unfortunately, there are no Marshal class methods that can help us determine this.

6.3 I personally deviced 2 ways to accomplish this :

  • By way of an unmanaged API.
  • By defining a managed structure that mimicks a VARIANT.

These techniques will be expounded in their own sections below.

7. Determining the Variant Type by way of an Unmanaged API.

7.1 To determine the variant type by way of an unmanaged API, we can use the following simple API :

__declspec(dllexport) VARTYPE __stdcall ReturnVariantType (/*[in]*/ VARIANT* pVariant)
{
  return V_VT(pVariant);
}

It takes a pointer to a VARIANT and simply returns the variant type using the V_VT() macro.

7.2 The following is how we would declare the API in C#

[DllImport(@"MyDLL.dll", CallingConvention = CallingConvention.StdCall)]
private static extern UInt16 ReturnVariantType([In] IntPtr pVariant);

7.3 The following is a sample function demonstrating how to use ReturnVariantType(). It is adapted from the C# function UseMyFunction() which we have seen previously :

private static void UseMyFunction_and_DetermineVariantType()
{
  IntPtr pVariant = MyFunction();
  object objRet = Marshal.GetObjectForNativeVariant(pVariant);

  VarEnum vt = (VarEnum)ReturnVariantType(pVariant);

  VariantClear(pVariant);
  Marshal.FreeCoTaskMem(pVariant);
  pVariant = IntPtr.Zero;
}

When the above code is run, vt will be set to VarEnum.VT_BSTR.

8. Determining the Variant Type by way of a Managed VARIANT Structure.

8.1 This technique relies on us defining in C# code a structure that resembles the unmanaged VARIANT :

[StructLayout(LayoutKind.Sequential)]
public struct Variant
{
  public ushort vt;
  public ushort wReserved1;
  public ushort wReserved2;
  public ushort wReserved3;
  public Int32 data01;
  public Int32 data02;
}

For our purposes, the most important field of this managed version of the VARIANT type is vt.

8.2 The other fields are not important to us and so we use blittable types to represent them. Using blittable types ensures that, whatever values they hold, they will always be treated as non-reference-types. Hence they do not ever need memory releasing even if their values can actually be interpreted as pointers to BSTRs or SAFEARRAYs etc.

8.3 The following is a sample C# function that uses the managed Variant structure to determine the variant type :

private static void UseMyFunction_and_DetermineVariantType2()
{
  IntPtr pVariant = MyFunction();
  object objRet = Marshal.GetObjectForNativeVariant(pVariant);

  Variant v = (Variant)Marshal.PtrToStructure(pVariant, typeof(Variant));
  VarEnum vt = (VarEnum)(v.vt);

  VariantClear(pVariant);
  Marshal.FreeCoTaskMem(pVariant);
  pVariant = IntPtr.Zero;
}

In the above code, the pointer to an unmanaged VARIANT, pVariant, is used to create a managed Variant structure. This is done by calling the Marshal.PtrToStructure() method.

8.4 A managed Variant structure will be instantiated using values from the unmanaged VARIANT structure. Because all fields of the Variant structure are blittable, the bytes of the unmanaged VARIANT are blitted to this managed structure. No interpretation of the fields of the unmanaged VARIANT is needed.

8.5 Hence, when the managed Variant structure v is completely initialized, v.vt will contain the variant type just like an unmanaged VARIANT would. In the case of the above example, v.vt will be set to VarEnum.VT_BSTR as usual.

8.6 Now, if we have a look at the other field values of the v structure, its data01 field will actually hold an integral value which is actually a pointer to the BSTR that was allocated inside MyFunction(). However, because data01 is taken as an Int32, no new managed string is allocated when Marshal.PtrToStructure() was called.

9. In Conclusion.

9.1 I hope the reader has benefitted from this discussion of using VARIANTs in managed code. We have studied how to receive unmanaged VARIANTs from unmanaged code.

9.2 We have also touched a little on how to construct a managed version of a VARIANT so as to obtain the variant type.

9.3 In part 2, I shall discuss how to create unmanaged VARIANT structures out of managed objects for passing to unmanaged APIs.

9.4 Proceed to Part 2.

9.5 Proceed to Part 3.

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

9 thoughts on “Using VARIANTs in Managed Code Part 1

  1. Your article is superb, but appears to only handle pointers to VARIANTs returned by unmanaged code. Is it possible to handle VARIANTs returned by value by unmanaged code? That is what an ActiveX COM object that I am working with is doing. My C# code is able to obtain simple scalars [int, float] from the ActiveX COM object, but the C# “object” returned indicates NULL. Can a C# program accept a VARIANT by value from an ActiveX COM object?

    Posted by Frank Natoli | August 5, 2014, 11:46 pm
    • Frank, did you ever figure out how to return a VARIANT by value? I’m in the same boat here.

      Posted by Brett | February 3, 2017, 1:17 pm
      • Hello Frank, Brett,

        1. My sincere apologies to Frank for my v late reply. I hope my answer below for Brett will help you too.

        2. In answer to Brett, yes. Please have a look at :

        Defining a VARIANT Structure in Managed Code Part 1
        https://limbioliong.wordpress.com/2011/09/08/defining-a-variant-in-managed-code-part-1/

        Defining a VARIANT Structure in Managed Code Part 2
        https://limbioliong.wordpress.com/2011/09/19/defining-a-variant-structure-in-managed-code-part-2/

        3. Using the VariantStructGeneric structure defined in “Defining a VARIANT Structure in Managed Code Part 2″, the following code demonstrates how you can pass a VARIANT structure as a return value from an unmanaged C++ exported function to managed code :

        3.1 Listed below is a C++ exported function :

        VARIANT __stdcall GetVariant()
        {
        VARIANT var;

        VariantInit(&var);
        V_VT(&var) = VT_BSTR;
        V_BSTR(&var) = ::SysAllocString(L”Hello From C++”);

        return var;
        }

        3.2 Listed below is a C# client app :

        [DllImport(“UnmanagedDLL.dll”, EntryPoint = “GetVariant”, CallingConvention = CallingConvention.StdCall)]
        public static extern VariantStructGeneric GetVariant();

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

        static void DoTest()
        {
        VariantStructGeneric var = GetVariant();

        IntPtr pbstr = var.variant_part.variant_union.pointer_data;
        string str = Marshal.PtrToStringBSTR(pbstr);
        // str will now contain “Hello From C++”.

        // This client app owns the VARIANT returned from GetVariant().
        // Hence it is responsible to free its contents via VariantClear().
        //
        // Allocate a GCHandle so that we can pin the
        // Variant Structure in memory.
        GCHandle gch = GCHandle.Alloc(var, GCHandleType.Pinned);
        // Use the GCHandle.AddrOfPinnedObject() method
        // to obtain a pointer to the (pinned) Variant Structure.
        IntPtr pVariant = gch.AddrOfPinnedObject();

        VariantClear(pVariant);

        gch.Free();
        }

        4. Hope the above will be helpful.

        – Bio.

        Posted by Lim Bio Liong | February 3, 2017, 4:19 pm
  2. I was wondering if you have any literature around returning a non-pointer Variant from c++ to c#. It looks as though you only covered returning a pointer to a variant.

    Posted by Brett | February 3, 2017, 1:16 pm

Trackbacks/Pingbacks

  1. Pingback: C# Interop : How to return a VARIANT from an Unmanaged Function « limbioliong - September 4, 2011

  2. Pingback: Using VARIANTs in Managed Code Part 2 « limbioliong - September 5, 2011

  3. Pingback: Using VARIANTs in Managed Code Part 3 « limbioliong - September 7, 2011

  4. Pingback: Defining a VARIANT in Managed Code Part 1 « limbioliong - September 8, 2011

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

Leave a comment