1

我有一个很奇怪的问题:

我正在使用 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)

解决方案:

问题是,编组器想要释放作为 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));

伟大的!

4

1 回答 1

4

这总是会导致 Vista 及更高版本崩溃,您如何避免它并不是很清楚。堆栈跟踪告诉我们,pinvoke marshaller 正在尝试释放为字符串分配的字符串缓冲区。它总是使用 CoTaskMemFree() 来执行此操作,这是对可能用于为字符串分配内存的分配器的唯一合理猜测。但这很少奏效,C 或 C++ 代码几乎总是使用 CRT 的私有堆。这不会在 XP 上崩溃,它有一个更宽容的内存管理器。这会产生无法诊断的内存泄漏。

值得注意的是,C 声明并没有给出太多的承诺,即您可以调用该函数,它不返回 a const char*。您唯一的希望是将返回类型声明为 IntPtr 而不是字符串,这样 pinvoke 编组器就不会尝试释放指向的内存。您需要使用 Marshal.PtrToStringAnsi() 将返回的 IntPtr 转换为字符串。

您需要对其进行测试,调用该函数十亿次以确保您不会泄漏内存。如果该测试因 OutOfMemoryException 而崩溃,那么您遇到了一个大问题。唯一的选择是用 C++/CLI 语言编写一个包装器,并确保它使用与本机代码完全相同的 CRT 版本,以便它们都使用相同的堆。如果您没有源代码,这将是棘手且不可能的。这个函数很难从任何语言调用,包括 C。它应该被声明为int atom_get_symbol(t_atom* a, char* buf, size_t buflen)可以使用客户端代码分配的缓冲区来调用它。

于 2012-05-13T16:04:38.313 回答