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?