在传递给非托管代码之前固定结构中的委托

时间:2009-04-06 09:18:40

标签: c# .net pinvoke unmanaged

我正在尝试使用非托管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;
}

启动st_ImageProtocol的类型是delgates:

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类中的静态字段,因此它们不应该被垃圾收集,但我想运行时可能正在移动它们。

This blog entry by Chris Brumme表示代理在传递给非托管代码之前不需要固定:

  

显然,非托管函数指针必须引用固定地址。如果GC重新安置那将是一场灾难!这导致许多应用程序为委托创建固定句柄。这完全没必要。非托管函数指针实际上是指我们动态生成以执行转换的本机代码存根。封送处理。此存根存在于GC堆外部的固定内存中。

但是当委托是结构的一部分时,我不知道这是否成立。它确实意味着可以手动固定它们,我很感兴趣如何做到这一点或任何更好的建议,为什么循环会运行19次然后突然失败。

感谢。


编辑回答Johan的问题......

分配结构的代码如下:

_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类的静态字段。如果我正确理解了这个内部,那意味着我将一个非托管指针传递给_imageProtocol变量的 copy 到C库中,但该副本包含对静态_sendLineFunc的引用。这应该意味着GC不会触及副本 - 因为它是非托管的 - 并且代理将不会被收集,因为它仍在范围内(静态)。

struct实际上作为另一个回调的返回值传递给库,但作为指针:

private static IntPtr beginCallback(IntPtr localData, en_ImageType imageType)
{
    return _imageProtocolPtr;
}

基本上还有另一种结构类型,它包含图像文件名和指向此回调的函数指针,库可以确定文件中存储的图像类型,并使用此回调为给定类型请求正确的协议结构。我的文件名结构以与上面的协议相同的方式声明和管理,因此可能包含相同的错误,但由于此委托只被调用一次并且快速调用,所以我还没有遇到任何问题。


已编辑更新

感谢大家的回应,但在花了几天时间解决问题并没有取得进展后,我决定搁置它。如果有人感兴趣我正在尝试为Lightwave 3D渲染应用程序的用户编写工具,一个很好的功能就是能够查看Lightwave支持的所有不同的图像格式(其中一些非常具有异国情调)。我认为最好的方法是为Lightwave用于图像处理的插件架构编写一个C#包装器,这样我就可以使用它们的代码来实际加载文件。不幸的是,在针对我的解决方案尝试了一些插件之后,我遇到了一些我无法理解或修复的错误,我的猜测是Lightwave没有以标准方式调用插件上的方法,可能是为了提高安全性运行外部代码(在黑暗中狂野刺,我承认)。暂时我将放弃图像功能,如果我决定恢复它,我会以不同的方式接近它。

再次感谢,即使我没有得到我想要的结果,我也通过这个过程学到了很多东西。

4 个答案:

答案 0 :(得分:2)

我在注册一个回调委托时遇到了类似的问题(它会被调用,然后是poof!)。我的问题是委托方法的对象是GC。我在一个更全球化的地方创建了这个对象,以防止它被GC控制。

如果这样的事情不起作用,还有其他一些事情要看:

作为附加信息,请查看Marshal课程中的GetFunctionPointerForDelegate。这是你可以做到的另一种方式。只需确保代表不是GC。然后,代替结构中的委托,将它们声明为IntPtr。

这可能无法解决固定问题,但请查看fixed关键字,即使这可能不适合您,因为您处理的生命周期比通常使用的更长。

最后,请查看stackalloc以创建非GC内存。这些方法需要使用unsafe,因此可能会对您的程序集设置一些其他约束。

答案 1 :(得分:0)

了解更多内容会很有趣:

  • 如何创建ImageProtocol结构?它是局部变量还是类成员,还是使用Marshal.AllocHGlobal在非托管内存中分配它?

  • 如何发送到C函数?直接作为堆栈变量还是作为指针?


一个非常棘手的问题!感觉就像委托数据被GC移动而导致访问冲突。有趣的是委托数据类型是一种引用数据类型,它将其数据存储在GC堆上。此数据包含要调用的函数的地址(函数指针)等内容,但也包含对包含该函数的对象的引用。这应该意味着即使实际的功能代码存储在GC堆之外,保存函数指针的数据也会存储在GC堆中,因此可以由GC移动。昨晚我对这个问题进行了很多思考,但没有提出解决方案......

答案 2 :(得分:0)

您没有确切地说明在C库中如何声明回调。除非明确声明__stdcall,否则你将慢慢腐败你的筹码。你会看到你的方法被调用(可能是参数反转),但在将来的某个时候程序会崩溃。

据我所知,除了在C语言中编写另一个回调函数之外,没有办法解决这个问题,该函数位于C#代码和想要__cdecl回调的库之间。

答案 3 :(得分:0)

如果c函数是__cdecl函数,那么你必须使用Attribut         [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 在代表声明之前。