1

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.

1
  • 1
    You're increasing the arrayPtr by 1 which shifts it 1 byte. But you want to make it point to the next array element, so you should add IntPtr.Size. Commented Mar 2, 2014 at 10:07

1 Answer 1

4

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Don't I need to check the arrayPtr for IntPtr.Zero after I increment it each time?
@tramp No. Just a single check up front suffices.
I wish I could upvote multiple times. Once for IntPtr.Size, Once for loop incorrect, Once for calling convention. You where correct about all three.
@trampster well thanks. Feel free to upvote a couple of my other answers if you are desperate to give thanks! ;-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.