P / Invoke调用中的AccessViolationException

时间:2012-06-30 07:17:28

标签: c# .net c

我正在通过P / Invoke调用编写一个小的zlib包装器。它在64位目标(64位C#构建,64位DLL)上运行完美,但在32位目标(32位C#构建,32位DLL)上抛出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#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;
}

3 个答案:

答案 0 :(得分:4)

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

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

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

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

但也不要这样做,你的帮助太多了。 pinvoke marshaller在必要时已经非常善于钉住物体。将instreamoutstream参数声明为byte []而不是byte *。并且直接传递数组而不做任何特殊的事情。此外,outlength参数应声明为ref int

答案 1 :(得分:1)

在64位中只有一个用于Windows的ABI(没有cdecl / stdcall),因此32位的问题似乎与调用约定有关。参数指针进入错误的寄存器,本机函数访问错误的内存区域。

要解决此问题:

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

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

  3. 要检查所有内容,请尝试转储指针值,看看它们是否与本机/托管函数重合。

  4. 编辑:

    然后是指针的问题。您正在使用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传递。

答案 2 :(得分:0)

实际问题是由MinGW-w64生成错误的DLL引起的。在构建zlib时,我一直在向gcc传递-ftree-vectorize,这产生了32位CLR不喜欢的代码。在使用不太激进的优化选项后,代码运行良好。