c# - 如何 "fix"lambda表达式?

标签 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", 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 应用于委托(delegate)?
    2. 即使我“修复”了它,它是否仍会因为超出范围而被清理/删除?

我做了一些实验。我在添加事件之后但在触发事件之前立即调用了 GC.Collect();。它抛出一个 CallbackOnCollectedDelegate 异常,这实际上比我预期的硬崩溃要愉快得多。

Darin's solution似乎确实有效,但 Marshal.GetFunctionPointerForDelegate 这一步似乎是不必要的。 C 回调将采用 SDL_EventFilter 就可以了,无需将其设为 IntPtr。此外,通过 GCHandle.ToIntPtr(gch) 创建 IntPtr 实际上会在触发事件时导致崩溃。不知道为什么;看来该方法是为此而构建的,它甚至用于 the MSDN example .

Darin 链接到的文章指出:

Note that [the handle] need not be fixed at any specific memory location. Hence the version of GCHandle.Alloc() that takes a GCHandleType parameter :

GCHandle gch = GCHandle.Alloc(callback_delegate, GCHandleType.Pinned);

need not be used.

但是,MSDN 没有说明任何关于 GCHandleType.Normal 阻止移动回调的内容。事实上,.Pinned 的描述是这样的:

This prevents the garbage collector from moving the object

所以我试了一下。它会导致 ArgumentException 以及帮助文本:

Object contains non-primitive or non-blittable data

我只能希望这篇文章没有说不需要固定,因为我不知道如何测试这种情况。


目前,这是我正在使用的解决方案:

[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) Is this true?

是的。

2) How do I apply fixed to a delegate?

像这样定义你的方法签名:

[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,回调就不会被移动或 GC。

以下文章有更多详细信息:http://limbioliong.wordpress.com/2011/06/19/delegates-as-callbacks-part-2/

关于c# - 如何 "fix"lambda表达式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18096809/

相关文章:

Clojure deftype 调用函数在同一个命名空间中抛出 "java.lang.IllegalStateException: Attempting to call unbound fn:"

c# - 如何处理未初始化的局部变量

c# - 如何从 Chrome 和 Firefox 获取打开页面的 URL?

c# - 如何找到最近的日期

.net - 如何在Delphi中使用.Net程序集而不在GAC或COM中注册它?

java - 使用 Clojure 中的 Apache Commons 编解码器解码Base64

java - Clojure Java Interop - 对字段的引用...无法解析

c# - vstest.console.exe 的并行选项无法按预期工作

c# - 生成随机整数时无法包含上限

C# - Java 互操作