//
you're reading...
Interop Marshaling

Returning an Array of Strings from C++ to C# Part 2

1. Introduction.

1.1 In part 1 of this series of blogs, I have provided a rigorous low-level technique for transferring an array of C-style strings from an unmanaged C++ API to a managed C# client application.

1.2 Here in part 2, a relatively simpler method (from the point of view of the C# client application) for achieving the same end is demonstrated.

1.3 It is also the recommended method for receiving an array of anything from unmanaged code : via a SAFEARRAY.

1.4 When a SAFEARRAY is used to receive an array of items from unmanaged code, the interop marshaler will do the job of counterpart managed object creation, copying and eventual deletion of the unmanaged items.

2. Returning an Array of Strings via a SAFEARRAY.

2.1 Similar to the requirement for an API that returns an array of strings via a pointer to an array of character pointers, an API that returns the equivalent of such an array via a SAFEARRAY must also provide the 2 important entities :

  • An actual array of strings.
  • A way to know how many strings there are in the array.

However, this time, in order that the strings be contained in a SAFEARRAY, it must be expressed in the form of BSTRs, not NULL-terminated C-style strings.

2.2 And always remember the memory allocation/deallocation mantra : because the array of strings is returned from the API to the client application, the protocol is such that the API will allocate the memory for the array and its string contents and the client (which is said to own this returned array) is responsible for freeing the array and their string contents.

2.3 On the unmanaged C++ side, the function would be declared as :

__declspec(dllexport) void __stdcall GenerateStringsAndStoreInSafeArray
(
  /*[out]*/ SAFEARRAY** ppSafeArrayOfStringsReceiver
);

2.4 The C++ side would allocate a SAFEARRAY of BSTRs and then pass a pointer to this SAFEARRAY back to the C# side. Here is a sample implementation for GenerateStringsAndStoreInSafeArray() :

__declspec(dllexport) void __stdcall GenerateStringsAndStoreInSafeArray
(
  /*[out]*/ SAFEARRAY** ppSafeArrayOfStringsReceiver
)
{
  BSTR bstrArray[10] = { 0 };

  for (int i = 0; i < 10; i++)
  {
    bstrArray[i] = ::SysAllocString(L"My String.");
  }

  SAFEARRAY* pSafeArrayOfBSTR = NULL;

  CreateSafeArrayFromBSTRArray
  (
    bstrArray,
    10,
    ppSafeArrayOfStringsReceiver
  );

  for (int i = 0; i < 10; i++)
  {
    ::SysFreeString(bstrArray[i]);
  }

  return;
}

The API creates a temporary array of BSTRs and then creates a SAFEARRAY of BSTRs from it. The temporary BSTR array is then freed and the newly created SAFEARRAY is returned as an “out” parameter.

2.5 The API uses a helper function CreateSafeArrayFromBSTRArray() which is listed below :

// CreateSafeArrayFromBSTRArray()
// This function will create a SafeArray of BSTRs using the BSTR elements found inside
// the first parameter "pBSTRArray".
//
// Note well that the output SafeArray will contain COPIES of the original BSTRs
// inside the input parameter "pBSTRArray".
//
long CreateSafeArrayFromBSTRArray
(
  BSTR* pBSTRArray,
  ULONG ulArraySize,
  SAFEARRAY** ppSafeArrayReceiver
)
{
  HRESULT hrRetTemp = S_OK;
  SAFEARRAY* pSAFEARRAYRet = NULL;
  SAFEARRAYBOUND rgsabound[1];
  ULONG ulIndex = 0;
  long lRet = 0;

  // Initialise receiver.
  if (ppSafeArrayReceiver)
  {
    *ppSafeArrayReceiver = NULL;
  }

  if (pBSTRArray)
  {
    rgsabound[0].lLbound = 0;
    rgsabound[0].cElements = ulArraySize;

    pSAFEARRAYRet = (SAFEARRAY*)SafeArrayCreate
    (
      (VARTYPE)VT_BSTR,
      (unsigned int)1,
      (SAFEARRAYBOUND*)rgsabound
    );
  }

  for (ulIndex = 0; ulIndex < ulArraySize; ulIndex++)
  {
    long lIndexVector[1];

    lIndexVector[0] = ulIndex;

    // Since pSAFEARRAYRet is created as a SafeArray of VT_BSTR,
    // SafeArrayPutElement() will create a copy of each BSTR
    // inserted into the SafeArray.
    SafeArrayPutElement
    (
      (SAFEARRAY*)pSAFEARRAYRet,
      (long*)lIndexVector,
      (void*)(pBSTRArray[ulIndex])
    );
  }

  if (pSAFEARRAYRet)
  {
    *ppSafeArrayReceiver = pSAFEARRAYRet;
  }

  return lRet;
}

2.6 Upon function return, the interop marshaler will know how many BSTRs there are inside the SAFEARRAY because a SAFEARRAY, like a C# array self-contains array item count and bounds information (i.e. the dimensions of the array).

2.7 A SAFEARRAY also self-contains the type of data each array element contains. In this case a BSTR.

2.8 The next section discusses the C# client code which, as the reader will undoubtedly notice, is remarkably simple.

3. The C# Client Code.

3.1 The following is how the GenerateStringsAndStoreInSafeArray() API will be declared in C# code :

[DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void GenerateStringsAndStoreInSafeArray
(
  [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)]
  out string[] ManagedStringArray
);

Notice that this time, the “out” parameter for the API is declared as a string array (viz the IntPtr type used in the GenerateStringsAndStoreInStringBuffer() API in part 1). This is certainly much more intuitive to C# developers.

The MarshalAsAttribute attached to the parameter is also important. It indicates that the counterpart parameter for the unmanaged API is a SAFEARRAY of BSTRs.

3.2 The following C# method shows how GenerateStringsAndStoreInSafeArray() can be called in managed code :

static void Call_BySafeArray()
{
  string[] ManagedStringArray = null;

  GenerateStringsAndStoreInSafeArray(out ManagedStringArray);

  for (int i = 0; i < ManagedStringArray.Length; i++)
  {
    Console.WriteLine("{0:S}", ManagedStringArray[i]);
  }
}

It is very simple indeed. Just a direct call to the API. No manual translation of memory locations is needed.

This method, when run, will produce the following console output :

My String.
My String.
My String.
My String.
My String.
My String.
My String.
My String.
My String.
My String.

3.3 What happens under the covers is as follows :

  • The C++ side would allocate a SAFEARRAY of BSTRs and then pass a pointer to this SAFEARRAY back to the C# side.
  • Because SAFEARRAYs are managed by standard Windows APIs, the interop marshaler can determine how many elements are contained in the SAFEARRAY and will know how to extract each BSTR element from the SAFEARRAY.
  • The interop marshaler can use the element count of the SAFEARRAY to pre-create a managed string array of a fixed length.
  • And because BSTRs are also managed by standard Windows APIs, the interop marshaler will know how to access the string of each BSTR and to create C# strings from it.
  • Each newly created string is then inserted into the array.
  • As each managed string is created from each BSTR, the interop marshaler is able to destroy the BSTR using ::SysFreeString().
  • After that, the interop marshaler is able to destroy the SAFEARRAY itself (::SafeArrayDestroy()).
  • The managed string array is then ready for use by the client code.

3.4 Compare the work done by the interop marshaler as mentioned in point 3.3 above with the marshaling work done manually in part 1. They are about the same except that this time, things are done automatically for us.

3.5 The down side of this approach is that it requires the C++ developer to know how to manipulate SAFEARRAYs and BSTRs which can be complicated.

4. In Conclusion.

4.1 I do hope that the reader has benefitted from this 2 part blog series expounding how to return an array of strings from C++ to C#.

4.2 Given the choice, always use the SAFEARRAY of BSTR approach. It is safe, reliable and (relatively) simple.

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 “Returning an Array of Strings from C++ to C# Part 2

  1. Thanks a lot for this helpful post.

    Posted by noname | December 5, 2011, 2:53 pm
  2. Thanks! This post and the one before has helped me quite a bit in understanding this. One question, though…what if you don’t know how many strings you’re planning to return? In your post, you have a ‘set’ number of BSTRs…but if the number of strings is unknown at compile time, this doesn’t work as well. 🙂

    Posted by Kenn GuilstorfKenn | September 5, 2013, 1:53 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: