魔兽的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给自己的脚本用