Question

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.

Was it helpful?

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)

OTHER TIPS

This kind of string is called a Multi-String, and it's quite common in the Windows API.

Marshaling them is fiddly. What you have to do is marshal it as a char[] array, rather than a string, and then convert the char[] array to a set of strings.

See here for an example solution. I've copied the relevant code into this answer, but it is copied from the link I gave:

static List<string> MultiStringToList(char[] multistring)
{
    var stringList = new List<string>();
    int i = 0;

    while (i < multistring.Length)
    {
        int j = i;

        if (multistring[j++] == '\0') 
            break;

        while (j < multistring.Length)
        {
            if (multistring[j++] == '\0')
            {
                stringList.Add(new string(multistring, i, j - i - 1));
                i = j;
                break;
            }
        }
    }

    return stringList;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top