//
you're reading...
Interop Marshaling

Using the StringBuilder in Unmanaged API Calls.

1. Introduction.

1.1 I have written extensively about passing strings to and from unmanaged APIs. Most notably Returning Strings from a C++ API to C#.

1.2 The managed type that I have used throughout these articles have been the string.

1.3 When working with unmanaged APIs the DllImport declarations of which take a string as parameter, one of these conditions will hold :

  • A buffer to a C-style string is allocated and freed on the managed caller side. This buffer contains a C-style string value derived from a managed string. The string parameter is typically declared to be passed to the unmanaged API as an “in” (read-only) parameter.
  • A buffer to a C-style string is allocated on the unmanaged API side, filled with character values and then returned to the managed side. This buffer is then freed on the managed side after it has been used to create a managed string. The string parameter is typically declared to be passed to the unmanaged API as an “out” (return) parameter.

1.4 However, there are APIs which require pre-prepared character buffers to be passed. These buffers are then filled by the APIs and returned to the client. In essence, these APIs require the buffer to be passed by reference, somewhat like the two scenarios described in the previous point holding true at the same time.

1.5 Such APIs are plenty to be found. E.g. : GetWindowsDirectory(), GetPrivateProfileString(), etc. These APIs each require the calling code to first prepare a buffer large enough to hold the string to be returned. A typical API like that usually returns an integer value which will indicate the required size of the buffer should it be too small. Throughout this article, I shall call such APIs “buffer-reference APIs”.

1.6 In such cases, the managed string type is not usable. A managed string cannot have its internal buffer pre-allocated and then passed to unmanaged code to be filled.

1.7 This is where the StringBuilder comes to the rescue. A StringBuilder behaves very much like a typical C-style character buffer and is particularly useful when used with buffer-reference APIs mentioned in point 1.5.

1.8 This article aims to demonstrate proper use of the StringBuilder with buffer-reference APIs, including how to use it to receive ANSI and Unicode character strings.

2. The StringBuilder and an Example Usage.

2.1 In this section, I will build a test C++ API that takes a string buffer as input, fills it with ANSI character values and then return it to the caller. The following is the code listing :

UINT __stdcall TestAPIAnsi(/*[out]*/ char* lpBuffer, /*[in]*/ UINT uSize)
{
  const char szReturnString[] = "Hello World";
  const UINT uiStringLength = strlen(szReturnString);

  if (uSize >= (uiStringLength + 1))
  {
    strcpy(lpBuffer, szReturnString);
    // Return the number of characters copied.
    return uiStringLength;
  }
  else
  {
    // Return the required size
    // (including the terminating NULL character).
    return uiStringLength + 1;
  }
}

The following is a summary of this API :

  • It takes a pointer to a single-byte character buffer.
  • A second parameter will indicate the size (in count of characters) of this buffer.
  • If the buffer size is sufficient, this API copies a constant string into the buffer. The total number of characters copied is returned.
  • If the size is not sufficient, this API does not perform the copy and returns the required size (in count of characters) of the buffer.

2.2 There are only 2 ways to call this API from managed code :

  • By using an IntPtr and manually allocating/deallocating the character buffer to be passed to the unmanaged API.
  • By using a StringBuilder.

I shall demonstrate below both techniques and show how the StringBuilder can make programming life simpler.

2.3 Using the IntPtr.

The following C# code demonstrates how to use the IntPtr for calling TestAPIAnsi() :

[DllImport("TestDLL.dll", CallingConvention=CallingConvention.StdCall, CharSet=CharSet.Ansi, EntryPoint="TestAPIAnsi")]
private static extern UInt32 TestAPIAnsiUsingIntPtr(IntPtr lpBuffer, UInt32 uiSize);

