如何“;“修复”;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中有对它的引用。

  1. 这是真的吗
  2. 如果是的话,我理解fixed关键字是用来防止这种移动的,
    1. 如何将fixed应用于代理人
    2. 即使我"修复"了它,它不会因为超出范围而被清理/删除吗

我做了一些实验。我在添加事件后立即调用了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是否做了其他事情。

如何“;“修复”;lambda表达式

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/