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

Defining a VARIANT Structure in Managed Code Part 2

1. Introduction.

1.1 In part 1 of this series of blogs, I introduced the concept of the customized VARIANT structure defined completely in managed code.

1.2 However, we know that these customized structures will have limited re-usability. They are also non-interchangeable because they are completely different types.

1.3 What we need is a generic VARIANT structure that closely mimicks its unmanaged counterpart, complete with all the fields : vt, wReserved1, wReserved2, wReserved3 and the __VARIANT_NAME_3 union.

1.4 Here in part 2, I shall attempt to do exactly this with the definition of several structures together with many example codes.

2. The Generic VARIANT Structure Defined in Managed Code.

2.1 The structures listed below provide the basis for such a definition :

[StructLayout(LayoutKind.Sequential)]
public struct CY_internal_struct_01
{
    public UInt32 Lo;
    public Int32 Hi;
}

[StructLayout(LayoutKind.Explicit)]
public struct CY
{
    [FieldOffset(0)]
    public CY_internal_struct_01 cy_internal_struct_01;
    [FieldOffset(0)]
    public long int64;
}

[StructLayout(LayoutKind.Sequential)]
public struct __tagBRECORD
{
    public IntPtr pvRecord;
    public IntPtr pRecInfo;
}

[StructLayout(LayoutKind.Explicit)]
public struct VariantUnion
{
    [FieldOffset(0)]
    public byte ubyte_data;  // This is the BYTE in C++.
    [FieldOffset(0)]
    public sbyte sbyte_data;  // This is the CHAR in C++.
    [FieldOffset(0)]
    public UInt16 uint16_data; // This is the USHORT in C++.
    [FieldOffset(0)]
    public Int16 int16_data;  // This is the SHORT in C++. Also used for the VARIANT_BOOL.
    [FieldOffset(0)]
    public UInt32 uint32_data; // This is the ULONG in C++. Also for the UINT.
    [FieldOffset(0)]
    public Int32 int32_data;  // This is the LONG in C++. Also used for the SCODE, the INT.
    [FieldOffset(0)]
    public ulong ulong_data;  // This is the ULONGLONG in C++.
    [FieldOffset(0)]
    public long long_data;   // This is the LONGLONG in C++.
    [FieldOffset(0)]
    public float float_data;  // This is the FLOAT in C++.
    [FieldOffset(0)]
    public double double_data; // This is the DOUBLE in C++. Also used for the DATE.
    [FieldOffset(0)]
    public IntPtr pointer_data;// Used for BSTR and all other pointer types.
    [FieldOffset(0)]
    public CY cy_data;  // This is the CY structure in C++.
    [FieldOffset(0)]
    public __tagBRECORD record_data; // This is the __tagBRECORD structure in C++.
};

[StructLayout(LayoutKind.Sequential)]
public struct VariantStructure
{
    public ushort vt;
    public ushort wReserved1;
    public ushort wReserved2;
    public ushort wReserved3;
    public VariantUnion variant_union;
}

[StructLayout(LayoutKind.Sequential)]
public struct Decimal_internal_struct_01
{
    public byte scale;
    public byte sign;
}

[StructLayout(LayoutKind.Explicit)]
public struct Decimal_internal_union_01
{
    [FieldOffset(0)]
    public Decimal_internal_struct_01 decimal_internal_struct_01;
    [FieldOffset(0)]
    public ushort signscale;
}

[StructLayout(LayoutKind.Sequential)]
public struct Decimal_internal_struct_02
{
    public UInt32 Lo32;
    public UInt32 Mid32;
}

[StructLayout(LayoutKind.Explicit)]
public struct Decimal_internal_union_02
{
    [FieldOffset(0)]
    public Decimal_internal_struct_02 decimal_internal_struct_02;
    [FieldOffset(0)]
    public ulong Lo64;
}

