2012-06-30 28 views
2

我正在通过P/Invoke调用写一个小的zlib包装。它在64位目标(64位C#构建,64位DLL)上运行完美,但在32位目标(32位C#构建,32位DLL)上引发AccessViolationException。P/Invoke调用中的AccessViolationException

这里的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 <stdint.h> 
#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#应用​​程序调用时会引发访问冲突。

有谁知道为什么会发生这种情况?

编辑: 更新我的代码,仍然在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; 
} 
+0

你使用的是32位MinGW还是你想交叉编译?你有没有尝试过从C代码使用你的库? – Mario

+0

我正在使用MinGW-w64构建一个32位DLL,并且是的,这些函数在C程序中调用时没有堆损坏就能正常工作。 –

+0

现在看不到任何问题。你有没有试过使用32位MinGW?虽然我不希望有任何区别。您错过了'unsafe'关键字,但除此之外......您可能实际上可以跳过整个指针的内容,只是传递我认为的数组(可能会使'unsafe'过时) - 可能会排除另一个可能的错误源。 – Mario

回答

3
fixed (byte* b = buffer) { 
     result = ZLibDecompress(c, len, b, &dataLength); 
    } 

不,这是行不通的。固定的关键字提供了一种高度优化的方式来确保垃圾收集器移动对象不会造成麻烦。它不通过固定对象来实现(如文档所述),它通过将b变量暴露给垃圾收集器来实现。然后它看到它引用缓冲区并在其移动buffer时更新b的值。

但是在这种情况下无法工作,b值的副本已传递给ZlibDecompress()。垃圾收集器无法更新该副本。如果在ZLibDecompress()运行时发生GC,结果会很差,本机代码将破坏垃圾收集堆的完整性,并最终导致AV。

您不能使用固定的,您必须使用GCHandle.Alloc()来固定缓冲区。

但是不要这样做,你太帮忙了。 pinvoke编组已经非常擅长在必要时锁定对象。声明instreamoutstream参数为byte []而不是byte *。直接传递数组而不做任何特殊的事情。此外,outlength论点应宣布为ref int

+0

感谢您的洞察力,我会记住固定不会固定对象。我试过直接传递数组,并使用GCHandle.Alloc(),但我仍然得到一个32位特定的访问冲突。同样,从C程序调用本机函数时也可以正常工作,但我很困惑,为什么现在不能正常工作。 –

+0

请注意帖子中的最后一段。 –

+0

我有,看我更新的例子。 –

1

在64位只有一种ABI的Windows(没有CDECL/STDCALL),所以对于32位的问题似乎与调用约定一致。您的参数指针将进入错误的寄存器,并且本地函数访问错误的内存区域。

要解决的问题:

  1. 尝试注释掉在本地函数中的行(看它是否崩溃 - 它是的,这不是调用约定)

  2. 尝试与调用打约定“cdecl/stdcall”

  3. 要检查所有内容,请尝试转储指针值并查看它们是否在本地/托管函数中一致。

编辑:

然后与指针的问题。您正在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]修饰符也可能有帮助。

是的,正如Hans所说,固定指针并且不允许它们被垃圾收集。

byte[] theStream = new byte[whateveyouneed]; 
// Pin down the byte array 
GCHandle handle = GCHandle.Alloc(theStream, GCHandleType.Pinned); 
IntPtr address = handle.AddrOfPinnedObject(); 

然后将它作为IntPtr传递。

+0

谢谢,从未想过使用stdcall。无论如何,禁用导出的__stdcall函数的名称修改?我知道Windows API使用stdcall并没有错位的函数名称。 –

+0

名称修改只与C++代码相关。 extern“C”在函数名称之前禁用了mangling。但我认为你知道这一点。 –

+0

没有关于名称的修改,通过传递给MinGW链接器来修复它。我仍然遇到了使用stdcall或cdecl的访问冲突,inStream和outStream的指针在托管代码和本地代码中完全相同,并且在C代码中调用uncompress()的行注释可以阻止崩溃。还有什么想法? –

0

实际问题是由MinGW-w64生成有问题的DLL引起的。在构建zlib时,我一直将-ftree-vectorize传递给gcc,而zlib则生成32位CLR不喜欢的代码。使用较不积极的优化选项后,代码运行良好。

相关问题