1

I am trying to call a class member function like a normal C function with the help of boost, but my efforts into looking how to do it just fail so I had come up with something on my own, something that sounds intuitive... I think.

But the problem is that it just keeps crashing when calling the pointers to the boost::function - fooA and fooB.

To do some tests on it:

#include <boost/bind.hpp>
#include <boost/function.hpp>

class MyOtherClass{
public:
    MyOtherClass(){}
};

class MyClass
{
    std::string name;
public:
    void MyFunction(MyOtherClass *data)
    {
        std::cout << "NAME:" << name << std::endl;
    }
    boost::function<void(MyOtherClass *)> bindMyFunction;
    MyClass(std::string _name)
        : name(_name)
    {
        bindMyFunction = std::bind1st(std::mem_fun(&MyClass::MyFunction), this);
    }
};

I then execute the code just once in a main to try it, I expect to see NAME:a\nNAME:b\n three times but it just crashes after two times:

int main()
{
    MyClass a("a");
    MyClass b("b");
    MyOtherClass c;

    //make sure we can save the Class::bindMyFunction variable
    void ** someTestA = (void**)&a.bindMyFunction;
    void ** someTestB = (void**)&b.bindMyFunction;

    //'convert' it to the needed type, just like function-pointers work
    //The library uses this below
    //                             Now the correct assignment is the hard part
    void(*fooA)(MyOtherClass*) = (void(*)(MyOtherClass*))(*someTestA);
    void(*fooB)(MyOtherClass*) = (void(*)(MyOtherClass*))(*someTestB);

    //Works
    a.MyFunction(&c);
    b.MyFunction(&c);

    //Works
    a.bindMyFunction(&c);
    b.bindMyFunction(&c);

    //execute the boost::function by calling it like a function
    //Crash
    fooA(&c);
    fooB(&c);
    return 0;
}

To my unfortunate luck, I use a library which only accepts a parameter void(*)(MyOtherClass*), but I need to encapsulate it in a class. The library uses Callbacks to notify of any changes. So I need to figure a way to convert boost::function<void(*)(MyOtherClass*)> to a void(*)(MyOtherClass*) type and then let the library be able to just call(parameters) it.

But this seems rather a very hard task to accomplish. Are there any approaches to do this the correct way?


Edit: As per request I will make this question more specific, but I am worrying about the question being too localized in this way, but here it goes:

I am trying to make a class which has RakNet encapsulated in it. Put everything in the class (even the RakNet callbacks). Each object instance of that class will be an own client which will connect to a game server:

class MyClass
{
    RakClientInterface* pRakClient;
    void MyFunction(RPCParameters *pParams){}
    boost::function<void(RPCParameters *)> bindMyFunction;
public:
    MyClass()
    {   
        pRakClient = RakNetworkFactory::GetRakClientInterface();
        if (pRakClient == NULL)
            return;

        bindMyFunction = std::bind1st(std::mem_fun(&MyClass::MyFunction), this);

        //void RakClient::RegisterAsRemoteProcedureCall( int* uniqueID, void ( *functionPointer ) ( RPCParameters *rpcParms ) )
        pRakClient->RegisterAsRemoteProcedureCall(&RPC_ServerJoin, /*<need to pass boost::function here somehow>*/);
    }
};
Community
  • 1
  • 1
Gizmo
  • 1,990
  • 1
  • 24
  • 50
  • 2
    Your code grabs first, say, 4 bytes of the object representation of `boost::function`, and tells the compiler it's a plain function pointer. Your code therefore lies to the compiler. Your program exhibits undefined behavior. – Igor Tandetnik Aug 10 '14 at 18:46
  • Well I hoped it'd work, I really desperately look for a way to somehow be able to execute `boost::function(params)` by using data from just a `void(*fooA)(MyOtherClass*)` variable and calling it like `fooA(params)`. – Gizmo Aug 10 '14 at 18:48
  • Does this look similar? https://stackoverflow.com/questions/25212423/function-pointer-with-variable-inputs – Deduplicator Aug 10 '14 at 18:56
  • By the way, you can statically cast a lambda that does not capture anything to a function pointer. `static_cast( [](MyOtherClass* p){return ((ActualClass*)p)->DoIt();} );` – André Puel Aug 10 '14 at 18:59
  • 2
    The library you work with appears rather poorly designed. It doesn't pass any context information at all to the callback - nothing to hang a `this` pointer on. I've seen a workaround for this situation - a dynamically generated machine code thunk with `this` pointer embedded in it - but I'm not qualified to actually write one, and it's very non-portable. In any case, your games with `boost::function` won't work - you can't extract missing information out of thin air, no matter how many weird casts you string together. – Igor Tandetnik Aug 10 '14 at 19:09

1 Answers1

1

If MyClass was just a singleton than you can do this with static members fairly easily. Unfortunately since you're creating multiple instances of MyClass then you don't have many options. You either need something to hang the this pointer on, like Igor Tandetnik said, or you need a closure you can safely convert to function type, and C++11 doesn't offer that. It requires dynamically created machine code to actually implement something like that and the GNU C (not C++) compiler is the only compiler I know of that can do that for you.

I'm not familiar with RakNet but I did some searching on the web and it looks like it just barely gives you something to hang the this pointer on, so to speak. The RPCParameters pointer the RPC function is passed contains a RakPeerInterface pointer named recipient which should to point to the same RakClient object that is pointed to by the RakClientInterface pointer you use to register the RPC functions. Assuming each instance of MyClass only has one RakClient object you can use the address of the RakClient as mapping to the MyClass object that created it.

I've posted a complete example that demonstrates how to do this. Rather than actually use RakNet, it uses a skeleton implementation of the relevant classes. It's not clear to me what version of RakNet you're using (most implementation seem to have uniqueID parameter of type char const * rather int *), but so long as RPCParamters has a recipient pointer this technique should work.

#include <stdint.h>
#include <assert.h>
#include <stdio.h>
#include <map>

struct RakPeerInterface;

struct RPCParameters {
    RakPeerInterface *recipient; 
};

typedef void (*rakrpcfunc)(RPCParameters *rpcParams);

struct RakPeerInterface {
    virtual void RegisterAsRemoteProcedureCall(int *, rakrpcfunc) = 0;

};

struct RakPeer: RakPeerInterface {
    virtual void
    RegisterAsRemoteProcedureCall(int *, rakrpcfunc func) {
        RPCParameters params;
        params.recipient = this;
        func(&params);
    }
};

struct RakClientInterface {
    virtual void RegisterAsRemoteProcedureCall(int *, rakrpcfunc) = 0;
};

struct RakClient: RakPeer, RakClientInterface {
    virtual void
    RegisterAsRemoteProcedureCall(int *id, rakrpcfunc func) {
        return RakPeer::RegisterAsRemoteProcedureCall(id, func);
    }
};

struct RakNetworkFactory {
    static RakClientInterface *
    GetRakClientInterface() { 
        return new RakClient;
    }
};

class MyClass;
typedef void (MyClass::*rpcmethod)(RPCParameters *rpcParams);

template <rpcmethod method>
class rak_thunk {
    static std::map<RakClient *, MyClass *> clientmap;

    static void thunk(RPCParameters *rpcParams) {
        RakPeerInterface *pip = rpcParams->recipient;
        RakClient *cp = dynamic_cast<RakClient *>(pip);
        return (clientmap[cp]->*method)(rpcParams);
    }

public:
    static rakrpcfunc
    make_thunk(MyClass *this_, RakClientInterface *cip) {
        RakClient *cp = dynamic_cast<RakClient *>(cip);
        assert(clientmap[cp] == NULL || clientmap[cp] == this_);
        clientmap[cp] = this_;
        return thunk;
    }
};

template <rpcmethod method>
std::map<RakClient *, MyClass *> rak_thunk<method>::clientmap;

int RPC_ServerJoin;

class MyClass {
    RakClientInterface *pRakClient;

    void
    MyFunction(RPCParameters *pParams) {
        printf("MyClass(%s)::MyFunction called\n", name);
    }

    typedef void (MyClass::*rpcmethod)(RPCParameters *rpcParams);

    char const *name;

public:
    MyClass(char const *s): name(s)  {   
        pRakClient = RakNetworkFactory::GetRakClientInterface();
        if (pRakClient == NULL)
            return;

        rakrpcfunc p = (rak_thunk<&MyClass::MyFunction>
                ::make_thunk(this, pRakClient));

        pRakClient->RegisterAsRemoteProcedureCall(&RPC_ServerJoin, p);
    }
};

int
main() {
    MyClass a("a");
    MyClass b("b");
}

This example compiles and runs using both the GNU C++ 4.8.1 and Visual Studio 2013 C++ compilers under Windows. Notably it doesn't use Boost or any fancy new C++11 features.

Ross Ridge
  • 38,414
  • 7
  • 81
  • 112