Class function callback

这篇文章介绍了如何使类中的非静态成员函数成为线程函数或者使其成为回调函数的方法,颇值得一读:

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! 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值