我正在尝试使用非托管 C dll 将图像数据加载到 C# 应用程序中。该库有一个相当简单的接口,您可以在其中传递一个包含三个回调的结构,一个用于接收图像的大小,一个用于接收每一行像素,最后一个在加载完成时调用。像这样(C# 托管定义):
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct st_ImageProtocol
{
public st_ImageProtocol_done Done;
public st_ImageProtocol_setSize SetSize;
public st_ImageProtocol_sendLine SendLine;
}
启动 stb_Image 协议的类型是委托:
public delegate int st_ImageProtocol_sendLine(System.IntPtr localData, int rowNumber, System.IntPtr pixelData);
在我使用的测试文件中,SetSize 应该被调用一次,然后 SendLine 将被调用 200 次(图像中的每行像素一次),最后触发 Done 回调。实际发生的情况是,SendLine 被调用 19 次,然后抛出 AccessViolationException,声称该库试图访问受保护的内存。
我可以访问 C 库的代码(尽管我无法更改功能),并且在调用 SendLine 方法的循环期间,它不会分配或释放任何新内存,所以我的假设是委托本身是问题,我需要在传递它之前固定它(目前我在委托本身内部没有代码,除了一个计数器来查看它被调用的频率,所以我怀疑我是否破坏了托管端的任何内容)。问题是我不知道该怎么做;我一直用来在非托管空间中声明结构的方法不适用于委托(Marshal.AllocHGlobal()),并且我找不到任何其他合适的方法。委托本身是 Program 类中的静态字段,因此它们不应该被垃圾收集,但我猜运行时可能会移动它们。
Chris Brumme 的这篇博客文章 http://blogs.msdn.com/cbrumme/archive/2003/05/06/51385.aspx表示在传递给非托管代码之前不需要固定委托:
显然,非托管函数指针必须引用固定地址。如果 GC 重新安置它,那将是一场灾难!这导致许多应用程序为委托创建固定句柄。这是完全没有必要的。非托管函数指针实际上指的是我们动态生成以执行转换和封送的本机代码存根。该存根存在于 GC 堆之外的固定内存中。
但我不知道当委托是结构的一部分时这是否成立。它确实意味着可以手动固定它们,而且我对如何执行此操作或关于为什么循环运行 19 次然后突然失败的任何更好建议感兴趣。
Thanks.
编辑回答约翰的问题......
分配struct的代码如下:
_sendLineFunc = new st_ImageProtocol_sendLine(protocolSendLineStub);
_imageProtocol = new st_ImageProtocol()
{
//Set some other properties...
SendLine = _sendLineFunc
};
int protocolSize = Marshal.SizeOf(_imageProtocol);
_imageProtocolPtr = Marshal.AllocHGlobal(protocolSize);
Marshal.StructureToPtr(_imageProtocol, _imageProtocolPtr, true);
其中 _sendLineFunc 和 _imageProtocol 变量都是 Program 类的静态字段。如果我正确理解了它的内部原理,那就意味着我将一个非托管指针传递给copy_imageProtocol 变量复制到 C 库中,但该副本包含对静态 _sendLineFunc 的引用。这应该意味着副本不会被 GC 触及 - 因为它是非托管的 - 并且委托不会被收集,因为它仍然在范围内(静态)。
该结构实际上作为另一个回调的返回值传递到库,但作为指针:
private static IntPtr beginCallback(IntPtr localData, en_ImageType imageType)
{
return _imageProtocolPtr;
}
基本上还有另一种结构类型保存图像文件名和指向此回调的函数指针,库确定文件中存储的图像类型,并使用此回调请求给定类型的正确协议结构。我的文件名结构的声明和管理方式与上面的协议相同,因此可能包含相同的错误,但由于该委托仅被调用一次并被快速调用,所以我还没有遇到任何问题。
编辑更新
感谢大家的回复,但在这个问题上又花了几天时间但没有取得任何进展后,我决定搁置它。如果有人感兴趣,我正在尝试为 Lightwave 3D 渲染应用程序的用户编写一个工具,一个很好的功能是能够查看 Lightwave 支持的所有不同图像格式(其中一些相当奇特)。我认为最好的方法是为 Lightwave 用于图像处理的插件架构编写一个 C# 包装器,这样我就可以使用他们的代码来实际加载文件。不幸的是,在针对我的解决方案尝试了许多插件之后,我遇到了各种我无法理解或修复的错误,我的猜测是 Lightwave 没有以标准方式调用插件上的方法,可能是为了提高安全性运行外部代码(我承认是在黑暗中进行的疯狂刺杀)。目前我将放弃图像功能,如果我决定恢复它,我将以不同的方式处理它。
再次感谢,尽管我没有得到我想要的结果,但通过这个过程我学到了很多东西。