2013-08-07 61 views
4

我有一個方法,如何「修復」一個lambda表達式?

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's solution看起來沒有問題,但Marshal.GetFunctionPointerForDelegate步驟似乎是不必要的。 C回調只需要SDL_EventFilter就好了,沒有必要使它成爲IntPtr。此外,通過GCHandle.ToIntPtr(gch)創建IntPtr實際上會在事件被觸發時導致崩潰。不知道爲什麼;看來該方法是爲此而構建的,甚至在the MSDN example中使用。

是達林鏈接到國文章:

注意[手柄]不必固定在任何特定的內存位置。因此,需要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是否會做任何事情。

回答

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/

+0

謝謝。我有一些後續問題:1.添加事件監視器後,您已經立即釋放了GCHandle,在回調發生時它不會死掉嗎? 2.如果僅僅持有一個GCHandle引腳的回調函數,那麼當C DLL似乎可以採用時,將委託轉換爲函數指針的目的是什麼? – mpen

+0

另外,如果我們確實需要一個'IntPtr',爲什麼'Marshal.GetFunctionPointerForDelegate'覆蓋'GCHandle.ToIntPtr',如[MSDN文檔]中所示(http://msdn.microsoft.com/zh-cn/library/a95009h1的.aspx)?他們差不多是一樣的嗎? – mpen

+0

是的,你的推理是正確的。一旦'gch'被釋放,回調可能已經失效。所以不要釋放它,直到你確定你已經完成了它。一旦可能的方式來處理這種情況是釋放回調內的句柄。 –