Callback of member functions through 3d party library

前端 未结 3 568
礼貌的吻别
礼貌的吻别 2020-12-22 10:16

I\'m working with a particle simulation library. The way interactions are added to particles it through the following library function:

AddInteraction(Partic         


        
相关标签:
3条回答
  • 2020-12-22 10:48

    It is possible to make that library function callback a member function without modifying the library. The technique is quite general and based on self modifying code. It is a rare case where self modifying code is justified and, in fact, applied by some widely used libraries (like ATL and WTL on Windows).

    In two words: you want to create a copy of a function like this:

    void callback(xyz* p, xyz* v) {
        YourClass *ptr = (YourClass*)0xABCDEF00; // this pointer
        ptr->member_callback(p, v);
    }
    

    for each instance of YourClass. Then you can pass it to the library as usuall:

    AddInteraction(particleSet, this->a_pointer_to_an_instance_of_that_function);
    

    This approach is simple once you familiar with machine code, but it's not portable (yet can be implemented on any reasonable architecture).

    For details see the description and implementation for Windows.

    EDIT: Here is how one can generate the machine code for the thunk without actually knowing assembly. Compile this code in release:

    void f(int *a, int *b)
    {
        X *ptr = reinterpret_cast<X*>(0xAAAAAAAA);
        void (*fp)(void*, int*, int*) = reinterpret_cast<void(*)(void*, int*, int*)>(0xBBBBBBBB);
        fp(ptr, a, b);
    }
    
    int main()
    {
        void (*volatile fp)(int*, int*) = f;
        fp(0, 0);
    }
    

    Put a breakpoint in f and run. Look at the assembly code in the debugger. On my machine it looks like this:

    00401000 8B 44 24 08      mov         eax,dword ptr [esp+8] 
    00401004 8B 4C 24 04      mov         ecx,dword ptr [esp+4] 
    00401008 50               push        eax  
    00401009 51               push        ecx  
    0040100A 68 AA AA AA AA   push        0AAAAAAAAh 
    0040100F BA BB BB BB BB   mov         edx,0BBBBBBBBh 
    00401014 FF D2            call        edx  
    00401016 83 C4 0C         add         esp,0Ch 
    00401019 C3               ret 
    

    The first column is the memory address of the code, the second is the machine code (you may need to enable it in MSVC by right clicking > show code bytes), the third is the disassembled code. What we need is the second column. You can just copy it from here or use any other method (like object file listing).

    This code corresponds to a function with a default calling convention, receives two pointers (the type doesn't matter here), returns nothing, and performs a call to a function at address 0xBBBBBBBB passing 0xAAAAAAAA as the first parameter. Voilà! This is exactly how our thunk should look like!

    Initialize the thunk with the machine code from above:

    8B 44 24 08 8B 4C 24 04 50 51 68 AA AA AA AA BA BB BB BB BB FF 83 C4 0C C3
    

    And replace AA AA AA AA with this address and BB BB BB BB with a pointer to the following function. To avoid endianess issues use unaligned access.

    void delegate(YourClass *that, xyz p, xyz* v) {
        that->member(p, v);
    }
    

    We magically encoded that and f inside a single function pointer! And since the last call is likely to be inlined, the whole thing costs just one more function call compared to the void* approach.

    ⚠ WARNING ⚠ There are some restrictions on what you can write in f above. Any code that will compile to machine code with relative addressing will not work. But the above is just enough for accomplishing the task.

    NOTE: It's possible to call the member function directly without the delegate function, as done in the CodeProject article. However, due to the complexities of what member function pointers are, I prefer to do not do this.

    NOTE: The code generate by this method is suboptimal. The optimal code equivalent to the above would be:

    00401026 68 AA AA AA AA   push        0AAAAAAAAh 
    0040102B BA BB BB BB BB   mov         edx,0BBBBBBBBh 
    00401030 FF E2            jmp         edx  
    
    0 讨论(0)
  • 2020-12-22 10:57

    What you describe is not possible because the library does not know it has to pass this parameter to your member functions. You could do it if interaction accepted an argument reserved for the user. If you have a single object instance calling AddInteraction at any given time, then you can store a pointer to the instance:

    Object *Object::only_instance;
    
    void Object::AddInteractionCaller() {
       only_instance = this;
       AddInteraction(set, interaction_fn); 
    }
    
    void interaction_fn(xyz* p, xyz* v) {
      only_instance->interaction(p, v);
    }
    
    0 讨论(0)
  • 2020-12-22 11:03

    Typically, callback function have a void * argument that allows client code a placeholder for any other information it may require.

    This way, the client code can pass in anything they want and recast it back to the original type when the callback is invoked. Note, the calling code knows the original type.

    This type of interface allows C only code to work but it's pretty easy to wrap this in a C++ object if needed. At the very least, the library author should provide this.

    EDIT TO ANSWER OPs COMMENT
    Below I've modified the Cell class appropriately.

    class Cell  
    { 
    public:
      static void UpdateParticle(void *stuff, xyz* p, xyz* v)  // this has to be static as others have mentioned, note the additional void * argument
      {
          Cell * c = (Cell *) stuff;
          // do work
      }  
    
      Cell()
      {
          AddInteraction(this, UpdateParticle); // note this takes a two items, both of which have to be saved for future use by AddInteraction and utilized by Simulate
      }
    };  
    
    0 讨论(0)
提交回复
热议问题