Click here to Skip to main content
Click here to Skip to main content

Class function callback

By , 3 May 2005
 

Introduction

In this article I'll show you how to make a callback to a function within a class that is NOT static. This will make use of functions, pointers and some assembly. It's not possible to my knowledge to do this without the ASM. However, this guide does not require you to know any ASM; instead it will explain what it does. This guide is not intended to teach out ASM, however. There's plenty of other good guides out there on the web for that. This is an advanced technique, however, but even beginners should be able to make use of it.

What do we need? A compiler that can do ASM and a class! In this article I will also show you how I used this technique to forward messages through a window proc. Let's get started then!

First then, we need a base-class. I use MFC in this sample to show you how this technique works. So if you want to follow in this sample, use MFC to create a dialog window. The class is the dialog class of your main window.

class CMyTestDlg
{
  public:
    ...
    CProcEdit m_MyEdit;
  protected:
    LRESULT WindowProc(CWnd* pCtrl, UINT message, WPARAM wParam, LPARAM lParam);
};

Perhaps you noticed the WindowProc isn't virtual plus an extra argument: pCtrl. This is because, in this example, I intend to make this a "global" WindowProc. All controls will forward messages to this one, kinda like the original window proc (non-MFC).

Now that we have this, we need to create the whole forwarding function. How? It's quite simple. We simply derive the controls you want to forward messages from with a new class. Let's define this one, too...

typedef LRESULT (WndProcPtr)(UINT,WPARAM,LPARAM);

class CProcEdit: public CEdit
{
  public:
    struct CallbackPtr
    {
      CWnd* pThisPtr;
      WndProcPtr* pWndProcPtr;
    } pCallbackPtr;
  protected:
     virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam);
};

We don't need any more complex class. It inherits everything from MFCs CEdit class, their edit class. In any case, this class' purpose is only to forward its messages. That's why we override the WindowProc. You're probably also wondering why the struct and two arguments? A function pointer is only one variable. Yes... but we need two arguments: one for the address to the function and one pointing to the class itself that contains this function. Got that? Good. As to why we need the second... let me get back to that later.

Now then, to make it able to do a callback, we must set these variables when initializing (or creating) the dialog or class. So we do this in the InitDialog of our main dialog. Now also comes the first part where we must use assembly. Let me show you the code first and then discuss what it does...

BOOL CMyTestDlg::OnInitDialog()
{
  WndProcPtr* pCallback;
  __asm
  {
    mov eax, WindowProc;
    mov pCallback, eax;
  };
  m_MyEdit.pCallbackPtr.pThisPtr = this;
  m_MyEdit.pCallbackPtr.pWndProcPtr = pCallback;
};

That's all. Now let's see what this code does... and why the ASM? ...and what does it do, the ASM? Let me explain... if you're experienced with this, you should know that...

pCallback = WindowProc;

...will give a compile error. That's why we must use assembly! There is no such restriction there. The "mov" opcode moves database from or to memory. So first, we move the offset of the WindowProc function into the eax register. And then we move the contents of the eax register into out pCallback variable. Note that we cannot move the contents directly into the variable due to restrictions of the ASM language. After this, the pCallback variable contains the address (or offset) of our function. So then we set the two variables of the edit class to point to our dialog class and the function. Okay, with me so far? I hope you are!

Now that we've initialized the class, we must make sure the edit class forwards out messages. This might be a little tricky... Anyway, here's the code:

LRESULT CProcEdit::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
  if (pWndProc) 
  {
    __asm
    { 
      // Pass arguments (right to left)
      mov eax, [lParam];
      push eax;
      mov eax, [wParam];
      push eax;
      mov eax, [message];
      push eax;
      mov eax, this;
      push eax;

      // Fill ecx with the class placement
      mov eax, this;
      add eax, pThisPtr;
      mov ecx, [eax];

     // Call function
     mov eax, pWndProc;
     add eax, this;
     mov eax, [eax];
     call eax;
    }
  }
  return CEdit::WindowProc(message, wParam, lParam);
}

Whew, that was sure a lot of assembly code. What does it do? Why do we need ASM to call a function pointer? Well, it's not as easy as you think... first, let me tell you why a normal call wouldn't work. As you know, in classes there is a "this" pointer. The "this" pointer is stored in the ecx register. The ecx register must point to the offset where the class is located. What happens then, if we call the function normally? The offset placed in ecx is the offset of the current class - or in other words the CProcEdit class! When this happens, the function we call won't be able to access its member correctly, possibly even creating a access violation. Why does this happen? The functions are in the classes, so... why can't you just call them and it works?

Good question... unfortunately, it doesn't work that way... did you ever export a function in a DLL that is part of a class? It works. But when you call them, the this pointer is NULL! Why? That's because functions are not stored in the classes! Look at this class...

class CAnotherClass
{
  int arg1;
  int arg2;
  void myfunc() { }
};

A sizeof on this class will return 8! Because there are two int arguments. The functions are NOT counted! So... when calling a function within a class, the function must know where the class exists in order to reach its data. That's why there is a this pointer, which is stored in the ecx register.

That is why we need to assembly. We need to make sure, we fill the ecx register properly. Hence, why we also took a pointer to the class itself. Okay then, what does the code do? Let's see here...

First, we push the arguments onto the stack. To push something onto the stack, we need to put them into a register first. All arguments are passed from right to left, so there. Second, we must fill in the ecx with the address (or offset) of its class! And thirdly, we call the function. How all this works, I'm not going to explain more detailed... if you wish to know, then go find an ASM course.

Lastly, we call the base class' windowproc function so that it may further handle the messages. This is all we need to do! There is one note on this example I wish to pass on, however. In the called windowproc of CMyTestDlg, do not call the base class's window proc function! Why? Because the messages are not intended for that class. If you do pass them along, you'll probably get weird painting on the form and other strange things. Instead, return 0 or some other value; it doesn't matter since we do not return the value returned by this window proc.

...And that's all folks! Now you can probably understand why callback to class functions aren't possible. And if not... I guess I can spoil it to you. It's simple. The compiler does not know to which class the function belongs.

Happy programming!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Tydia-kun
Sweden Sweden
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Questionwhat about invoking class function outside the class ?sussScherbina Vladimir28 Aug '05 - 8:52 
you showed how to invoke function inside class, but how to implement this:
 
CSomeClass cl;
cl.Foo();
?
 

AnswerRe: what about invoking class function outside the class ?memberTydia-kun29 Aug '05 - 6:35 
You can use the above code, but instead of moving this into ecx (mov ecx, this), place the class's address.
mov ecx, cl;
 
You can also check out how to do this without assembly. I made another article that uses c++ standard function pointers.
GeneralRe: what about invoking class function outside the class ?memberVladimir Scherbina29 Aug '05 - 8:18 
>>mov ecx, cl
i guess instead of moving actual object to ecx i need to move pointer to it.
and call member function by function pointer.
yea it works fine, i am just confused that for calling member function i need to declare pointer to it using c++ stlye:
int (CSomeClass::*pt2Foo)();
set pointer to function
pt2Foo = &CSomeClass::Foo
and only then call pt2Foo.
 

 

GeneralRe: what about invoking class function outside the class ?memberTydia-kun30 Aug '05 - 0:19 
I'm not 100% of what you wish to achieve... and concerning asm, everything is pointers. You cannot move an object into a register. Instead, it moves the address of that object.
GeneralRe: what about invoking class function outside the class ?memberf228 Jul '06 - 7:10 
Tydia-kun wrote:
You cannot move an object into a register. Instead, it moves the address of that object.

 
since u say so, why u dun want to use '&' (address of). we are doing this all the while.

 
from,
-= aLbert =-

GeneralRe: what about invoking class function outside the class ?memberTydia-kun28 Jul '06 - 7:17 
You must use & is c++, but not in assembly. The registers are small piece of memory that holds data and addresses. They cannot hold things as classes. Therefore, the compiler automatically assumes you are moving the address of the object to it.
GeneralTry this. It works, ...memberWREY2 May '05 - 12:31 
and it doesn't require any assembly codes.
 
http://www.codeproject.com/cpp/ElmueCallbackDemo.asp
 

William
 
Fortes in fide et opere!
GeneralRe: Try this. It works, ...memberTydia-kun2 May '05 - 20:10 
Ohh yes, looks very nice. Although complex, yet easy to use. This article is just a small sample of how it's done. Feel free to use that example, as it seems to be amazing.
GeneralThat code has bugs! [Re: Try this. It works...]memberDon Clugston4 May '05 - 17:39 
Unfortunately, it _doesn't_ always work. It fails when multiple inheritance is involved. Internally, it uses a nasty hack which isn't properly documented and doesn't work in all cases. Caveat emptor!
GeneralRe: That code has bugs! [Re: Try this. It works...]memberWREY4 May '05 - 20:33 
Nothing is perfect on this earth, and if something works within reason, I'm not going to test its limit just to find out where it fails.
 
I had no need to test it using multiple inheritance, and if that's where it fails then I'll cross that bridge when I reach it.
 
Like Clint Eastwood said, "A man ought to know his limitations," which means, if I ever have a need to apply it using multiple inheritance, then I'll make certain it gets properly tested to hold up under that situation.
 
Hmmm | :|
 
William
 
