P/Invoke? No way! (Pt. 2)

[7/20/05: Additional comment added for Marshal::GetFunctionPointerForDelegate()]

There are some instances where Win32 requires a callback function pointer.  This frequently comes up in Enumeration APIs, such as EnumWindows.  In this post we’ll look at some code that makes this possible in Managed C++ (2005 -- C++/CLI).

One could create an unmanaged callback, but we’ll look at the completely managed route here.  (One could also use P/Invoke, but you know how I feel about that, no?)  (Don’t forget to read my earlier post on how to set up a C++ project for interop with the Win32 API if you haven’t already.)

You need to do just a few basic things:

  • Create a method that matches the desired callback signature.
  • Create a delegate type that matches the above method.
  • Create an instance of the delegate that points to your callback method.
  • Pin the delegate.
  • Convert the delegate into a function pointer.
  • Use it.

Here’s some sample code:

 public ref class Window
{
private:
   // Private flag for keeping track of when we're enumerating windows.
   static bool enumeratingWindows = false;
 
   // Private list of top level windows;
   static List<IntPtr>^ topLevelWindows = gcnew List<IntPtr>();
 
   // Delegate type for the EnumWindowsProc callback.
   // Matches the required signature of the API.  Note the use of ‘BOOL’, not ‘bool’.
   delegate BOOL EnumWindowsDelegate(HWND hwnd, LPARAM lParam);
 
   // Private method used for EnumWindows calls.
   static BOOL EnumWindowsProc(HWND hwnd, LPARAM lParam)
   {
      // Not much to this.  Note the use of ‘TRUE’, not ‘true’.
      Window::topLevelWindows->Add((IntPtr)hwnd);
      return TRUE;
   }
 
public:
   // Returns the top-level window handles.
   // Will return empty List if currently enumerating.
   static property List<IntPtr>^ TopLevelWindowHandles
   {
      List<IntPtr>^ get()
      {
         List<IntPtr>^ foundWindows = gcnew List<IntPtr>();
         if (Window::enumeratingWindows == true)
         {
            return foundWindows;
         }
 
         // Flag that we're currently enumerating windows.
         Window::enumeratingWindows = true;
         Window::topLevelWindows->Clear();
 
         // Setup the managed callback.  Need to create the delegate we need.  (Step 1.)
         //  (This is a delegate to the managed method defined above.)
         EnumWindowsDelegate^ enumWindowsDelegate = gcnew EnumWindowsDelegate(Window::EnumWindowsProc);
 
         // Pin the delegate so the GC won’t move it. (Step 2.)
         pin_ptr<EnumWindowsDelegate^> pinnedDelegate = &enumWindowsDelegate;
         
         // Get the function pointer for the delegate and cast it to the type the API expects. (Step 3.)
         // 7/20: Note that you can also pass *pinnedDelegate here.  It doesn't matter as they represent the
         // same object.  Having a pin_ptr in scope is what keeps the object itself pinned.
         IntPtr delegatePointer = Marshal::GetFunctionPointerForDelegate(enumWindowsDelegate);
         WNDENUMPROC enumWindowsProc = static_cast<WNDENUMPROC>(delegatePointer.ToPointer());
      
         if (!EnumWindows(enumWindowsProc, 0))
         {
            // Failed! Throw appropriate fit here...
         }
      
         // Add what we found and return them.
         foundWindows->AddRange(Window::topLevelWindows);
         Window::enumeratingWindows = false;
         return foundWindows;
      }
   }
}; 

No unmanaged code used, no P/Invoke, I like it. :)  Hopefully you’ll find it useful too.

Some other potentially useful links: