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

Using VARIANTs in Managed Code Part 2

1. Introduction.

1.1 In part 1, we discussed how to receive native VARIANTs from unmanaged code.

1.2 Here in part 2, we will explore techniques for passing a native VARIANT from managed code to unmanaged code.

1.3 Both low- and high-level techniques will be studied. We shall also peer down into the low-level activities of the interop marshaler.

1.4 Just as we did in part 1, we shall use C++ to implement the unmanaged API and use C# for coding the managed application.

2. Technique 1 : Passing a VARIANT to an Unmanaged API via a Pointer.

2.1 The first technique that we shall study is the low-level one. The VARIANT is passed to the unmanaged API as a VARIANT pointer. The following is an example C++ API that takes such a parameter :

__declspec(dllexport) void __stdcall DisplayVariant(/*[in]*/ const VARIANT* pVariant);

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

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

2.3 The following is a simple C++ implementation of DisplayVariant() :

__declspec(dllexport) void __stdcall DisplayVariant(/*[in]*/ const VARIANT* pVariant)
{
  if (V_VT(pVariant) == VT_BSTR)
  {
    printf ("BSTR contained inside variant : [%S].\r\n", V_BSTR(pVariant));
  }
}

This function takes a pointer to a VARIANT as an “in” parameter.The input VARIANT must be treated as read-only. Hence the use of the const keyword.

The function tests to see if the input VARIANT contains a BSTR. If so, it displays the BSTR on the console output.

2.4 The following is a sample C# client code that calls DisplayVariant() :

const Int32 SizeOfNativeVariant = 16;

private static void DisplayVariant()
{
  string str = "Hello World";
  IntPtr pVariant = IntPtr.Zero;

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

  // Use the string inside str to construct
  // a native VARIANT that contains a BSTR
  // with the same value as str.
  Marshal.GetNativeVariantForObject(str, pVariant);

  // Call the API.
  DisplayVariant(pVariant);

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

Note that I have included a constant integer declaration SizeOfNativeVariant (equals 16) for use in allocating the number of bytes for a VARIANT structure.

Here are some pertinent points connected with the code above :

  • A managed string str is created and initialized.
  • A native VARIANT is allocated in unmanaged memory using Marshal.AllocHGlobal().
  • The string inside str is used to initialize the native VARIANT to contain a BSTR with the same value as str. This is done by using Marshal.GetNativeVariantForObject().
  • The DisplayVariant() API is then called with the pointer to the VARIANT passed as parameter.
  • Note that the VARIANT is passed to the API as an “in” read-only parameter. Hence the API is not expected to modify it nor to free it. The VARIANT remains owned by the caller side and it is the caller that is responsible for freeing it.
  • After the API is called, we clear the contents of the unmanaged VARIANT using VariantClear(). The BSTR that is held by the VARIANT will now be freed.
  • Thereafter, the memory space occuppied by the unmanaged VARIANT is itself freed by using Marshal.FreeHGlobal().

2.5 The code above demonstrates how we have to control each step of the processing of the VARIANT : from memory allocation (Marshal.AllocHGlobal()) to initialization (Marshal.GetNativeVariantForObject()) to content clearing (VariantClear()) to final deallocation (Marshal.FreeHGlobal()).

2.6 The next section demonstrates a technique that achieves the same end albeit by using a high-level approach.

3. Technique 2 : Passing a System.Object to an Unmanaged API with Direct VARIANT Conversion.

3.1 In this section, we present a delightfully simple way of passing a VARIANT to an unmanaged API. The following is an example of such a C++ API :

__declspec(dllexport) void __stdcall DisplayVariantAsObject(/*[in]*/ const VARIANT var)
{
  if (V_VT(&var) == VT_BSTR)
  {
    printf ("BSTR contained inside variant : [%S].\r\n", V_BSTR(&var));
  }
}

In spirit, the API performs the same action as that of the earlier DisplayVariant() API. However, this time, the VARIANT parameter is not a pointer.

3.2 Now how do we declare such an API in C# ? The following is an example :

[DllImport(@"MyDLL.dll", CallingConvention = CallingConvention.StdCall)]
private static extern void DisplayVariantAsObject([In] [MarshalAs(UnmanagedType.Struct)] object var);

Notice that the parameter is actually a System.Object type. The twist is that a VARIANT still gets passed to the unmanaged API.

The magic lies in the use of the MarshalAsAttribute with the UnmanagedType.Struct argument which instructs the interop marshaler to use the System.Object parameter var to construct an unmanaged VARIANT and then pass it to the API as the parameter. More details on this later.

3.3 The following is a sample C# code that calls the DisplayVariantAsObject() API :

private static void DisplayVariantAsObject()
{
  string str = "Hello World";

  DisplayVariantAsObject(str);
}

See how elegantly simple it is.

3.4 The following is a summary of what takes place behind the scenes throughout the call to the API :

  • Because the API expects an entire VARIANT parameter (and not a pointer to it), this parameter is passed on the stack.
  • The interop marshaler thus constructs the required VARIANT structure using the stack memory.
  • The interop marshaler uses the str string and constructs a BSTR from it in some other memory area. This is done using the SysAllocStringLen() API.
  • The VARIANT is then initialized with its variant type field set to VT_BSTR and its internal data pointing to the allocated BSTR.
  • The DisplayVariantAsObject() API is then called.
  • When the API returns, the interop marshaler calls VariantClear() on the VARIANT. The BSTR is thus cleared by a call to SysFreeString().
  • Then, because the VARIANT was constructed on the stack, its memory is automatically recovered.

3.5 This technique is clean, elegant and uses the services of the interop marshaler for automatic handling of low-level activities.

4. Any Limitations on User-Defined Types ?

4.1 There is no limitation on the passing of structures (User-Defined Types or UDTs) from managed code to unmanaged code via a VARIANT either through a VARIANT pointer or through an object.

4.2 For an example of this, please refer to : Interoping COM Structures.

4.3 However, note that it is not possible to return a VARIANT (containing a UDT) from unmanaged code. This was demonstrated in part 1.

5. In Conclusion.

5.1 In this blog, I have presented 2 techniques for passing VARIANTs to unmanaged code.

5.2 The low-level method is unintuitive but a pattern is certainly observable. The fact that an unmanaged VARIANT is a separate entity from its originating managed object is the key to understanding how it works.

5.3 The high-level technique is great in terms of simplicity. Most of the hard work is done by the interop marshaler. Judicious use of the MarshalAsAttribute paves the way for its success.

5.4 In part 3, I shall expound on how to exchange a VARIANT with unmanaged code by reference. That is, where the VARIANT is passed into unmanaged code and then possibly modified before being returned to managed code.

5.5 Return to Part 1.

5.6 Proceed to Part 3.

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

5 thoughts on “Using VARIANTs in Managed Code Part 2

  1. this seems pretty obsolete nowdays…

    Posted by Monte | December 27, 2012, 10:22 pm
  2. thank you. your blog very good!.
    3. Technique 2 : string str = “Hello World”;
    but my output : “H” ..
    only print string first data.
    please help me.

    Posted by sanghaha | February 1, 2013, 3:07 am
    • Hello SANGHAHA,

      1. Note that a BSTR is a wide character string :

      typedef /* [wire_marshal] */ OLECHAR *BSTR;
      typedef WCHAR OLECHAR;
      typedef wchar_t WCHAR;

      2. Hence, if you are using printf() as shown in the example, then the format specification for the string must be %S (uppercase ‘S’) and not %s.

      – Bio.

      Posted by Lim Bio Liong | February 20, 2013, 9:58 am

Trackbacks/Pingbacks

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

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