//
you're reading...
BSTR

Using BSTR in Managed Code Part 1

1. Introduction.

1.1 The COM BSTR may be used within managed code for various purposes including the exchanging of strings to and from unmanaged code.

1.2 There are several ways to work with BSTRs and these are listed in summary below :

  • Using Marshal Class methods (e.g. Marshal.PtrToStringBSTR(), Marshal.StringToBSTR(), Marshal.FreeBSTR()).
  • Using Unmanaged APIs (e.g. SysAllocString(), SysFreeString(), etc).
  • Using the Unmanaged VARIANT.

1.3 This blog explores how to use these various facilities to work with BSTRs from within managed code.

2. Using the Marshal Class.

2.1 A BSTR can be created using Marshal.StringToBSTR() and then freed using Marshal.FreeBSTR() as the following example shows :

private static void UseMarshalClass01()
{
  IntPtr pbstr = Marshal.StringToBSTR("Hello World");

  DisplayBSTR(pbstr);

  Marshal.FreeBSTR(pbstr);
}

DisplayBSTR() is an unmanaged C++ API which prints out the incoming BSTR onto the console. Its C# declaration is as follows :

[DllImport(@"TestDLL.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void DisplayBSTR(IntPtr pBstr);

And its C++ definition :

void __stdcall DisplayBSTR(/*[in]*/ BSTR pBstr)
{
  const OLECHAR* pWideString = (const OLECHAR*)pBstr;

  printf("%S.\r\n", pWideString);
}

3. Using Unmanaged BSTR APIs.

3.1 Unmanaged BSTR APIs like SysAllocString(), SysFreeString(), etc may be freely used within managed code. They are fully compatible with their managed counterparts Marshal.StringToBSTR() and Marshal.FreeBSTR() and may even be used together seamlessly.

3.2 In fact, Marshal.StringToBSTR() internally uses SysAllocString() and Marshal.FreeBSTR() internally uses SysFreeString().

3.3 Here is a sample code that demonstrates this :

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

private static void UseSysAllocString()
{
  IntPtr pbstr = SysAllocString("Hello World");

  DispalyBSTR(pbstr);

  Marshal.FreeBSTR(pbstr);
}

Please note the way the SysAllocString() API is declared (via DllImportAttribute). It takes a managed string which is marshaled as an unmanaged NULL-terminated wide character array.

Also note that I have used Marshal.FreeBSTR() to free the SysAllocString() allocated BSTR. This will go through fine.

4. Using VARIANTs.

4.1 The unmanaged VARIANT may also be used to pass a BSTR from managed to unmanaged code. However, this time, we have to rely on the Marshal class to first generate the BSTR (will be explained later).

4.2 The following is a sample C++ API that displays the BSTR contents of a VARIANT :

void __stdcall DisplayBSTRByVARIANT(/*[in]*/ VARIANT* var)
{
  // Check to ensure that the incoming VARIANT
  // contains a BSTR.
  if (V_VT(var) != VT_BSTR)
  {
    return;
  }
  // Extract the BSTR from the VARIANT.
  BSTR pBstr = V_BSTR(var);
  const OLECHAR* pWideString = (const OLECHAR*)pBstr;
  // Display the BSTR.
  printf("%S.\r\n", pWideString);
}

Note that the incoming VARIANT parameter must be expressed as a pointer. This API is declared in C# as follows :

[DllImport(@"TestDLL.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void DisplayBSTRByVARIANT(IntPtr variant);

4.3 Next, we list a sample C# code that uses the above API :

const int SIZE_OF_UNMANAGED_VARIANT = 16;

[DllImport(@"Oleaut32.dll", CallingConvention = CallingConvention.StdCall)]
public static extern uint VariantClear(IntPtr pVarg);

private static void DispalyUsingVariant()
{
  string str = "Hello Variant";
  IntPtr pVariant = IntPtr.Zero;
  // Allocate memory that holds a VARIANT struct.
  pVariant = Marshal.AllocCoTaskMem(SIZE_OF_UNMANAGED_VARIANT);
  // Convert the managed string into a BSTR
  // which is stored inside a VARIANT.
  Marshal.GetNativeVariantForObject((object)str, pVariant);
  // Display the BSTR contained inside the VARIANT.
  DisplayBSTRByVARIANT(pVariant);
  // Clear the VARIANT. This will free the BSTR.
  VariantClear(pVariant);
  // Finally free the memory used by the VARIANT.
  Marshal.FreeCoTaskMem(pVariant);
  pVariant = IntPtr.Zero;
}

The following is a summary of the above code listing :

4.3.1 A managed string “str” (with value “Hello Variant”) is created. This managed string is the one used to generate the BSTR.

4.3.2 In order to use a VARIANT in managed code, memory must be allocated to store such an unmanaged structure. This is done by using Marshal.AllocCoTaskMem(), passing along the value SIZE_OF_UNMANAGED_VARIANT (16 bytes) which is the byte size of a VARIANT. A pointer to this memory location will be returned.

4.3.3 The Marshal.GetNativeVariantForObject() method is used to convert “str” into a BSTR which is then stored into the allocated VARIANT (per point 4.3.2). Besides this, there is no other way (using managed code) to make a VARIANT point to a BSTR.

4.3.4 We then pass the pointer to the VARIANT to the DisplayBSTRByVARIANT() API which will display it on the application’s console.

4.3.5 N.B. Note well that clean up is a two-part process :

  • First the contents of the VARIANT must be cleared by using VariantClear(). This will free up the BSTR that was allocated by the Interop Marshaler when we called Marshal.GetNativeVariantForObject() (point 4.3.3).
  • Next, the VARIANT itself which was allocated by Marshal.AllocCoTaskMem() (point 4.3.2) must be freed.

5. A Word About BSTR Caching.

5.1 As an optimization, the COM sub-system performs caching of memory which is used to allocate BSTRs. This is for fast allocation, re-allocation and de-allocation of BSTRs.

5.2 As a direct result of this, when you free a BSTR (using SysFreeString() or Marshal.FreeBSTR()), the previous contents of the freed BSTR may still be present in memory although that memory may now be re-used for allocating other BSTRs.

5.3 You can see this in action by using the memory viewer and observing the memory area at the address held by a BSTR.

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

5.5 Normally this is not necessary. However, you might want to try it in order to see the result of calls to SysFreeString(), Marshal.FreeBSTR() and VariantClear().

6. In Summary.

6.1 In this part 1 we have covered in detail the use of BSTRs in managed code. More specifically, we have shown how BSTRs can be allocated in managed code and then passed to unmanaged code.

6.2 In part 2, I shall expound on the receiving of BSTRs from unmanaged code.

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

2 thoughts on “Using BSTR in Managed Code Part 1

  1. Hi Bio,

    This articale is very usefull for my work. Thanks very much!

    My C# Client codes as following:

    public static externbool GetDeviceInfo(ITopologyPath pPath, string networkAddress, ref StringBuilder manufacturer);

    It is StringBuilder, not string. Can I use the following codes?

    [MarshalAs(UnmanagedType.BStr)] ref StringBuilder manufacturer

    I also post this issue in the following link:
    http://social.msdn.microsoft.com/Forums/en-US/clr/thread/28d6362d-9026-48ea-afee-fd8f2c7ebee1

    Thanks
    Scott

    Posted by scottq1 | March 30, 2012, 4:35 am
  2. Hi Bio,

    This article is very useful for me, thanks very much!

    My C# Client code as following:
    [DllImport(“DeviceAccess.dll”, CharSet = CharSet.Auto)]
    public static externbool GetDeviceInfo(ref StringBuilder manufacturer);

    Can I modify “ref StringBuilder manufacturer” to “[MarshalAs(UnmanagedType.BStr)] ref StringBuilder hardwareType”?

    I also post this issue into following link:
    http://social.msdn.microsoft.com/Forums/en-US/clr/thread/28d6362d-9026-48ea-afee-fd8f2c7ebee1

    Thanks
    Scott

    Posted by scottq1 | March 30, 2012, 4:39 am

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: