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: