Question

I am trying to pinvoke to a function with the following signature:

const char ** SWDLLEXPORT org_crosswire_sword_SWModule_parseKeyList(SWHANDLE hSWModule, const char *keyText);

This returns an array of string.

I have example usage of this from c

const char **results = org_crosswire_sword_SWModule_parseKeyList(module, argv[2]);
while (results && *results) {
    printf("%s\n", *results);
    ++results;
}

The pinvoke I have tried is as follows:

[DllImport(DLLNAME)]
public static extern IntPtr org_crosswire_sword_SWModule_parseKeyList(IntPtr hSWModule, string keyText);

And code to use it:

public IEnumerable<string> ParseKeyList(string keyText)
{
    IntPtr keyListPtrs = NativeMethods.org_crosswire_sword_SWModule_parseKeyList(_handle, keyText);
    return NativeMethods.MarshalStringArray(keyListPtrs);
}

public static IEnumerable<string> MarshalStringArray(IntPtr arrayPtr)
{
    IntPtr ptr = Marshal.ReadIntPtr(arrayPtr);
    while(arrayPtr != IntPtr.Zero && ptr != IntPtr.Zero)
    {
        ptr = Marshal.ReadIntPtr(arrayPtr);
        string key = Marshal.PtrToStringAnsi(ptr);
        yield return key;
        arrayPtr = new IntPtr(arrayPtr.ToInt64() + 1);
    }
}

This works for the first item and segfaults for the second on the PtrToStringAnsi line. What am I doing wrong, and what is the correct way to pinvoke to this function.

Was it helpful?

Solution

The C++ code increments the pointer like this:

++results;

That increments the address by sizeof(*results) because that is how C++ pointer arithmetic works. So, suppose that sizeof(*results) is equal to 4, as would be the case on a 32 bit machine. Then ++results will increment the address by 4.

Now, your C# code is different. The pointer is untyped and the compiler knows nothing about the underlying array element type. So your code

arrayPtr = new IntPtr(arrayPtr.ToInt64() + 1);

increments the address by 1. Instead you need to supply the missing type information. Like this:

arrayPtr = new IntPtr(arrayPtr.ToInt64() + IntPtr.Size);

On top of that, your loop is implemented incorrectly. You fail to update ptr at the correct time. It should be:

public static IEnumerable<string> MarshalStringArray(IntPtr arrayPtr)
{
    if (arrayPtr != IntPtr.Zero)
    {
        IntPtr ptr = Marshal.ReadIntPtr(arrayPtr);
        while (ptr != IntPtr.Zero)
        {
            string key = Marshal.PtrToStringAnsi(ptr);
            yield return key;
            arrayPtr = new IntPtr(arrayPtr.ToInt64() + IntPtr.Size);
            ptr = Marshal.ReadIntPtr(arrayPtr);
        }
    }
}

You might prefer to re-cast the method so that the code that it contains only a single call to Marshal.ReadIntPtr.

One final point. The C++ function looks like it might be using the cdecl calling convention. You should check what the definition of SWDLLEXPORT is. Your p/invoke is only correct if SWDLLEXPORT specifies __stdcall.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top