C# 如何";“固定”;lambda表达式?
我有一个方法C# 如何";“固定”;lambda表达式?,c#,interop,C#,Interop,我有一个方法 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", CallingC
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
异常,这实际上比我预期的硬崩溃要愉快得多
看起来确实有效,但似乎没有必要执行封送处理.GetFunctionPointerForDelegate
步骤。C回调将采用SDL\u EventFilter
很好,不需要将其设置为IntPtr
。此外,通过GCHandle.ToIntPtr(gch)
创建IntPtr
实际上会在触发事件时导致崩溃。不知道为什么;看来这个方法就是为这个目的而建立的,而且它甚至还被应用于其他领域
Darin链接到的文章指出:
注意,[手柄]不需要固定在任何特定的内存位置。因此,GCHandle.Alloc()的版本采用GCHandleType参数:
GCHandle gch = GCHandle.Alloc(callback_delegate, GCHandleType.Pinned);
不需要使用
但是,MSDN没有说明任何有关防止回调被移动的GCHandleType.Normal
。实际上,.pinted
的描述如下:
这将防止垃圾收集器移动对象
所以我试过了。它会导致一个带有帮助文本的参数异常
:
对象包含非基本数据或不可删除的数据
我只希望这篇文章没有撒谎,不需要钉住,因为我不知道如何测试这个场景
目前,这是我正在使用的解决方案:
[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();
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
内部委托int SDL_EventFilter(IntPtr userData,ref SDL_Event@Event);
[dlliport(“SDL2.dll”,CallingConvention=CallingConvention.Cdecl,EntryPoint=“SDL_AddEventWatch”)]
内部静态外部无效SDL_AddEventWatch(SDL_EventFilter filter,IntPtr userData);
[dlliport(“SDL2.dll”,CallingConvention=CallingConvention.Cdecl,EntryPoint=“SDL_DelEventWatch”)]
内部静态外部无效SDL_DelEventWatch(SDL_事件过滤器过滤器,IntPtr用户数据);
公共委托无效事件过滤器(事件@事件);
私有静态只读字典_eventWatchers=new Dictionary();
公共静态void AddEventWatch(事件过滤器)
{
SDL_事件过滤器ef=(IntPtr数据,参考SDL_事件e)=>
{
过滤器(新事件(参考e));
返回0;
};
var gch=GCHandle.Alloc(ef);
_Add(filter,Tuple.Create(ef,gch));
SDL_加法器(ef,IntPtr.Zero);
}
公共静态void DelEventWatch(事件筛选器筛选器)
{
var tup=_eventWatchers[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
下面的文章将详细介绍:谢谢。我有一些后续问题:1。在添加事件观察程序后,您已经立即释放了GCHandle,在回调发生时它不会死吗?2.如果只需按住GCHandle就可以锁定回调,那么当C DLL似乎同时执行这两种操作时,将委托转换为函数指针的目的是什么?此外,如果我们确实需要IntPtr
,为什么Marshal.GetFunctionPointerForDelegate
超过GCHandle.ToIntPtr
,如中所示?它们大致相同吗?是的,你的推理是正确的。一旦释放了gch
,回调可能会死机。因此,在确定已完成使用之前,不要释放它。处理这种情况的一种可能方法是释放回调中的句柄;我可以用相应的方法释放它,没问题。您知道[2]的答案吗?我是否确实需要将该代理设置为IntPtr?