Safe Subclassing

Kyle Marsh

Microsoft Developer Network Technology Group

Created: March 20, 1992

ABSTRACT

This article describes subclassing, how it is done, and the rules that should be followed to make subclassing safe. Both instance and global subclassing are covered. Superclassing is described as an alternative to global subclassing.

SUBCLASSING DEFINED

Subclassing is a technique that allows an application to intercept messages destined for another window. An application can augment, monitor, or modify the default behavior of a window by intercepting messages meant for another window. Subclassing is an effective way to change or extend the behavior of a window without redeveloping the window. Subclassing the default control window classes (button controls, edit controls, list controls, combo box controls, static controls, and scroll bar controls) is a convenient way to obtain the functionality of the control and to modify its behavior. For example, if a multiline edit control is included in a dialog box and the user presses the ENTER key, the dialog box closes. By subclassing the edit control, an application can have the edit control insert a carriage return and line feed into the text without exiting the dialog box. An edit control does not have to be developed specifically for the needs of the application.

THE BASICS

The first step in creating a window is registering a window class by filling a WNDCLASS structure and calling RegisterClass. One element of the WNDCLASS structure is the address of the window procedure for this window class. When a window is created, the MicrosoftÒ WindowsÔ graphical environment takes the address of the window procedure in the WNDCLASS structure and copies it to the new window’s information structure. When a message is sent to the window, Windows calls the window procedure through the address in the window’s information structure. To subclass a window, you substitute a new window procedure that receives all the messages meant for the original window by substituting the window procedure address with the new window procedure address.

When an application subclasses a window, it can take three actions with the message: (1) pass the message to the original window procedure; (2) modify the message and pass it to the original window procedure; (3) not pass the message.

The application subclassing a window can decide when to react to the messages it receives. The application can process the message before, after, or both before and after passing the message to the original window procedure.

TYPES OF SUBCLASSING

The two types of subclassing are instance subclassing and global subclassing. Instance subclassing is subclassing an individual window’s information structure. With instance subclassing, only the messages of a particular window instance are sent to the new window procedure. Global subclassing is replacing the address of the window procedure in the WNDCLASS structure of a window class. All subsequent windows created with this class have the substituted window procedure’s address. Global subclassing affects only windows created after the subclass has occurred. If any windows exist of the window class that is being globally subclassed at the time of the subclass, the existing windows are not affected by the global subclass. If the application needs to affect the behavior of the existing windows, the application must subclass each existing instance of the window class.

Instance Subclassing

The SetWindowLong function is used to subclass an instance of a window. The application must have the procedure-instance address of the subclass function. The subclass function is the function that receives the messages from Windows and passes the messages to the original window procedure. To get the procedure-instance address of the subclass function, the application calls MakeProcInstance. If the subclass function resides in a dynamic link library (DLL), the application does not need to call MakeProcInstance to get the procedure-instance address. The subclass function must be exported in the application’s or the DLL’s module definition file.

The application subclassing the window calls SetWindowLong with the handle to the window the application wants to subclass, the GWL_WNDPROC option (defined in WINDOWS.H), and the procedure-instance address of the new subclass function. SetWindowLong returns a DWORD, which is the address of the original window procedure for the window. The application must save this address to pass the intercepted messages to the original window procedure and to remove the subclass from the window. The application passes the messages to the original window procedure by calling CallWindowProc with the address of the original window procedure and the hWnd, Message, wParam, and lParam parameters used in Windows messaging. Usually, the application simply passes the arguments it receives from Windows to CallWindowProc.

The application also needs the original window procedure address for removing the subclass from the window. The application removes the subclass from the window by calling SetWindowLong again. The application passes the address of the original window procedure with the GWL_WNDPROC option and the handle to the window being subclassed.

The following code subclasses and removes a subclass to an EDIT control:

LONG FAR PASCAL SubClassFunc(HWND hWnd,WORD Message,WORD wParam,

LONG lParam);

FARPROC lpfnOldWndProc;

HWND hEditWnd;

//

// Create an EDIT control and subclass it.

// The details of this particular EDIT control are not important.

//

hEditWnd = CreateWindow("EDIT", "EDIT Test",

WS_CHILD | WS_VISIBLE | WS_BORDER ,

0, 0, 50, 50,

hWndMain,

NULL,

hInst,

NULL);

//

// Now subclass the window that was just created.

//

lpfnSubClassProc=MakeProcInstance((FARPROC) SubClassFunc,hInst);

lpfnOldWndProc =

(FARPROC)SetWindowLong(hEditWnd, GWL_WNDPROC, (DWORD)

lpfnSubClassProc);

.

.

.

//

// Remove the subclass for the EDIT control.

//

SetWindowLong(hEditWnd, GWL_WNDPROC, (DWORD) lpfnOldWndProc);

//

// Here is a sample subclass function.

//

LONG FAR PASCAL SubClassFunc( HWND hWnd,

WORD Message,

WORD wParam,

LONG lParam)

{

//

// When the focus is in an EDIT control inside a dialog box, the

// default ENTER key action will not occur.

//

if ( Message == WM_GETDLGCODE )

return DLGC_WANTALLKEYS;

return CallWindowProc(lpfnOldClassProc, hWnd, Message, wParam,

lParam);

}

Potential pitfalls

Instance subclassing is generally safe, but observing the following rules ensures safety. When subclassing a window, you must know what owns the window’s behavior. For example, Windows owns all controls it supplies; applications own all windows they define. Subclassing can be done on any window in the system; however, when an application subclasses a window that the application does not own, the application must ensure that the subclass function does not break the original behavior of the window. Because the application does not control the window, it should not rely on any information about the window that the owner might change in the future. A subclass function should not use the extra window bytes or the class bytes for the window unless it knows exactly what they mean and how the original window procedure uses them. Even if the application knows everything about the extra window bytes or the class bytes, it should not use them unless the application owns the window. If an application uses the extra window bytes of a window that another application owns and the owner decides to update the window and change some aspect of the extra bytes, the subclass procedure is likely to fail. For this reason, Microsoft suggests that you not subclass the control classes. Windows owns the controls it supplies, and aspects of the controls might change from one version of Windows to the next. If your application must subclass a control supplied by Windows, it may need to be updated when a new version of Windows is released.

Because instance subclassing occurs after a window is created, the application subclassing the window cannot add any extra bytes to the window. Applications that subclass windows should store any data needed for an instance of the subclassed window in the window’s property list.

The SetProp function attaches properties to a window. The application calls SetProp with the handle to a window, a string identifying the property, and a handle to the data. The handle to the data is usually obtained with a call to either LocalAlloc or GlobalAlloc. When the application uses the data in a window’s property list, the application calls the GetProp function with the handle to the window and the string that identifies the property. GetProp returns the handle to the data that was set with SetProp. When the application is finished with the data or when the window is to be destroyed, the application must remove the property from the property list by calling the RemoveProp function with the handle to the window and the string identifying the property. RemoveProp returns the handle to the data, which the application then uses in a call to either LocalFree or GlobalFree. The Microsoft Windows Software Development Kit (SDK) Reference manual describes SetProp, GetProp, and RemoveProp.

When an application subclasses a subclassed window, the subclasses must be removed in reverse of the order in which they were performed.

Global Subclassing

Global subclassing is similar to instance subclassing. The application calls SetClassLong to globally subclass a window class. As it does with instance subclassing, the application needs the procedure-instance address of the subclass function, and the subclass function must be exported in the application’s or the DLL’s module definition file.

To globally subclass a window class, the application must have a handle to a window of that class. To get a handle to a window in the desired class, most applications create a window of the class to be globally subclassed. When the application removes the subclass, it needs a handle to a window of the type the application wants to subclass, so creating and keeping a window for this purpose is the best technique. If the application creates a window of the type it wants to subclass, the window is usually hidden. After obtaining a handle to a window of the correct type, the application calls SetClassLong with the window handle, the GCL_WNDPROC option (defined in WINDOWS.H), and the procedure-instance address of the new subclass function. SetClassLong returns a DWORD, which is the address of the original window procedure for the class. The original window procedure address is used in global subclassing in the same way it is used in instance subclassing. Messages are passed to the original window procedure in the same way as in instance subclassing, by calling CallWindowProc. The application removes the subclass from the window class by calling SetClassLong again. The application passes the address of the original window procedure with the GCL_WNDPROC option and a handle to the window of the class being subclassed. An application that globally subclasses a control class must remove the subclass when the application finishes.

The following code globally subclasses and removes a subclass to an EDIT control:

LONG FAR PASCAL SubClassFunc(HWND hWnd,WORD Message,WORD wParam,

LONG lParam);

FARPROC lpfnOldClassWndProc;

HWND hEditWnd;

//

// Create an EDIT control and subclass it.

// Notice that the EDIT control is not visible.

// Other details of this particular EDIT control are not important.

//

hEditWnd = CreateWindow("EDIT", "EDIT Test",

WS_CHILD,

0, 0, 50, 50,

hWndMain,

NULL,

hInst,

NULL);

lpfnSubClassProc=MakeProcInstance((FARPROC) SubClassFunc,hInst);

lpfnOldClassWndProc =

(FARPROC)SetClassLong(hEditWnd, GCL_WNDPROC, (DWORD)

lpfnSubClassProc);

.

.

.

//

// To remove the subclass:

//

SetClassLong(hEditWnd, GWL_WNDPROC, (DWORD) lpfnOldClassWndProc);

DestroyWindow(hEditWnd);

Potential pitfalls

Global subclassing has the same limitations as instance subclassing but presents some additional problems. The application should not attempt to use the extra bytes for either the class or the window instance unless it knows exactly how the original window procedure is using them. If data must be associated with a window, the window’s properties list should be used in the same way as in instance subclassing.

Global subclassing is dangerous when used with the Windows-supplied control classes, especially if more than one application globally subclasses a control class.

The following sequence of events illustrates this problem. Application A globally subclasses the EDIT control class and stores the original window procedure’s address. Application B then also globally subclasses the EDIT control class. When application B calls SetClassLong with the address of application B’s subclass function, SetClassLong returns the address of application A’s subclass function instead of the address of the original window procedure for the window class. If application A removes the subclass from the EDIT control class, it replaces the original window procedure’s address in the WNDCLASS structure. From this point on, application B’s subclass is effectively removed. New EDIT controls have the original window procedure’s address as their window procedure. If application B then removes its subclass of the EDIT control class, even more damaging events occur. Application B replaces the original window procedure’s address in the WNDCLASS structure with the address of application A’s subclass function. New EDIT controls now attempt to use application A’s subclass function. If application A is still loaded, its subclass of the EDIT control class has effectively been reinstalled, even though application A is not aware of this. If application A is no longer loaded, Windows version 3.0 still attempts to call a procedure at the address of application A’s subclass function; this may cause a FatalExit, a UAE, or the system to hang. Windows version 3.1 does not allow this to happen.

Because of the dangers of globally subclassing the control classes, global subclassing should be done only on application-specific window classes. If your application could benefit from globally subclassing a control class, your application should use an alternative technique called superclassing.

Superclassing

Subclassing a window class causes messages meant for the window procedure to be sent to the class function. The class function then passes the message to the original window procedure. Superclassing creates a new window class. The new window class uses the window procedure from an existing class to give the new class the functionality of the existing class. The superclass is based on some other window class, known as the base class. Frequently the base class is a Windows-supplied control class, but it can be any window class. Do not superclass the scroll bar control class because Windows uses the class name to produce the correct behavior for scroll bars.

The superclass has its own window procedure, the superclass procedure, which can take the same actions a subclass procedure can. The superclass procedure can take three actions with the message: (1) pass the message directly to the original window procedure; (2) modify the message before passing it to the original window procedure; (3) not pass the message. The superclass can react to the message before, after, or both before and after passing the message to the original window procedure.

Unlike a subclass procedure, a superclass procedure receives create (WM_NCCREATE, WM_CREATE, and so on) messages from Windows. The superclass procedure can process these messages, but it must also pass these messages to the original base-class window procedure so that the base-class window procedure can initialize.

The application calls GetClassInfo to base a superclass on a base class. GetClassInfo fills a WNDCLASS structure with the values from the base class’s WNDCLASS structure. The application that is superclassing the base class then sets the hInstance field of the WNDCLASS structure to the instance handle of the application. The application must also set the WNDCLASS structure’s lpszClassName field to the name it wants to give this superclass. If the base class has a menu, the application superclassing the base class must supply a new menu that has the same menu IDs as the base class’s menu. If the superclass intends to process the WM_COMMAND message and not pass the message to the base class’s window procedure, the menu does not have to have corresponding IDs. GetClassInfo does not return the lpszMenuName, lpszClassName, or hInstance field of the WNDCLASS structure.

The last field that must be set in the superclass’s WNDCLASS structure is the lpfnWndProc field. GetClassInfo fills this field with the original class window procedure. The application must save this address so that it can pass messages to the original window procedure with a call to CallWindowProc. The application must put the address of its subclass function into the WNDCLASS structure. This address is not a procedure-instance address because RegisterClass gets the procedure-instance address. The application can modify any other fields in the WNDCLASS structure to suit the application’s needs.

The application can add to both the extra class bytes and the extra window bytes because it is registering a new class. The application must follow two rules when doing this: (1) The original extra bytes for both the class and the window must not be touched by the superclass for the same reasons that an instance subclass or a global subclass should not touch these extra bytes; (2) if the application adds extra bytes to either the class or the window instance for the application’s own use, it must always reference these extra bytes relative to the number of extra bytes used by the original base class. Because the number of bytes used by the base class may be different from one version of the base class to the next, the starting offset for the superclass’s own extra bytes is also different from one version of the base class to the next.

After the WNDCLASS structure is filled, the application calls RegisterClass to register the new window class. Windows of this class can now be created and used.