Lua擴充套件

weixin_34075551發表於2016-08-16

lua作為配置檔案使用

-- win_conf.lua 定義視窗大小
width = 200
height = 300

使用LUA API分析這個檔案,並獲取width和height

void load(lua_State*L,const char*fname,int *w,int*h){
    if(luaL_loadfile(L,fname) ||lua_pcall(L,0,0,0))
        error(L,"cannot run config file:%s",lua_tostring(L,-1));
    lua_getglobal(L,"width");//push width
    lua_getglobal(L,"height");//-1 push height
    if(!lua_isnumber(L,-2))
        error(L,"width should be number");
    if(!lua_isnumber(L,-1))
        error(L,"height should be number");
    *w = lua_tointeger(L,-2);
    *h = lua_tointeger(L,-1);
}

table操作
lua 5.1提供了lua_getfiled和lua_setfield函式,lua5.1之前可以使用下面的方式,操作table
假設table在棧頂

-- win_conf.lua
BLUE = {r=0,g=0,b=1}
background = BLUE
...
lua_getglobal(L,"background");
if(!lua_istable(L,-1))
    error(L,"'background' is not a table")
red = getfield(L,"r")
...
/*假設table在棧頂*/
int getfield(lua_State*L,const char* key){
    int result;
    lua_pushstring(L,key);//push key to stack
    lua_gettable(L,-2);// get background[key] 同時刪除了KEY
    //lua 5.1提供了 lua_getfiled(L,-1,key),可以簡化上面兩行
    if(!lua_isnumber(L,-1))
        error(L,"...");
    result = (int)lua_tonumber(L,-1)*255;
    lua_pop(L,1);/*刪除數字*/
}
/*table在棧頂*/
void setfield(lua_State* L,const char* key,int value){
    lua_pushstring(L,key);
    lua_pushnumber(L,(double)value/255);
    lua_settable(L,-3) //table會自動變成棧頂
}

C呼叫Lua

-- f.lua
function f(x,y)
    return x+y
end
-- f.c
double f(double x,double y){
    double z;
    lua_getglobal(L,"f");//func in lua
    lua_pushnumber(L,x);//param 1
    lua_pushnumber(L,y);//param 1
    //呼叫(2個引數,1個結果)
    if(lua_pcall(L,2,1,0)){
        error(L,"error running function 'f':%s",lua_tostring(L,-1));//錯誤資訊
    }
    if(!lua_isnumber(L,-1)) error(L,"function 'f' must return a number");
    z = lua_tonumber(L,-1);
    lua_pop(L,1); /*彈出返回值(彈出1個元素)*/
    return z;
}

Lua呼叫C

-- l_sin.c
static in l_sin(lua_State* L){
    //double d = lua_tonumber(L,1);
    double d = luaL_checknumber(L,1);//檢查並轉換(輔助庫函式)
    lua_pushnumber(L,sin(d));
    return 1;
}
//所有註冊到Lua中函式原型都如下(lua.h中定義)
typedef int (*lua_CFunction)(lua_State*);
//這個函式無須再壓入結果前清空棧。在它返回後,Lua會自動清空棧中結果之下的內容
//lua使用了l_sin之前必須先註冊,使用lua_pushcfunction來進行註冊
lua_pushcfuction(L,l_sin);
lua_setglobal(L,"mysin");

C模組
當用C函式擴充套件Lua時,最好將程式碼設計為一個C模組。輔助庫提供了一個函式luaL_register,這個函式接受一些C函式及名稱,並將這些函式註冊到一個與模組同名的table中去。

//宣告一個陣列(包含模組中所有的函式和名稱)
static const struct luaL_Reg mylib[] = {
    {"dir",l_dir},
    {NULL,NULL} /*結尾*/
};
//註冊函式
int luaopen_mylib(lua_State* L){
    luaL_register(L,"mylib",mylib);
    return 1;
}
--lua中使用(自動載入mylib.so)
require("mylib")

編寫函式的技術:陣列操作,字串操作,狀態儲存
陣列操作:針對陣列型的table

//index為table在棧中的位置,key為陣列索引
//等價於 lus_pushnumber(L,key);lua_rawget(L,index);
void lua_rawgeti(lua_State* L,int index,int key);
//等價於 lus_pushnumber(L,key);lua_insert(L,-2);lua_rawset(L,index);
void lua_rawseti(lua_State* L,int index,int key);

字串操作

//把字串的子串([i,j])傳入Lua
lua_pushlstring(L,s+i,j-i+1)
//l_split ("hi,ho,three")->{"hi","ho","there"}
static int l_split(lua_State*L){
    const char* s = luaL_checkstring(L,1);//第一個引數
    const char* sep = luaL_checkstring(L,2);
    const char* e;
    int i = 1;
    lua_newtable(L);//函式結果
    while((e=strchr(s,*sep))!=NULL){
        lua_pushlstring(L,s,e-s);
        lua_rawseti(L,-2,i++);
        s=e+1;//跳過分隔符
    }
    lua_pushlstring(L,s);//壓入最後一個子串
    lua_rawseti(L,-2,i);
    return 1;
}

C函式中儲存狀態
登錄檔(registry)
登錄檔總是位於一個"偽索引"上,這個索引由LUA_REGISTRYINDEX定義,這個索引上有一個table。
登錄檔是一個普通的LUA table,可以用任何Lua值(除了nil)來索引它。Lua API中的大多數函式都接受偽索引,但是lua_remove,lua_insert這種操作棧本身的函式只能用普通索引。獲得登錄檔中key為"KEY"的值

lua_getfield(L,LUA_REGISTRYINDEX,"KEY");

所有的C模組共享一個登錄檔,請注意KEY不要衝突。可以使用UUID做KEY,定義特定的巨集來定義庫。
另外登錄檔中不可以使用數字做key,這種key被“引用系統”所保留。這個系統是有輔助系統中的一系列函式組成,它可以向一個table儲存value時,忽略如何建立一個唯一的key

int r = luaL_ref(L,LUA_REGISTR)
//r 即為引用,當需要使用一個C變數儲存一個指向Lua值得引用時,就需要使用引用系統
lua_rawgeti(L,LUA_REGESTRYINDEX,r);//將r關聯的值壓入棧
luaL_unref(L,LUA_REGSTRYINDEX,r);//釋放引用和其值,再呼叫luaL_ref會返回相同的引用
//引用系統將nil視為一種特殊情況。為一個nil值呼叫luaL_ref是,不會建立新的應用,返回一個常量LUA_REFNIL
lua_unref(L,LUA_REGSTRYINDEX,LUA_REFNIL);//無效果
lua_rawgeti(L,LUA_REGSTRYINDEX,LUA_REFNIL);//會壓入nil

C 函式環境(lua5.1特性)
環境Table的偽索引是LUA_ENVIRONINDEX。儘可能的使用環境表來代替登錄檔,出發需要在不同模組間共享資料。

int luaopen_foo(lua_State*L){
    lua_newtable(L);//建立環境table
    lua_replace(L,LUA_ENVIRONINDEX);//加入到環境表
    luaL_register(L,"mylib",funcList);//自動設為mylib的環境
    ...
}

upvalue:閉包值儲存,類似於C函式內靜態變數機制.
每當在Lua中建立函式時,可以將任意數量的upvalue與這個函式關聯。每個upvalue都可以儲存一個Lua值。以後,呼叫這個函式時,可以同偽索引來訪問這些upvalue。將這種C 函式與upvalue的關聯稱為closure.

static int counter(lua_State*L);
//每次呼叫返回一個新的賬號函式
int newCounter(lua_State*L){
    //一個upvalue
    lua_pushinteger(L,0);
    //建立一個新的closure,1表示upvalue的數量
    lua_pushcclosure(L,&counter,1);
    return 1;
}
//counter的定義
static int counter(lua_State*L){
    int val = lua_tointeger(L,lua_upvalueindex(1));
    lua_pushinteger(L,++va);
    lua_pushvalue(L,-1);//複製到棧頂
    lua_replace(L,lua_upvalueindex(1));//更新upvalue
    return 1;
}
//lua_upvalueindex(1)可以生成一個upvalue的偽索引,這個索引可以像其他棧索引一樣使用

高階用法(元組的C實現)

-- lua
x = tuple.new(10,"hi",{},3)
print(x(1)) -->10
print(x(2)) -->hi
print(x()) --> 10,hi table:0x...,3
-- lua_tuple.c
int t_tuple(lua_State*L){
    int op = luaL_opint(L,1,0);
    if(op == 0) { //無引數,顯示所有的值
        int i;
        for(i = 1;!lua_isnone(L,lua_upvalueindex(i));i++)
            lua_pushvalue(L,lua_upvalueindex(i));
        reutnr i-1;/*棧中的值*/
    }
    else{
        luaL_argcheck(L,0<op,1,"index out of range);//check index
        if(lua_isnone(L,lua_upvalueindex(op)))
            return 0;//無此欄位
        lua_pushvalue(L,lua_upvalueindex(op));
        return 1;
    }
}
int t_new(lua_State* L){
    lua_pushcclosure(L,t_tuple,lua_gettop(L));//返回閉包物件
    return 1;
}
static const struct luaL_Reg tuplelib [] = {
    {"new',t_new},
    {NULL,NULL}
};
int luaopen_tuple(lua_State*L){
    luaL_register(L,"tuple",tuplelib);
}

由於可以不適應引數,luaL_optint來獲取可選引數,此函式類似於luaL_checkint,但引數可以不存在,不存在,返回預設值。

相關文章