我有一个方法,
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
会做除此之外的任何事情。