How to "fix" a lambda expression?

I have a method

public static void AddEventWatch(EventFilter filter) { SDL_AddEventWatch((IntPtr data, ref SDL_Event e) => { filter(new Event(ref e)); return 0; }, IntPtr.Zero); } 

This calls function C ,

 [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_AddEventWatch")] internal static extern void SDL_AddEventWatch(SDL_EventFilter filter, IntPtr userData); 

which is expecting a callback.

As shown above, I pass the SDL_EventFilter in the form of a lambda expression, which is later called by the C API.

Through preliminary testing, this works great as it is. My understanding is that a lambda expression can be cleared by the CLR garbage collector or moved to memory, because it does not know that the DLL holds a reference to it.

  • It's true?
  • If so, I understand that the fixed keyword is used to prevent such a move,
    • How to apply fixed to a delegate?
    • Even if I fix it, will it still not be cleaned / deleted because it is out of scope?

I experimented a bit. I called GC.Collect(); immediately after adding an event, but before it is triggered. It throws a CallbackOnCollectedDelegate exception, which is actually much nicer than the expected hard crash.

Darin's solution works, but the Marshal.GetFunctionPointerForDelegate step is unnecessary. The C SDL_EventFilter will take SDL_EventFilter just fine, there is no need to do it IntPtr . Additionally, creating IntPtr via GCHandle.ToIntPtr(gch) actually causes a crash when the event GCHandle.ToIntPtr(gch) . Not sure why; the method seems to be built for this, and it is even used in the MSDN example .

An article in which Darin refers to conditions:

Note that the [descriptor] does not need to be committed to any specific memory location. Therefore, the version of GCHandle.Alloc () that accepts the GCHandleType parameter:

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

no need to use.

However, MSDN doesn't say anything about GCHandleType.Normal , which prevents callback movement. In fact, the .Pinned description reads:

This prevents the object garbage collector from moving.

So I tried it. It throws an ArgumentException using the help text:

The object contains non-primitive or non-reproducible data.

I can only hope that the article does not lie that it does not need to be riveted because I do not know how to test this scenario.


At the moment, this is the solution I'm working with:

 [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(); } 

However, simply adding the ef dictionary to the dictionary prevents garbage collection. I'm not sure if GCHandle.Alloc doing anything besides this.

+4
source share
1 answer

1) Is this true?

Yes.

2) How to apply fixed to a delegate?

Define your method signature as follows:

 [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_AddEventWatch")] internal static extern void SDL_AddEventWatch(IntPtr filterPointer, IntPtr userData); 

and then:

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

Basically, as long as you hold the GCHandle in memory, the callback will not be moved or GCed.

The following article is more detailed: http://limbioliong.wordpress.com/2011/06/19/delegates-as-callbacks-part-2/

+3
source

Source: https://habr.com/ru/post/1495682/


All Articles