我有一个方法,
public static void AddEventWatch(EventFilter filter)
{
SDL_AddEventWatch((IntPtr data, ref SDL_Event e) =>
{
filter(new Event(ref e));
return 0;
}, IntPtr.Zero);
}
调用 C
函数,
[DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_AddEventWatch")]
internal static extern void SDL_AddEventWatch(SDL_EventFilter filter, IntPtr userData);
期待回调。
如上所示,我以 lambda 表达式的形式传递 SDL_EventFilter
,稍后由 C API 调用。
通过初步测试,这按原样工作得很好。不过我的理解是,lambda 表达式可以被 CLR 垃圾收集器清除或在内存中四处移动,因为它不知道 DLL 持有对它的引用。
- 这是真的吗?
- 如果是这样,我明白
fixed
关键字是用来防止这种移动的,- 如何将
fixed
应用于委托(delegate)? - 即使我“修复”了它,它是否仍会因为超出范围而被清理/删除?
- 如何将
我做了一些实验。我在添加事件之后但在触发事件之前立即调用了 GC.Collect();
。它抛出一个 CallbackOnCollectedDelegate
异常,这实际上比我预期的硬崩溃要愉快得多。
Darin's solution似乎确实有效,但 Marshal.GetFunctionPointerForDelegate
这一步似乎是不必要的。 C 回调将采用 SDL_EventFilter
就可以了,无需将其设为 IntPtr
。此外,通过 GCHandle.ToIntPtr(gch)
创建 IntPtr
实际上会在触发事件时导致崩溃。不知道为什么;看来该方法是为此而构建的,它甚至用于 the MSDN example .
Darin 链接到的文章指出:
Note that [the handle] need not be fixed at any specific memory location. Hence the version of GCHandle.Alloc() that takes a GCHandleType parameter :
GCHandle gch = GCHandle.Alloc(callback_delegate, GCHandleType.Pinned);
need not be used.
但是,MSDN 没有说明任何关于 GCHandleType.Normal
阻止移动回调的内容。事实上,.Pinned
的描述是这样的:
This prevents the garbage collector from moving the object
所以我试了一下。它会导致 ArgumentException
以及帮助文本:
Object contains non-primitive or non-blittable data
我只能希望这篇文章没有说不需要固定,因为我不知道如何测试这种情况。
目前,这是我正在使用的解决方案:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int SDL_EventFilter(IntPtr userData, ref SDL_Event @event);
[DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_AddEventWatch")]
internal static extern void SDL_AddEventWatch(SDL_EventFilter filter, IntPtr userData);
[DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_DelEventWatch")]
internal static extern void SDL_DelEventWatch(SDL_EventFilter filter, IntPtr userData);
public delegate void EventFilter(Event @event);
private static readonly Dictionary<EventFilter, Tuple<SDL_EventFilter, GCHandle>> _eventWatchers = new Dictionary<EventFilter, Tuple<SDL_EventFilter, GCHandle>>();
public static void AddEventWatch(EventFilter filter)
{
SDL_EventFilter ef = (IntPtr data, ref SDL_Event e) =>
{
filter(new Event(ref e));
return 0;
};
var gch = GCHandle.Alloc(ef);
_eventWatchers.Add(filter, Tuple.Create(ef,gch));
SDL_AddEventWatch(ef, IntPtr.Zero);
}
public static void DelEventWatch(EventFilter filter)
{
var tup = _eventWatchers[filter];
_eventWatchers.Remove(filter);
SDL_DelEventWatch(tup.Item1, IntPtr.Zero);
tup.Item2.Free();
}
但是,仅仅向字典中添加 ef
就可以防止垃圾回收。我不太确定 GCHandle.Alloc
是否还有其他功能。
最佳答案
1) Is this true?
是的。
2) How do I apply fixed to a delegate?
像这样定义你的方法签名:
[DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_AddEventWatch")]
internal static extern void SDL_AddEventWatch(IntPtr filterPointer, IntPtr userData);
然后:
public static void AddEventWatch(EventFilter filter)
{
SDL_EventFilter myFilter = (IntPtr data, ref SDL_Event e) =>
{
filter(new Event(ref e));
return 0;
};
GCHandle gch = GCHandle.Alloc(myFilter);
try
{
var filterPointer = Marshal.GetFunctionPointerForDelegate(myFilter);
SDL_AddEventWatch(filterPointer, IntPtr.Zero);
}
finally
{
gch.Free();
}
}
基本上,只要您在内存中持有 GCHandle
,回调就不会被移动或 GC。
以下文章有更多详细信息:http://limbioliong.wordpress.com/2011/06/19/delegates-as-callbacks-part-2/
关于c# - 如何 "fix"lambda表达式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18096809/