private static void TestCall_TestAPIAnsiUsingIntPtr()
{
    // First allocate a buffer of 1 byte.
    IntPtr lpBuffer = Marshal.AllocHGlobal(1);
    // Call the API. If the size of the buffer
    // is insufficient, the return value in
    // uiRequiredSize will indicate the required
    // size.
    UInt32 uiRequiredSize = TestAPIAnsiUsingIntPtr(lpBuffer, (UInt32)1);

    if (uiRequiredSize > 1)
    {
        // The buffer pointed to by lpBuffer needs to be of a
        // greater size than the current capacity.
        // This required size is the returned value in "uiRequiredSize"
        // (including the terminating NULL character).
        lpBuffer = Marshal.ReAllocHGlobal(lpBuffer, (IntPtr)uiRequiredSize);
        // Call the API again.
        TestAPIAnsiUsingIntPtr(lpBuffer, (UInt32)uiRequiredSize);
    }

    // Convert the characters inside the buffer
    // into a managed string.
    string str = Marshal.PtrToStringAnsi(lpBuffer);

    // Free the buffer.
    Marshal.FreeHGlobal(lpBuffer);
    lpBuffer = IntPtr.Zero;

    // Display the string.
    Console.WriteLine("TestAPIAnsiUsingIntPtr return string : [" + str + "]");
}

The following are the pertinent points about the code above :

  • A declaration for TestAPIAnsi() is provided but a different name is used : TestAPIAnsiUsingIntPtr(). The correct API is nevertheless invoked because of the use of the EntryPoint argument.
  • This is done so that we can provide more than one declaration for the same TestAPIAnsi() API as will be shown later when we make another declaration for calling TestAPIAnsi() with a StringBuilder.
  • At the start of the TestCall_TestAPIAnsiUsingIntPtr() function, a buffer of 1 byte is allocated using Marshal.AllocHGlobal().
  • The TestAPIAnsiUsingIntPtr() API is then called. The size of the buffer is surely insufficient and so we re-allocate the buffer by calling Marshal.ReAllocHGlobal() using the initial return value of the TestAPIAnsiUsingIntPtr() API call.
  • The TestAPIAnsiUsingIntPtr() API is then called again and this time, the buffer is indeed filled with the intended value.
  • The characters inside the buffer is then converted into a managed string by calling the Marshal.PtrToStringAnsi() function.
  • The buffer must now be freed. This is done by calling Marshal.FreeHGlobal().

2.4 Using StringBuilder.

The following C# code demonstrates how to use the StringBuilder for calling TestAPIAnsi() :

[DllImport("TestDLL.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, EntryPoint = "TestAPIAnsi")]
private static extern UInt32 TestAPIAnsiUsingStringBuilder(StringBuilder lpBuffer, UInt32 uiSize);

private static void TestCall_TestAPIAnsiUsingStringBuilder()
{
    StringBuilder sbBuffer = new StringBuilder(1);
    UInt32 uiRequiredSize = TestAPIAnsiUsingStringBuilder(sbBuffer, (UInt32)sbBuffer.Capacity);

    if (uiRequiredSize > sbBuffer.Capacity)
    {
        // sbBuffer needs to be of a greater size than current capacity.
        // This required size is the returned value in "uiRequiredSize"
        // (including the terminating NULL character).
        sbBuffer.Capacity = (int)uiRequiredSize;
        // Call the API again.
        TestAPIAnsiUsingStringBuilder(sbBuffer, (UInt32)sbBuffer.Capacity);
    }

    Console.WriteLine("TestAPIAnsiUsingStringBuilder return string : [" + sbBuffer.ToString() + "]");
}

The following is a summary of the code above :

  • A declaration for TestAPIAnsi() is provided but a different name is used : TestAPIAnsiUsingStringBuilder(). The correct API is called nevertheless because of the EntryPoint argument with value “TestAPIAnsi”.
  • This is done so that we can provide more than one declaration for the same TestAPIAnsi() API. There was already one declaration made previously (TestAPIAnsiUsingIntPtr()) that uses IntPtr as the first parameter. We now make another declaration that uses the StringBuilder as the first parameter.
  • Note well the CharSet argument and its set value of CharSet.Ansi. This is important because it will indicate to the Interop Marshaler to marshal the internal buffer of the StringBuilder parameter as a pointer to ANSI characters (LPSTR).
  • An alternative to using the CharSet argument is to use the MarshalAsAttribute with UnmanagedType.LPStr as argument for the StringBuilder parameter :
[DllImport("TestDLL.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "TestAPIAnsi")]
private static extern UInt32 TestAPIAnsiUsingStringBuilder([MarshalAs(UnmanagedType.LPStr)]StringBuilder lpBuffer, UInt32 uiSize);
  • At the start of TestCall_TestAPIAnsiUsingStringBuilder(), a StringBuilder is instantiated and its initial size is indicated (i.e. 1).
  • The TestAPIAnsiUsingStringBuilder() API is then called with the StringBuilder instance “sbBuffer” passed as parameter and the size of the buffer indicated by sbBuffer.Capacity.
  • If the size of the buffer is not sufficient, the return value in uiRequiredSize will be greater than sbBuffer.Capacity and so we re-allocated the buffer of the StringBuilder by simply setting a new (larger) value for sbBuffer.Capacity (i.e. uiRequiredSize).
  • TestAPIAnsiUsingStringBuilder() is then called again with a new larger buffer and a new value which indicates the size.
  • Now, on return, when the StringBuilder “sbBuffer” copies the unmanaged string from the buffer, it will stop the copying process when it encounters a NULL character. This is the only way a StringBuilder will know how many characters from the unmanaged string to copy.
  • Finally, because the buffer is intrinsically part and parcel of the StringBuilder, it will be decallocated when the StringBuilder is garbage collected.

2.5 Comparing the 2 methods of calling TestAPIAnsi(), we can see that using the StringBuilder is much simpler. The translation of the unmanaged string from ANSI to Unicode (the default character set of the .NET Framework) is done automatically. The internal allocation/deallocation of the buffer is also done automatically.

3. Working with Unicode Characters.

3.1 When working with buffer-reference APIs that returns Unicode characters, the StringBuilder again emerge as the choice type to use. In this section, I will demonstrate how to use the IntPtr type and the StringBuilder type to obtain a Unicode character string from an unmanaged API via a pre-prepared buffer.

3.2 The following is a listing of a C++ buffer-reference API that works with Unicode characters :

UINT __stdcall TestAPIUnicode(/*[out]*/ wchar_t* lpBuffer, /*[in]*/ UINT uSize)
{
  const wchar_t wszReturnString[] = L"Hello World";
  const UINT uiStringLength = wcslen(wszReturnString);

  if (uSize >= (uiStringLength + 1))
  {
    wcscpy(lpBuffer, wszReturnString);
    // Return the number of characters copied.
    return uiStringLength;
  }
  else
  {
    // Return the required size
    // (including the terminating NULL character).
    return uiStringLength + 1;
  }
}

3.3 Using the IntPtr.

The following C# code demonstrates how to use the IntPtr for calling TestAPIUnicode() :

[DllImport("TestDLL.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, EntryPoint = "TestAPIUnicode")]
private static extern UInt32 TestAPIUnicodeUsingIntPtr(IntPtr lpBuffer, UInt32 uiSize);

private static void TestCall_TestAPIUnicodeUsingIntPtr()
{
    // First allocate a buffer of 1 byte.
    IntPtr lpBuffer = Marshal.AllocHGlobal(1 * sizeof(char));
    // Call the API. If the size of the buffer
    // is insufficient, the return value in
    // uiRequiredSize will indicate the required
    // size.
    UInt32 uiRequiredSize = TestAPIUnicodeUsingIntPtr(lpBuffer, (UInt32)1);

    if (uiRequiredSize > 1)
    {
        // The buffer pointed to by lpBuffer needs to be of a
        // greater size than the current capacity.
        // This required size is the returned value in "uiRequiredSize"
        // (including the terminating NULL character).
        lpBuffer = Marshal.ReAllocHGlobal(lpBuffer, (IntPtr)(uiRequiredSize * sizeof(char)));
        // Call the API again.
        TestAPIUnicodeUsingIntPtr(lpBuffer, (UInt32)uiRequiredSize);
    }

    // Convert the characters inside the buffer
    // into a managed string.
    string str = Marshal.PtrToStringUni(lpBuffer);

    // Free the buffer.
    Marshal.FreeHGlobal(lpBuffer);
    lpBuffer = IntPtr.Zero;

    // Display the string.
    Console.WriteLine("TestAPIUnicodeUsingIntPtr return string : [" + str + "]");
}

The following is a summary of the code above :

  • A declaration for TestAPIUnicode() is provided but a different name is used, i.e. TestAPIUnicodeUsingIntPtr(). The EntryPoint argument is used to locate the actual API to be used.
  • This is done so that we can provide more than one declaration for the same TestAPIUnicode() API which will be shown later when we examine how to call TestAPIUnicode() using a StringBuilder.
  • Much of what goes on inside TestCall_TestAPIUnicodeUsingIntPtr() is similar to the TestCall_TestAPIAnsiUsingIntPtr() function that we met earlier in point 2.3.
  • However, note that this time, whenever we have to allocate the buffer using Marshal.AllocHGlobal() or Marshal.ReAllocHGlobal(), we have to specify the size of the buffer in terms of Unicode characters. We have to bear in mind that the size of a Unicode character is 2 bytes.
  • Also, this time, when converting the unmanaged Unicode string to a managed string, we must use Marshal.PtrToStringUni().
  • Finally the buffer must be freed manually when no longer needed.

3.4 Using StringBuilder.

The following C# code demonstrates how to use the StringBuilder for calling TestAPIUnicode() :

[DllImport("TestDLL.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, EntryPoint = "TestAPIUnicode")]
private static extern UInt32 TestAPIUnicodeUsingStringBuilder(StringBuilder lpBuffer, UInt32 uiSize);

private static void TestCall_TestAPIUnicodeUsingStringBuilder()
{
    StringBuilder sbBuffer = new StringBuilder(1);
    UInt32 uiRequiredSize = TestAPIUnicodeUsingStringBuilder(sbBuffer, (UInt32)sbBuffer.Capacity);

    if (uiRequiredSize > sbBuffer.Capacity)
    {
        // sbBuffer needs to be of a greater size than current capacity.
        // This required size is the returned value in "uiRequiredSize"
        // (including the terminating NULL character).
        sbBuffer.Capacity = (int)uiRequiredSize;
        // Call the API again.
        TestAPIUnicodeUsingStringBuilder(sbBuffer, (UInt32)sbBuffer.Capacity);
    }

    Console.WriteLine("TestAPIUnicode return string : [" + sbBuffer.ToString() + "]");
}

The following is a summary of the code above :

  • A declaration for TestAPIUnicode() is provided but a different name is used, i.e. TestAPIUnicodeUsingStringBuilder(). The EntryPoint argument is used to locate the actual API (TestAPIUnicode()) to be used.
  • This is done so that we can provide yet another declaration for the same TestAPIUnicode() API with different parameter types. A previous one, namely TestAPIUnicodeUsingIntPtr(), was declared to use IntPtr for the first parameter.
  • Note the CharSet argument used for the declaration of TestAPIUnicodeUsingStringBuilder(). It is set to CharSet.Unicode so that the Interop Marshaler will marshal the internal buffer of the StringBuilder parameter as a wide character pointer (LPWSTR).
  • An alternative to using the CharSet argument is to use the MarshalAsAttribute with UnmanagedType.LPWStr as argument :
[DllImport("TestDLL.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "TestAPIUnicode")]
private static extern UInt32 TestAPIUnicodeUsingStringBuilder([MarshalAs(UnmanagedType.LPWStr)]StringBuilder lpBuffer, UInt32 uiSize);
  • The entire TestCall_TestAPIUnicodeUsingStringBuilder() function is identical to the TestCall_TestAPIAnsiUsingStringBuilder() function that we met in point 2.4 with the exception that TestAPIUnicodeUsingStringBuilder() is called in place of TestAPIAnsiUsingStringBuilder().
  • The fact that TestAPIUnicodeUsingStringBuilder() uses Unicode characters is indicated in the way it is declared.
  • This shows how much simpler it is to use the StringBuilder.

4. In Conclusion.

4.1 The StringBuilder is certainly a very useful object to use when calling unmanaged APIs that require a pre-prepared character buffer.

4.2 With this article, I hope to have done yet another coverage of string interactions between managed and unmanaged code.

 

About these ads

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

One thought on “Using the StringBuilder in Unmanaged API Calls.

  1. Good response in return of this matter with genuine
    arguments and describing the whole thing about that.

    Posted by Judson | April 26, 2013, 6:45 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

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: