【问题标题】:Unmanaged DLL causing AccessViolationException非托管 DLL 导致 AccessViolationException
【发布时间】:2012-07-27 18:52:00
【问题描述】:

这个真的开始让我头疼了:(

我有一个非托管的 DLL,我正在尝试与之互操作,但运行不顺利。应用程序有时会正常工作......但大多数时候,随机通过 AccessViolationException 并可怕地崩溃。

我想我已经把它缩小到我对单个 DllImport 的错误处理:

C++ 函数:

HTMLRENDERERDLL_REDIST_API void SetDataBuffer( int windowHandle, unsigned char* dataSource, int format, int stride, int totalBufferSize );

C# DllImport:

[DllImport("MyDll.dll", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
    static private extern unsafe void SetDataBuffer(Int32 windowHandle, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] dataSource, Int32 format, Int32 stride, Int32 totalBufferSize);

调用上述函数:

var buffer = new byte[windowWidth * windowHeight * bytesPerPixel];
SetDataBuffer(windowHandle, buffer, (Int32)0, (Int32)(windowWidth * bytesPerPixel), (Int32)(windowWidth * windowHeight * bytesPerPixel));

这有什么明显的问题吗?我怀疑dataSource 是罪魁祸首,但……不知道如何证明!

谢谢

【问题讨论】:

  • 如果你有MyDLL的源码,你可以通过将调试器模式设置为“混合”来调试崩溃
  • 很遗憾我没有
  • 调用api时是否发生访问冲突,还是使用marshaled指针?
  • 当我调用 API 时。还有另一个函数——“更新”——被重复调用,并且在执行的 1-30 帧内的某个时间点,该函数将抛出。它没有参数,所以不会出错...
  • 错字:可能只是为了显示用法,但您创建 var buffer = ... 但您使用:w.buffer

标签: c# c++ dll interop


【解决方案1】:

您的问题可以从函数的名称中推断出来。当您“设置缓冲区”时,本机代码很可能稍后会使用该缓冲区。这与垃圾收集器不兼容,它会在压缩堆时移动数组。当本机代码写入缓冲区时,这是一个很大的 Kaboom,它将写入不再存在的内存。当垃圾收集器检测到堆完整性受到损害时,最典型的结果是 FatalExecutionEngineException。

需要固定数组,这是 pinvoke 编组器在调用函数时所做的事情,但它在调用后取消固定数组。

您可以使用 GCHandle.Alloc() 固定托管数组,但是如果您长时间固定它,这对垃圾收集器非常不利。到目前为止,最好的解决方案是使用 Marshal.AllocHGlobal 分配一块非托管内存,它永远不会移动。

如果您仍然遇到问题,请担心缓冲区的大小。而且只是原生代码中的简单痛苦,它很少需要太多帮助才能在 AccessViolation 上摔倒。这是本机代码的标准故障模式。很难诊断,如果你没有源代码是不可能的。联系代码所有者寻求支持,有一个小的 repro sn-p 可以帮助他找到问题。

【讨论】:

    【解决方案2】:

    我同意数据源很可能是问题所在。

    尝试更改 dllimport,使其使用 IntPtr 而不是 byte[]。

    [DllImport("MyDll.dll", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
    static private extern unsafe void SetDataBuffer(Int32 windowHandle, IntPtr dataSource, Int32 format, Int32 stride, Int32 totalBufferSize);
    

    然后当你调用它时,像这样显式地为缓冲区分配内存:

    IntPtr buffer = Marshal.AllocHGlobal(windowWidth * windowHeight * bytesPerPixel);
    SetDataBuffer(windowHandle, buffer, (Int32)0, (Int32)(windowWidth * bytesPerPixel), (Int32)(windowWidth * windowHeight * bytesPerPixel));
    

    完成后不要忘记致电Marshal.FreeHGlobal(buffer)

    我认为缓冲区正在由 Update 方法在内部使用?

    【讨论】:

    • 没有骰子,不幸的是:(没用。是的,缓冲区正在写入每一帧。
    • 真可惜。汉斯在那里说的话让我想到了缓冲区的大小。它可能不适用于这种情况,但有时,如果每个像素的字节数小于 4,则每行将根据需要用一些额外的字节填充,以使行的大小达到 4 字节的倍数。您使用的图像是否始终具有一致的大小,或者它们可能会有所不同?
    • 计算下一个 4 字节边界的简写方法 (((windowWidth * windowHeight * bytesPerPixel) + 3) & ~3)
    • 抱歉,windowHeight 不应该这样包含,它应该是 , (((windowWidth * bytesPerPixel) + 3) & ~3) * windowHeight
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-14
    • 2017-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多