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

问题描述:

我正在尝试使用非托管的C dll将图像数据加载到C#应用程序中.该库具有一个相当简单的接口,您可以在其中传递一个结构,该结构包含三个回调,一个用于接收图像的大小,一个用于接收像素的每一行,最后一个在加载完成时调用.像这样(C#托管定义):

I'm trying to use an unmanaged C dll for loading image data into a C# application. The library has a fairly simple interface where you pass in a struct that contains three callbacks, one to receive the size of the image, one that receives each row of the pixels and finally one called when the load is completed. Like this (C# managed definition):

[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开头的类型是代理:

The types starting st_ImageProtocol are delgates:

public delegate int st_ImageProtocol_sendLine(System.IntPtr localData, int rowNumber, System.IntPtr pixelData);

使用我正在使用SetSize的测试文件应该被调用一次,然后SendLine将被调用200次(对于图像中的每一行像素一次),最后触发完成回调.实际发生的情况是,SendLine被调用了19次,然后抛出AccessViolationException,声称该库试图访问受保护的内存.

With the test file that I'm using the SetSize should get called once, then the SendLine will get called 200 times (once for each row of pixels in the image), finally the Done callback gets triggered. What actually happens is that the SendLine is called 19 times and then a AccessViolationException is thrown claiming that the library tried to access protected memory.

我可以访问C库的代码(尽管不能更改功能),并且在调用SendLine方法的循环中,它不会分配或释放任何新内存,因此我的假设是委托本身就是问题,我需要在传递它之前将其固定(当前,委托本身中没有代码,此外还没有计数器来查看调用频率,因此我怀疑是否会破坏托管端的任何内容) .问题是我不知道该怎么做.我一直用来在非托管空间中声明结构的方法不适用于委托(Marshal.AllocHGlobal()),并且找不到任何其他合适的方法.委托本身是Program类中的静态字段,因此不应该对其进行垃圾回收,但是我想运行时可能会移动它们.

I have access to the code of the C library (though I can't change the functionality) and during the loop where it calls the SendLine method it does not allocate or free any new memory, so my assumption is that the delegate itself is the issue and I need to pin it before I pass it in (I have no code inside the delegate itself currently, besides a counter to see how often it gets called, so I doubt I'm breaking anything on the managed side). The problem is that I don't know how to do this; the method I've been using to declare the structs in unmanaged space doesn't work with delegates (Marshal.AllocHGlobal()) and I can't find any other suitable method. The delegates themselves are static fields in the Program class so they shouldn't be being garbage collected, but I guess the runtime could be moving them.

克里斯·布鲁姆(Chris Brumme)的这篇博客条目说无需在传递给非托管代码之前固定代表:

This blog entry by Chris Brumme says that delegates don't need to be pinned before being passed into unmanaged code:

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

Clearly the unmanaged function pointer must refer to a fixed address. It would be a disaster if the GC were relocating that! This leads many applications to create a pinning handle for the delegate. This is completely unnecessary. The unmanaged function pointer actually refers to a native code stub that we dynamically generate to perform the transition & marshaling. This stub exists in fixed memory outside of the GC heap.

但是我不知道当委托是结构的一部分时,这是否成立.但这确实意味着可以手动固定它们,并且我对如何执行此操作或对为什么循环将运行19次然后突然失败的任何更好的建议感兴趣.

But I don't know if this holds true when the delegate is part of a struct. It does imply that it is possible to manually pin them though, and I'm interested in how to do this or any better suggestions as to why a loop would run 19 times then suddenly fail.

谢谢.

经过编辑可以回答约翰的问题...

Edited to answer Johan's questions...

分配结构的代码如下:

_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变量的副本的非托管指针传递到C库中,但是该副本包含对静态_sendLineFunc的引用.这应该意味着该副本不会被GC触及-因为它是不受管的-并且由于它仍在范围内(静态),因此不会收集该委托.

Where the _sendLineFunc and the _imageProtocol variables are both static fields of the Program class. If I understand the internals of this correctly, that means that I'm passing an unmanaged pointer to a copy of the _imageProtocol variable into the C library, but that copy contains a reference to the static _sendLineFunc. This should mean that the copy isn't touched by the GC - since it is unmanaged - and the delegate won't be collected since it is still in scope (static).

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

The struct actually gets passed to the library as a return value from another callback, but as a pointer:

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

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

Basically there is another struct type that holds the image filename and the function pointer to this callback, the library figures out what type of image is stored in the file and uses this callback to request the correct protocol struct for the given type. My filename struct is declared and managed in the same way as the protocol one above, so probably contains the same mistakes, but since this delegate is only called once and called quickly I haven't had any problems with it yet.

编辑以更新

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

Thanks to everybody for their responses, but after spending another couple of days on the problem and making no progress I decided to shelve it. In case anyone is interested I was attempting write a tool for users of the Lightwave 3D rendering application and a nice feature would have been the ability to view all the different image formats that Lightwave supports (some of which are fairly exotic). I thought that the best way to do this would be to write a C# wrapper for the plugin architecture that Lightwave uses for image manipulation so I could use their code to actually load the files. Unfortunately after trying a number of the plugins against my solution I had a variety of errors that I couldn't understand or fix and my guess is that Lightwave doesn't call the methods on the plugins in a standard way, probably to improve the security of running external code (wild stab in the dark, I admit). For the time being I'm going to drop the image feature and if I do decide to reinstate it I'll approach it in a different way.

再次感谢,尽管我没有得到想要的结果,但我在整个过程中学到了很多东西.

Thanks again, I learnt a lot through this process even though I didn't get the result I wanted.

注册回调委托时,我遇到了类似的问题(它将被调用,然后发出of声!).我的问题是使用委托方法的对象正在被GC处理.我在更全局的位置创建了该对象,以防止它被GC处理.

I had a similar problem when registering a callback delegate (it would be called, then poof!). My problem was that the object with the method being delegated was getting GC'ed. I created the object in a more global place so as to keep it from being GC'ed.

如果类似的方法不起作用,请注意以下其他事项:

If something like that doesn't work, here are some other things to look at:

作为其他信息,请查看 GetFunctionPointerForDelegate .这是您可以执行此操作的另一种方法.只需确保未对这些代表进行GC处理即可.然后,不要将您的结构中的委托声明为IntPtr.

As additional info, take a look at GetFunctionPointerForDelegate from the Marshal class. That is another way you could do this. Just make sure that the delegates are not GC'ed. Then, instead of delegates in your struct, declare them as IntPtr.

这可能无法解决固定问题,但请查看

That may not solve the pinning, but take a look at fixed keyword, even though that may not work for you since you are dealing with a longer lifetime than for what that is typically used.

最后,查看 stackalloc 用于创建非GC内存.这些方法将需要使用unsafe,因此可能会对您的程序集施加其他一些约束.

Finally, look at stackalloc for creating non-GC memory. These methods will require the use of unsafe, and might therefore put some other constraints on your Assemblies.