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

Defining a VARIANT Structure in Managed Code Part 1

1. Introduction.

1.1 Not content with the 3 part “Using VARIANTs in Managed Code” write-up, I decided to start a new series of articles that expound further on the VARIANT structure.

1.2 This time, the goal is to study how we can represent a VARIANT structure in managed code. Along with this, various techniques for working with such a “managed” VARIANT are also explored.

1.3 Although one would seldom find the occasion to construct and use a VARIANT-like structure in managed code except maybe in custom marshaling, it can be a valuable asset in overcoming the known limitation of exchanging a VT_RECORD VARIANT with unmanaged code. This is certainly a subject I will want to write on one of these days.

1.4 Nevertheless, even if the reader will never ever require the use of such a structure, it is my hope that the techniques and examples presented here will further strengthen the reader’s understanding of sharing structures with unmanaged code.

2. Defining a VARIANT Structure in Managed Code.

2.1 To view the VARIANT structure in C++, refer to OAIdl.h. On my machine where I am using Visual Studio 2008, this structure is found in c:\Program Files\Microsoft SDKs\Windows\v6.0A\Include\OAIdl.h.

2.2 The VARIANT structure is essentially a union of 2 types :

  • A __tagVARIANT structure.
  • A DECIMAL structure.

Although the DECIMAL structure is part of the VARIANT, we will not concern ourselves with this structure throughout this blog. We will concentrate instead on the __tagVARIANT structure which is the more often used part of the union.

2.3 The __tagVARIANT structure itself is composed of 5 members :

  • vt (type VARTYPE).
  • wReserved1 (type WORD).
  • wReserved2 (type WORD).
  • wReserved3 (type WORD).
  • __VARIANT_NAME_3 (a huge union of various types).

For practical purposes, when defining a VARIANT structure in managed code, we model it after __tagVARIANT.

2.4 The first 4 fields of __tagVARIANT are unsigned shorts and hence are easily represented in managed code as ushort types.

2.5 The __VARIANT_NAME_3 union is certainly a handful. However, note that in practice, it is not entirely necessary to formally and meticulously define each and every field member of such a union to ensure complete compatibility.

2.6 What we can do for now is to “split” up this union into artificial parts that will suit our purposes. Here in part 1, I shall be working with the following VARTYPEs :

  • VT_UI1 – byte type.
  • VT_BSTR – BSTR type.
  • (VT_ARRAY | VT_I4)  – SAFEARRAY of 4-byte integers.

I shall demonstrate how to customize the VARIANT struct for each of these variant types. In part 2, I shall show how to build up a generic VARIANT that provides its own union similar to __VARIANT_NAME_3.

3. The C++ API.

3.1 I have defined a C++ API that displays the contents of a VARIANT :

__declspec(dllexport) void __stdcall DisplayContentsOfVariant(/*[in]*/ const VARIANT* pVariant)
{
  if (V_VT(pVariant) == VT_UI1)
  {
    printf ("BYTE contained inside variant : [0x%x].\r\n", V_UI1(pVariant));
  }
  if (V_VT(pVariant) == VT_UI2)
  {
    printf ("Unsigned short contained inside variant : [%d].\r\n", V_UI2(pVariant));
  }
  if (V_VT(pVariant) == VT_UI8)
  {
    printf ("ULONGLONG contained inside variant : [%d].\r\n", V_UI8(pVariant));
  }
  if (V_VT(pVariant) == VT_R4)
  {
    printf ("Float contained inside variant : [%1.4f].\r\n", V_R4(pVariant));
  }
  if (V_VT(pVariant) == VT_R8)
  {
    printf ("Double contained inside variant : [%1.8f].\r\n", V_R8(pVariant));
  }
  else if (V_VT(pVariant) == VT_BSTR)
  {
    printf ("BSTR contained inside variant : [%S].\r\n", V_BSTR(pVariant));
  }
  else if (V_VT(pVariant) == (VT_ARRAY | VT_I4))
  {
        SAFEARRAY* pSafeArrayOfIntegers = V_ARRAY(pVariant);
	// Obtain bounds information of the SAFEARRAY.
	long lLbound = 0;
	long lUbound = 0;

	SafeArrayGetLBound(pSafeArrayOfIntegers, 1, &lLbound);
	SafeArrayGetUBound(pSafeArrayOfIntegers, 1, &lUbound);
	long lDimSize = lUbound - lLbound + 1;	

	// Obtain a copy of each BSTR contained in the SAFEARRAY.
	for (long l = 0; l < lDimSize; l++)
	{
		long rgIndices[1];
		int  iValue = 0;

		rgIndices[0] = l;
		SafeArrayGetElement
		(
			pSafeArrayOfIntegers,
			rgIndices,
			(void FAR*)&iValue
		);

		printf ("SafeArray[%d] : [%d].\r\n", l, iValue);
	}
  }
}

