MathematicalOrchid
MathematicalOrchid

Reputation: 62868

What type to PInvoke for a char**

Consider the following C function:

void get_lib_version(const char **ver_string);

How do I marshall this correctly with PInvoke? The documentation says it returns a pointer to a static string. I thought this would do it:

[DllImport(DllPath, CallingConvention = CallingConvention.Cdecl)]
public static extern int get_lib_version(StringBuilder version);

but all I get is gibberish.

Upvotes: 3

Views: 580

Answers (2)

Hans Passant
Hans Passant

Reputation: 942438

The function returns a brand new C-string. The pinvoke marshaller always makes sure that the memory required to store a string that's returned by native code is released again. This will not come to a good end, surely the caller of this function is not supposed to release it. The const keyword is a strong hint that the native code will return a pointer to a string literal that's not allocated on the heap. Trying to release such a pointer will crash your program on later Windows versions, the kind that have a strict heap implementation (after XP).

You have to help to stop the marshaller from doing this. This requires you to declare the argument as a raw pointer, not a string:

  [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl)]
  public static extern int get_lib_version(out IntPtr version);

And you have to make the extra step to convert the pointer to a string:

  public string GetLibraryVersion() {
      IntPtr strptr;
      get_lib_version(out strptr);
      return Marshal.PtrToStringAnsi(strptr);
  }

Write a little test program to verify this assumption. Call GetLibraryVersion() a billion times. If the memory usage doesn't explode then you're good.

Upvotes: 4

MathematicalOrchid
MathematicalOrchid

Reputation: 62868

According to this answer, when you marshal something as string, PInvoke makes all sorts of assumptions about how it's supposed to get freed. Notice that this is a const char *; it's a constant string somewhere. It never needs to be deallocated!

Apparently the way to deal with this is

  1. Marshall as IntPtr.

  2. Use Marshall.PtrToStringAnsi() to copy the result into a C# string.

I managed to get this to work correctly:

[DllImport(DllPath, CallingConvention = CallingConvention.Cdecl)]
private static extern int get_lib_version(ref IntPtr version);

public static string GetLibVersion()
{
  var ptrVersion = IntPtr.Zero;
  get_lib_version(ref ptrVersion);
  var version = Marshal.PtrToStringAnsi(ptrVersion);
  return version;
}

Upvotes: 0

Related Questions