2014-01-06 60 views
0

我正在使用SWIG將C++代碼綁定到Lua。到目前爲止它看起來不錯,但現在我需要「欺騙」並從Lua中擴展單個用戶數據,添加自定義字段和方法等。如何在Lua代碼中擴展SWIG的userdata?

我無法找到一種方法在SWIG指令中工作時完成它。我知道包裝代碼中魔術發生的地方,但我不完全理解__index和__newindex是如何工作的。此外,SWIG使用__setitem和__getitem,它被註釋爲「/ * NEW:查找__setitem()fn這是用戶提供的集合fn * /」 - 不知道這意味着什麼。最後,我的環境會自動調用腳本,在每次構建之前將SWIG指令綁定到C++包裝器,因此,如果選擇重新編譯或添加更多Lua綁定,修改源代碼非常繁瑣。

到目前爲止,我唯一的結論就是去找帶有這個功能的Lua ++。請幫助我避免很多過渡工作!

編輯:我還發現,在5.2中的「lua_setuservalue」API調用 - 它似乎很有用,但我不明白我會在所有SWIG綁定代碼中調用它。

編輯:具有

SoundObj *loadSound(const char *name) 
{ 
    return g_audio->loadSound(name); // Returns SoundObj pointer 
} 

這個功能是通過痛飲勢必通過在*。我痛飲定義,它的原型C函數

我創建對象:要明確的事情了。

在Lua中我寫這樣的代碼:

sound = audio.loadSound("beep.mp3") 
sound.scale = 0.2 
sound:play() 

編輯:進步!我遵循Schollii的指示並編寫了以下Lua代碼。在這裏,「地」是使用綁定C++代碼早先獲得的用戶數據。該代碼存儲swig的__index和__newindex版本,然後重新創建這些函數,首先查詢另一個(「_other」)表。

我目前的問題是,存儲在「_other」表中的新值將在該類型的所有用戶數據對象之間共享。換句話說,如果ground.nameFoo =「ha!」,所有其他對象都有nameFoo字段存儲「ha!」。我該如何解決?

mt = getmetatable(ground) 
mt._other = {} 
mt.__oldindex = mt.__index 
mt.__oldnewindex = mt.__newindex 

mt.__index = function(tbl, key) 
    if mt.__oldindex(tbl, key) == nil then 
     if mt._other[key] ~= nil then 
      return mt._other[key] 
     end 
    else 
     return mt.__oldindex(tbl, key) 
    end 
end 

mt.__newindex = function(tbl, key, val) 
    if mt.__oldnewindex(tbl, key, val) == nil then 
     mt._other[key] = val 
    end 
end 

編輯:我實現了這個答案的解決方案:Add members dynamically to a class using Lua + SWIG

的問題是,現在我的目標類型不再是用戶數據,它是一個表。這意味着我不能再有機地將它作爲參數傳遞給以userdata作爲參數的其他綁定C++函數。任何解決方案?我必須對每個返回userdata對象的函數都這樣做。

+0

我認爲這真的歸結爲您是否可以清楚地描述您的問題,以便有人可以提供幫助。再一次,展示你想要做的事情,不要只是描述它(代碼就像一個1000字)。你說「從Lua內部擴展一個用戶數據,添加自定義字段和方法」:好的,但是什麼?我將以添加答案爲例。注意:我沒有意識到您可以在tolua ++中做的事情無法在SWIG中完成。 – Schollii

回答

0

我已經制定了一個解決方案,遵循Schollii的例子......但犧牲較少。這不會將您的userdata轉換爲表格,所以您仍然有用戶數據類型(我可以將它傳遞給其他採用userdata的C++函數)。 Schollii的解決方案有效地將用戶數據轉換爲表格封裝。

所以第一行使用變量「ground」 - 這是一個由SWIG包裝的userdata實例。在執行開始時執行此操作將更改該類型的所有實例的「地面」用戶數據類型的元數據,但每個實例保留由用戶數據的內存位置索引的個人表。該表位於_G._udTableReg表中。

-- Store the metatable and move the index and newindex elsewhere 
mt = getmetatable(ground) 
mt.__oldindex = mt.__index 
mt.__oldnewindex = mt.__newindex 

-- Store the global registry of tables associated with userdata, make a pointer to it in the metatable 
_G._udTableReg = {} 
mt._udTableReg = _G._udTableReg 

-- Rewrite the new index function that looks in the udTableReg using 'self' as index before proceeding to use the old index as backup 
mt.__index = function(self, key) 
    local ret; 
    local privateTable = mt._udTableReg[self] 

    -- If the private table exists and has key, return key 
    if privateTable ~= nil and privateTable[key] ~= nil then 
     ret = privateTable[key] 
    -- Use the old index to retrieve the original metatable value 
    else ret = mt.__oldindex(self, key) end 

    if ret == nil then return 0 
    else return ret end 
