Yet Another Code Site

Creating a Windows Callback to a Member Function

One day, I decided that it would be nice to write my own TMenu and TMenuItem classes to draw bitmaps on menus like Office97 does.  So I derived my new classes from TMenu and TMenuItem, added variables here and member functions there.  It did not take long to realize that I needed to trap certain Windows messages so that the menu would know when to draw itself.  TMenu and TMenuItem do not have a Windows handle and, therefore, do not get Windows messages.  Simple enough -- just add message maps to the form to call my class when needed.

That worked fine until I decided to turn the new classes into components that could be dragged from the IDE control palette and dropped on a form.  There was no way for the new components to add the message maps to the form class.  I could have required the user to manually add them, but components should not require such things.  The solution, it seemed to me, was to have my components subclass the form's WndProc to trap the messages that I needed.

That turned out to be a bit tougher than I expected.

The Problem

The problem was that the WndProc replacement was a member function and Windows does not understand C++ class member functions.  Class member functions have a "this" pointer as a hidden first parameter which uniquely identifies a class object (keep in mind that an object is a specific instantiation of a class).  The API callback does not know how to pass "*this."

There are a couple of solutions.  First, you can define your callback as a global function.  Of course, now your callback has no way to determine which class object was involved.  Second, you can declare the callback as a static class member function.  Same problem -- static member functions have no "*this."

The solution to this problem has been documented elsewhere, but here is the essence of the cure:  Allocate a block of memory for each class object, fill it with the machine code necessary to jump to a unique class object member function, and use the address of that block of memory as the callback address.

Borland has already done the hard part of this for you.  Unfortunately, the magic calls, MakeObjectInstance() and FreeObjectInstance(), are undocumented.  Borland, therefore, has no obligation to support, maintain, or answer questions about these functions so do not bother asking them.  However, they have existed in all versions of Borland C++ Builder and Delphi that I am aware of, so using them seems safe enough for now.

The functions are declared as follows:

VCL (Delphi Pascal) Forms.pas:

        function MakeObjectInstance(Method: TWndMethod): Pointer;

        procedure FreeObjectInstance(ObjectInstance: Pointer);

BCB 3.0 Forms.hpp:

        extern PACKAGE void * __fastcall MakeObjectInstance(Controls::TWndMethod Method);

        extern PACKAGE void __fastcall FreeObjectInstance(void * ObjectInstance);

The BCB 1.0 declaration should be the same without the "PACKAGE" macro.

How To Use MakeObjectInstance()/FreeObjectInstance()

Here is the code, stripped down to the essentials.

[ The header file ]

//---------------------------------------------------------------------------

// note: remove PACKAGE for BCB 1.0 and Delphi 1.0 & 2.0

//

class PACKAGE TMyComponent : public TComponent

{

protected:

        void* FNewWndProcInst;

        void* FOldWndProcInst;

        virtual void __fastcall MyWndProc(Messages::TMessage& Msg);

public:

        __fastcall TMyComponent(TComponent* Owner);

        __fastcall ~TMyComponent(void);

};

//---------------------------------------------------------------------------

[ End the header file, begin the implementation ]

//---------------------------------------------------------------------------

// this is the constructor. create the instance callback and save it

// to FNewWndProcInst and save the old window proc to FOldWndProcInst.

// subclass the form.

//

__fastcall TMyComponent::TMyComponent(TComponent* Owner)

        : TComponent(Owner), FOldWndProcInst(0), FNewWndProcInst(0)

{

        // if the owner is a form... (your needs may differ)

        TForm* form = dynamic_cast<TForm*>(Owner);

        if (form) {

                FNewWndProcInst = MakeObjectInstance(MyWndProc);

                FOldWndProcInst = (void*) ::SetWindowLong(form->Handle, GWL_WNDPROC, 

                        (LONG) FNewWndProcInst);

                if (!FOldWndProcInst) { /* error */ };

                }

        // other initialization

}

//---------------------------------------------------------------------------

// this is the destructor. restore the original WndProc and free the 

// instance callback.

//

TMyComponent::~TMyComponent(void)

{

        if (FNewWndProcInst) {

                ::SetWindowsLong(form->Handle, GWL_WNDPROC, (LONG) FOldWndProcInst);

                FreeObjectInstance(FNewWndProcInst);

                }

}

//---------------------------------------------------------------------------

// this is the callback method, in this example it is used to filter

// messages to the underlying Windows control.

//

void __fastcall TMyComponent::MyWndProc(Messages::TMessage& msg)

{

        // do whatever you want with the message before calling (or not calling) the

        // the original WndProc() to handle the message

        // call the original WndProc -- note that, if you have instantiated

        // multiple objects of this class, then you are actually chaining back

        // to MyWndProc() in each earlier instantiation until you get back to 

        // the original WndProc

        msg.Result = CallWindowProc((int(__stdcall*)()) FOldWndProcInst, form->Handle, 

                msg.Msg, msg.WParam, msg.LParam);

        // do whatever you want after the original WndProc() has handled the message

}

//---------------------------------------------------------------------------

[ End the implementation ]

There is a fairly big gotcha here:  If you subclass a window more than once, you must un-subclass it in reverse order.

Home | Top Of Page | Code | Papers | FAQs | Links | Search | Feedback

Page updated

Copyright © 1998-2001 Thin Air Enterprises and Robert Dunn.  All rights reserved.