Nov 292009
 

In the last post, we saw how the Message Loop in Windows works, with Windows constantly getting messages from the various events and dispatching them to correct window procedures. The Window Procedure then takes the message and it is responsible for handling the event. This handling of the message is done in the callback method WndProc. Most of them times the default processing is done by calling the DefWindowProc method, but it is left to the procedure which can override any messages it wants.

Since its message based all windows can talk to each other using these messages and pass commands. An extremely useful feature which means your .NET application can communicate with your C application and send it commands as well. I tried to get a C application to display a message box triggered from a .NET application and then bring it to top.

First we need to write the C application to display a simple window which has a Hello World message. I will post just the relevant code here.

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
 
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HDC hdc ;
	PAINTSTRUCT ps ;
	RECT rect ;
	switch (message)
	{
		case WM_PAINT:
			hdc = BeginPaint (hwnd, &ps) ;
			GetClientRect (hwnd, &rect) ;
			DrawText (hdc, TEXT ("Hello World"), -1, &rect,
			DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
			EndPaint (hwnd, &ps) ;
			return 0 ;
		case WM_USER:
			MessageBox (NULL, TEXT (".NET App Says Hello"), TEXT(".NET"), 0) ;
			return 0 ;
		case WM_DESTROY:
			PostQuitMessage (0) ;
			return 0 ;
	}
	return DefWindowProc (hwnd, message, wParam, lParam) ;
}

Along with the rest of the plumbing code, this will draw a window on the screen with the text “Hello World” written in the center. It behaves like any other normal window where you can resize it, maximize or minimize it etcetera.

CApplicationWindow

If you see in the WndProc code there are three messages being intercepted. All others will be handled as default. One is the WM_PAINT, which is responsible for drawing the window and the text written in the center. The last one is WM_DESTROY which is called when the user closes the window. The WM_USER is of our interest. All we did is call another API function MessageBox which displays a message box with the text “.NET App says Hello” and the title “.NET”. Now the idea is to call this message from our .NET application

In our .NET Application we will pass the message to the Hello World window and make it the Active Window. To do this, a handle to the window is needed. Handles are nothing but an integer which Windows (OS) uses to uniquely identify each window. Think of it as something like a pointer to the window we want to modify or retrieve information about. In .NET the IntPtr class can be used in place of a Handle to a window. From .NET 2.0 onwards, the SafeHandle can be used to do the same thing and is more reliable. Getting a handle is done through the FindWindow API call. Then using the handle, the window is sent a message and brought to the foreground. The Pinvoke declarations are:-

[DllImport("user32.dll", SetLastError = true)]
   static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

   [DllImport("user32.dll")]
   [return: MarshalAs(UnmanagedType.Bool)]
   static extern bool SetForegroundWindow(IntPtr hWnd);

   [DllImport("user32.dll", CharSet = CharSet.Auto)]
   static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

As you can see the FindWindow takes in strings the class name and the Window name. The class name and the Window name are used while calling the RegisterClass WIN32 function. Since we do this manually for C we know these values. There is no way in .NET to get the WIN 32 class name for a window. We need to use the GetClassName function for doing that.

So in .NET we first get the IntPtr handle to the C application window, and then check if its a valid window handle. If so we send a message WM_USER to the application. This WM_USER is defined as a constant in .NET. “C” however is aware of all these constants. After sending the WM_USER , we set the window as foreground by calling the SetForegroundWindow and passing the handle to it. The code is given below.

public const int WM_USER = 0X400;
static void Main(string[] args)
{
    IntPtr _CWindowHandle = FindWindow("HelloWin", "The Hello Program");

    if (_CWindowHandle.ToInt32() != 0)
    {
        SendMessage(_CWindowHandle, WM_USER, IntPtr.Zero, IntPtr.Zero);

        SetForegroundWindow(_CWindowHandle);
    }
}

So run the C program first and leave it as such. Now run the .NET application and it will work. The message box (written in the C Program) will be triggered from the .NET application and once you click on it, the window will automatically come to the foreground. Of course this is just a trivial functionality that we just implemented. A lot more complex things can be achieved once you start playing around with it.

MessageBox

SetForegroundWindow