2013-08-16 137 views
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傳遞(它也解決了所有權和刪除問題)。它運行良好,但是,它使用靜態庫時會出現段錯誤。

然後,我有一個簡單的模塊電池,編譯爲共享對象和鏈接到公共庫。

對於本示例的範圍內,它不含有大量的功能,但它實際上使用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()加載它。

最後主要是。它也鏈接到通用,並用它來創建對象。

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

退出時的主要segfaults,核心文件表示它在std :: function <>析構函數>所以我想它是多次調用同一個指針,是嗎?

靜態庫數據是否在所有代碼中共享?我怎樣才能避免這個問題?

的核心

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

下一個跟蹤的開始僅僅是不可讀的,無法使用。

+0

也將libcommon構建爲共享庫,可能Battery和Main都擁有自己獨立的庫副本(並因此獨立拷貝全局'LuaeClass :: converter'變量)。 – DanielKO

+0

我剛剛嘗試過,它也崩潰。 – markand

+1

你不是在用'_static LuaConverters converters_'違反[** One Definition rule **](http://en.wikipedia.org/wiki/One_Definition_Rule)嗎?看看[這篇文章](http://stackoverflow.com/questions/3409034)例如 –

回答

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*,所以你並不需要一個轉換功能。你可以use meta table to distinguish your pointers from other userdata。如果你需要B*,只是:

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

而且你未必真的需要一個shared_ptr。當GC收集Lua對象時(因爲shared_ptr仍然存在於堆中),shared_ptr無助於刪除C++對象。相反,你必須implement a __gc callback in the meta table to delete your object

+0

然後,如果底層對象不是C而是C(又名D或E)的子項,會發生什麼?另外,對於__gc它已經完成了,它只是調用shared_ptr析構函數:) – markand

+0

@markand如果底層對象是C的一個子對象,它繼承了C中的'pushThis'方法,不是嗎?而'C :: getThis'仍然有效。 – Chen

+0

我想過這個,這是一個可能性,但這將需要所有的對象都應該繼承這個基類。我會挖掘它。謝謝 :)。然而,luabind通過添加向上/向下轉換來做類似的事情(所以它不需要繼承特定的基類) – markand