0

我正在创建一个助手来创建 C++ 类和 Lua 对象之间的多重继承。因为 Lua 将 C/C++ 用户对象存储为 void *,所以在检索对象时很难进行安全转换。

例如,

如果你有

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

并且您将 C 类型的对象传递给 Lua,传递 C 实例的地址,当您需要将其强制转换为 B 时,C++ 编译器会自动将指针与 C 中 B 的位置对齐,因此,这是不安全的将 void * 指针从 C 直接转换为 B。

为了避免这个问题,我使用了一种转换器。在 Lua 中,对象包含它们的名称作为字符串,因此当您需要将对象从类型转换为其他类型时,它使用如下转换器:

转换器["B"]["C"](mypointer, myresultpointer);

这是帮助创建这些转换器的类:

// 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);
        };
    }
};

此类编译为静态库,可在项目中多次使用。

对象需要作为 shared_ptr 传递(它也解决了所有权和删除的问题)。它运行良好,但是,在用作静态库时会出现段错误。

然后,我有一个简单的模块 Battery,编译为共享对象并链接到公共库。

对于示例的范围,它并没有包含太多的功能,但它实际上使用了 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;
}

这编译为一个名为 battery.so 的共享对象,Lua 将使用 dlopen() 和 dlcose() 来加载它。

最后是主线。它也链接到 common 并使用它来创建对象。

// 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;
}

总结一下:

  • Common.cpp、Common.h 被编译为简单的静态库 (libcommon.a)
  • Main.cpp,编译并链接到 libcommon.a
  • Battery.cpp,编译为共享对象并链接到 libcommon.a

退出时的主要段错误,核心文件说它在 std::function<> 的析构函数中,所以我猜它在同一个指针上被多次调用,是吗?

静态库数据是否在所有代码中共享?我怎样才能避免这个问题?

核心的开始

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

下一个跟踪只是不可读且无法使用。

4

1 回答 1

0

静态库的代码和全局/静态数据将被注入到链接它的每个模块中。因此,对于您的情况,您的项目中存在多个LuaeClass::converters实例。并且您需要调用luaopen_battery()链接静态库的每个模块。

我不确定您的崩溃是否与静态链接有关,但我很确定您进行了复杂的实现。

您要解决的第一个问题是安全地转换void*A*, B*, C*. 您想将哪个类/接口导出到 Lua?如果是class C,您可以在 C 类中定义以下方法:

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

两种方法都使用C*,因此您不需要转换功能。您可以使用元表将您的指针与其他用户数据区分开来。如果您需要B*,只需:

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

而且您可能并不真的需要 shared_ptr。当您的 Lua 对象被 GC 收集时,shared_ptr 无助于删除您的 C++ 对象(因为 shared_ptr 仍然存在于堆中)。相反,您必须在元表中实现__gc回调来删除您的对象。

于 2013-08-16T16:18:33.433 回答