P / Invoke调用中的AccessViolationException
我正在通过P / Invoke调用编写一个小的zlib包装器。 它在64位目标(64位C#构建,64位DLL)上运行完美,但在32位目标(32位C#构建,32位DLL)上抛出AccessViolationException。
这是抛出exception的C#签名和代码:
[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编译):
#include #include "zlib.h" #define ZLibCompressSuccess 0 #define ZLibCompressFailure 1 __cdecl __declspec(dllexport) uint8_t 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; }
我已经查看了所有内容,无法弄清楚为什么在32位版本上而不是在64位版本上发生访问冲突。 从C应用程序调用时,ZLibDecompress可以很好地解压缩相同的流,但是当从我的C#app调用时会抛出访问冲突。
有谁知道为什么会发生这种情况?
编辑:更新了我的代码,仍然在32位版本上获得访问冲突,但不是64位。
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; }
C代码:
__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; }
fixed (byte* b = buffer) { result = ZLibDecompress(c, len, b, &dataLength); }
不,那不行。 fixed关键字提供了一种高度优化的方法,可确保垃圾收集器移动对象不会造成麻烦。 它不是通过固定对象(如文档所说)来实现的,它是通过将b
变量暴露给垃圾收集器来实现的。 然后,它会看到它引用缓冲区并在移动buffer
时更新b
的值。
然而,在这种情况下无法工作, b
值的副本被传递给ZlibDecompress()。 垃圾收集器无法更新该副本。 当ZLibDecompress()运行时GC发生时,结果将很差,本机代码将破坏垃圾收集堆的完整性,并最终导致AV。
你不能使用fixed ,你必须使用GCHandle.Alloc()来固定缓冲区。
但是,不要这样做,你正在帮助太多。 pinvoke marshaller在必要时已经非常善于钉住物体。 将instream
和outstream
参数声明为byte []而不是byte *。 并且直接传递数组而不做任何特殊的事情。 此外, outlength
参数应声明为ref int
。
在64位中只有一个用于Windows的ABI(没有cdecl / stdcall),因此32位的问题似乎与调用约定有关。 您的参数指针进入错误的寄存器,本机函数访问错误的内存区域。
要解决此问题:
-
尝试注释掉本机函数中的行(看它是否崩溃 – 是的,它不是调用约定)
-
尝试使用调用约定“cdecl / stdcall”
-
要检查所有内容,请尝试转储指针值并查看它们是否与本机/托管函数重合。
编辑:
那么指针就是一个问题。 您正在使用C#分配数组(因此它们驻留在托管堆中)。 你必须使用“[MarshalAs(UnmanagedType.LPArray)]”属性来编组它们。
[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传递。
实际问题是由MinGW-w64生成错误的DLL引起的。 在构建zlib时,我一直在向gcc传递-ftree-vectorize,这产生了32位CLR不喜欢的代码。 在使用不太激进的优化选项后,代码运行良好。