如何“;“修复”;lambda表达式
本文关键字:表达式 lambda 修复 如何 | 更新日期: 2023-09-27 18:26:44
我有一个方法,
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
应用于代理人 - 即使我"修复"了它,它不会因为超出范围而被清理/删除吗
- 如何将
我做了一些实验。我在添加事件后立即调用了GC.Collect();
,但在触发它之前。它抛出了一个CallbackOnCollectedDelegate
异常,这实际上比我预期的严重崩溃要愉快得多。
Darin的解决方案看起来确实有效,但Marshal.GetFunctionPointerForDelegate
步骤似乎没有必要。C回调将取一个SDL_EventFilter
,没有必要将其设为IntPtr
。此外,当事件被触发时,通过GCHandle.ToIntPtr(gch)
创建IntPtr
实际上会导致崩溃。不确定原因;该方法似乎是为此而构建的,甚至在MSDN示例中也使用了它。
Darin链接到各州的文章:
请注意,[句柄]不需要固定在任何特定的内存位置。因此,GCHandle.Aloc()的版本采用GCHandleType参数:
GCHandle gch = GCHandle.Alloc(callback_delegate, GCHandleType.Pinned);
不需要使用。
但是,MSDN没有说明GCHandleType.Normal
阻止回调被移动的任何内容。事实上,.Pinned
的描述是:
这样可以防止垃圾收集器移动对象
所以我尝试了一下。它导致了一个带有帮助文本的ArgumentException
:
对象包含非基元或非blitable数据
我只能希望这篇文章没有撒谎,不需要固定,因为我不知道如何测试这个场景。
目前,这是我正在使用的解决方案:
[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) 这是真的吗?
是的。
2) 如何将固定应用于代理人?
定义你的方法签名如下:
[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
,回调就不会四处移动或GCed。
以下文章详细介绍:http://limbioliong.wordpress.com/2011/06/19/delegates-as-callbacks-part-2/