我有一個方法,如何「修復」一個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持有對它的引用。
- 這是真的嗎?
- 如果是這樣,我明白,
fixed
關鍵字用來防止這種運動,- 我該如何申請
fixed
的代表? - 即使我「修復」它,豈不仍然被清理/刪除,因爲它超出範圍?
- 我該如何申請
我做了一些實驗。我在添加事件後但在觸發它之前調用了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
是否會做任何事情。
謝謝。我有一些後續問題:1.添加事件監視器後,您已經立即釋放了GCHandle,在回調發生時它不會死掉嗎? 2.如果僅僅持有一個GCHandle引腳的回調函數,那麼當C DLL似乎可以採用時,將委託轉換爲函數指針的目的是什麼? – mpen
另外,如果我們確實需要一個'IntPtr',爲什麼'Marshal.GetFunctionPointerForDelegate'覆蓋'GCHandle.ToIntPtr',如[MSDN文檔]中所示(http://msdn.microsoft.com/zh-cn/library/a95009h1的.aspx)?他們差不多是一樣的嗎? – mpen
是的,你的推理是正確的。一旦'gch'被釋放,回調可能已經失效。所以不要釋放它,直到你確定你已經完成了它。一旦可能的方式來處理這種情況是釋放回調內的句柄。 –