Fortes in fide et opere!
GeneralRe: That code has bugs! [Re: Try this. It works...]memberDon Clugston4 May '05 - 20:59 
WREY wrote:
Like Clint Eastwood said, "A man ought to know his limitations," which means, if I ever have a need to apply it using multiple inheritance, then I'll make certain it gets properly tested to hold up under that situation.

 
I agree, but unfortunately there was nothing in the article or code to tell you that multiple inheritance is a potential problem. If it fails, you don't get any clue why it didn't work. (Or even worse in this case, it is possible for it to compile, and appear to work, but actually be calling the wrong function, a bug which would be incredibly difficult to track down).
 
It's like a land mine. Once you know exactly where it is, it's easy to avoid.
 
Of course, now that I've told you, you know the limitations. Smile | :)
 

GeneralRe: That code has bugs! [Re: Try this. It works...]memberWREY4 May '05 - 23:09 
How do you know there aren't other land mines undiscovered, those for which you, yourself, may not be aware of?
 
It all comes down to the moment of application, "Make sure it works for what you want it to do!!!"
 
Suspicious | :suss:
 
William
 
Fortes in fide et opere!
Generalsignal/slotmemberf228 Jul '06 - 7:03 
what about signal/slot by boost?
i find the idea is very elegant where it combines observer design pattern with member function callback, and more templatized! what can be better than that?
QT uses it, and was praised by ppl...until today.
 
from,
-= aLbert =-

GeneralRe: signal/slotmemberTydia-kun28 Jul '06 - 7:18 
Yeah, this is a pretty crude way of doing callbacks to classes. And it isn't the best way either. I just didn't know better then.
Generalanother possible subtle bugmemberemilio_grv28 Apr '05 - 20:37 
There could be also another subtle bug:
MFC, while dispatching messages with OnWindowMessage, keeps a cache of the "most popularly dispatched messages". This "cache" is seized before the message map and filled in by the message map processing of a message.
But –here come the problem- is unique for each HWND.
Now, if you attach more than one MFC derived classes (even with its own subclassing procedure like here) to a same HWND (if you do or don't doesn't depend on this article, but you CAN do it, with this article's code) and dispatch a same message in more than one class, if the message is enough frequent (depend on the cache length) it will be forwarded always to the same class function, independently on the class that is actually processing OnWindowMessage.
To ge t away from this, either:
  • Don't dispatch messaged captured with this class to OnWindowMessage
  • Don't hook a same window more than once (same MFC limitation)
  • Don't derive these classes (having CallbackPtr) from MFC classes
In case you want to derive from MFC, take also care of the m_hwnd member.

 

2 bugs found.
> recompile ...
65534 bugs found.
D'Oh! | :doh:

GeneralRe: another possible subtle bugmemberTydia-kun28 Apr '05 - 21:16 
If I get this correct... if I derive more windows (controls) from the forwarding class, the wrong class will intercept the message?
If this is the case then... it is truly disturbing. I take it then, this is not a good way to capture messages. Instead... I should use... a hook?
GeneralRe: another possible subtle bugmemberemilio_grv28 Apr '05 - 22:11 
Not exactly:
The problem arise when a same HWND (for example an editor control) is handled by more classes (for example a CYellowEditor, that makes the background yellow, and a CHexEditor that makes the editor only accepting Hex digits).
If –for some reasons- both the clasees window procedures call their base's CWnd::OnWindowMessage and both have –for example- an ON_SIZE entry in their message map (of course, to do their own different actions), because of the way OnWindowMessage is implemented inside the MFC code, there is the risk that WM_SIZE even if intercepted by a different thunk (yes: what you do is essentially the same as WTL thunking) and a different associated window procedure will go to the same class message entry (wrong in the 50% of the cases).
 
There is also another problem: your window procedure should call –for unprocessed messages and where default actions must also take place- the original window procedure (or… the window procedure in place before you attach the class, you can get it with GetWindowLong) by calling it through the CallWndProc API. (You cannot call a WNDPROC directly, since this address may refer to a different process address space)
 
Give a look at http://www.codeproject.com/cpp/ge_stdx1.asp[^], or to the ATL CWindowImpl class, for possible alternatives.
 

 

 

 

2 bugs found.
> recompile ...
65534 bugs found.
D'Oh! | :doh:

GeneralRe: another possible subtle bugmemberTydia-kun28 Apr '05 - 22:17 
Ah well... they only forward the messages. The base class's window proc only uses the information. For example if I wanted to see when each class loses or gains focus. The base class does NOT process the message further. Instead, it simply returns and the window proc of the class that forwarded it pushes it further to its base class for further processing. So no harm done there...
 
I did encounter strangeness if the base window proc also handeled the message, which also is why I specifically wrote that this shouldn't be done.
 
I'll check out that article in any case =)
GeneralActually, this doesn't workmemberDon Clugston28 Apr '05 - 16:06 

pCallback = WindowProc;
...will give a compile error. That's why we must use assembly!

 
There's a good reason why it doesn't compile. They might not be the same size!
Asm doesn't save you from this fact.
Example:
Compile with the /vmg switch, and &WindowProc is 16 bytes long.
Using asm can result in the wrong function being called.
btw, you need the &. VC6-7.1 will compile it, but it's non-standard, and VC8, gcc + everything else rejects it.
 
Have a look at my article.(See link in my earlier comment).
GeneralRe: Actually, this doesn't workmemberTydia-kun28 Apr '05 - 20:27 
This works for me. You don't need a & for functions. It wouldn't really matter whatever way you do it, since it's impossible to do a callback to a member function.
It is the same size since if you would put the WindowProc outside the class and assign the pointer to it, it would compile, although not when placed inside a class.
 
Or am I mistaking what you're explaining here? Btw, your article looks very interesting! I'll skip through it later.
Generalnot quite truememberZdeslav Vojkovic28 Apr '05 - 0:32 
pointers to member functions are part of C++ standard:
 
class MyClass
{
long m_Val;
 
public:
MyClass(long lVal) : m_Val(lVal){}
bool Fn(int i)
{
std::cout << m_Val << "\n";
std::cout << i << "\n";
return true;
}
};
 
int main(int argc, char* argv[])
{
typedef bool (MyClass::*MemFunPtr)(int i);
 
MemFunPtr ptr = &MyClass::Fn;
MyClass obj(10);
(obj.*ptr)(1);
return 0;
}


GeneralI don't think you understood the article.memberyafan28 Apr '05 - 4:30 

I think what the author articulated was a way to use a member function of a class to be used as a WIN32 callback; where you have the problem the going from a __thiscall to a __stdcall function signature.
 
What you proposed doesn't doesn't work at all in these types of situations.
 

-yafan.
 

GeneralRe: I don't think you understood the article.memberZdeslav Vojkovic28 Apr '05 - 6:13 
this is basically the same, only without any assembly code:
 

class CMyTestDlg
{
public:
...
CProcEdit m_MyEdit;
protected:
LRESULT WindowProc(CWnd* pCtrl, UINT message, WPARAM wParam, LPARAM lParam);
};
 
//////////////////////////////////////////////////////////////////////////////
 
typedef LRESULT (CMyTestDlg::*WndProcPtr)(CWnd*, UINT,WPARAM,LPARAM);
 
class CProcEdit: public CEdit
{
public:
struct CallbackPtr
{
CMyTestDlg* pThisPtr;
WndProcPtr pWndProcPtr;
} pCallbackPtr;
protected:
virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam);
};
 
//////////////////////////////////////////////////////////////////////////////
 
BOOL CMyTestDlg::OnInitDialog()
{
m_MyEdit.pCallbackPtr.pThisPtr = this;
m_MyEdit.pCallbackPtr.pWndProcPtr = &CMyTestDlg::WindowProc;
};
 
//////////////////////////////////////////////////////////////////////////////
 

 
LRESULT CProcEdit::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
(pThisPtr->*pWndProcPtr)(this, message, wParam, lParam);
return CEdit::WindowProc(message, wParam, lParam);
}

GeneralRe: I don't think you understood the article.memberyafan28 Apr '05 - 8:51 

I am not convinced.
 
The following line of code completely fails compilation (you must be using a different compiler to me - vc60).
 
(this->*pCallbackPtr)(this, nMsg, wParam, lParam);
 
I get the following error:
 
error C2297: '->*' : illegal, right operand has type 'struct CProcEdit::CallbackPtr', which makes sense since you are forcing the memory layout of a structure to overlay that of a callback function signature. This really is a horrible way to achieve the same thing. And secondly, you can never be sure you have setup the stack frame correctly.
 
There is no way a compiler is going to let you do this unless you really "bend" the code. You can do it, but what's the point when you can build your own thunk mechanism using a little assembler.
 

 
-yafan
 

 


GeneralRe: I don't think you understood the article.memberDon Clugston28 Apr '05 - 15:56 

yafan wrote:
The following line of code completely fails compilation (you must be using a different compiler to me - vc60).
 
(this->*pCallbackPtr)(this, nMsg, wParam, lParam);

 
You mistyped it! Should be:
 
(pThisPtr->*pWndProcPtr)(this, message, wParam, lParam);
 
Zdeslav's code is great when you know what class you'll be using.
Note also that if you use my FastDelegate code, you avoid the need to hard-code CProcEdit into the definition of CallbackPtr, and your source code will look very simple.
www.codeproject.com/cpp/fastdelegate.asp
My code uses some nasty hacks, but even it doesn't use any assembler.
The code in this article won't work for 64 bit windows (the this pointer needs to go into rcx, for example).
 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 3 May 2005
Article Copyright 2005 by Tydia-kun
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid