[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:
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: