C# P/Invoke调用中的AccessViolationException
我正在通过p/Invoke调用编写一个小的zlib包装器。它在64位目标(64位C#build,64位DLL)上运行良好,但在32位目标(32位C#build,32位DLL)上抛出AccessViolationException 下面是引发异常的C#签名和代码:C# P/Invoke调用中的AccessViolationException,c#,.net,c,C#,.net,C,我正在通过p/Invoke调用编写一个小的zlib包装器。它在64位目标(64位C#build,64位DLL)上运行良好,但在32位目标(32位C#build,32位DLL)上抛出AccessViolationException 下面是引发异常的C#签名和代码: [DllImport(Program.UnmanagedDll, CallingConvention = CallingConvention.Cdecl)] private static extern ZLibResult ZLibDe
[DllImport(Program.UnmanagedDll, CallingConvention = CallingConvention.Cdecl)]
private static extern ZLibResult ZLibDecompress(byte[] inStream, uint inLength, byte[] outStream, ref uint outLength);
internal enum ZLibResult : byte {
Success = 0,
Failure = 1,
InvalidLevel = 2,
InputTooShort = 3
}
internal static ZLibResult Decompress(byte[] compressed, out byte[] data, uint dataLength) {
var len = (uint) compressed.Length;
fixed (byte* c = compressed) {
var buffer = new byte[dataLength];
ZLibResult result;
fixed (byte* b = buffer) {
result = ZLibDecompress(c, len, b, &dataLength);
}
if(result == ZLibResult.Success) {
data = buffer;
return result;
}
data = null;
return result;
}
}
下面是C代码(用MinGW-w64编译):
C代码:
[DllImport(Program.UnmanagedDll, CallingConvention = CallingConvention.Cdecl)]
private static extern ZLibResult ZLibDecompress(
[MarshalAs(UnmanagedType.LPArray)]byte[] inStream, uint inLength,
[MarshalAs(UnmanagedType.LPArray)]byte[] outStream, ref uint outLength);
internal static ZLibResult Decompress(byte[] compressed, out byte[] data, uint dataLength) {
var buffer = new byte[dataLength];
var result = ZLibDecompress(compressed, (uint)compressed.Length, buffer, ref dataLength);
if(result == ZLibResult.Success) {
data = buffer;
return result;
}
data = null;
return result;
}
__declspec(dllexport) uint8_t __cdecl ZLibDecompress(uint8_t* inStream, uint32_t inLength,
uint8_t* outStream, uint32_t* outLength) {
uLongf oL = (uLongf)*outLength;
int result = uncompress(outStream, &oL, inStream, inLength);
*outLength = (uint32_t)oL;
if(result == Z_OK)
return ZLibCompressSuccess;
return ZLibCompressFailure;
}
在64位中,Windows只有一个ABI(没有cdecl/stdcall),因此32位的问题似乎在于调用约定。您的参数指针进入了错误的寄存器,并且本机函数访问了错误的内存区域 要解决这个问题:
[DllImport(Program.UnmanagedDll, CallingConvention = CallingConvention.Cdecl)]
private static extern ZLibResult ZLibDecompress(
[MarshalAs(UnmanagedType.LPArray)] byte[] inStream,
uint inLength,
[MarshalAs(UnmanagedType.LPArray)] byte[] outStream,
ref UInt32 outLength);
[In,Out]修饰符可能也有帮助
是的,正如汉斯所说,别上指针,不要让它们被垃圾收集
byte[] theStream = new byte[whateveyouneed];
// Pin down the byte array
GCHandle handle = GCHandle.Alloc(theStream, GCHandleType.Pinned);
IntPtr address = handle.AddrOfPinnedObject();
然后将其作为IntPtr传递
fixed (byte* b = buffer) {
result = ZLibDecompress(c, len, b, &dataLength);
}
不,那不行。fixed关键字提供了一种高度优化的方法,以确保垃圾收集器移动对象不会造成问题。它不是通过固定对象(如文档所述)来实现的,而是通过向垃圾收集器公开b
变量来实现的。然后看到它引用缓冲区,并在移动buffer
时更新b
的值
然而,在这种情况下,它无法工作,b
值的副本被传递给ZlibDecompress()。垃圾收集器无法更新该副本。当ZLibDecompress()运行时发生GC时,结果将很糟糕,本机代码将破坏垃圾收集堆的完整性,并最终导致AV
不能使用fixed,必须使用GCHandle.Alloc()固定缓冲区
但是也不要那样做,你帮了太多的忙了。pinvoke marshaller已经非常擅长在必要时固定对象。将
流内
和流外
参数声明为byte[]而不是byte*。直接传递数组而不做任何特殊的操作。另外,应该声明outlength
参数ref int
实际问题是由MinGW-w64生成错误的DLL引起的。在构建zlib时,我一直在将-ftree矢量化传递给gcc,zlib正在生成32位CLR不喜欢的代码。使用不太激进的优化选项后,代码运行良好。您是使用32位MinGW还是尝试交叉编译?您是否尝试过从C代码中使用库?我正在使用MinGW-w64构建一个32位DLL,是的,在C程序中调用时,这些函数工作正常,没有堆损坏。现在看不到任何问题。您是否尝试过使用32位MinGW?虽然我不希望有什么不同。您缺少不安全的关键字,但除此之外。。。实际上,您可能可以跳过整个指针内容,只传递我认为(使不安全过时)的数组,这可能会排除另一个可能的错误源。我使用的是32位MinGW,MinGW-w64项目提供32位和64位编译器。另外,包含Decompress()的类是不安全的,所以这不是问题所在。我不希望使用.NET数组,因为它会增加不必要的编组开销。正如我所说,它可以使用64位构建,但不能使用32位构建,这很奇怪,因为我没有使用任何特定于平台的数据类型。谢谢,我从来没有想过使用stdcall。是否仍然可以禁用导出的stdcall函数的名称损坏?我知道Windows API使用STDCULL并且没有被函数化的名称。名称MINGLING只与C++代码相关。函数名前的extern“C”将禁用损坏。但我想你知道的,别管名字弄乱了,把它传给MinGW链接器,把它搞定了。我仍然会遇到stdcall或cdecl的访问冲突,托管代码和本机代码中的inStream和outtream指针完全相同,注释掉C代码中调用uncompress()的行可以阻止崩溃。还有其他想法吗?那么我想我们应该检查编组属性。看起来字节*指向托管堆。请参阅我的编辑。尝试了这两个示例,但它们仍然在32位上提供访问冲突。我应该放弃吗?谢谢你的洞察力,我会记住fixed不会锁定对象。我尝试过直接传递数组,并使用GCHandle.Alloc(),但仍然遇到了32位特定的访问冲突。同样,当从C程序调用本机函数时,它可以正常工作。我很困惑为什么现在不能工作。请注意文章的最后一段。您删除了CallingConvention,但没有。将其重新添加,结果相同。StdCall不应该默认吗?您添加了错误的调用约定,它是Cdecl。你有没有考虑过用别人的图书馆来完成这件事?SharpZipLib和DotNetZip也可以做到这一点。
fixed (byte* b = buffer) {
result = ZLibDecompress(c, len, b, &dataLength);
}