4

我有一个方法,

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);

需要回调。

如上所示,我SDL_EventFilter以 lambda 表达式的形式传递,稍后由 C API 调用。

通过初步测试,这可以正常工作。不过我的理解是 lambda 表达式可以由 CLR 垃圾收集器清理或在内存中移动,因为它不知道 DLL 持有对它的引用。

  1. 这是真的?
  2. 如果是这样,我了解该fixed关键字用于防止此类移动,
    1. 我如何fixed向代表提出申请?
    2. 即使我“修复”它,它是否仍不会因为超出范围而被清理/删除?

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

Darin 的解决方案似乎确实有效,但该Marshal.GetFunctionPointerForDelegate步骤似乎是不必要的。C 回调会SDL_EventFilter很好,没有必要将它变成IntPtr. 此外,当事件被触发时,创建IntPtr通路实际上会导致崩溃。GCHandle.ToIntPtr(gch)不知道为什么;看来该方法是为此而构建的,甚至在MSDN 示例中也使用了它。

Darin 链接到的文章指出:

请注意,[句柄] 不需要固定在任何特定的内存位置。因此,采用 GCHandleType 参数的 GCHandle.Alloc() 版本:

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

不需要使用。

但是,MSDN 没有说明GCHandleType.Normal防止回调被移动。事实上,描述.Pinned如下:

这可以防止垃圾收集器移动对象

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

对象包含非原始或非 blittable 数据

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


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

[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会做除此之外的任何事情。

4

1 回答 1

3

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/

于 2013-08-07T07:23:37.953 回答