5

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 called '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!

Brian
  • 145
  • 1
  • 3
  • 13

4 Answers4

14

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).

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 1
    That's definitely not the easiest solution. The easiest is to make the method static. – Šimon Tóth Aug 02 '11 at 07:34
  • 1
    Nice and complete answer. +1. – Christopher Creutzig Aug 02 '11 at 07:43
  • 1
    @Let_Me_Be: It depends on your class, doesn't it -- if your class is really just an wrapper for what should be global functions anyway, then `static` is the way to go. But that may not always be an option. – Kerrek SB Aug 02 '11 at 07:44
  • The wrapper solution fixed the issue. – Brian Aug 02 '11 at 07:49
  • The engine class wraps the graphics and event pointers so I don't have to pass a global context struct everywhere. – Brian Aug 02 '11 at 07:51
  • @Brian: Yes, sure, but if there is only a single instance of those two pointers, too, then they too could be static, etc. etc. It's just a matter of design! Don't get too hung up on it now, but if you later discover that you need multiple engines, then you know where to start. – Kerrek SB Aug 02 '11 at 07:53
  • Thank you for your advice, I really appreciate it! Code design is my weakness, I usually just wing it and hope I can piece it together :) – Brian Aug 02 '11 at 07:58
3

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.

David Gausmann
  • 1,570
  • 16
  • 20
  • An important note to my answer: The user-defined parameter (param called above) must be the first parameter of the callback function to get this to work. On member functions the this pointer is always the first parameter on the stack. So if we cast a member function to a non-member function then we must ensure to pass the this pointer as the first argument. – David Gausmann Apr 20 '20 at 16:47
0

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.

KeyC0de
  • 4,728
  • 8
  • 44
  • 68
0

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.

sergio
  • 68,819
  • 11
  • 102
  • 123