//
you're reading...
Interop Marshaling

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

1. Introduction.

1.1 I’ve previously written a blog entry expounding on ways to return a C-style string from C++ unmanaged code to C#.

1.2 Besides those that return a single string, APIs that return an array of strings are also never short in demand.

1.3 In this 2-part series of blogs I shall be discussing ways to return an array of C-style strings from a C++ API to C#.

1.4 This first part presents a very low-level technique which involves manual marshaling that can be rather convoluted. The aim is to show how memory is transferred from unmanaged code to managed code.

1.5 The second part presents a simpler and recommended method that uses automatic marshaling served by the interop marshaler.

1.6 The techniques presented in the first part serves as a knowledge base for understanding the low-level activities that are performed under the covers by the interop marshaler which is used in the second part.

2. Returning an Array of Strings via a Pointer to an Array of Char Pointers.

2.1 An API that returns an array of strings must provide 2 important entities :

  • A pointer to an array of strings.
  • An interger indicating how many strings there are in the array.

2.2 Furthermore, 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 this memory.

2.3 As mentioned in the introduction, the low-level technique presented in this part does not use the services of the interop marshaler. The marshaling of strings from unmanaged memory to managed memory is done completely manually. This being the case, the way memory is allocated on the C++ side is crucial in ensuring smooth string data transformation (from unmanaged string buffers to C# strings) and eventual memory freeing.

2.4 The delaration of the C++ function should be something like the following :

__declspec(dllexport) void GenerateStringsAndStoreInStringBuffer
(
/*[out]*/ char*** ppStringBufferReceiver,
/*[out]*/ int* piStringsCountReceiver
);

Note well that the first parameter “ppStringBufferReceiver” which is to receive the array of C-style string pointers, is actually a pointer to a pointer to a pointer of char. There are 3 ‘*’s.

2.5 This is because the C++ function must perform the following :

  • Allocate memory to hold an array of char pointers.
  • Then for each char pointer (char*) in the array, memory must be allocated to hold a character buffer and each character pointer must point to this buffer.

2.6 The following is a sample implementation of GenerateStringsAndStoreInStringBuffer() :

__declspec(dllexport) void __stdcall GenerateStringsAndStoreInStringBuffer
(
  /*[out]*/ char*** ppStringBufferReceiver,
  /*[out]*/ int* piStringsCountReceiver
)
{
  char* MyStrings[] =
  {
    "A",
    "AB",
    "ABC",
    "ABCD",
    "ABCDE",
    "ABCDEF",
    "ABCDEFG",
    "ABCDEFGH",
    "ABCDEFGHI",
    "ABCDEFGHIJ"
  };

  *piStringsCountReceiver = 10;
  size_t stSizeOfArray = sizeof(char*) * 10;

  *ppStringBufferReceiver = (char**)::CoTaskMemAlloc(stSizeOfArray);
  memset (*ppStringBufferReceiver, 0, stSizeOfArray);

  for (int i = 0; i < 10; i++)
  {
    (*ppStringBufferReceiver)[i] = (char*)::CoTaskMemAlloc(strlen(MyStrings[i]) + 1);
    strcpy((*ppStringBufferReceiver)[i], MyStrings[i]);
  }

  return;
}

This API will return a pointer to an array of char*. Each char* points to a copy of a string from the array :

char* MyStrings[] =
{
  "A",
  "AB",
  "ABC",
  "ABCD",
  "ABCDE",
  "ABCDEF",
  "ABCDEFG",
  "ABCDEFGH",
  "ABCDEFGHI",
  "ABCDEFGHIJ"
};

2.7 When GenerateStringsAndStoreInStringBuffer() completes, the memory layout of the C-style string array pointed to by ppStringBufferReceiver is as follows :

2.8 This API uses a rather convoluted style of memory allocation. Nevertheless, it must be adhered to. I will explain why in the next section where the C# client code will be listed and discussed.

3. The C# Client Code.

3.1 Let’s look at how the GenerateStringsAndStoreInStringBuffer() function is to be declared in C# :

[DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void GenerateStringsAndStoreInStringBuffer
(
  out IntPtr UnmanagedStringArray,
  out int iStringsCountReceiver
);

The parameter that will receive the array of unmanaged strings is declared as an IntPtr.

3.2 A sample call to this function is listed below :

int iStringsCountReceiver = 0;
IntPtr UnmanagedStringArray = IntPtr.Zero;

GenerateStringsAndStoreInStringBuffer(out UnmanagedStringArray, out iStringsCountReceiver);

When the GenerateStringsAndStoreInStringBuffer() function completes, UnmanagedStringArray will internally point to unmanaged memory which is actually an array of char pointers.

Note well : UnmanagedStringArray does not point to any actual string. It points to an array of char pointers. It is these char pointers that each point to a C-style string.

3.3 Next comes the tough part : we must transform the data that UnmanagedStringArray points to and transform it into an array of C# strings. The following must be done :

  • For each char pointer in “UnmanagedStringArray”, we use the C-style string that it points to and create a C# string object from it. We know how many char pointers there are inside “UnmanagedStringArray” – the iStringsCountReceiver variable receives this number – so we can do this inside a for loop.
  • After the C# string creations are done, the individual unamanged char buffers (for each string) must be freed. This must be done via Marshal.FreeCoTaskMem(). The use of Marshal.FreeCoTaskMem() is important because it matches how memory was allocated inside GenerateStringsAndStoreInStringBuffer() which is by CoTaskMemAlloc().
  • Then, after all char pointers have been freed, “UnmanagedStringArray” itself must be freed. Remember that “UnmanagedStringArray” is also an array (of unmanaged char pointers). Again this is done by Marshal.FreeCoTaskMem().

3.4 The tasks described in point 3.3 above can be encapsulated inside a C# function as listed below :

// This method transforms an array of unmanaged character pointers (pointed to by pUnmanagedStringArray)
// into an array of managed strings.
//
// This method also destroys each unmanaged character pointers and will also destroy the array itself.
static void MarshalUnmananagedStrArray2ManagedStrArray
(
  IntPtr pUnmanagedStringArray,
  int StringCount,
  out string[] ManagedStringArray
)
{
  IntPtr[] pIntPtrArray = new IntPtr[StringCount];
  ManagedStringArray = new string[StringCount];

  Marshal.Copy(pUnmanagedStringArray, pIntPtrArray, 0, StringCount);

  for (int i = 0; i < StringCount; i++)
  {
    ManagedStringArray[i] = Marshal.PtrToStringAnsi(pIntPtrArray[i]);
    Marshal.FreeCoTaskMem(pIntPtrArray[i]);
  }

  Marshal.FreeCoTaskMem(pUnmanagedStringArray);
}

Essentially we perform a reverse of the actions performed in the GenerateStringsAndStoreInStringBuffer() API.

3.5 I hope that the reader will understand why we must perform such elaborate memory allocation and deallocation. It is because the C# side does not know how many strings will be allocated on the C++ side and how long each string will be.

3.6 As such, the GenerateStringsAndStoreInStringBuffer() function must return an integer telling how many unmanaged strings have been allocated. Furthermore, each unmanaged string must be separately allocated so that on the C# side, each can be separately freed.

3.7 The following C# code demonstrates a complete call to GenerateStringsAndStoreInStringBuffer() and a subsequent call to MarshalUnmananagedStrArray2ManagedStrArray() to create managed strings from the returned unmanaged character pointers :

static void Call_ByIntPtr()
{
  int iStringsCountReceiver = 0;
  IntPtr UnmanagedStringArray = IntPtr.Zero;

  GenerateStringsAndStoreInStringBuffer
  (
    out UnmanagedStringArray,
    out iStringsCountReceiver
  );

  string[] ManagedStringArray = null;

  MarshalUnmananagedStrArray2ManagedStrArray
  (
    UnmanagedStringArray,
    iStringsCountReceiver,
    out ManagedStringArray
  );

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

3.8 When the Call_ByIntPtr() is run, the following console output will be displayed :

A
AB
ABC
ABCD
ABCDE
ABCDEF
ABCDEFG
ABCDEFGH
ABCDEFGHI
ABCDEFGHIJ

3.9 After MarshalUnmananagedStrArray2ManagedStrArray() is called, ManagedStringArray will contain an array of C# strings each containing a copy of a corresponding unmanaged string. Each unmanaged string, and the unmanaged array pointed to by UnmanagedStringArray will be destroyed.

4. In Conclusion.

4.1 This part 1 has presented a rigorous low-level string array allocation technique.

4.2 In part 2, a simpler and largely automatically supported marshaling technique will be presented. In part 2, I shall often refer to the low-level marshaling operations of part 1 for comparison purposes.

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

10 thoughts on “Returning an Array of Strings from C++ to C# Part 1

  1. Brilliant!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    Thank you soo much you save me 🙂

    Posted by Vince | January 25, 2012, 3:58 am
  2. Thanks!

    Posted by Alan8 | August 29, 2012, 9:56 pm
  3. Thanks, this was very helpful!

    Posted by Barney Anda | January 8, 2013, 7:16 pm
  4. Thanks! very helpful!!

    Posted by antoniobuffa | October 7, 2013, 8:30 am
  5. I couldn’t find any suitable article for my issue, so please let me post it here.

    My C++ DLL exported function has an argument like this: char** param.
    I want to pass an array of string from C# to above C++ function.

    When declare DllImport, there are 2 ways to deal with that argument
    1. use string[ ]
    2. use IntPtr[ ]

    When working with ANSI string (Japanese), using method ② is OK.
    If method ① used, I received an exception with message:
    System.Runtime.InteropServices.COMException (0x8007007A): The data area passed to a system call is too small.

    If passing string contain only Latin characters, method ① works OK.

    Any explanation for exception ? How to keep using string[ ] and avoid it ?

    Posted by TienNQ | May 28, 2016, 5:01 am
    • Hello Tien,

      1. I first want to be sure that you have declared the DLL function fully and properly as follows :

      [DllImport(“UnmanagedDLL.dll”, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, EntryPoint = “PassArrayOfString”)]
      [return : MarshalAs(UnmanagedType.Bool)]
      public extern static bool PassArrayOfString
      (
      [MarshalAs(UnmanagedType.I4)] int iCount,
      [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr)] string[] pArrayOfCharPtr
      );

      2. How did you declare the same function using IntPtr[] ?

      Posted by Lim Bio Liong | May 29, 2016, 11:57 am
      • Thank you! I reply as below.

        1. I’ve declared the DLL function as your suggestion, but still received same exception.

        2. Using IntPtr[] version

        [DllImport(“UnmanagedDLL.dll”,
        CallingConvention = CallingConvention.StdCall,
        CharSet = CharSet.Ansi,
        EntryPoint = “PassArrayOfString”)]
        [return : MarshalAs(UnmanagedType.Bool)]
        public extern static bool PassArrayOfIntPtr
        (
        [MarshalAs(UnmanagedType.I4)] int iCount,
        [MarshalAs(UnmanagedType.LPArray)] // it’s working even without this properties
        IntPtr[] pArrayOfCharPtr
        );

        If I use this version, before passing parameter to Unmanaged code,
        I’ve to use Marshal.StringToCoTaskMemAnsi() to convert param from string to IntPtr

        Posted by TienNQ | May 29, 2016, 3:08 pm
  6. Hi Lim, thanks a lot for this post. I spent quite sometime on how to read the data buffer of type char ***msg, your solution worked perfectly. thanks again.

    Best regards,
    Siddharth

    Posted by Siddhartha | August 4, 2016, 4:57 am
  7. thanks!!!!!!! very helpful!!!!!!!! 🙂

    Posted by ogoki | February 9, 2017, 9:53 am

Trackbacks/Pingbacks

  1. Pingback: Returning an Array of Strings from C++ to C# Part 2 « limbioliong - August 14, 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: