我有一个很奇怪的问题:
我正在使用NUnit测试对非托管C dll的几个函数调用。奇怪的是,测试在正常运行时失败,但是当我用调试器运行它时(即使没有断点)它也没问题。
那么,调试器是否具有比普通NUnit应用程序更广泛的内存访问权限?
我已经隔离了失败的电话。它传递一个字符串的char指针,编组器应将其转换为C#字符串。 C面看起来像这样:
#define get_symbol(a) ((a).a_w.w_symbol->s_name)
EXTERN char *atom_get_symbol(t_atom *a);
...
char *atom_get_symbol(t_atom *a) {
return get_symbol(*a);
}
和C#代码:
[DllImport("csharp.dll", EntryPoint="atom_get_symbol")]
[return:MarshalAs(UnmanagedType.LPStr)]
private static extern string atom_get_symbol(IntPtr a);
从c返回的指针在代码和列表的一部分内部非常深。所以我只是错过了一些安全设置?
编辑:这是我得到的例外:
System.AccessViolationException :(翻译成英语:)尝试读取或写入受保护的内存。这可能表明其他内存已损坏。
at Microsoft.Win32.Win32Native.CoTaskMemFree(IntPtr ptr)
at ....atom_get_symbol(IntPtr a)
解决方案:
问题是,marshaller想要释放作为C结构一部分的内存。但它只需要复制字符串并保留内存:
[DllImport("csharp.dll", EntryPoint="atom_get_symbol")]
private static extern IntPtr atom_get_symbol(IntPtr a);
然后在代码中获取字符串的副本:
var string = Marshal.PtrToStringAnsi(atom_get_symbol(ptrToStruct));
太棒了!
答案 0 :(得分:4)
这将导致Vista及其崩溃,你如何避免它根本不是很清楚。堆栈跟踪告诉故事,pinvoke marshaller正试图释放为字符串分配的字符串缓冲区。它总是使用CoTaskMemFree()来做,这是对可能用于为字符串分配内存的分配器的唯一合理猜测。但这很少有效,C或C ++代码几乎总是使用CRT的私有堆。这不会在XP上崩溃,它有一个更宽容的内存管理器。这会产生无法识别的内存泄漏。
值得注意的是,C声明并没有给出很多可以推送该函数的承诺,它不会返回const char*
。你唯一的希望是将返回类型声明为IntPtr而不是string,因此pinvoke marshaller不会尝试释放指向的内存。您需要使用Marshal.PtrToStringAnsi()将返回的IntPtr转换为字符串。
你需要对它进行测试,调用该函数十亿次以确保你不会泄漏内存。如果该测试与OutOfMemoryException崩溃,那么你有一个很大的问题。唯一的选择是用C ++ / CLI语言编写一个包装器,并确保它使用与本机代码完全相同的CRT版本,以便它们都使用相同的堆。如果您没有源代码,这是棘手的,也是不可能的。这个函数很难用任何语言调用,包括C.它应该被声明为int atom_get_symbol(t_atom* a, char* buf, size_t buflen)
,因此可以使用客户端代码分配的缓冲区调用它。