end 

-- Try to assign value using the original newindex, and if that fails - store the value in 
mt.__newindex = function(self, key, val) 
    -- If old newindex assignment didn't work 
    if mt.__oldnewindex(self, key, val) == nil then 
     -- Check to see if the custom table for this userdata exists, and if not - create it 
     if mt._udTableReg[self] == nil then 
      mt._udTableReg[self] = {} 
     end 
     -- Perform the assignment 
     mt._udTableReg[self][key] = val 
    end 
end 

我還沒有想出把這個Lua代碼,或如何得到抓住了用戶數據的元表的更優雅的方式,實際上並沒有使用現有變量的最佳場所。

+0

希望我可以幫助更多,但恐怕我只是沒有得到你想要做的事情(但看看我的第二個答案,也許我更接近)。 – Schollii

1

我在這裏會馬虎:在Lua中,一張表通過使用它作爲metatable從另一個表「繼承」。因此,如果您將C++ Foo導出到Lua並希望從Foo派生Lua「類」,則可以創建一個表並將其metatable設置爲Foo。然後,當您使用表中不存在的字段訪問新表時,它將以metatable查看它是否存在。例如僞代碼:

baseTable = {a=123} 
assert(getmetatable(baseTable) == nil) 
derived={b=456} 
assert(derived.a == nil) 
setmetatable(derived, baseTable) 
assert(derived.a == 123) 

的baseTable是C++類通過SWIG出口到Lua,你不能改變它的元表,但你可以改變你在Lua創建表的。所以你不需要對SWIG代碼進行任何修改或者使用SWIG指令,你可以在Lua中運行。例如:

-- define Foo class: 
Foo = {} 
Foo.__index = Foo -- make Foo a Lua "class" 
setmettable(Foo, YourCppBaseClass) -- derived from your C++ class 
-- instantiate it: 
foo = {} 
setmetatable(foo, Foo) -- foo is of "class" Foo 
+0

謝謝Schollii,我想到了這個選項,但我所有的對象都是用C++創建和綁定的。幾乎所有東西(減去以表格作爲參數的複雜函數)都由SWIG完成。我不能在SWIG中做本地類方法。此外,這意味着我將不得不將所有的用戶數據創建代碼包裝在Lua中。謝謝,但這不是一個理想的解決方案。我真的想讓SWIG工作。 – Kaa

+0

@Kaa我不明白「我的所有對象都是用C++創建和綁定的」,這使得它無關緊要。也許我根本不理解你的問題。 「SWIG中的原生類方法」:這是什麼意思。 「我將不得不將所有的用戶數據創建代碼封裝在Lua中:」我不明白爲什麼。你是否嘗試使用從C++中的C++對象派生的Lua對象?如果你可以嘗試澄清(在你的問題中,而不是通過評論),通過放置示例代碼(一些.cpp和.lua來顯示用法),我可能會發現更相關的s/t。 – Schollii

+0

謝謝你的代碼!我明白了,但在用戶編碼器掌握之前,我永遠都不會在Lua中保存userdata對象。如果你看到上面的示例代碼 - 用你的方法,我將不得不重新命名這些C++函數,然後在Lua中創建可執行metatable賦值的包裝器。 – Kaa

1

因此,您在C++中有一個SoundObject類,可以將它導出到Lua(無論它是通過SWIG還是通過tolua ++或手動)。你的應用程序運行一個lua腳本,創建一個SoundObject的實例並添加一個屬性(在Lua中)和一個方法(再次在Lua中),然後你希望能夠使用該屬性並從C++調用該方法。類似於:

-- Lua script: 
sound = audio.loadSound("beep.mp3") 
sound.scale = 0.2 -- this property exported by SWIG 
sound.loop = true -- this is Lua only 
sound.effect = function (self, a) print(a) end -- this is Lua only 
sound:play()  -- this method exported by SWIG 

// C++ pseudocode: 
bind SoundObject* load_sound(string) to "audio.loadSound" 
run Lua script 

而您希望play()方法調查其他屬性和方法(如循環和效果)並執行某些操作。什麼,我無法想象,希望這可以讓你知道如何更清楚地表達你的問題。

+0

非常接近。我還希望Lua中的聲音對象保持爲用戶數據類型。目前 - SWIG不允許我默認這樣做。 – Kaa

+0

是什麼讓你認爲它不允許Lua中的聲音對象保持用戶數據類型?比如,在sound.effect被分配後,lua_isuserdata返回false?或luaL_checkudata變成虛假? – Schollii

+0

如果我使用你的初始解決方案,是的,在封裝以啓用此功能後,該類型變成表格。你如何建議我啓用修改用戶數據的能力,而不像你建議的那樣將它包裝在表中? – Kaa