开发者

C++ class member function pointer to function pointer

开发者 https://www.devze.com 2023-03-25 03:02 出处:网络
I am using luabind as my lua to C++ wrapper. Luabind offers a method to use my own callback function to handle exceptions thrown by lua, set_pcall_callback(). So I paraphrased an example from the docu

I am using luabind as my lua to C++ wrapper. Luabind offers a method to use my own callback function to handle exceptions thrown by lua, set_pcall_callback(). So I paraphrased an example from the documentation, the changes being the logger->log() function and putting the function in a class c开发者_如何学Pythonalled 'Engine', so instead of it being a regular global function it is now a member function, which is where my problem seems to be.

Here are the relevant code snips:

class Engine //Whole class not shown for brevity
{
public:
    Engine();
    ~Engine();
    void Run();
    int pcall_log(lua_State*);
private:
    ILogger *logger;
};

Engine::Run()
{
lua_State* L = lua_open();
luaL_openlibs(L);
open(L);
luabind::set_pcall_callback(&Engine::pcall_log); //<--- Problem line
//etc...rest of the code not shown for brevity
}

int Engine::pcall_log(lua_State *L)
{
lua_Debug d;
lua_getstack( L,1,&d);
lua_getinfo( L, "Sln", &d);
lua_pop(L, 1);
stringstream ss;
ss.clear();
ss.str("");
ss << d.short_src;
ss << ": ";
ss << d.currentline;
ss << ": ";
if ( d.name != 0)
{
    ss << d.namewhat;
    ss << " ";
    ss << d.name;
    ss << ") ";
}
ss << lua_tostring(L, -1);
logger->log(ss.str().c_str(),ELL_ERROR);
return 1;
}

Here is what the compiler says during compilation:

C:\pb\engine.cpp|31|error: cannot convert 'int (Engine::*)(lua_State*)' to 'int (*)(lua_State*)' for argument '1' to 'void luabind::set_pcall_callback(int (*)(lua_State*))'|

So it seems that the error is that the function expects a regular function pointer, not a class member function pointer. Is there a way to cast or use an intermediate function pointer to pass to the set_pcall_callback() function?

Thank you!


No. A member function is not a free function. The type is entirely different, and a pointer to a member function (PTMF) is a completely different, incompatible object from a function pointer. (A PTMF is usually much bigger, for example.) Most importantly a pointer-to-member must always be used together with an instance pointer to the object whose member you want to call, so you cannot even use a PTMF the same way you use a function pointer.

The easiest solution for interacting with C code is to write a global wrapper function that dispatches your call, or to make your member function static (in which case it becomes essentially a free function):

// global!

Engine * myEngine;
int theCallback(lua_State * L)
{
  return myEngine->pcall_log(L);
}

Engine::Run()
{
  /* ... */
  myEngine = this;
  luabind::set_pcall_callback(&theCallback);
  /* ... */
}

The conceptual problem here is that you have an engine class, although you will practically only have one single instance of it. For a genuine class with many objects, a PTMF wouldn't make sense because you'd have to specify which object to use for the call, whereas your engine class perhaps is essentially a singleton class which could be entirely static (i.e. a glorified namespace).


Not suitable for your LUA problem, but maybe on other libraries: If a function requests a a function pointer like func(void* param, ...) and you can ensure that the lifetime of your object is greater than the stored function pointer, then you could technically also use a method pointer (looks the same on the stack), but C++ prevents direct casting of method pointers to function pointers.

But with a little trick, you can also cast method pointers to function pointers:

template<typename M> inline void* GetMethodPointer(M ptr)
{
    return *reinterpret_cast<void**>(&ptr);
}

Using that, you can use method pointers for example with libmicrohttpd:

this->m_pDaemon = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, this->m_wPort, NULL, NULL, reinterpret_cast<MHD_AccessHandlerCallback>(GetMethodPointer(&CMyWebServer::AccessHandlerCallback)), this, MHD_OPTION_END);

But be aware of it. You must take care of the lifetime of that object. Also the calling conventions must match.


As a callback, it is usual to use static functions:

class Engine //Whole class not shown for brevity
{
    ....
    static int pcall_log(lua_State*);
    ...
}

This would solve your issue.


Explicit conversion from method pointer to function pointer is illegal in C++ - period.

But there is a hack. We have to first convert the (const) method pointer to (const) void* and then to (const) function pointer. And it works. And why wouldn't it? Everything and I mean everything can be pointed to by a void* because everything has an address.


WARNING: The below is DAAEINGEROUS hack territory. If you're developing software for a fighter jet or whatnot, you should know better than to use this. I'm not responsible! I'm just providing this here for educational purposes.


The trick is we must convert between method pointer and function pointer through an intermediate conversion to, potentially cv (const-volatile) qualified, void*.

This way we are able to call a member function (pointer) through a function pointer, with the first argument being a Type* pointer to the target class object, which is equivalent to the member function call's this*.

Given a: MethodPointerType f;

then

FunctionPointerType m_pFn = reinterpret_cast<FunctionPointerType>( reinterpret_cast<void*&>( f ) );

or to make it more explicit use two of the following in sequence, for non-const and const member functions:

template<typename MP>
void* getMethodVoidPointer( MP ptr )
{
    return *reinterpret_cast<void**>( &ptr );
}
template<typename FP>
FP getFunctionPointer( void* p )
{
    return reinterpret_cast<FP>( p );
}
template<typename MP>
const void* getMethodConstVoidPointer( MP ptr )
{
    return *reinterpret_cast<const void**>( &ptr );
}
template<typename FP>
FP getConstFunctionPointer( const void* p )
{
    return reinterpret_cast<FP>( p );
}

Play with it live here with a complete compilable sample in C++17: https://onlinegdb.com/HybR8crqw

It works in Visual Studio too.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号