[StructLayout(LayoutKind.Sequential)]
public struct DecimalStructure
{
    public ushort wReserved;
    public Decimal_internal_union_01 decimal_internal_union_01;
    public UInt32 Hi32;
    public Decimal_internal_union_02 decimal_internal_union_02;
}

[StructLayout(LayoutKind.Explicit)]
public struct VariantStructGeneric
{
    [FieldOffset(0)]
    public VariantStructure variant_part;
    [FieldOffset(0)]
    public DecimalStructure decimal_part;
}

The main structure in the code listing above is VariantStructGeneric. This structure subsumes all the preceeding ones. And just like its unmanaged VARIANT counterpart, it is a union which consists of 2 mutually-exclusive parts : VariantStructure and DecimalStructure.

In C#, a union is constructed out of judicious use of the StructLayoutAttribute (together with the LayoutKind.Explicit argument) and the FieldOffsetAttribute.

2.2 The VariantStructure structure is a managed representation of the unmanaged __tagVARIANT structure that we have met in part 1. The DecimalStructure structure is the other part of the union which corresponds to the unmanaged DECIMAL structure.

2.3 Note that for the rest of this blog, we shall not be discussing DecimalStructure any further. We shall instead focus our attention on VariantStructure.

2.4 As mentioned, VariantStructure is modeled after the unmanaged __tagVARIANT structure. The first 4 fields are familiar and they have been expounded in detail in part 1. The 5th member, of type VariantUnion is modeled after the __VARIANT_NAME_3 union.

2.5 The largest types used in VariantUnion are ulong_data, long_data, double_data, CY and __tagBRECORD, each of which takes up 8 bytes. Hence VariantUnion is of size 8 bytes.

2.6 And VariantStructure (together with vt, wReserved1, wReserved2, wReserved3 and VariantUnion) adds up to 16 bytes. The VariantStructGeneric structure is thus a complete replica of the unmanaged VARIANT structure.

3. Demonstrating the use of the VariantStructGeneric Structure.

3.1 For the rest of this blog I shall be demonstrating the use of the VariantStructGeneric structure through many example codes. Some explanatory notes will be provided for the first example which serves as a kind of template for the others (the only difference being that in the other examples, the variant type is different and a different member of VariantUnion is used to hold the contained data).

3.2 This is with the exception of examples 6 and 7 where :

  • The common VariantStructGeneric.variant_part.variant_union.pointer_data field is used generically to hold a pointer to referenced data : a BSTR and a SAFEARRAY respectively.
  • The VariantClear() API is used effectively to clear the referenced data within the VARIANT.

3.3 I shall also make extensive use of the DisplayContentsOfVariant() API which we have used in part 1. The code for this API is listed below :

__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 : [%llu].\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.4 Its C# declaration is as follows :

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

4. Example 1 : Displaying a VARIANT Containing a BYTE.

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

private static void DisplayByteVariantGeneric()
{
    VariantStructGeneric Variant = new VariantStructGeneric();
    byte byteData = 0xFF;

    Variant.variant_part.vt = (ushort)(VarEnum.VT_UI1);
    Variant.variant_part.variant_union.ubyte_data = 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();
}

The following points summarizes the above function :

  • A VariantStructGeneric structure name Variant is instantiated.
  • The vt field of the variant_part member is set to value VarEnum.VT_UI1 indicating an unsigned integer of 1 byte.
  • The byte value is then set to the value in byteData (i.e. 0xFF).
  • We then use the services of the GCHandle class to pin the Variant in memory and then obtain a pointer to it.
  • The pointer is then passed as parameter to the DisplayContentsOfVariant() API.
  • Because the VariantStructGeneric structure is a complete replica of the unmanaged VARIANT structure, as far as the API is concerned, it will receive a pointer to a VARIANT.
  • The API will duely display the value of the incoming VARIANT : 0xFF.
  • The VariantClear() API will then be used to clear the VARIANT.
  • The GCHandle is then freed.

5. Example 2 : Displaying a VARIANT Containing a USHORT.

private static void DisplayUShortVariantGeneric()
{
    VariantStructGeneric Variant = new VariantStructGeneric();
    ushort usData = 0xFFFF;

    Variant.variant_part.vt = (ushort)(VarEnum.VT_UI2);
    Variant.variant_part.variant_union.uint16_data = usData;

    // 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();
}

6. Example 3 : Displaying a VARIANT Containing a LONGLONG.

private static void DisplayULongLongVariantGeneric()
{
    VariantStructGeneric Variant = new VariantStructGeneric();
    ulong ulongData = 0xFFFFFFFFFFFFFFFF;

    Variant.variant_part.vt = (ushort)(VarEnum.VT_UI8);
    Variant.variant_part.variant_union.ulong_data = ulongData;

    // 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();
}

7. Example 4 : Displaying a VARIANT Containing a Float.

private static void DisplayFloatVariantGeneric()
{
    VariantStructGeneric Variant = new VariantStructGeneric();
    float fData = 0.123F;

    Variant.variant_part.vt = (ushort)(VarEnum.VT_R4);
    Variant.variant_part.variant_union.float_data = fData;

    // 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();
}

8. Example 5 : Displaying a VARIANT Containing a Double.

private static void DisplayDoubleVariantGeneric()
{
    VariantStructGeneric Variant = new VariantStructGeneric();
    double dblData = 0.123456;

    Variant.variant_part.vt = (ushort)(VarEnum.VT_R8);
    Variant.variant_part.variant_union.double_data = dblData;

    // 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();
}

9. Example 6 : Displaying a VARIANT Containing a BSTR.

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

    Variant.variant_part.vt = (ushort)(VarEnum.VT_BSTR);
    Variant.variant_part.variant_union.pointer_data = 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();
}

Note that for this example, the generic VariantUnion.pointer_data member is used to hold the BSTR (which is a pointer). There is no specific bstr_data member in VariantUnion to point to the BSTR. This is because all pointer types can be represented by the generic VariantUnion.pointer_data member.

Also, VariantClear() API is finally put to good use : it will clear the BSTR that is contained inside the variant.

10. Example 7 : Displaying a VARIANT Containing a SAFEARRAY of Integers.

The DisplaySafeArrayVariantGeneric() C# function listed below demonstrates the use of the VariantStructGeneric structure to contain a SAFEARRAY of 32-bit signed integers. It uses the helper function CreateSafeArrayFromManagedArrayOfIntegers() to create a SAFEARRAY out of a managed array 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 DisplaySafeArrayVariantGeneric()
{
    VariantStructGeneric Variant = new VariantStructGeneric();
    Int32[] managed_array_of_integers = new Int32[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    IntPtr pAafeArrayOfIntegers = CreateSafeArrayFromManagedArrayOfIntegers(managed_array_of_integers);

    Variant.variant_part.vt = (ushort)(VarEnum.VT_ARRAY | VarEnum.VT_I4);
    Variant.variant_part.variant_union.pointer_data = pAafeArrayOfIntegers;

    // 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();
}

Just like the BSTR example, the generic VariantUnion.pointer_data member is used to hold the pointer to the SAFEARRAY. There is no specific safearray_data member in VariantUnion to point to it. All pointer types can be represented by the generic VariantUnion.pointer_data member.

And just like the BSTR example, the VariantClear() API used in DisplaySafeArrayVariantGeneric() will clear the unmanaged SAFEARRAY and free it.

11. In Conclusion.

11.1 I certainly hope that the reader has benefitted from this rather long post.

11.2 Although undoubtedly complex, the unmanaged VARIANT structure can be replicated in managed code and used effectively.

11.3 In another series of blogs that I am now planning to write, I shall show how VariantStructGeneric can be used to contain a VT_RECORD VARIANT and exchanged with unmanaged code.

11.4 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.

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: