.NET互操作中是否有内存安全级别?

时间:2012-05-13 15:35:40

标签: c# interop nunit unmanaged

我有一个很奇怪的问题:

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

太棒了!

1 个答案:

答案 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),因此可以使用客户端代码分配的缓冲区调用它。