AccessViolationException在P / Invoke调用AccessViolationException、Invoke

2023-09-05 04:34:46 作者:一滴泪、染湿了整个世界

我通过P写一个小包装的zlib / Invoke的调用。它完美运行在64位的目标(64位C#编译,64位的DLL),而是抛出一个AccessViolationException在32位的目标(32位C#编译,32位的DLL)。

下面是C#的签名和code会抛出异常:

  [的DllImport(Program.UnmanagedDll,CallingConvention = CallingConvention.Cdecl)
私人静态外部ZLibResult ZLibDecom preSS(byte []的inStream中,UINT inLength,byte []的outStream,裁判UINT outLength);

内部枚举ZLibResult:字节{
        成功= 0,
        失败= 1,
        InvalidLevel = 2,
        InputTooShort = 3
}

内部静态ZLibResult DECOM preSS(byte []的COM pressed,出byte []的数据,UINT DATALENGTH){
    VAR的len =(UINT)COM pressed.Length;
    固定(字节* C = com的pressed){
        VAR缓冲区=新的字节[数据长度]
        ZLibResult结果;
        固定(BYTE * B =缓存){
            结果= ZLibDecom preSS(三,len个,B&安培; DATALENGTH);
        }
        如果(结果== ZLibResult.Success){
            数据=缓冲区;
            返回结果;
        }
        数据= NULL;
        返回结果;
    }
}
 

和这里的C code(编译MinGW的-W64):

 的#include< stdint.h>
的#includezlib.h

#定义ZLibCom pressSuccess 0
#定义ZLibCom pressFailure 1

__cdecl __declspec(dllexport)的uint8_t有ZLibDecom preSS(uint8_t有* inStream中,uint32_t的inLength,
                                                     uint8_t有* outStream,uint32_t的* outLength)
{
    uLongf OL =(uLongf)* outLength;
    INT结果= uncom preSS(outStream,和放大器;醇,inStream中,inLength);
    * outLength =(uint32_t的)OL;
    如果(结果== Z_OK)
        返回ZLibCom pressSuccess;
    返回ZLibCom pressFailure;
}
 
同居三月后男友第一次看到她化妆的脸,万万没想到....

我看过了一切,想不通为什么访问冲突会发生在一个32位版本,而不是64位版本。 ZLibDecom preSS工作正常DECOM pressing从C应用程序调用时相同的流,但抛出从我的C#应用​​程序调用时访问冲突。

有谁知道这可能是为什么发生?

编辑: 更新我的code,仍然得到32位构建一个访问冲突,而不是64位。

C#code:

  [的DllImport(Program.UnmanagedDll,CallingConvention = CallingConvention.Cdecl)
私人静态外部ZLibResult ZLibDecom preSS(
    [的MarshalAs(UnmanagedType.LPArray)的byte [] inStream中,UINT inLength,
    [的MarshalAs(UnmanagedType.LPArray)的byte [] outStream,裁判UINT outLength);

内部静态ZLibResult DECOM preSS(byte []的COM pressed,出byte []的数据,UINT DATALENGTH){
    VAR缓冲区=新的字节[数据长度]
    VAR的结果= ZLibDecom preSS(COM pressed,(UINT)COM pressed.Length,缓冲,参考DATALENGTH);
    如果(结果== ZLibResult.Success){
        数据=缓冲区;
        返回结果;
    }
    数据= NULL;
    返回结果;
}
 

C code:

  __ declspec(dllexport)的uint8_t有__cdecl ZLibDecom preSS(uint8_t有* inStream中,uint32_t的inLength,
                                 uint8_t有* outStream,uint32_t的* outLength){
    uLongf OL =(uLongf)* outLength;
    INT结果= uncom preSS(outStream,和放大器;醇,inStream中,inLength);
    * outLength =(uint32_t的)OL;
    如果(结果== Z_OK)
        返回ZLibCom pressSuccess;
    返回ZLibCom pressFailure;
}
 

解决方案

 固定(BYTE * B =缓存){
        结果= ZLibDecom preSS(三,len个,B&安培; DATALENGTH);
    }
 

没有,不能正常工作。在固定的关键字提供一个高度优化的方法,以确保垃圾回收器移动的物体不会造成麻烦。它不被钉住对象(如文档说),它是通过使 B 变量,垃圾收集器做到这一点。然后看到它引用的缓冲区,并更新了 b值当它移动缓存

这却无法在这种情况下,的复制的的的b 工作的价值传递给ZlibDecom preSS()。垃圾收集器无法更新该副本。当发生GC而ZLibDecom preSS()运行时,本机code会破坏垃圾回收堆的完整性,这将最终导致一个AV的效果会很差。

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

但不这样做,要么,你是在帮助太多。该PInvoke的编组已经是非常善于牵制的对象,如果必要。声明河道 outstream 参数作为字节[],而不是字节*。并通过阵列直接没有做什么特别的事情。此外, outlength 参数应声明 REF INT

I'm writing a small zlib wrapper via P/Invoke calls. It runs perfectly on a 64-bit target (64-bit C# build, 64-bit DLL), but throws an AccessViolationException on a 32-bit target (32-bit C# build, 32-bit DLL).

Here's the C# signature and code which throws the exception:

[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;
    }
}

And here's the C code (compiled with 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;
}

I've looked over everything and can't figure out why an access violation would be happening on a 32-bit build and not on a 64-bit build. ZLibDecompress works fine decompressing the same stream when called from a C app, but throws an access violation when called from my C# app.

Does anyone know why this could be happening?

EDIT: Updated my code, still getting an access violation on 32-bit builds, but not 64-bit.

C# Code:

[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 Code:

__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);
    }

No, that can't work. The fixed keyword provides a highly optimized way to ensure that the garbage collector moving objects doesn't cause trouble. It doesn't do it by pinning the object (like the documentation says), it does it by exposing the b variable to the garbage collector. Which then sees it referencing the buffer and updates the value of b when it moves buffer.

That however can't work in this case, a copy of the b value was passed to ZlibDecompress(). The garbage collector cannot update that copy. The outcome will be poor when a GC occurs while ZLibDecompress() is running, the native code will destroy the integrity of the garbage collected heap and that will eventually cause an AV.

You cannot use fixed, you must use GCHandle.Alloc() to pin the buffer.

But don't do that either, you are helping too much. The pinvoke marshaller is already very good at pinning objects when necessary. Declare the instream and outstream arguments as byte[] instead of byte*. And pass the arrays directly without doing anything special. Also, the outlength argument should be declared ref int.