.NET/Link Example: EnumWindows

This example demonstrates calling "unmanaged" Windows DLLs (that is, an old C-style DLLs, not .NET assemblies) from Mathematica using DefineDLLFunction. It demonstrates the use of DefineNETDelegate and NETNewDelegate to call a C function that takes a callback function pointer as an argument.

The function called is the Windows API function EnumWindows. The example demonstrates how to collect a list of the HWNDs of all the windows that exist on your system.

To try this example, evaluate all the initialization cells (you can do this with the menu command Kernel/Evaluation/Evaluate Initialization). Then go to the Example section.

Code and Explanation

Needs["NETLink`"]

Here is the C-language prototype for the EnumWindows function, straight out of the Windows API documentation. This function is found in user32.dll:

BOOL EnumWindows (WNDENUMPROC lpEnumFunc, LPARAM lParam);

The first argument is a function pointer that is used as a callback by the internals of EnumWindows. For every window, the callback function is called with two arguments: the HWND of the window and the lParam argument (which can be any data that you want to have passed to your callback). Here is the signature that the callback function must adhere to:

BOOL CALLBACK EnumWindowsProc (HWND hWnd, LPARAM lParam);

.NET maps delegates to C function pointers, so to supply a .NET function to be called for the EnumWindowsProc, you simply need to create an instance of a .NET delegate type that matches the EnumWindowsProc signature. Unfortunately, there is no built-in .NET delegate type having the required signature. .NET/Link provides the DefineNETDelegate function to allow you to define a new delegate type from Mathematica code. This is how you define the appropriate delegate type:

delegateType = DefineNETDelegate["EnumWindowsCallback", "BOOL", {"HWND", "LPARAM"}]

The first argument is the name given to the type, and it can be any legal type name you like. The second argument is the return type of the delegate, and the third argument is a list of the argument types. These last arguments are interpreted in the same way as the last arguments to DefineDLLFunction. Note that .NET/Link understands some common Win32 types like BOOL (mapped to int), LPARAM (mapped to int), and HWND (mapped to IntPtr).

The above call to DefineNETDelegate does not create a delegate object—it just declares a new type. It is analogous to the following line in a C# program.

delegate int EnumWindowsCallback(IntPtr hwnd, int lparam);

Now that you have defined a delegate type with the appropriate signature, you can call DefineDLLFunction to create a Mathematica function that calls the EnumWindows function. You pass the delegateType object returned from DefineNETDelegate as the type descriptor for the first argument slot of the EnumWindows function. DefineDLLFunction is usually called with strings to specify the types (e.g., "int", "BOOL", etc.), but you can also pass in a .NET Type object, which is what you are doing here. When .NET sees a delegate type in a DLL function declaration, it knows to marshal the argument as a function pointer.

EnumWindows = DefineDLLFunction["EnumWindows", "user32.dll", "BOOL", {delegateType, "LPARAM"}];

It still remains to create an actual instance of the new delegate type. As always, you use NETNewDelegate to create this instance.

callbackDelegate = NETNewDelegate[delegateType, EnumWindowsCallbackFunction]

All that is left is to define the Mathematica function EnumWindowsCallbackFunction. The function should return 0 to stop the enumeration, or 1 to continue. Because all calls to Mathematica defined via NETNewDelegate are implicitly wrapped in NETBlock, if you want an object to persist beyond the bounds of the call, you need to call KeepNETObject on it. That is necessary here because you are accumulating the HWNDs in a persistent list, and each HWND is represented in Mathematica as an IntPtr object.

EnumWindowsCallbackFunction[hwnd_, lparam_] := (AppendTo[hwnds, hwnd]; KeepNETObject[hwnd]; 1)
hwnds = {};

Example

EnumWindows[callbackDelegate, 0];
hwnds

The list of HWNDs can be operated on by other Windows API functions, such as those in the companion example file WindowsAPI.nb.