John
John

Reputation: 7049

Safe way to use callbacks and heap objects with P/Invoke

The following sample is from Microsoft's documentation:

public delegate bool CallBack(int handle, IntPtr param);

public class LibWrap
{
  // passing managed object as LPARAM
  // BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam);

  [DllImport("user32.dll")]
  public static extern bool EnumWindows(CallBack cb, IntPtr param);
}

public class App
{
  public static void Main()
  {
    Run();
  }

  [SecurityPermission(SecurityAction.Demand, UnmanagedCode=true)]
  public static void Run()
  {
    TextWriter tw = System.Console.Out;
    GCHandle gch = GCHandle.Alloc(tw);

    CallBack cewp = new CallBack(CaptureEnumWindowsProc);

    // platform invoke will prevent delegate to be garbage collected
    // before call ends

    LibWrap.EnumWindows(cewp, GCHandle.ToIntPtr(gch));
    gch.Free();
  }

  private static bool CaptureEnumWindowsProc(int handle, IntPtr param)
  {
    GCHandle gch = GCHandle.FromIntPtr(param);
    TextWriter tw = (TextWriter)gch.Target;
    tw.WriteLine(handle);
    return true;
  }
}

There are two things that mystify me.

Firstly, the documentation of GCHandle.Alloc only talks about preventing a an object to be garbage collected. If that was all, you wouldn't need GCHandle.Alloc: Obviously in the sample, tw isn't going to be collected during the call to EnumWindows - there's a reference to it in the function scope.

The concern here is that one needs to make sure that it isn't moved. But the documentation of GCHandle.Alloc doesn't talk about that. So what's going on?

Secondly, what about the delegate? There may not be a problem in this sample, but what if the delegate is bound to an object (a lambda with a closure or a non-static method of a class)? In that case, one needs to take care of the delegate too, right? Is that another GCHandle.Alloc(myDelegate) or are there more things to consider?

Upvotes: 2

Views: 335

Answers (1)

David Heffernan
David Heffernan

Reputation: 612954

Moving is not a concern. GCHandle.ToIntPtr promises to give you an integer value that you can, at a later date, pass to GCHandle.FromIntPtr to retrieve the original handle. That's all. If you need to stop the object moving in memory then you would have to pin it. But you don't actually need to pin the object, you just need to stop it being collected, and be able to retrieve it in your callback.

The delegate's lifetime is not an issue here because the p/invoke framework will make sure it is not collected during the external call to EnumWindows, as noted in the comment. If you pass a delegate to unmanaged code, and that unmanaged code holds a reference to the delegate, then you have work to do. You must ensure that the delegate outlives the unmanaged reference to it.

Upvotes: 2

Related Questions