This code tests the vt field of the input VARIANT and, if the type is a recognized one, will format the display of the contents accordingly.

3.2 Its C# declaration is as follows :

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

3.3 We shall be using the above API extensively throughout the examples of this blog.

4. Customizing a VARIANT to contain a Byte Data.

4.1 For this, we can define the following VARIANT struct in managed code :

[StructLayout(LayoutKind.Sequential)]
public struct VariantStructForOneByteData
{
  public ushort vt;
  public ushort wReserved1;
  public ushort wReserved2;
  public ushort wReserved3;
  public byte byteData;
  public byte UnusedData01;
  public byte UnusedData02;
  public byte UnusedData03;
  public IntPtr UnusedData04;
}

The following is a summary of why we define the struct the way we do :

  • The vt, wReserved1, wReserved2 and wReserved3 fields are self-explanatory. They match directly their counterpart fields in the unmanaged VARIANT.
  • The next 5 fields byteData, UnusedData01 through UnusedData04 taken together as a whole will represent the __VARIANT_NAME_3 union.
  • The __VARIANT_NAME_3 union takes up 8 bytes. The members that consume the largest size are : LONGLONG, DOUBLE, CY, DATE, ULONGLONG, DECIMAL, __tagBRECORD. Each of these take up 8 bytes.
  • The other members take up from one byte through 4 bytes.
  • For a VARIANT that contains a VT_UI1 (i.e. a byte), only the first byte of the union matters.
  • The 5 fields byteData, UnusedData01 through UnusedData04 together take up 8 bytes just like __VARIANT_NAME_3 hence they can be taken to represent __VARIANT_NAME_3 albeit only the byteData field is relevant and contains usable data.
  • The other fields UnusedData01 through UnusedData04, as their names imply, are unused. They are defined so that the entire VariantStructForOneByteData structure adds up to 16 bytes which will match the size of an unmanaged VARIANT.
  • Another important point about the structure is that all the members are blittable types which means that an instance of such a structure can be pinned in memory and can be marshaled across to unmanaged code as it is.
  • All fields of the structure are embedded as part of its body in memory. Unmanaged code will be able to reference the fields of the structure using a pointer to it.

4.2 The following is a sample C# code that uses the VariantStructForOneByteData structure :

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

private static void DisplayByteVariant()
{
  VariantStructForOneByteData Variant = new VariantStructForOneByteData();
  byte byteData = 0xFF;

  Variant.vt = (ushort)(VarEnum.VT_UI1);
  Variant.byteData = byteData;

  // Allocate a GCHandle so that we can pin the
  // Variant Structure in memory.
  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();

  DisplayContentsOfVariant(pVariant);

  VariantClear(pVariant);

  gch.Free();
}

Note that I have also provided a declaration for the VariantClear() API in the code block above. It is used in the DisplayByteVariant() function.

The following are important points to note about the DisplayByteVariant() function :

  • A VariantStructForOneByteData structure is allocated.
  • Only its vt and byteData fields are assigned values.
  • A GCHandle object is then created to pin the structure in memory and to obtain a pointer to it.
  • This is possible because all the fields of the structure are blittable types and hence all fields are embedded inside such a structure in memory.
  • The pointer to the structure is then passed to the DisplayContentsOfVariant() API.
  • The API accesses the relevant field of the VARIANT with no difficulty.
  • Thereafter, VariantClear() is used to clear the contents of the VariantStructForOneByteData structure which is taken to be a VARIANT.
  • The allocated GCHandle is then freed.

4.3 This function displays the following console output :

BYTE contained inside variant : [0xff].

4.4 This example is a simple demonstration of how we can manually define a managed equivalent of a VARIANT, set its value and then pass it onto an unmanaged API that indeed expects a VARIANT.

4.5 In the next example, we shift to a higher gear and create a BSTR VARIANT.

5. Customizing a VARIANT to contain a BSTR.

5.1 For this, we define the following structure :

[StructLayout(LayoutKind.Sequential)]
public struct VariantStructForPointerTypes
{
  public ushort vt;
  public ushort wReserved1;
  public ushort wReserved2;
  public ushort wReserved3;
  public IntPtr pVariantData;
  public IntPtr UnusedData;
}

