INF: Safe Subclassing

PSS ID Number: Q101180
Article last modified on 09-07-1993

3.10
WINDOWS

----------------------------------------------------------------------
The information in this article applies to:

 - Microsoft Windows Software Development Kit (SDK) for Windows,
   version 3.1
----------------------------------------------------------------------

SUMMARY
=======
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.

MORE INFORMATION
================

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, edit,
list box, combo box, static, 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 linefeed 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 SDK "Programmer's
Reference, Volume 2, Functions" 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.

Additional reference words: 3.1 3.10
KBCategory:
KBSubcategory: UsrWinSubclass UsrCtlSubclass
Copyright Microsoft Corporation 1993.