我是在Win7下,用Sublime Text + Cygwin開(kāi)發(fā)的,配置方法請(qǐng)參考《Sublime Text 3下C/C++開(kāi)發(fā)環(huán)境搭建》。
要注意的是:在Cygwin中安裝Lua解析器后,SublimeClang插件就能識(shí)別出可飲用的Lua頭文件了,因?yàn)锽uild System中我們已經(jīng)配置過(guò)"-I", "D:\\cygwin64\\usr\\include"
,而新安裝的Lua頭文件會(huì)添加到這里。但是,編譯時(shí)卻無(wú)法鏈接到頭文件對(duì)應(yīng)的動(dòng)態(tài)鏈接庫(kù)。此時(shí),還需要添加一個(gè)鏈接選項(xiàng)lua5.1,修改后的完整Build System配置文件如下:
{ "path": "D:\\cygwin64\\bin", "cmd": ["gcc", "-I", "D:\\cygwin64\\usr\\include", "${file}", "-o", "${file_path}/${file_base_name}", "-lm", "-llua5.1", "-Wall", "&", "start", "${file_path}/${file_base_name}.exe"], "file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$", "working_dir": "${file_path}", "selector": "source.c, source.c++", "shell": true, "variants": [ { "name": "Run::Cygwin", "cmd": [ "start", "${file_path}/${file_base_name}.exe"] } ]}
首先創(chuàng)建一個(gè)最簡(jiǎn)單的helloworld腳本hello.lua:
print("helloworld!")
下面詳細(xì)解釋一下從C代碼中如何執(zhí)行Lua腳本文件。不管是如何執(zhí)行,Lua腳本的執(zhí)行過(guò)程都分為以下五步。以下面一段代碼框架適用于后面所有示例程序:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <lua.h>#include <lualib.h>#include <lauxlib.h>void execute_from_script(char *filename);int main(int argc, char const *argv[]){ execute_from_script("hello.lua"); return 0;}/** * Execute from Lua script. * @param filename script file name */void execute_from_script(char *filename) { /* Lua interpreter */ lua_State *lua = lua_open(); /* Open Lua standard lib: io, string... */ luaL_openlibs(lua); /* Execute code in script */ if (luaL_dofile(lua, filename)) { fprintf(stderr, "Error when executing script: %s, %s\n", filename, lua_tostring(lua, -1)); /* Remove error handler */ lua_pop(lua, 1); return; } /* Release all resource used */ lua_close(lua);}
為了簡(jiǎn)化后面的示例代碼,對(duì)錯(cuò)誤處理統(tǒng)一封裝成bail()函數(shù):
void bail(lua_State *lua, char *msg, char *arg) { fprintf(stderr, "%s %s: %s\n", msg, arg, lua_tostring(lua, -1)); exit(-1);}
這一次我們不單獨(dú)創(chuàng)建一個(gè)Lua腳本文件,而是將Lua代碼嵌入到C代碼中直接執(zhí)行!
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <lua.h>#include <lualib.h>#include <lauxlib.h>void execute_from_code(char *code);int main(int argc, char const *argv[]){ execute_from_code("print(\"hello world!!!\")"); return 0;}/** * Execute Lua command directly. * @param code Lua command */void execute_from_code(char *code){ lua_State *lua = lua_open(); luaL_openlibs(lua); // Load & compile command and execute immediately if (luaL_loadbuffer(lua, code, strlen(code), "line") || lua_pcall(lua, 0, 0, 0)) bail(lua, "Error when executing code", code); lua_close(lua);}
在這個(gè)例子中,我們執(zhí)行腳本文件中的函數(shù),而不是直接一段Lua代碼。在C代碼中調(diào)用Lua函數(shù)時(shí),如何傳入?yún)?shù)值和獲取返回值是學(xué)習(xí)的重點(diǎn):
Lua腳本如下:
function say_hello(name) return "Hello, " .. name .. "!"end
C示例代碼如下。注意加載并編譯函數(shù)后,lua_getglobal(lua, funcname)是關(guān)鍵,這一句會(huì)在全局中查找函數(shù),并將函數(shù)的指針壓到棧上。這樣后面調(diào)用lua_pcall()時(shí)才不會(huì)報(bào)錯(cuò):
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <lua.h>#include <lualib.h>#include <lauxlib.h>void execute_function_from_script(char *filename, char *funcname, char *arg);void execute_function_from_code(char *code);int main(int argc, char const *argv[]){ execute_function_from_script("hellofunc.lua", "say_hello", "cdai008"); return 0;}/** * Execute Lua function from script * @param filename script file name * @param funcname function name * @param arg arguments */void execute_function_from_script(char *filename, char *funcname, char *arg){ lua_State *lua = lua_open(); luaL_openlibs(lua); /* 1.Load and compile function code */ if (luaL_loadfile(lua, filename) || lua_pcall(lua, 0, 0, 0)) bail(lua, "Error when loading/compiling function", filename); /* 2.Prepare function and arguments */ lua_getglobal(lua, funcname); lua_pushstring(lua, arg); /* 3.Do the call (1 arg, 1 result) */ if (lua_pcall(lua, 1, 1, 0) != 0) bail(lua, "Error when calling function", funcname); /* 4.Retrieve result */ char *ret = lua_tostring(lua, -1); printf("Result: %s\n", ret); lua_pop(lua, 1); lua_close(lua);}
首先看幾條關(guān)于Lua棧的事實(shí):
Lua棧最讓人困惑的就是棧操作函數(shù)中的下標(biāo)參數(shù),有的用正數(shù)有的用負(fù)數(shù)。Lua官方文檔中解釋說(shuō):lua_gettop()返回棧中元素個(gè)數(shù),也就是棧頂元素的下標(biāo)。負(fù)數(shù)下標(biāo)negative_i = positive_i - (gettop() + 1)
。這一點(diǎn)與Redis的List數(shù)據(jù)結(jié)構(gòu)很像,例如當(dāng)查看List中所有元素時(shí),為了方便我們會(huì)用lrange lista 0 -1
,而不會(huì)將-1寫(xiě)成真的去求一下末尾元素的下標(biāo)。
下面看一段示例代碼,加深一下理解:
static void stackDump(lua_State *L){ int i; int top = lua_gettop(L); printf("---- Begin Stack %i ----\n", top); for (i = 1; i <= top; i++) { int t = lua_type(L, i); int ni = i - (top + 1); switch (t) { case LUA_TSTRING: /* strings */ printf("%i -- (%i) ---- '%s'", i, ni, lua_tostring(L, i)); break; case LUA_TBOOLEAN: /* booleans */ printf("%i -- (%i) ---- %s", i, ni, lua_toboolean(L, i) ? "true" : "false"); break; case LUA_TNUMBER: /* numbers */ printf("%i -- (%i) ---- %g", i, ni, lua_tonumber(L, i)); break; default: /* other values */ printf("%i -- (%i) ---- '%s'", i, ni, lua_typename(L, t)); break; } printf("\n"); } printf("---- End Stack ----\n\n");}void test_lua_stack_order(){ lua_State *L = lua_open(); lua_pushstring(L, "hi there"); lua_pushnumber(L, 17); lua_pushboolean(L, 1); lua_pushstring(L, "foobar"); stackDump(L); /* ---- Begin Stack 4 ---- 1 -- (-4) ---- 'hi there' 2 -- (-3) ---- 17 3 -- (-2) ---- true 4 -- (-1) ---- 'foobar' ---- End Stack ---- */ lua_pushvalue(L, -4); stackDump(L); /* ---- Begin Stack 5 ---- 1 -- (-5) ---- 'hi there' 2 -- (-4) ---- 17 3 -- (-3) ---- true 4 -- (-2) ---- 'foobar' 5 -- (-1) ---- 'hi there' ---- End Stack ---- */ lua_replace(L, 3); stackDump(L); /* ---- Begin Stack 4 ---- 1 -- (-4) ---- 'hi there' 2 -- (-3) ---- 17 3 -- (-2) ---- 'hi there' 4 -- (-1) ---- 'foobar' ---- End Stack ---- */ lua_settop(L, 6); stackDump(L); /* ---- Begin Stack 6 ---- 1 -- (-6) ---- 'hi there' 2 -- (-5) ---- 17 3 -- (-4) ---- 'hi there' 4 -- (-3) ---- 'foobar' 5 -- (-2) ---- 'nil' 6 -- (-1) ---- 'nil' ---- End Stack ---- */ lua_remove(L, -3); stackDump(L); /* ---- Begin Stack 5 ---- 1 -- (-5) ---- 'hi there' 2 -- (-4) ---- 17 3 -- (-3) ---- 'hi there' 4 -- (-2) ---- 'nil' 5 -- (-1) ---- 'nil' ---- End Stack ---- */ lua_settop(L, -5); stackDump(L); /* ---- Begin Stack 1 ---- 1 -- (-1) ---- 'hi there' ---- End Stack ---- */ lua_close(L);}
注意棧操作函數(shù)中參數(shù)的意義:
table在棧上的創(chuàng)建方式有些tricky。首先lua_newtable()會(huì)壓入“table”到棧頂,然后依次壓入key-value鍵值對(duì),然后調(diào)用lua_settable()會(huì)使鍵值對(duì)被彈出,形成真正的table。此時(shí),棧上又只剩字符串“table”了。數(shù)據(jù)跑哪里去了?此時(shí)要使用lua_next()函數(shù)對(duì)table進(jìn)行遍歷:
lua_newtable(L); lua_pushnumber(L, 1); lua_pushstring(L, "allen"); stackDump(L); lua_settable(L, -3); stackDump(L); /* ---- Begin Stack 4 ---- 1 -- (-4) ---- 'hi there' 2 -- (-3) ---- 'table' 3 -- (-2) ---- 1 4 -- (-1) ---- 'allen' ---- End Stack ---- ---- Begin Stack 2 ---- 1 -- (-2) ---- 'hi there' 2 -- (-1) ---- 'table' ---- End Stack ---- */ lua_pushstring(L, "hank"); /* set table at index -2, table["2"]="hank" */ lua_setfield(L, -2, "2"); lua_pushstring(L, "carter"); /* set table at index -2, table[3]="carter" */ lua_rawseti(L, -2, 3); /* Push nil as first key */ lua_pushnil(L); /* Pops a key from the stack, and pushes a key–value pair from the table at the given index (the "next" pair after the given key) */ while(lua_next(L, -2) != 0) { /* uses 'key' (at index -2) and 'value' (at index -1) */ int t = lua_type(L, -2); switch (t) { case LUA_TSTRING: printf("table['%s']='%s'\n", lua_tostring(L, -2), lua_tostring(L, -1)); break; case LUA_TNUMBER: printf("table[%g]='%s'\n", lua_tonumber(L, -2), lua_tostring(L, -1)); break; } /* removes 'value'; keeps 'key' for next iteration */ lua_pop(L, 1); }
答案就在lua_next()中。我們?cè)跅m敺胖昧藅able和一個(gè)nil,然后調(diào)用lua_next(),并訪問(wèn)key和value后移除棧頂?shù)膙alue而保留key,這樣就能依次迭代整個(gè)table。注意lua_settable()、lua_setfield()和lua_rawseti()三個(gè)函數(shù)的用法。
終于到了本文的重點(diǎn),模擬一下Redis是如何執(zhí)行Lua腳本的,分為以下幾步:
核心部分的C示例代碼:
/** * Showcase of how to deal with return values * @param cmd Lua command */void execute_function_from_code(char *cmd){ // 1.Prepare Lua execution enviornment lua_State *lua = lua_open(); luaL_openlibs(lua); // 2.Create function dynamically char funcdef[100], *funcname = "fun1"; memset(funcdef, 0, sizeof(funcdef)); strcat(funcdef, "function "); strcat(funcdef, funcname); strcat(funcdef, "() "); strcat(funcdef, cmd); strcat(funcdef, " end"); printf("Code: %s\n", funcdef); // 3.Compile code in buffer and push onto stack if(luaL_loadbuffer(lua, funcdef, strlen(funcdef), "@user_script") || lua_pcall(lua, 0, 0, 0)) bail(lua, "Error when loading/compiling function", funcname); // 4.Prepare function and global table 'redis' lua_getglobal(lua, funcname); lua_newtable(lua); lua_pushstring(lua,"call"); lua_pushcfunction(lua, luaRedisCallCommand); lua_settable(lua, -3); lua_setglobal(lua, "redis"); // 5.Execute Lua function if (lua_pcall(lua, 0, 0, -2)) bail(lua, "Error when calling function", funcname); // 6.Cleanup lua_close(lua);}
測(cè)試main函數(shù)和回調(diào)函數(shù)。main函數(shù)測(cè)試一下在Lua代碼中執(zhí)行redis.call(“set”, “foo”, “bar”),而回調(diào)函數(shù)luaRedisCallCommand()則簡(jiǎn)單地打印一下入?yún)ⅲ?/p>
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <lua.h>#include <lualib.h>#include <lauxlib.h>void execute_function_from_code(char *code);int main(int argc, char const *argv[]){ execute_function_from_code("redis.call(\"set\", \"foo\", \"bar\")"); return 0;}int luaRedisCallCommand(lua_State *lua) { int i, argc = lua_gettop(lua); for (i = 0; i < argc; i++) { char *obj_s; size_t obj_len; obj_s = (char *)lua_tolstring(lua, i + 1, &obj_len); printf("Argument[%d]=%s\n", i, obj_s); } return 1;}
這里只是一個(gè)演示的小例子,詳細(xì)介紹請(qǐng)參考《Redis設(shè)計(jì)與實(shí)現(xiàn)》。但Lua腳本這一章是免費(fèi)Web版里沒(méi)有的,得看實(shí)體書(shū)。真正的Redis代碼流程要復(fù)雜得多,包括:
- 執(zhí)行前:Lua環(huán)境里某些東西只初始化一次,準(zhǔn)備KEYS和ARGV兩個(gè)全局變量,設(shè)置超時(shí)控制hook。
- 執(zhí)行后:定時(shí)Lua GC回收資源,用字典緩存已經(jīng)執(zhí)行過(guò)的Lua腳本。
聯(lián)系客服