简体   繁体   中英

C++ member function as callback function to external library

So below is a basic idea of what I'm trying to do. I have an external library that I would like to use in an existing project. I cannot change anything in the external library or the main function in the existing project of course.

The problem I face is how to pass a callback function I make in my class to this external function as a pointer to function. At the same time, this callback function has to have access to members of the class so I cannot simply make it static. How can I do it?

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*/)
    }
}

The callback you have shown does not allow a user-defined value to be passed to it (otherwise you could use that for passing around your object pointer). It expects a standalone non-class function, so you have two options:

1) if the callback only ever calls into a single object at one time, then you can store the object pointer in a global or static variable, and then use a standalone function (or static class method) as the callback and have it use the global/static pointer to call your class method:

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) if you need to have callbacks for multiple objects at a time, you will have to wrap your class method inside a per-object thunk, where each thunk knows which object to call into, and then you use the thunks as callbacks. This is a more advanced technique, requiring an understanding of x86/x64 assembly and calling conventions, as you have to allocate memory dynamically and populate it with assembly instructions for each thunk to execute at runtime. For example, at least on 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, ...);
    }
};

When ExternalFunc() calls its callback, it will be calling the thunk, executing the instructions it contains. The thunk above is injecting the object's this pointer into the call stack as a parameter for Callback() as if ExternalFunc() had called it directly.

Update: in lieu of new information about the callback actually accepting a user-defined value, that greatly simplifies things:

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);
    }
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM