0
votes

I'm creating a helper to create multiple inheritance between C++ classes and Lua objects. Because Lua store C/C++ user objects as void *, it's hard to do safe casts when you retrieve objects.

For instance,

if you have

class A { }
class B { }
class C : public A, public B { }

And you pass an object of type C to Lua, you pass the address of the C instance, when you need to cast it to B, the C++ compiler will automatically align the pointer to the position of B in C and thus, it's not safe to cast the void * pointer from C to B directly.

To avoid this issue, I use a kind of converter. In Lua, the objects contains their name as a string, so when you need to cast the object from type to an other type, it uses the converter like this:

converters["B"]["C"](mypointer, myresultpointer);

This is the class that helps creating these converters :

// Common.h

#include <functional>
#include <memory>
#include <unordered_map>
#include <string>

typedef std::function<void (void *, void *)> LuaConverter;

typedef std::unordered_map<
        std::string,
        std::unordered_map<
            std::string,
            LuaConverter
        >
    > LuaConverters;

class LuaeClass {
public:
    static LuaConverters converters;

public:
    template <class From, class To>
    static void createConverter(const std::string &fromName,
                    const std::string &toName)
    {
        converters[toName][fromName] = [&] (void *ptr, void *result) -> void {
            std::shared_ptr<From> *from = static_cast<std::shared_ptr<From> *>(ptr);
            *((std::shared_ptr<To> *)result) = std::static_pointer_cast<To>(*from);
        };
    }
};

This class is compiled as static library to be used many times in the project.

Object need to be passed as shared_ptr (it also solve the problem of ownership and deletion). It works well, however, it segfaults when using as static libraries.

Then, I have a simple module Battery, compiled as shared object and links to the common library.

For the scope of the example, it does not contains much functions, but it actually use the LuaeClass:

// Battery.cpp

#include <lua.hpp>

#include "Common.h"

class Battery {
public:
    int getPercent() {
        return 100;
    }
};

extern "C" int luaopen_battery(lua_State *L)
{
    LuaeClass::createConverter<Battery, Battery>("Battery", "Battery");

    return 0;
}

This compiled as a shared object named battery.so, Lua will use dlopen() and dlcose() to load it.

Finally, the main. It links to common also and use it to create objects.

// main.cpp

#include <iostream>
#include <memory>
#include <string>

#include <lua.hpp>

#include "Common.h"

using namespace std;

class LuaDeleter {
public:
    void operator()(lua_State *L) {
        lua_close(L);
    }
};

typedef unique_ptr<lua_State, LuaDeleter> LuaState;

int main(void)
{
    LuaState L(luaL_newstate());

    luaL_requiref(L.get(), "_G", luaopen_base, 1);
    luaL_requiref(L.get(), "package", luaopen_package, 1);

    // This will dlopen() and dlclose()
    string code = "local battery = require \"battery\"";

    LuaeClass::createConverter<int, int>("Int", "Int");

    if (luaL_dostring(L.get(), code.c_str()) != LUA_OK) {
        cerr << lua_tostring(L.get(), -1) << endl;
    }

    return 0;
}

To summary:

  • Common.cpp, Common.h are compiled as simple static library (libcommon.a)
  • Main.cpp, compiled and links to libcommon.a
  • Battery.cpp, compiled as a shared object and links to libcommon.a

The main segfaults at exit, the core file says it's in the destructor of std::function<> so I guess it is called multiple times on the same pointer, is it?

Is the static library data shared in all code? How can I avoid this issue?

The begin of the core

#0  0x0000000000404062 in std::__1::function<void (void*, void*)>::~function() ()
#1  0x0000000000404025 in std::__1::function<void (void*, void*)>::~function() ()

The next trace are just unreadable and unusable.

1
Build libcommon as a shared library too, it's likely both Battery and Main have their own independent copies of the library (and thus independent copies of the global LuaeClass::converter variable).DanielKO
I've just tried, it also crashes.markand
Aren't you violating the One Definition rule with the 'static LuaConverters converters'? See this post for instanceCome Raczy
Actually, from the code posted, it appears that no "converters" was ever defined, I'm surprised this even links.DanielKO
Yes, it is the only line defined in Common.cpp: LuaConverter LuaeClass::converters; I've forgot to add itmarkand

1 Answers

0
votes

The code and global/static data of static library will be injected into each module which links it. So for your case, there are multiple LuaeClass::converters instances exist in your project. And you need to call luaopen_battery() in each module which links the static library.

I am not sure if your crash has anything to do with static link, but I am quite sure you went to a complicated implementation.

The first issue you want to address is safely converting void* to A*, B*, C*. Which class/interface you want to export to Lua? If it's class C, you can define below methods in class C:

void pushThis(lua_State *L);
static C* getThis(lua_State *L, int idx);

Both methods use C*, so you don't need a convert function. You can use meta table to distinguish your pointers from other userdata. If you need B*, just:

B* b = (B*)C::getThis(L, idx);

And you may not really need a shared_ptr. shared_ptr doesn't help on deleting your C++ object when your Lua object is collected by GC(because the shared_ptr still exist in heap). Instead, you have to implement a __gc callback in the meta table to delete your object.