This structure is similar but not quite the same as the earlier VariantStructForOneByteData. Only the first 4 fields are the same. Note the following :

  • Instead of defining a string field decorated with the MarshalAsAttribute with the UnmanagedType.BSTR argument, I have opted to use the IntPtr type.
  • Using the string type would have rendered the structure un-blittable and would have required the services of the interop marshaler.
  • This is undesireable because at runtime, we want to pin this structure in memory and pass a pointer to such a structure as it is to unmanaged code with no processing by the interop marshaler.
  • I have also named the structure as VariantStructForPointerTypes and the relevant field as pVariantData (instead of pBSTR) because it can be re-used when we want to create VARIANTs of other pointer types (e.g. SAFEARRAY, IUnknown and IDispatch pointers).

5.2 The following is a sample C# code that uses the VariantStructForPointerTypes structure :

[DllImport("oleaut32.dll", CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr SysAllocString([MarshalAs(UnmanagedType.LPWStr)] string sz);

private static void DisplayBSTRVariant()
{
  VariantStructForPointerTypes Variant = new VariantStructForPointerTypes();
  IntPtr pbstr = SysAllocString("Hello World");

  Variant.vt = (ushort)(VarEnum.VT_BSTR);
  Variant.pVariantData = pbstr;

  // Allocate a GCHandle so that we can pin the
  // Variant Structure in memory.
  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();

  DisplayContentsOfVariant(pVariant);

  VariantClear(pVariant);

  gch.Free();
}

The DisplayBSTRVariant() function above uses the unmanaged API SysAllocString() which is contained in oleaut32.dll. The C# declaration for this API is included at the top of the code block above.

The following are important points to note about the function :

  • A VariantStructForPointerTypes structure is defined and instantiated.
  • An unmanaged BSTR is created using SysAllocString().
  • The vt field of the structure is assigned the proper VarEnum value of VT_BSTR.
  • The pVariantData field is assigned the pointer value of the BSTR.
  • Just as we did in section 4, we then use the GCHandle class to pin the structure in memory and obtain a pointer to it.
  • The DisplayContentsOfVariant() API is then called.
  • This time, the V_VT() macro will indicate that the VARTYPE of the input VARIANT is VT_BSTR and the V_BSTR() macro returns a pointer to the BSTR that was allocated from managed code.
  • When VariantClear() is called on VariantStructForPointerTypes, the BSTR is indeed cleared.
  • The allocated GCHandle is then freed.
  • Note that the VariantStructForPointerTypes structure is a managed structure and so it will be garbage collected eventually.
  • Although it sort of “points” to the BSTR through its pVariantData field, this field is in actual fact an IntPtr. It is certainly not a string. No memory deallocation will be done through this field. The BSTR has been deallocated by the VariantClear() call.

5.3 This function displays the following console output :

BSTR contained inside variant : [Hello World].

6. Customizing a VARIANT to contain a SAFEARRAY.

6.1 The VARIANT structure VariantStructForPointerTypes can certainly be re-used to hold other VARIANT types that are pointers to other structures. In this section, I will re-use this structure to produce another complex example that involves the SAFEARRAY.

6.2 The following code block contains several code constructs to present an example that uses the VariantStructForPointerTypes structure to hold a SAFEARRAY of integers :

        [StructLayout(LayoutKind.Sequential)]
        struct SAFEARRAYBOUND
        {
            public UInt32 cElements;
            public Int32 lLbound;
        };

        [DllImport("Oleaut32.dll", CallingConvention = CallingConvention.StdCall)]
        private static extern IntPtr SafeArrayCreate(VarEnum vt, uint cDims, SAFEARRAYBOUND[] rgsabound);

        [DllImport("Oleaut32.dll", CallingConvention = CallingConvention.StdCall)]
        private static extern uint SafeArrayPutElement(IntPtr pSafeArray, UInt32[] rgIndices, IntPtr pv);

        private static IntPtr CreateSafeArrayFromManagedArrayOfIntegers(Int32[] managed_array_of_integers)
        {
            SAFEARRAYBOUND[] rgsabound = new SAFEARRAYBOUND[1];

            rgsabound[0].lLbound = 0;
            rgsabound[0].cElements = (uint)managed_array_of_integers.Length;

            IntPtr pSafeArrayOfInt = SafeArrayCreate
            (
                VarEnum.VT_I4, // This signifies the integer type.
                1,  // This signifies 1 dimension.
                rgsabound  // Bounds information.
            );

            // Assign values to the SafeArray from elements of
            // the managed array.
            for (int i = 0; i < managed_array_of_integers.Length; i++)
            {
                UInt32[] rgIndices = new UInt32[1];
                Int32 value = managed_array_of_integers[i];
                GCHandle gch = GCHandle.Alloc(value, GCHandleType.Pinned);

                rgIndices[0] = (UInt32)i;
                SafeArrayPutElement
                (
                    pSafeArrayOfInt,
                    rgIndices,
                    gch.AddrOfPinnedObject()
                );

                gch.Free();
            }

            return pSafeArrayOfInt;
        }

        private static void DisplaySafeArrayVariant()
        {
            VariantStructForPointerTypes Variant = new VariantStructForPointerTypes();
            Int32[] managed_array_of_integers = new Int32[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
            IntPtr pSafeArrayOfIntegers = CreateSafeArrayFromManagedArrayOfIntegers(managed_array_of_integers);

            Variant.vt = (ushort)(VarEnum.VT_ARRAY | VarEnum.VT_I4);
            Variant.pVariantData = pSafeArrayOfIntegers;

            // Allocate a GCHandle so that we can pin the
            // Variant Structure in memory.
            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();

            DisplayContentsOfVariant(pVariant);

            VariantClear(pVariant);

            gch.Free();
        }

Note the following pertinent points concerning the code block above :

  • Because a SAFEARRAY is to be created in managed code, we need to use the SafeArrayCreate() API to create it and to use SafeArrayPutElement() to add elements to it. The SAFEARRAYBOUND structure is also required. Definitions for these are provided.
  • The CreateSafeArrayFromManagedArrayOfIntegers() C# function uses the SAFEARRAY APIs to create and return a SAFEARRAY from a managed integer array.
  • Note that the SAFEARRAY that it produces is a separate entity from the managed array from which it was generated.
  • The DisplaySafeArrayVariant() function instantiates a managed array of 10 integers with values from 1 through 10.
  • The CreateSafeArrayFromManagedArrayOfIntegers() function is then called to create a SAFEARRAY of VT_I4 items.
  • The vt field of the VariantStructForPointerTypes structure is then set to the appropriate value of (VarEnum.VT_ARRAY | VarEnum.VT_I4).
  • The pVariantData field is then made to point to the newly created SAFEARRAY.
  • A GCHandle is then allocated to pin the VariantStructForPointerTypes structure and obtain a pointer to the pinned structure.
  • DisplayContentsOfVariant() is then called.
  • When it returns, VariantClear() is called to clear the contents of the pinned VariantStructForPointerTypes  structure. The SAFEARRAY will be deallocated.
  • Just like the BSTR example, the pVariantData field of the VariantStructForPointerTypes structure is a mere IntPtr that holds the address of the SAFEARRAY. It is not a reference type as far as the CLR is concerned.
  • When the VariantStructForPointerTypes is garbage collected, no deallcation will be done through pVariantData.
  • The SAFEARRAY is deallocated via VariantClear().

6.3 The following console output will be displayed :

SafeArray[0] : [1].
SafeArray[1] : [2].
SafeArray[2] : [3].
SafeArray[3] : [4].
SafeArray[4] : [5].
SafeArray[5] : [6].
SafeArray[6] : [7].
SafeArray[7] : [8].
SafeArray[8] : [9].
SafeArray[9] : [10].

7. Various Other Customizations.

7.1 I encourage the reader to personally attempt to customize the VARIANT structure for various other VARTYPEs, e.g. VT_I2/VT_UI2 (signed/unsigned shorts), VT_R4 (floats), VT_R8 (double).

7.2 Use the various example C# functions that we have studied so far : DisplayByteVariant(), DisplayBSTRVariant(), DisplaySafeArrayVariant() as start-up templates.

8. In Conclusion.

8.1 Through the various examples that we have gone through, I hope the reader will be enlightened with knowledge on how to work with structures and unmanaged code.

8.2 The examples in this part 1 used two customized versions of the VARIANT struct. Although this certainly does not conform to good design for the long term, the purpose behind this is to provide a solid knowledge foundation towards the eventual designing of a generic structure.

8.3 In part 2, I shall attempt to define such a generic version of the VARIANT structure that will ever closer mimick its unmanaged counterpart.

8.4 Proceed to Part 2.

 

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: Defining a VARIANT Structure in Managed Code Part 2 « limbioliong - September 20, 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: