C++ 成员函数作为外部库的回调函数

Posted

技术标签:

【中文标题】C++ 成员函数作为外部库的回调函数【英文标题】:C++ member function as callback function to external library 【发布时间】:2015-06-24 05:02:50 【问题描述】:

所以下面是我正在尝试做的基本想法。 我有一个想在现有项目中使用的外部库。 当然,我无法更改外部库中的任何内容或现有项目中的主要功能。

我面临的问题是如何将我在我的类中创建的回调函数作为指向函数的指针传递给这个外部函数。同时,这个回调函数必须能够访问类的成员,所以我不能简单地将其设为静态。我该怎么做?

Class ExternalClass //This I cannot mess with.

    //somestuff
    void ExternalFunc (void(* callback)(int, const void*), const void *);


Class MyClass

    //somestuff
    ExternalClass m_ExObj;
    void Callback(int x, const void *p)
        //dosomething
        //How do I use this pointer ?
    

    void MyFunc()
        m_ExObj.ExternalFunc(/*Some way to put MyClass::Callback() in here*/)
    

【问题讨论】:

你的程序是单线程的吗? ExternalFunc 有多少个参数? @kfsone 它是单线程的,ExternaFunc 采用函数指针和 void 指针。我现在把那个空指针放进去。 您可以使用成员函数指针。它的类型为void (MyClass::*)(int),其地址由&MyClass::Callback 获取。将其存储在变量memfcn 中后,您可以通过执行(obj.*memfcn)(42) 在对象obj 上调用它。 edit 哦,等等,你不能弄乱函数签名。然后你需要摆脱它。 您必须传递一个静态成员函数,因为这是库所要求的。让该静态成员函数调用您的成员函数。这意味着您需要找到实例。外部库如何让您做到这一点? 传入一个没有传递给回调的void*指针有什么意义? 【参考方案1】:

您显示的回调不允许将用户定义的值传递给它(否则您可以使用它来传递对象指针)。它需要一个独立的非类函数,所以你有两个选择:

1) 如果回调一次只调用一个对象,那么您可以将对象指针存储在全局变量或static 变量中,然后使用独立函数(或static 类方法)作为回调并让它使用全局/静态指针来调用您的类方法:

class MyClass

    //somestuff
    ExternalClass m_ExObj;

    void Callback(int x)
    
        //dosomething
    

    static MyClass* objForCallback;
    static void exObjCallback(int x)  objForCallback->Callback(x); 

    void MyFunc()
    
        objForCallback = this;
        m_ExObj.ExternalFunc(&exObjCallback);
    
;

2) 如果您需要一次对多个对象进行回调,则必须将类方法包装在每个对象的 thunk 中,其中每个 thunk 都知道要调用哪个对象,然后将 thunk 用作回调。这是一种更高级的技术,需要了解 x86/x64 汇编和调用约定,因为您必须动态分配内存并使用汇编指令填充它,以便每个 thunk 在运行时执行。例如,至少在 Windows 32bit 上:

#pragma pack(push, 1)
struct MyThunk

    unsigned char PopEAX_1;     // POP the caller's return address off the stack
    unsigned char PushThis;     // PUSH the object 'this' pointer on to the stack
    void *ThisValue;
    unsigned char PushEAX_1;    // PUSH the caller's return address back on to the stack
    unsigned char Call;         // CALL the callback function
    __int32 CallAddr;
    unsigned char PopEAX_2;     // POP the caller's return address off the stack
    unsigned char AddESP[3];    // Remove the object 'this' pointer from the stack
    unsigned char PushEAX_2;    // PUSH the caller's return address back on to the stack
    unsigned char Return;       // return to the caller
;
#pragma pack(pop)

typedef void (*CallbackType)(int);

class MyClass

    CallbackType exObjCallback;

    MyClass()
    
        MyThunk *thunk = (MyThunk*) VirtualAlloc(NULL, sizeof(MyThunk), MEM_COMMIT, PAGE_READWRITE);
        if (thunk)
        
            thunk->PopEAX_1 = 0x58;
            thunk->PushThis = 0x68;
            thunk->ThisValue = this;
            thunk->PushEAX_1 = 0x50;
            thunk->Call = 0xE8;
            thunk->CallAddr = reinterpret_cast<__int32>(Callback) - (reinterpret_cast<__int32>(&thunk->Call) + 5);
            thunk->PopEAX_2 = 0x58;
            thunk->AddESP[0] = 0x83;
            thunk->AddESP[1] = 0xC4;
            thunk->AddESP[2] = 0x04;
            thunk->PushEAX_2 = 0x50;
            thunk->Return = 0xC3;

            DWORD dwOldProtect;
            VirtualProtect(thunk, sizeof(MyThunk), PAGE_EXECUTE, &dwOldProtect);

            FlushInstructionCache(GetCurrentProcess(), thunk, sizeof(MyThunk));

            exObjCallback = (CallbackType) thunk;
        
    

    ~MyClass()
    
        if (exObjCallback)
            VirtualFree(exObjCallback, 0, MEM_RELEASE);
    

    //somestuff
    ExternalClass m_ExObj;

    // NOTE: pCtx is the return address inside of ExternalFunc()
    // where the callback is being called from.  Because the
    // callback is using the __cdecl calling convention, the
    // thunk needs to remember this value and restore it after
    // Callback() exits.  Passing it as a parameter to Callback()
    // is a quick-n-dirty way for the thunk to do that...
    static void __cdecl Callback(void *pCtx, MyClass *pThis, int x)
    
        //dosomething with pThis
    

    void MyFunc()
    
        if (exObjCallback)
            m_ExObj.ExternalFunc(exObjCallback, ...);
    
;

ExternalFunc() 调用它的回调时,它将调用thunk,执行它包含的指令。上面的 thunk 将对象的 this 指针作为 Callback() 的参数注入调用堆栈,就好像 ExternalFunc() 直接调用它一样。

更新:代替有关回调实际接受用户定义值的新信息,这大大简化了事情:

class MyClass

    //somestuff
    ExternalClass m_ExObj;

    static void Callback(int x, const void *p) 
        MyClass *pThis = (MyClass*) p;
        //dosomething with pThis
    

    void MyFunc() 
        m_ExObj.ExternalFunc(&Callback, this);
    
;

【讨论】:

对此我很抱歉,但回调确实允许一个空指针,我完全错过了。那我该怎么办。我对使用回调真的很陌生,所以再次对给您带来的麻烦深表歉意。 再次感谢您的大力帮助和耐心等待。

以上是关于C++ 成员函数作为外部库的回调函数的主要内容,如果未能解决你的问题,请参考以下文章

C++中类成员函数作为回调函数

如何使用 C++ 成员函数作为 C 框架的回调函数

如何实现类的成员函数作为回调函数

如何使用 C++ lambda 将成员函数指针转换为普通函数指针以用作回调

c++ 回调类成员函数实现

让 C 回调调用 C++ 成员函数的最佳方法?