我有一个方法,
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);
需要回调。
如上所示,我SDL_EventFilter以 lambda 表达式的形式传递,稍后由 C API 调用。
通过初步测试,这可以正常工作。不过我的理解是 lambda 表达式可以由 CLR 垃圾收集器清理或在内存中移动,因为它不知道 DLL 持有对它的引用。
- 这是真的?
- 如果是这样,我了解该
fixed关键字用于防止此类移动,- 我如何
fixed向代表提出申请? - 即使我“修复”它,它是否仍不会因为超出范围而被清理/删除?
- 我如何
我做了一些实验。GC.Collect();我在添加事件后立即调用,但在触发它之前。它抛出了一个CallbackOnCollectedDelegate异常,这实际上比我预期的硬崩溃要愉快得多。
Darin 的解决方案似乎确实有效,但该Marshal.GetFunctionPointerForDelegate步骤似乎是不必要的。C 回调会SDL_EventFilter很好,没有必要将它变成IntPtr. 此外,当事件被触发时,创建IntPtr通路实际上会导致崩溃。GCHandle.ToIntPtr(gch)不知道为什么;看来该方法是为此而构建的,甚至在MSDN 示例中也使用了它。
Darin 链接到的文章指出:
请注意,[句柄] 不需要固定在任何特定的内存位置。因此,采用 GCHandleType 参数的 GCHandle.Alloc() 版本:
GCHandle gch = GCHandle.Alloc(callback_delegate, GCHandleType.Pinned);不需要使用。
但是,MSDN 没有说明GCHandleType.Normal防止回调被移动。事实上,描述.Pinned如下:
这可以防止垃圾收集器移动对象
所以我试了一下。它会导致ArgumentException带有帮助文本:
对象包含非原始或非 blittable 数据
我只能希望这篇文章不要说不需要置顶,因为我不知道如何测试这种情况。
目前,这是我正在使用的解决方案:
[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会做除此之外的任何事情。