gpt4 book ai didi

c# - P/Invoke 调用中的 AccessViolationException

转载 作者:行者123 更新时间:2023-12-02 22:37:23 25 4
gpt4 key购买 nike

我正在通过 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 位版本上。 ZLibDecompress 在从 C 应用程序调用时可以很好地解压缩相同的流,但在从我的 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;
}

最佳答案

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

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

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

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

但是也不要那样做,你帮的太多了。 pinvoke marshaller 已经非常擅长在必要时固定对象。将 instreamoutstream 参数声明为 byte[] 而不是 byte*。并直接传递数组而不做任何特殊操作。此外,outlength 参数应声明为 ref int

关于c# - P/Invoke 调用中的 AccessViolationException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11272256/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com