魔獸的UI插件結構
1.使用lua+XML作為配置
分析:XML雖然人機交互很好,但其實沒有幾個UI是真正用純XML寫的,大多還是用編輯器比較方便。速度很慢,但尚不清楚魔獸代碼里是否進行優化
2. Interface\Addons為插件目錄,文件夾可以堆疊
3. 每個插件組,需要一個toc文件來做文件讀取列表描述,類似于:
# Libraries
embeds.xml
AceGUIWidget-DragLink.lua
Core.lua
# Localization
Locale-enUS.lua
Locale-zhCN.lua
Locale-zhTW.lua
AutoBarDB.lua
AutoBarOptions.lua
AutoBarSearch.lua
…
4.一個插件組里可以擁有多個lua文件,都共享一個獨立的全局空間
5.WTF\Account\賬號名\服務器名\角色名\AddOns.txt文件描述哪些插件需要讀取
根據分析:每次魔獸啟動時,都會掃描一次插件目錄,并更新這個列表,但是原有的插件讀取狀態仍然保留,類似于:
Combuctor: enabled
Combuctor_Config: enabled
Parrot: disabled
BattleInfo: disabled
BigWigs: disabled
BigWigs_Extras: disabled
BigWigs_BlackTemple: disabled
leafZone: enabled
InFlight: disabled
…
6. WTF\下的很多SavedVariables目錄都是用于保存插件狀態的,沒有對lua的擴展庫進行研究(ACE2/3等等),但是這是一種很好的保存插件數據的方法
OZ_Config = {
{
["bottomCol"] = {
["a"] = 1,
["r"] = 0,
["g"] = 0,
["b"] = 0.6,
},
["maxBars"] = 40,
["barHeight"] = 16,
["titleHeight"] = 20,
["sort2"] = 0,
["fadeAlpha"] = 0.3999999761581421,
["textSize"] = 10,
["colour"] = 2,
["minBars"] = 1,
["heading"] = {
3, -- [1]
0, -- [2]
0, -- [3]
…
7.暫時沒有找到魔獸UI的核心API是否用純腳本提供的證據,但是可以推斷,按照暴雪的實力,應該是全lua api寫成。
構建安全的lua沙箱
所謂沙箱,就是每個插件擁有獨立的_G全局環境,即便用戶誤將print修改,其他的插件也不會受到影響. 同時,考慮到沙箱的安全性和權限,需要對沙箱函數訪問進行訂制.以下是本人摸索出的一種方案:
先看下我的UI環境及lua嵌入架構:
1. C++層將必要的API注冊到lua層.但都是基于id的全局函數(考慮到效率及便捷),但是實際使用時再在lua層進行OO封裝,這和WINDOWS API及MFC的原理類似
2. C++層只提供4種原生控件: Button,Label, EditBox,MultiLineEditBox。其他的控件都是由這些組成。
3. 可以將整個系統分為內核模式和用戶模式。
內核模式:可以使用完整的API訪問及權限。
用戶模式:被沙箱保護,無法訪問一些危險的API,例如io
這里,我們使用lua_setfenv進行沙箱構建,首先我們必須將創建每個沙箱對應的table
// 放入沙箱名稱
lua_pushstring( mLua, "mysandbox" );
// 將一個table壓入棧
dotlua::table ts( mLua , false );
// 調用之前載入好的一個訂制沙箱環境的函數
gt.call<void>("SetupSandBox", ts );
// 將沙箱以mysandbox的key保存在注冊表中
lua_settable( mLua, LUA_REGISTRYINDEX );
之后使用lua_loadfile載入需要放進沙箱的代碼
// 這里將本沙箱對應的環境取出來
lua_pushstring( mLua, "mysandbox" );
lua_gettable( mLua, LUA_REGISTRYINDEX );
// 棧內的情形為
// -1 沙箱table
// -2 chunk function
// 這里必須調用chunk函數
lua_setfenv( mLua, -2 );
調用pcall執行代碼
這里的chunk函數,來源于lua_loadfile或者lua_loadbuffer,這2個函數將代碼讀入,但并不會執行,包括定義全局函數之類的操作。
沙箱訂制函數必須提前載入,這里發一個做參考
function SetupSandBox( e )
e._G = e
-- system lib
e.print = print
e.printf = printf
e.table = table
e.string = string
e.debug = debug
e.math = math
e.assert = assert
e.getmetatable = getmetatable
e.ipairs = ipairs
e.pairs = pairs
e.pcall = pcall
e.setmetatable = setmetatable
e.tostring = tostring
e.tonumber = tonumber
e.type = type
e.unpack = unpack
e.collectgarbage = collectgarbage
-- class
e.TREENODE = WIDGET_TREENODE
e.SERIALIZE = WIDGET_SERIALIZE
-- function
e.CreateWidget = CreateWidget
e._Inherit = _Inherit
end
只有被訂制的函數,才能被調用
擴展:
為了獲得完整的內核模式開發,但又擁有獨立的沙箱環境,可以使用setmetatable方式設置一個對全局table的引用,雖然速度慢了點。。
當然,可以支持一套可載入dll訂制權限,并注冊更多的api給自己的腳本用