考虑以下C函数:
void get_lib_version(const char **ver_string);
如何使用PInvoke正确编组?文档说它返回一个指向静态字符串的指针。我以为会这样做:
[DllImport(DllPath, CallingConvention = CallingConvention.Cdecl)]
public static extern int get_lib_version(StringBuilder version);
但我得到的只是胡言乱语。
答案 0 :(得分:3)
该函数返回一个全新的C字符串。 pinvoke marshaller总是确保存储由本机代码返回的字符串所需的内存再次释放。这不会是一个好的结局,当然这个函数的调用者不应该释放它。 const 关键字强烈暗示本机代码将返回指向未在堆上分配的字符串文字的指针。尝试发布这样的指针会使程序在以后的Windows版本上崩溃,这种版本具有严格的堆实现(在XP之后)。
你必须帮助阻止编组人员这样做。这要求您将参数声明为原始指针,而不是字符串:
[DllImport(DllPath, CallingConvention = CallingConvention.Cdecl)]
public static extern int get_lib_version(out IntPtr version);
你必须做一个额外的步骤,将指针转换为字符串:
public string GetLibraryVersion() {
IntPtr strptr;
get_lib_version(out strptr);
return Marshal.PtrToStringAnsi(strptr);
}
编写一个小测试程序来验证这个假设。调用GetLibraryVersion()十亿次。如果内存使用量没有爆炸,那么你很好。
答案 1 :(得分:0)
根据this answer,当你将某些东西编组为string
时,PInvoke会对它应该如何被释放做出各种各样的假设。请注意,这是const char *
;它在某个地方是一个常数字符串。 从不需要解除分配!
显然,解决这个问题的方法是
马歇尔为IntPtr
。
使用Marshall.PtrToStringAnsi()
将结果复制到C#字符串中。
我设法让它正常工作:
[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;
}