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.
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.
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.
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.
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.
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.
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 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.
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 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.
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.
HP VEE Import Library Definition File
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.