LuaTinker的bug和缺陷

LuaTinker的bug和缺陷

LuaTinker是一套還不錯的C++代碼和Lua代碼的綁定庫,作者是韓國人Kwon-il Lee,作者應該是參考了LuaBind後,爲了簡化和避免過重而實現的。其官網在http://gpgstudy.com/gpgiki/LuaTinker ,但可惜全部是韓文的,而最新的代碼可以在Git上下載,https://github.com/zupet/LuaTinker 。對比LuaBind,LuaPlus這類庫,其實現非常非常非常非常非常輕,大約只有1000多行,至少給了你一個機會,去了解和改寫部分功能,所以其在國內也有不少羣衆基礎。而且其輕薄的身材也讓剖析一下一個腳本粘合層是如何工作成爲了可能。

但另外一方面LuaTinker的bug數量並不在少數。也有不少同學曾經零散的提出來過。這兒只是做個總結,另外感謝fergzhang同學。很多問題都是他幫忙指出的。

  1. BUG(錯誤)

第一個問題,int64_t 數據的比較是錯誤的,完全沒有考慮符號位的情況嘛。

static int lt_s64(lua_State *L)
{
     //完全沒有考慮符號位的情況,居然用memcmp
    lua_pushboolean(L, memcmp(lua_topointer(L, 1), lua_topointer(L, 2), sizeof(long long)) < 0);
    return 1;
}

 

第二個問題,在處理類成員的修改函數(__newindex)時,沒有考慮要處理父類成員的,而LuaTinker是支持繼承的,而且int lua_tinker::meta_get(lua_State *L) (對應__index)也是支持。這應該是作者的一個疏漏。

int lua_tinker::meta_set(lua_State *L)
{
    enum_stack(L);
    lua_getmetatable(L, 1);
    lua_pushvalue(L, 2);
    lua_rawget(L, -2);
    enum_stack(L);

    if (lua_isuserdata(L, -1))
    {
        user2type<var_base *>::invoke(L, -1)->set(L);
}
    else if (lua_isnil(L, -1))
{
    //這兒沒有調用invoke_parent(L)處理父類的情況
        lua_pushvalue(L, 2);
        lua_pushvalue(L, 3);
        lua_rawset(L, -4);
    }
    lua_settop(L, 3);
    return 0;
}

 

第三個bug,使用I64d這種過時的標籤,I64d應該是微軟很老很老很老的一個格式化字符串標籤,而且完全不具備可移植性。應該改爲%lld。

static int tostring_s64(lua_State *L)
{
    char temp[64];
    sprintf_s(temp, "%I64d", *(long long*)lua_topointer(L, 1));
    lua_pushstring(L, temp);
    return 1;
}

 

第4個bug,var_base基類的析構函數沒有寫virtual

struct var_base
{
    //原來的析構函數沒有寫virtual
    virtual ~var_base() {};
    virtual void get(lua_State *L) = 0;
    virtual void set(lua_State *L) = 0;
};

 

第5個bug,table的3個構造函數中有一個沒有增加引用計數,這個bug在網上很多同學都指出過。

lua_tinker::table::table(lua_State* L, const char* name)
{
    lua_pushstring(L, name);
    lua_gettable(L, LUA_GLOBALSINDEX);

    if(lua_istable(L, -1) == 0)
    {
        lua_pop(L, 1);

        lua_newtable(L);
        lua_pushstring(L, name);
        lua_pushvalue(L, -2);
        lua_settable(L, LUA_GLOBALSINDEX);
    }

    m_obj = new table_obj(L, lua_gettop(L));
        //原來的代碼沒有這段,缺少增加引用計數處理
        m_obj->inc_ref();
}

, CSDN的ainn_pp也寫過這個問題,參考:http://blog.csdn.net/ainn_pp/article/details/2773855

  1. 缺陷

LuaTinker也有很多缺陷和不足,

第一個不足,各種函數參數的支持個數參差不齊。LuaTinker的寫的時間應該比較早,沒有C++ 11的模版變參(Variadic Template)的支持,所以只能用寫多個模版函數(類)的方式解決多模板參數問題。但LuaTinker一方面寫的參數個數很少,一方面LuaTinker的個個地方支持的參數個數數量完全不統一,3個,4個,5個的都有。其實這部分最好用C++ 11的(Variadic Template)重寫。

