Using Delegates with Native Function Callbacks in Managed C++

Last year, I wrote an article on using delegates and events with Managed C++. That article illustrated how to set up a publish/subscribe scenario where a publisher defines an event to which one or more clients (or subscribers) can subscribe. Each subscriber needing notification of the event basically adds its handler (in the form of a delegate) to the list of handlers that will be called when the event fires. Creating an event-driven architecture is a popular and effective use of delegates. However, another important use for delegates is in the area of callbacks, which this article discusses.

Delegates as Callback Functions

A function often will perform work asynchronously and call the client either when it's done or periodically while it's processing the client's request. For example, when calling the Win32 EnumWindows function, a client will pass a function pointer representing a callback function. The EnumWindows returns almost immediately to the caller and begins enumerating the open windows on the system. As it locates each window, the EnumWindows function invokes the client's specified callback function (passing the handle to the window).

However, one problem with callback functions—especially in C/C++—is the lack of type safety with regards to ensuring that the specified callback function is what the called function expects. This is due to the fact that you can pretty much cast any function pointer to the needed function pointer type in order to get your code to compile. However, since the stack is manipulated in order to dynamically invoke the callback function, the typical results of passing a function pointer with an incorrect signature are along the lines of stack corruption and access violations.

This is where delegates come in. Delegates are essentially typesafe method pointer declarations, which are perfectly suited for being passed as callback functions. While delegates are .NET types, they can also be used when calling native Win32 API functions from Managed C++.

Defining Delegates for Native Functions

The only real challenge in using delegates as callback function pointers with native functions is that the callback function often is defined as needing parameters that are not directly supported as .NET types. As an example, consider EnumWindows, which is defined as follows:

BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam);

As you can see, EnumWindows requires a function pointer of type WNDENUMPROC to be passed as the first parameter. Because this will dictate how the delegate must be declared, you look it up to find its definition as follows:

typedef BOOL (CALLBACK* WNDENUMPROC)(HWND, LPARAM);

As you can see, this callback function must be defined as taking two parameters: HWND and LPARAM. However, these types are not defined in .NET. So, you look for a type that will basically occupy the same amount of space on the stack. In cases of pointers and handles, that type usually can be the .NET IntPtr type. Knowing this, you can define the delegate (using the __delegate keyword) as follows:

__delegate bool EnumWindowsCallback(IntPtr hwnd, IntPtr lParam);

Now, you have the delegate defined and you can proceed to importing the native function into your code so that your code compiles properly.

Importing a Native Function

In the case of a Managed C++ application needing to call a native function, you need to import the native function's prototype. This is accomplished via the DllImport attribute. When a given method name is "decorated" with the DllImport attribute, that tells .NET that the specified method is exposed by the indicated native DLL as a static entry point. Here's an example of how to import the definition of the EnumWindows function:

using namespace System::Runtime::InteropServices;
...
[DllImport("user32")] 
extern "C" int EnumWindows(EnumWindowsCallback* callback, int lParam);

Using Delegates with Native Function Callbacks in Managed C++

Native Callback Function Example

Using the EnumWindows function as an example, here's a very simple Managed C++ class that exposes a static method to enumerate all the open windows on your system:

__delegate bool EnumWindowsCallback(IntPtr hwnd, IntPtr lParam);

[DllImport("user32")] 
extern "C" int EnumWindows(EnumWindowsCallback* callback, int lParam);

[DllImport("user32")]
extern "C" int GetWindowText(IntPtr hwnd, char title[], int nMaxCount );

__gc class WindowLister
{
public:
  static void EnumWindows()
  {
    EnumWindowsCallback* cb =
      new EnumWindowsCallback(NULL, &__EnumWindows);
    ::EnumWindows(cb,0);
  }
protected:
  static bool __EnumWindows(IntPtr hwnd, IntPtr lParam)
  {
    char title[201];
    int c = ::GetWindowText(hwnd, title, 200);
    if (c > 0) Console::WriteLine(title);

    return true;
  }
};

Note that the method also calls the GetWindowText Win32 API function (which also has to imported), so as to display a more user-friendly name for the window (as opposed to its hwnd value).

Now that the Win32 EnumWindows function is encapsulated in the WindowLister class, calling it as simple as follows:

int _tmain()
{
  WindowLister::EnumWindows();

  return 0;
}

Acknowledgements

Many thanks to James Johnson, who is one of the most knowledgeable people in the world on pretty much everything associated with Managed C++ and .NET programming. James introduced me to this technique (and many others) when he, Nishant Sivakumar, and I wrote Extending MFC Applications with the .NET Framework.



About the Author

Tom Archer - MSFT

I am a Program Manager and Content Strategist for the Microsoft MSDN Online team managing the Windows Vista and Visual C++ developer centers. Before being employed at Microsoft, I was awarded MVP status for the Visual C++ product. A 20+ year veteran of programming with various languages - C++, C, Assembler, RPG III/400, PL/I, etc. - I've also written many technical books (Inside C#, Extending MFC Applications with the .NET Framework, Visual C++.NET Bible, etc.) and 100+ online articles.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • With JRebel, developers get to see their code changes immediately, fine-tune their code with incremental changes, debug, explore and deploy their code with ease (both locally and remotely), and ultimately spend more time coding instead of waiting for the dreaded application redeploy to finish. Every time a developer tests a code change it takes minutes to build and deploy the application. JRebel keeps the app server running at all times, so testing is instantaneous and interactive.

  • Instead of only managing projects organizations do need to manage value! "Doing the right things" and "doing things right" are the essential ingredients for successful software and systems delivery. Unfortunately, with distributed delivery spanning multiple disciplines, geographies and time zones, many organizations struggle with teams working in silos, broken lines of communication, lack of collaboration, inadequate traceability, and poor project visibility. This often results in organizations "doing the …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds