Background

Many instrument-control systems are comprised of several cooperating applications, some of which may be commercial programs. When deploying these types of systems, it may be beneficial to exercise a degree of control over an application or implement functionality that the program doesn't normally provide. For instance, it may be important in a given system to have an application's window always appear at the same screen position, and you may wish to prevent any other window from occluding your application window. If you don't have access to the application source, and the application doesn't provide the capability to set the characteristics you are interested in, you may use the technique we will present in this article to give an application the capabilities you desire.

This article illustrates one way to customize or extend an application's behavior through a mechanism known as window subclassing. We develop a sample DLL that extends an application's behavior by allowing you to control how the associated window appears. The DLL has the capability to make the window cover the entire screen, remove all of its decorations, and prevent any other window from becoming active while our application window is in its full-screen mode. There is also a corresponding capability to put all the decorations back on and restore the original attributes.

Overview

Window subclassing allows you to customize an application's behavior by instructing the window system to send events to a message-processing function you provide, instead of the message-processing function the application implements. Your function may then process the events in any manner it sees fit. It may elect to forward the messages on to the application's original message handler or not.

How Does Window Subclassing Work?

One of the pieces of information you supply to the window system, when you create a window, is the name of a function in your application that should be called when the system needs to notify the application that some event has taken place. Moving the mouse into the window, clicking a button, and pressing a keyboard key are examples of actions that will result in the window system delivering an event to your program's message processing function.

Every event has a corresponding identifier. The message-processing function will normally check this identifier to see if it wants to do something in response to the event. Events that need not be handled are passed on to the window system, which supplies a default response to an event.

When you subclass a window, the window system calls the new message-processing function, supplying the same information it would had it been calling the application's original message handler. Your new function may elect to take action in response to the event or not. It may also elect to forward the event to the original message-processing function or not. In this way, the subclass function can customize a behavior, or implement new behavior, by deciding which messages to forward and which to discard.

Implementation

There are a few points worth mentioning before we delve into the mechanics of window subclassing. The make file we provide with the article sources is intended for use with a Microsoft® C compiler. There is nothing to prevent building our DLL with another compiler, but that would require the use of a different make file. we have also supplied a pre-built DLL that contains debugging information, in case you wish to step through the source as your application runs. If you wish to deploy a system using the information presented here, you will probably want to build a non-debug version of the DLL. Non-debug DLL's are generally smaller and faster than their debuggable counterparts.

Let's take a look at what takes to subclass a window.

Getting the DLL Into the Application's Process Space

We have already said that subclassing a window involves creating a function to receive and process events. That subclass function must exist within the process space of the application whose windows are to be subclassed. There are several ways to accomplish this, and the method you chose will probably be driven by the application you wish to subclass.

If the application has a mechanism to allow you to attach a DLL, then getting your subclass function into the application's process space requires only that you instruct the application to load our DLL. Because our DLL subclasses the attaching application's window when it (the application) loads our DLL, we need no further intervention on the part of the application in order for our subclass function to work. This is the situation we will assume as we continue discussing the implementation. Our example will use HP VEE as the application to be subclassed, and it has the capability to load a DLL.

In the case where the application you intend to subclass doesn't provide the capability to attach a DLL, you can inject the DLL into its process space. To find out how to inject a DLL into another process, I would recommend reading the article listed in the References section at the end of this article.

Finding the Application's Window

Once our DLL is in the intended application's process space, it needs to find out which window belongs to the application. Win32™ systems notify a DLL, by calling the DLL's main entry point (DllMain) whenever an application or thread attaches or detaches the DLL. The system passes a value that lets the entry-point function know which of the four possible events triggered this function call. We know that Win32 systems serialize access to DllMain, so we can be confident that DllMain will execute to completion before being called again. In our case, we only really care about process attachment and detachment. When a process is attaching, we know that we can ask the system for a value that uniquely identifies the attaching process' main thread. We also know that a window maintains knowledge about the thread that created it. We can use these two pieces of information to identify the window our application created.

The following two functions, taken together, identify our application window.

HWND findApplicationWindow(struct SubClassInfo *scInfo){

	BOOL result;
	
	scInfo->windowOfInterest=(HWND)0;
	scInfo->windowThread=GetCurrentThreadId();

	result=EnumThreadWindows(scInfo->windowThread,
							 enumWindowsCallback,
							 (LPARAM)scInfo);
	
	return scInfo->windowOfInterest;
}
BOOL CALLBACK enumWindowsCallback(HWND aWnd, LPARAM windowThreadInfo){
	DWORD windowThreadId;
	struct SubClassInfo *sci;
	
	sci=(struct SubClassInfo *)windowThreadInfo;
	
	windowThreadId=GetWindowThreadProcessId(aWnd, (LPDWORD)0);
	
	if (windowThreadId == (DWORD)sci->windowThread) {
		sci->windowOfInterest=aWnd;

		/* we found our window.  stop looking */
		return(FALSE);
	}

	/* this isn't our window.  continue looking */
	return(TRUE);
}

The function findApplicationWindow takes an argument that is a pointer to a structure. The structure groups information related to our application window, including its creating thread and its window handle. The handle is how the system uniquely identifies a particular window. This function then calls the Win32 function EnumThreadWindows. This causes the window system to invoke a callback function, passing it the handle of each current top-level window and another, user-defined, value.

The callback function is enumWindowsCallback. The user-defined data that is passed as the second argument is a pointer to the structure we described above. For each top-level window we process, we call the Win32 function GetWindowThreadProcessId. This tells us which thread created the window we are currently processing. We can compare the thread that created the current window with the application thread identifier stored in our structure. When they match, we know we have found our window. At that point, we instruct the window system to stop looking through its list of windows and store the window handle in our structure for later use.

Subclassing a Window

Now that we have identified our application window, we can subclass it to give it the behavior we want. You subclass a window by telling the window system to substitute a message-processing function you supply in place of the one the application originally supplied.

Our DLL defines the following function to subclass our application window.

BOOL SubclassAppWindow(struct SubClassInfo *sci, WNDPROC newProc){

	BOOL returnVal=SUCCESS;
	long result;

	result=SetWindowLong(sci->windowOfInterest, GWL_WNDPROC, (LONG) newProc);
	if(result){
		sci->originalWndProc=(WNDPROC)result;
		sci->windowIsSubclassed=TRUE;
	}
	else{
		returnVal=FAILURE;
	}
	
	return returnVal;
}

The call to the Win32 function SetWindowLong changes the value of some window-specific parameter. The manifest constant GWL_WNDPROC tells the window system to substitute the next argument, which is the name of a new message-processing function, for the currently-defined message-processing function. If this function succeeds, the return value is the original message handler. We store this value to put the original handler back in place when the application detaches the DLL (which will happen as a result of closing the application).

The Message Processing Function

The following listing is our subclass function. It gets the same arguments as any windows message handler and looks a lot like the handler functions you would create for any windows application.

LRESULT CALLBACK SubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
	TCHAR charCode;
	
	switch(uMsg){
		case WM_KEYDOWN:
			charCode=(TCHAR)wParam;
			switch(charCode){
				case VK_F12:
					CoverScreen(hwnd);

					return FALSE;
				case VK_F11:
					UncoverScreen(hwnd);

					return FALSE;
				default:

					break;
			}

			break;
		default:

			break;

	}

	return CallWindowProc(subClassInfo.originalWndProc,
						  subClassInfo.windowOfInterest,
						  uMsg,
						  wParam,
						  lParam);
}

Our subclass function looks for, and consumes, key press events, where the keys are the F11 or F12 function keys. All events other than these are forwarded to the application's original message handler. The implication here is that the application will not receive F11 or F12 key press events while its window is subclassed.

Covering and Uncovering the Screen

Pressing the F12 function key causes our subclass function to call the function CoverScreen, defined in our DLL. A long time ago, in a fit of dementia, I wrote a function that makes a window cover the entire screen, removes all the window sizing decorations, title bar, and system menu, then sets the window's always-on-top property. The result is that it is really difficult to do anything but interact with the application until you put all the decorations back on its window. The CoverScreen function is listed below.

BOOL CoverScreen(HWND aWindow){
	SetWindowLong(aWindow, GWL_STYLE, GetWindowLong(aWindow, GWL_STYLE)
				  & ~(STYLE_BITS));
	SetWindowPos(aWindow, HWND_TOPMOST, 0, 0, GetSystemMetrics(SM_CXSCREEN),
				 GetSystemMetrics(SM_CYSCREEN), 0 /* flags */);

	return SUCCESS;
}

Removing the window sizing decorations involves changing the window's style bits. Style bits govern how the window system presents the window and what it looks like. To change the style bits, we again call the Win32 function SetWindowLong, this time specifying the manifest constant GWL_STYLE. We first get the current window style bits (by calling GetWindowLong), then turn off the bits that control the attributes we are interested in.

The style bits we turn off are:

We make the window cover the entire screen by calling the Win32 function SetSetWindowPos. The calls to the Win32 functions GetSystemMetrics(SM_CXSCREEN) and GetSystemMetrics(SM_CYSCREEN) tell us how big the screen is in the X and Y axis dimensions, respectively. These are the values we pass to SetWindowPos.

We also specify the manifest constant HWND_TOPMOST in our call to SetWindowPos. This sets the window's always-on-top property, preventing any non-top-most windows from popping up in front of our application window.

Pressing the F11 function key moves the window to the upper left corner of the screen, puts back all the decorations, and removes the always-on-top property by calling the UncoverScreen function.

We make the same calls in UncoverScreen as we did in CoverScreen, except we turn the style bits back on and specify the manifest constant HWND_NOTOPMOST to remove the always-on-top property.

BOOL UncoverScreen(HWND aWindow){
	SetWindowLong(aWindow, GWL_STYLE, GetWindowLong(aWindow, GWL_STYLE)
				  | (STYLE_BITS));
	SetWindowPos(aWindow, HWND_NOTOPMOST, 0, 0, GetSystemMetrics(SM_CXSCREEN)/2,
				 GetSystemMetrics(SM_CYSCREEN)/2, 0 /* flags */);

	return SUCCESS;
}

The Example Application

The example HP VEE program doesn't do much, other than load the DLL. Executing the Import Library object is all that is required to get the subclass function in place. Deleting the DLL, by closing the application or by executing the Delete Library object, will remove the subclass function.


Conclusion

Window subclassing is a powerful technique that allows you to customize the behavior of, or implement new behavior in, a commercial application. In a system comprised of several cooperating applications, window subclassing can allow you to develop custom protocols by sending user-defined windows events between programs, linking applications that would otherwise be unable to communicate.

DLL Sources, Example Files, and the Pre-built DLL

Implementation File

Header File

Linker Definition File

Make File

HP VEE Import Library Definition File

An Example HP VEE Program

Using and Redistributing this DLL

You may feel free to use and redistribute this DLL and its sources in any way you see fit, as long as you adhere to two simple rules. If you redistribute this DLL, or an altered form of it, you must also redistribute all of the source files. If you use or redistribute this DLL, or an altered form of it, you must retain the copyright information.

References