Search code examples
c#c++marshallingunmanagedmanaged

How do I marshal a pointer to a series of null-terminated strings in C#?


I need some help with the following. I've got a c++ API (no access to source) and I'm struggling with the methods returning char* attributes, or returned structures containing char* attributes. According to the API's documentation the return value is as follows:

Return Values
If the function succeeds, the return value is a pointer to a series of null-terminated strings, one for each project on the host system, ending with a second null character. The following example shows the buffer contents with <null> representing the terminating null character:

project1<null>project2<null>project3<null><null>

If the function fails, the return value is NULL

The problem I'm having is that the returned pointer in C# only contains the first value... project1 in this case. How can I get the full list to be able to loop through them on the managed side?

Here's the c# code:

    [DllImport("vmdsapi.dll", EntryPoint = "DSGetProjectList", CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr DSGetProjectList();

Calling method:

   IntPtr ptrProjectList = DSAPI.DSGetProjectList();
   string strProjectList = Marshal.PtrToStringAnsi(ptrProjectList).ToString();

strProjectList only contains the first item.
Here's the info from the API's header file...

   DllImport char *DSGetProjectList dsproto((void));

Here's some sample code from a c++ console app which I've used for testing purposes...

   char *a;
   a = DSGetProjectList( );
   while( *a ) { 
    printf("a=%s\n", a); 
    a += 1 + strlen(a); 
   } 

Each iteration correctly displays every project in the list.


Solution

  • The problem is that when converting the C++ char* to a C# string using Marshal.PtrToStringAnsi, it stops at the first null character.

    You shouldn't convert directly the char* to a string.

    You could copy the char* represented by an IntPtr to a byte[] using Marshal.Copy and then extract as many string as necessary (see Matthew Watson's answer for extracting strings from a managed array), but you'll need to get the multi-string size first.

    As leppie suggest you can also extract the first string using Marshal.PtrToStringAnsi then increment the pointer by this string size and extract the next string and so on. You stops when is extracts an empty string (from the last NULL character).

    Something like :

    IntPtr ptrProjectList = DSAPI.DSGetProjectList();
    List<string> data;
    string buffer;
    do {
        buffer = Marshal.PtrToStringAnsi(ptrProjectList);
        ptrProjectList += buffer.size() + 1;
        data.Add(buffer);
    }while(buffer.size() > 0)