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

C# Interop : How to return a VARIANT from an Unmanaged Function

1. To return a VARIANT from an unmanaged API, it must be 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. In the C++ implementation of MyFunction(), you must use the CoTaskMemAlloc() API to allocate the memory for a 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 memry for a VARIANT struct and then set it to contain a BSTR.

3. On the C# side, use the Marshal.GetObjectForNativeVariant() function to obtain 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 example listed in point 2 above, “objRet” will contain a string.

4. There are some important memory release issues associated with the exchange of VARIANTs between managed and unmanaged code. These are :

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

4.2 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. This matches the use of CoTaskMemAlloc() which was used in the C++ implementation for MyFunction().

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);

static void Main(string[] args)
{
  IntPtr pVariant = MyFunction();
  object objRet = Marshal.GetObjectForNativeVariant(pVariant);

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

5. Note an important limitation. It is not possible to return a VT_RECORD VARIANT to managed code. An ArgumentException will be thrown if this is attempted when you call Marshal.GetObjectForNativeVariant(). Hence it is not possible to return a VARIANT that contains a User-Defined Type (i.e. a structure).

6. Note that this blog has been superceded by a newer series of write-ups on using VARIANTs in managed code. Please refer to : Using VARIANTs in Managed Code Part 1. More example codes are provided with greater in-depth analysis on VARIANTs.

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

8 thoughts on “C# Interop : How to return a VARIANT from an Unmanaged Function

  1. Thanks for posting this article. It was very helpful. We need to pass a table of Variant from unmanaged code to a managed code and we are facing a serious memory leak issue. We tried all of the pointers in your article but it didn’t help. The issue is very obvious when the data type of the VARIANT is string. Did you encounter this issue before? Do you have any idea how we can resolve this issue?

    Posted by betheresoon | February 18, 2013, 3:01 pm
  2. Hello Betheresoon,

    A few points which may be helpful :

    1. Call SysFreeString() to free the BSTRs that are contained in the VARIANTs. This must be done on the unmanaged side if the VARIANT parameter is an “in” parameter.

    2. Alternatively, you may also use VariantClear() to clear the BSTR in a VARIANT. However, do not call both SysFreeString() and VariantClear(). Just use one of them.

    3. Note well that BSTR caching could be happening. Please see the section “A Word About BSTR Caching” in my article “Using BSTR in Managed Code Part 1”. Or search Google for “BSTR Caching”.

    4. Basically, for optimization purposes, the COM sub-system may cache memory previously used and freed by BSTRs.

    5. To turn off this COM feature, you need to set the following environment variable : “OANOCACHE” to value “1″. This will turn off BSTR caching for all applications that uses the BSTR APIs.

    – Bio.

    Posted by Lim Bio Liong | February 19, 2013, 3:26 am
  3. Hi Bio,

    Thank you for the hints. I tried these but they didn’t help. Basically my issue is located in the managed code part. I was able to reproduce the memory leak behavior without using my unmanaged code. Here is my sample code:

    private void timer1_Tick(object sender, EventArgs e)
    {
    //the variant size
    int variant_size = 16;
    //length of the array
    int count = 1000;
    object obj = new object();
    //allocate memory
    IntPtr pValues = Marshal.AllocCoTaskMem(count * variant_size);
    IntPtr pos = pValues;
    //test with string value
    obj = “This is a test string”;

    for (int ii = 0; ii < count; ii++)
    {
    //convert object to intptr
    Marshal.GetNativeVariantForObject(obj, pos);
    //add the converted pbject to the end of the allocated address
    pos = (IntPtr)(pos.ToInt32() + variant_size);

    }
    //free memory
    VariantClear(pValues);
    Marshal.FreeCoTaskMem(pValues);
    pValues = IntPtr.Zero;

    }

    This sample executes the same steps as my program. I need to return a table of Variants periodically from the unmanaged code to the managed code part. When I run this sample, I can see the memory increasing very quickly. I feel I'm missing something but I don't know what exactly. Do you have any ideas?

    Posted by betheresoon | February 19, 2013, 8:59 am
  4. I think I found it.

    pos = pValues;
    for (int ii = 0; ii < count; ii++)
    {
    VariantClear(pos);
    //add the converted pbject to the end of the allocated address
    pos = (IntPtr)(pos.ToInt32() + variant_size);

    }

    Posted by betheresoon | February 19, 2013, 2:01 pm
  5. The memory consumption is better but there is still a leak. Any ideas?

    Posted by betheresoon | February 19, 2013, 3:00 pm
  6. and when I run my stress test, it is very obvious

    Posted by betheresoon | February 19, 2013, 5:36 pm
    • Hello BETHERESOON,

      The call to VariantClear() is definitely necessary as it serves to free the memory occupied by the BSTR which is pointed to by a VARIANT.

      Calling VariantClear() in the loop to clear each VARIANT created by Marshal.GetNativeVariantForObject() is correct and necessary as far as memory recovery on a BSTR VARIANT is concerned.

      I have no comments on the nature of the other memory leaks that you mentioned.

      – Bio.

      Posted by Lim Bio Liong | February 20, 2013, 8:50 am

Trackbacks/Pingbacks

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