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

Using VARIANTs in Managed Code Part 3

1. Introduction.

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

1.2 In part 2, we explored techniques for passing a native VARIANT from managed code to unmanaged code.

1.3 Here in part 3, we shall study techniques for passing a native VARIANT (created from managed code) to unmanaged code by reference.

1.4 Such a VARIANT will have its original value marshaled across to unmanaged code. It will then be reverted back to managed code with possibly modified values.

1.5 Just as we did in parts 1 and 2, we shall explore low- and high-level techniques for achieving this end. We shall also continue to use C++ to implement the unmanaged API and use C# for coding the managed application.

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

2.1 Once again, we begin with the low-level technique. The VARIANT is passed by reference to the unmanaged API as a VARIANT pointer. The following is an example C++ API that takes such a parameter :

__declspec(dllexport) void __stdcall ChangeVariant(/*[in, out]*/ VARIANT* pVariant, /*[in]*/ BOOL bChangeType);

Note that the pVariant parameter is not declared with the “const” keyword which means that the API is allowed to make changes to it. The bChangeType parameter indicates whether the variant type of the VARIANT is to be altered as well, making the VARIANT hold a different type from the original (see point 2.3 below).

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

[DllImport(@"MyDLL.dll", CallingConvention = CallingConvention.StdCall)]
private static extern void ChangeVariant(IntPtr pVariant, [MarshalAs(UnmanagedType.Bool)] bool bChangeType);

Here are some important notes concerning the above declaration :

  • The first parameter pVariant is declared as an IntPtr with no MarshalAsAttributes. Therefore pVariant is passed with no interpretation by the interop marshaler.
  • There is also no “ref” keyword used on pVariant which means that the value of pVariant is passed as-is to the API as a number, so to speak.
  • If the “ref” keyword was used, it will imply that we are passing a pointer to a VARIANT that may need to be de-allocated. A brand new VARIANT structure will then need to be re-allocated with new values.
  • In this case, the ChangeVariant() API declaration in point 2.1 would need to have its first parameter declared as a double pointer to a VARIANT (i.e. VARIANT**).
  • This is not necessary because all we want is to have the contents of the original VARIANT changed. Hence all that is required is a single pointer to a VARIANT. And a non-ref IntPtr counterpart C# parameter declaration.

More on this in point 2.4 later.

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

__declspec(dllexport) void __stdcall ChangeVariant(/*[in, out]*/ VARIANT* pVariant, /*[in]*/ BOOL bChangeType)
{
  if (V_VT(pVariant) == VT_BSTR)
  {
    printf ("Before Change : V_VT(pVariant)   : [%d].\r\n", V_VT(pVariant));
    printf ("Before Change : V_BSTR(pVariant) : [%S].\r\n", V_BSTR(pVariant));

    VariantClear(pVariant);

    if (bChangeType)
    {
      V_VT(pVariant) = VT_I4;
      V_I4(pVariant) = 100;
    }
    else
    {
      V_VT(pVariant) = VT_BSTR;
      V_BSTR(pVariant) = ::SysAllocString(L"New string");
    }
  }
}

The following are pertinent points about this API :

  • The API tests to see if the input VARIANT holds a BSTR. If so, it displays the value of the BSTR.
  • It then clears the VARIANT. Hence the originally held BSTR is freed.
  • Then, depending on the value of the boolean parameter bChangeType, it will either completely change the VARIANT to hold an integer of value 100, or let the VARIANT remain a BSTR holder but assigns a new string for it.

2.4 The following is a sample C# client code that calls ChangeVariant() via low-level techniques :

const Int32 SizeOfNativeVariant = 16;

private static void ChangeVariant()
{
  string str = "Old string";
  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.
  ChangeVariant(pVariant, true);

  // Convert the contents of the VARIANT (which may
  // now have changed) back into a System.Object.
  object new_object = Marshal.GetObjectForNativeVariant(pVariant);

  Console.WriteLine("After Change : new_object type  : {0:S}.", new_object.GetType().ToString());
  Console.WriteLine("After Change : new_object value : {0:S}.", new_object.ToString());

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

Here are the main 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 ChangeVariant() API is then called. Now recall from point 2.2 that the VARIANT is passed as an IntPtr as-is with no manipulation by the interop marshaler.
  • Hence, since the IntPtr is the address of the unmanaged VARIANT, the API is called with the pointer to the VARIANT passed as parameter.
  • The API is displays the original type and value and then clears the VARIANT.
  • Then, because we have passed true as the second parameter, the API will change the variant type to VT_I4 (4-byte signed integer value) and assign 100 as the value.
  • When the API returns, we construct a brand new object from the now changed VARIANT by calling Marshal.GetObjectForNativeVariant(). Note well : a brand new object (new_object) is created from the VARIANT.
  • The original str string will not be touched in any way.
  • The new object’s type and value is displayed.
  • The native VARIANT is then freed by first having its contents cleared (using VariantClear()) and then its own memory space recovered (using Marshal.FreeHGlobal()).

Note that the string str and the object new_object are managed objects and are thus subject to garbage collection. We do not need to concern ourselves with their memory recovery.

2.5 The console will display the following output :

Before Change : V_VT(pVariant)   : [8].
Before Change : V_BSTR(pVariant) : [Old string].
After Change : new_object type  : System.Int32.
After Change : new_object value : 100.

Note that the number 8 is the value of VT_BSTR (i.e. the output of V_VT() when used on a VT_BSTR VARIANT).

2.6 Note that because the returned pVariant may be changed from holding a BSTR to an integer, we must not use code like the following :

// Do not do this :
str = (string)Marshal.GetObjectForNativeVariant(pVariant);

If pVariant is changed to hold a VT_I4, the System.InvalidCastException.will be thrown.

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

3.1 In this section, we present a very simple high-level way of passing a VARIANT by reference to an unmanaged API. The API, in fact, is the same as the one we used in section 2.

__declspec(dllexport) void __stdcall ChangeVariant(/*[in, out]*/ VARIANT* pVariant, /*[in]*/ BOOL bChangeType);

3.2 The difference lies in the way we declare this API in C# code :

[DllImport(@"MyDLL.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "ChangeVariant")]
private static extern void ChangeVariantAsObject
(
  [In][Out][MarshalAs(UnmanagedType.Struct)] ref object var,
  [MarshalAs(UnmanagedType.Bool)] bool bChangeType
);

The following is a summary of the important points about the declaration :

  • Because we will be using the same API (albeit with different types used for its parameters), we use the EntryPoint argument for the DllImportAttribute to indicate to the interop marshaler that the API to call is actually ChangeVariant().
  • The first parameter is now a System.Object type to be passed by reference.
  • This means that the object var is expected to be marshaled into and out of the API with the possibility of changes.
  • The MarshalAsAttribute, used with the UnmanagedType.Struct argument, indicates to the interop marshaler to marshal the object var as a VARIANT. Then, because var is to be passed by reference, this VARIANT is to be passed as a pointer.

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

private static void ChangeVariantAsObject()
{
  object obj = (string)("Old string");

  ChangeVariantAsObject(ref obj, false);

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

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

  • The interop marshaler will allocate space in memory for an unmanaged VARIANT structure.
  • This memory need not always be freshly allocated. It may be from a pre-allocated working memory cache.
  • The interop marshaler will then initialize the VARIANT with data from the object obj.
  • Since obj contains a string, a BSTR will be allocated (via SysAllocStringLen()) and the VARIANT will point to this BSTR.
  • The API will then be called. Just like the example given in section 2, a pointer to the VARIANT will be passed as parameter.
  • The API goes through its routine of displaying the current BSTR, clearing the VARIANT and then, because false was passed as the second parameter, the VARIANT is re-assigned as a holder of a BSTR and a new BSTR is assigned to it.
  • When the API returns, the interop marshaler will use the BSTR contained in the VARIANT to re-construct the object obj.
  • The interop marshaler will then call VariantClear() on the VARIANT and because it contains a BSTR, SysFreeString() will be called to free it.

3.5 The console will display the following expected output :

Before Change : V_VT(pVariant)   : [8].
Before Change : V_BSTR(pVariant) : [Old string].
After Change : obj type  : System.String.
After Change : obj value : New string.

4. Limitations.

4.1 The limitation on the passing of structures (User-Defined Types or UDTs) via a VARIANT applies where the VARIANT is passed by reference.

4.2 If a native VARIANT is allocated dynamically using Marshal.AllocHGlobal(), an exception will be thrown when Marshal.GetObjectForNativeVariant() is called to convert the native VARIANT back to a structure.

4.3 If an API that takes a by-reference object variable decorated with a MarshalAsAttribute, e.g. :

private static extern void ChangeRecordVariantAsObject([In][Out][MarshalAs(UnmanagedType.Struct)] ref object var);

the same exception will be thrown when the API is called.

5. In Conclusion.

5.1 I have thus presented the various techniques of exchanging native VARIANTs between managed and unmanaged code by reference.

5.2 Throughout the low-level examples, I have attempted to mimick the way the interop marshaler does its facinating work.

5.3 Once the low-level concepts are solidly grasped, using high-level code constructs can be used with confidence.

5.4 Return to Part 2.

5.5 Return to Part 1.

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 VARIANTs in Managed Code Part 1 « limbioliong - September 7, 2011

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