第二個缺陷是個硬傷,LuaTinker對Lua 協程的支持,用了一個很費力,但又不討好的方式。你必須把協程的第一個參數定義爲lua_State *L,而且返回值必須是lua_yield(L, 0)。這樣的限制,大大限制了使用。

//第一個參數必須是(lua_State *L
int TestFunc2(lua_State *L, float a)
{
printf("# TestFunc2(L,%f) is invoke.\n", a);
//返回的地方必須是調用lua_yield
    return lua_yield(L, 0);
}

class TestClass
{
public:

    //類函數也一樣
    int TestFunc2(lua_State *L, float a)
    {
        printf("# TestClass::TestFunc2(L,%f) is invoke.\n", a);
        return lua_yield(L, 0);
    }
};

 

而如果其實在其函數的封裝上加以區分,自己在最後調用lua_yield,這就可以避免這個麻煩。這個實現實在讓我沒有多大胃口。

第三個地方是,是對引用變量(參數),註冊的問題,其實這個也不是LuaTinker的問題,而是C++模版的問題,C++的自動模版函數參數推導是存在一些潛規則的。其中有一個就是左值變換,在《CUJ:高效使用標準庫:顯式函數模板參數申明與STL》 一文中有比較清晰的解釋。如下示例:

//下面的寫法是無法得到引用的,必須顯式指定參數。
//lua_tinker::set(L, "ref_a", ref_a);

//顯式聲明引用參數
lua_tinker::set<TestA &>(L, "ref_a", ref_a);

 

第4個問題就是,模版處理中,對於cv(const volatile)的去除掉處理也並不理想。Lua內部對於外部的class的註冊是使用函數class_add,也就是用一個名稱關聯一個Lua的meta table,在類的後面的使用中,通過class_name<T>::name函數取得類名,但實際中,很多時候T是帶有const 或者 volatile的修飾符的。包括,LuaTinker在部分處理中去掉了const,但很多地方又忽略問題,編譯器不會認爲classA和const class A是一個東東的。所以結果就是有時候無法讓你的userdata找到對應的meta table。

lua_tinker::class_add<TestA>(L, "TestA");
lua_tinker::class_con<TestA>(L, lua_tinker::constructor<TestA>);

//用模板函數輔助幫忙實現一個方法,可以通過class 找到對應的類名稱(註冊到LUA的名稱),
template<typename T>
struct class_name
{
    // global name
    static const char *name(const char *name = NULL)
    {
        static char temp[256] = "";
        if (name)
        {
            strcpy_s(temp, name);
        }
        return temp;
    }
};

 

fergzhang同學,針對LuaTinker的一些bug修正做了一個版本。同時好像支持了5.2的版本,他放在了https://github.com/zfengzhen/lua_tinker_5.2.git

而我自己針對上面的問題實現了一套LuaTie的庫,沒有時間,有時間整理出來。內部用C++ 11的特性做了一些改寫。

https://github.com/sailzeng/zcelib/blob/master/src/commlib/zcelib/zce_script_lua_tie.cpp

https://github.com/sailzeng/zcelib/blob/master/src/commlib/zcelib/zce_script_lua_tie.h

工程內部是有測試的例子的,但沒有整理出來,看起來比較麻煩。等後面整理出來再寫一篇介紹的文章把。

最後,雖然我在挑刺,但還是再次表達一下對 LuaTinker作者Kwon-il Lee的感謝,這套代碼幫助我瞭解瞭如何綁定腳本以及瞭解MPL的一些基本知識.而且坦白講LuaBind是和Boost綁定的,能費力從這些代碼中簡化出LuaTinker(閱讀Boost代碼實在談不上愉快),真的是一件很了不起的事情,LuaTinker的作者可能更明白輕對於代碼的好處,Kwon-il Lee在主頁上有一段說明其爲什麼沒有支持LuaBind所支持的重載,而我也認爲一個優秀的庫始終要是簡單的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章