Lua與C的交互

Lua 與 C 的交互

 

Lua是一個嵌入式的語言,它不只能夠是一個獨立運行的程序,也能夠是一個用來嵌入其它應用的程序庫。html

C API是一個C代碼與Lua進行交互的函數集,它由如下幾部分構成:程序員

一、  讀寫Lua全局變量的函數;數組

二、  調用Lua函數的函數;閉包

三、  運行Lua代碼片斷的函數;函數

四、  註冊C函數後能夠在Lua中被調用的函數;ui

 

在C和LUA之間交互的關鍵在於一個虛擬棧(virtual stack),數據交互經過棧進行。操做數據時,首先將數據拷貝到棧上,而後獲取數據,棧中的每一個數據經過索引值進行定位,索引值爲正時表示相對於棧底的偏移索引,索引值爲負時表示相對於棧頂的偏移索引。索引值以1或 -1起始值,所以棧頂索引值永遠爲-1, 棧底索引值永遠爲1 。 "棧"至關於數據在Lua和C之間的中轉地,每種數據都有相應的存取接口 。lua

另外,還可使用棧來保存臨時變量。棧的使用解決了C和LUA之間兩個不協調的問題:spa

一、  Lua使用自動垃圾收集機制,而C要求顯式的分配和釋放內存;操作系統

二、  Lua使用動態數據類型,而C使用靜態類型;線程

 

特別注意的是:

一、每當Lua調用C函數時,C函數會使用一個局部棧這個局部棧與以前的棧,以及其它正在調用的C函數使用的棧都是相互獨立的。Lua和C就使用這個局部的棧進行數據交互。

二、當Lua調用C時,棧至少包含LUA_MINSTACK(20)個位置,程序員也可使用lua_checkstack函數來增長棧的大小。

三、使用僞索引(Pseudo-Indices)來表示一些不在棧中的數據,好比thread環境、C函數環境、registry、C閉包的upvalues。

  • thread環境(全局變量也在這裏),使用僞索引 LUA_GLOBALSINDEX
  • 運行中的C函數環境,使用僞索引 LUA_ENVIRONINDEX;
  • Registry,使用僞索引 LUA_REGISTRYINDEX;
  • C閉包的upvalues,可使用lua_upvalueindex(n)來訪問第n個upvalue;

 

關於Registry:

在C裏面若是函數要保存持久狀態,只能依靠全局或static變量,但這樣C API庫就沒法爲多個LuaState狀態同時提供服務(就相似於帶有static變量的C函數是不可重入的)。爲此Lua提供了一個名爲Registry的預約義table,容許C API往Registry裏面存儲數據。

 

關於References:

Reference其實就是在一個指定的表t中保存一次lua的數據對象,Refenence自己其實就是表t的索引子,簡稱RefIndex,當RefIndex做爲Refenence時,t[RefIndex]其實就是用戶要求引用的lua數據對象。當RefIndex被luaL_unref()回收時,t每個被回收的RefIndex構成一個單向鏈表: t[Refindex] = Refindex0, t[Refindex0] = Refindex1, t[Refindex1] = Refindex2 ... t[0] = Refindex。

 

注意,t[0]始終是指向了空閒鏈表的頭部。每次調用luaL_ref()且回收鏈表爲空時,都會產生一個新的Reference,每次調用luaL_unref()都會銷燬一個指定的Reference存入空閒鏈表中。

 

 

 


C API接口 

 

類型聲明

typedef double lua_Number;
typedef ptrdiff_t lua_Integer;

 

初始化lua狀態機

lua_State* lua_open();

lua_State *lua_newstate (lua_Alloc f, void *ud);

lua_newstate 建立一個新的、獨立的Lua狀態機,若是由於內存不足致使建立失敗,返回NULL。參數f 指定內存分配函數,參數ud是傳給f 函數的指針。

lua_open 沒有指定內存分配函數的功能,不建議再使用。

注意:lua_State表示的一個Lua程序的執行狀態,它表明一個新的線程(注意是指Lua中的thread類型,不是指操做系統中的線程),每一個thread擁有獨立的數據棧以及函數調用鏈,還有獨立的調試鉤子和錯誤處理方法。

 

銷燬lua狀態機

void lua_close(lua_State *L);

銷燬Lua狀態機的全部對象,回收分配的內存。

 

加載lua庫

void luaL_openlibs(lua_State *L);                   

void luaopen_base(lua_State *L);

void luaopen_package(lua_State *L);

void luaopen_string(lua_State *L);

void luaopen_io(lua_State *L);

void luaopen_table(lua_State *L);

void luaopen_math(lua_State *L);

luaL_openlibs 在給定的Lua狀態機中打開全部的標準Lua庫;

 

編譯/加載 lua代碼

int luaL_dofile(lua_State *L, char *lua_script);

int luaL_dostring (lua_State *L, const char *str);

int lua_load (lua_State *L, lua_Reader reader, void *data,const char *chunkname);

int luaL_loadbuffer (lua_State *L, const char *buff, size_t sz, const char *name);

int luaL_loadfile (lua_State *L, const char *filename);

int luaL_loadstring (lua_State *L, const char *s);

luaL_dofile 加載並執行給定lua文件,成功返回0,錯誤返回1;

luaL_dostring 加載並執行給定string,成功返回0,錯誤返回1;

lua_load 加載一段chunk(但並不執行它),並將編譯後的代碼做爲一個函數壓入堆棧,若是發生錯誤,則將錯誤消息壓入堆棧;

luaL_loadbuffer 從一個buffer中加載chunk;

luaL_loadfile從文件加載chunk;

luaL_loadstring從字符串加載chunk;

 

函數參數檢查

void luaL_checkany (lua_State *L, int narg);
int luaL_checkint (lua_State *L, int narg);
lua_Integer luaL_checkinteger (lua_State *L, int narg);
long luaL_checklong (lua_State *L, int narg);
const char *luaL_checklstring (lua_State *L, int narg, size_t *l);
lua_Number luaL_checknumber (lua_State *L, int narg);
int luaL_checkoption (lua_State *L, int narg, const char *def, const char *const lst[]);
const char *luaL_checkstring (lua_State *L, int narg);
void luaL_checktype (lua_State *L, int narg, int t);
void *luaL_checkudata (lua_State *L, int narg, const char *tname);

luaL_checkany 檢查函數有多少個(由narg指定)參數;

luaL_checktype 檢查函數第narg個參數是否爲指定類型;

luaL_checkudata 檢查函數第narg個參數是否爲name類型的userdata;
luaL_checkint/luaL_checkinterger 等接口很是相似:

以luaL_checkint爲例,它是檢查函數的第narg個參數類型是否number型,而且返回這個number型參數;

luaL_checkoption 檢查函數第narg個參數是否位字符串類型,而且在lst[](字符串數組)中搜索這個字符串,最後返回匹配的數組下標,如未能匹配,引起一個錯誤。若是參數def非空,當narg參數不存在或爲nil時,就是要def做爲搜索串。這個函數的做用是將字符串映射爲C的enum類型。

 

 

table操做 

void lua_createtable (lua_State *L, int narr, int nrec);

void lua_newtable (lua_State *L);
void lua_settable (lua_State *L, int index);
void lua_gettable (lua_State *L, int index);

lua_createtable 建立一個空table並壓入堆棧,它會爲narr個數組風格元素,和nrec個記錄風格元素預分配內存空間。

lua_newtable 建立一個空table,並壓入stack,等價於 lua_createtable(L, 0, 0);
lua_settable 至關於t[k]=v 操做,其中值t由參數index指定,v是棧頂,k是棧頂下一個元素;這個函數會將key和value都彈出棧;

lua_gettable 將t[k]壓入堆棧,由參數index指定操做的table,k是棧頂元素。這個函數會彈出棧頂的key,並由t[k]代替;

 

metatable操做

int luaL_newmetatable (lua_State *L, const char *tname);

void luaL_getmetatable (lua_State *L, const char *tname);

int lua_getmetatable (lua_State *L, int index);

int lua_setmetatable (lua_State *L, int index);

int luaL_getmetafield (lua_State *L, int obj, const char *e);

luaL_newmetatable 在Registry中建立一個key爲tname的metatable,並返回1;若是Registry 已經有tname這個key,則返回0;這兩種狀況都會將metatable壓入堆棧;

luaL_getmetatable 將Registry中key爲tname的metatable壓入堆棧;

luaL_getmetatable 將index處的元表壓入堆棧;

lua_setmetatable 彈出棧頂,並將它做爲由index指定元素的元表;

luaL_getmetafield 將索引obj處的元素的元表的e字段壓入堆棧;

 

 

stack操做

void lua_pushboolean (lua_State *L, int b);
void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n);
void lua_pushcfunction (lua_State *L, lua_CFunction f);
const char *lua_pushfstring (lua_State *L, const char *fmt, ...);
void lua_pushinteger (lua_State *L, lua_Integer n);
void lua_pushlightuserdata (lua_State *L, void *p);
void lua_pushliteral (lua_State *L, const char *s);
void lua_pushlstring (lua_State *L, const char *s, size_t len);
void lua_pushnil (lua_State *L);
void lua_pushstring (lua_State *L, const char *s);

lua_pushfstring 將一個格式化字符串壓入堆棧,並返回指向這個字符串的指針;

lua_pushlstring 將一個指定大小的字符串壓入堆棧;

lua_pushvalue 將棧中指定索引的元素複製一份到棧頂;

值得注意的是,向棧中壓入一個元素時,應該確保棧中具備足夠的空間,能夠調用lua_checkstack來檢測是否有足夠的空間。

實質上這些API是把C語言裏面的值封裝成Lua類型的值壓入棧中的,對於那些須要垃圾回收的元素(如string、full userdata),在壓入棧時,都會在Lua(也就是Lua虛擬機中)生成一個副本,今後不會再依賴於原來的C值。例如lua_pushstring 向棧中壓入一個以'\0'結尾的字符串,在C中調用這個函數後,能夠任意修改或釋放這個字符串,也不會出現問題。

 

 

void lua_pop(lua_State *L, int n);

int lua_gettop (lua_State *L);

void lua_concat (lua_State *L, int n);

void lua_getfield (lua_State *L, int index, const char *k);

void lua_setfield (lua_State *L, int index, const char *k);

void lua_getglobal(lua_State *L, char *name);

void lua_setglobal (lua_State *L, const char *name);

void lua_insert (lua_State *L, int index);

void lua_remove (lua_State *L, int index);

void lua_replace (lua_State *L, int index);

int lua_next (lua_State *L, int index);

size_t lua_objlen (lua_State *L, int index);

void luaL_checkstack (lua_State *L, int sz, const char *msg);

 lua_pop 從棧中彈出n個元素;

lua_gettop 返回棧頂元素的索引(也即元素個數);

lua_concat 將棧頂開始的n個元素鏈接起來,並將它們出棧,而後將結果入棧;

lua_getfield 將t[k]壓入堆棧,t由參數index指定在棧中的位置;

lua_setfield 至關於t[k]=v,t由參數index指定在棧中的位置,v是棧頂元素,改函數會將棧頂的value出棧;

lua_getglobal(L,s) 等價於 lua_getfield(L, LUA_GLOBALSINDEX, s),注意:棧中LUA_GLOBALSINDEX索引位置處是當前Lua狀態機的全局變量環境

lua_setglobal(L,s) 等價於 lua_setfield(L, LUA_GLOBALSINDEX, s);

lua_insert 移動棧頂元素到index指定的位置;

lua_remove 移除index處的元素,index之上的元素均下移一個位置;

lua_replace 將棧頂元素移到指定位置,並取代原來的元素,原先的棧頂元素彈出;

lua_next 彈出一個key,而後將t[key]入棧,t是參數index處的table;在利用lua_next遍歷棧中的table時,對key使用lua_tolstring尤爲須要注意,除非知道key都是string類型。

lua_objlen 返回index處元素的長度,對string,返回字符串長度;對table,返回"#"運算符的結果;對userdata,返回內存大小;其它類型返回0;

luaL_checkstack 增長棧大小(新增sz個元素的空間),若是grow失敗,引起一個錯誤,msg參數傳遞錯誤消息。

 

int lua_isboolean (lua_State *L, int index);
int lua_iscfunction (lua_State *L, int index);
int lua_isfunction (lua_State *L, int index);
int lua_islightuserdata (lua_State *L, int index);
int lua_isnil (lua_State *L, int index);
int lua_isnone (lua_State *L, int index);
int lua_isnoneornil (lua_State *L, int index);
int lua_isnumber (lua_State *L, int index);
int lua_isstring (lua_State *L, int index);
int lua_istable (lua_State *L, int index);
int lua_isthread (lua_State *L, int index);
int lua_isuserdata (lua_State *L, int index);

這些都是棧中指定元素類型檢查的接口;

 

 

函數調用

void lua_call (lua_State *L, int nargs, int nresults);
int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc);

int lua_cpcall (lua_State *L, lua_CFunction func, void *ud);

lua_call 調用函數,參數nargs指定函數參數個數,參數nresults指定返回值個數。首先,被調函數必須在棧中;其次,函數參數必須是按從左往右的順序入棧的;函數調用時,全部函數參數都會彈出堆棧。函數返回時,其返回值入棧(第一個返回最最早入棧)。

lua_pcall 以保護模式調用函數,若是發生錯誤,捕捉它,並將錯誤消息壓入棧,而後返回錯誤碼。

lua_cpcall 以保護模式調用C函數func,參數ud指針指向一個用戶自定義數據。

 

錯誤處理

int luaL_error (lua_State *L, const char *fmt, ...);

引起一個錯誤。

 

類型轉換

int lua_toboolean (lua_State *L, int index);
lua_CFunction lua_tocfunction (lua_State *L, int index);
lua_Integer lua_tointeger (lua_State *L, int index);
const char *lua_tolstring (lua_State *L, int index, size_t *len);
lua_Number lua_tonumber (lua_State *L, int index);
const void *lua_topointer (lua_State *L, int index);
const char *lua_tostring (lua_State *L, int index);
lua_State *lua_tothread (lua_State *L, int index);
void *lua_touserdata (lua_State *L, int index);

lua_toboolean 將給定index索引處的元素轉換爲bool類型(0或1);
lua_tocfunction 將給定index索引處的元素轉換爲C函數;
lua_tointeger 將給定index索引處的元素轉換爲int類型;
lua_tolstring 將給定index索引處的元素轉換爲char*類型,若是len不爲空,同時還設置len爲字符串長度;該函數返回的指針,指向的是Lua虛擬機內部的字符串,這個字符串是以'\0'結尾的,但字符串中間也可能包含值爲0的字符。
lua_tostring 等價於參數len=NULL時的lua_tolstring;
lua_tonumber 將給定index索引處的元素轉換爲double類型;
lua_topointer 將給定index索引處的元素轉換爲void*類型;
lua_tothread 將給定index索引處的元素轉換爲lua_State*類型(即一個thread);
lua_touserdata 返回給定index索引處的userdata對應的內存地址;

 

 

thread 操做

lua_State *lua_newthread (lua_State *L);

int lua_resume (lua_State *L, int narg);

int lua_yield (lua_State *L, int nresults);

 

lua_newthread 建立一個新的thread,而後壓入堆棧,並返回一個lua_State*指針表示建立的新thread。
新建立的thread與當前thread共享一個全局環境。沒有銷燬thread的顯式調用,它由垃圾收集器負責回收。

 

 

 


 

C 調用 Lua代碼

 

一個簡單的例子:

// test.c

#include        <stdio.h>
#include        "lua.h"
#include        "lualib.h"
#include        "lauxlib.h"

/*the lua interpreter*/
lua_State* L;

int luaadd(int x, int y)
{
    int sum;

    lua_getglobal(L,"add");

    lua_pushnumber(L, x); 

    lua_pushnumber(L, y); 

    lua_call(L, 2, 1); 

    sum = (int)lua_tonumber(L, -1);

    lua_pop(L,1);

    return sum;
}

int main(int argc, char *argv[])
{
    int sum;

    L = lua_open();

    luaL_openlibs(L);

    luaL_dofile(L, "add.lua");

    sum = luaadd(10, 15);

    printf("The sum is %d \n",sum);

    lua_close(L);

    return 0;
}

注意:在C代碼裏面咱們要引入三個頭文件lua.h,lauxlib.h和lualib.h:

lua.h中定義的是最基礎的API;

lauxlib.h中的函數都以luaL_開頭,他們是比基礎API更抽象的函數;

lualib.h中定義了打開標準類庫的API,好比luaL_openlibs(L)。

程序開始用luaL_open()函數建立一個lua_State。lua_State中保存了Lua運行時的全部的狀態信息(好比變量的值等),而且全部的Lua的C的API都有一個lua_newstate指針的參數。luaL_open函數會建立一個全新的Lua運行時狀態,其中沒有任何預先定義好的函數(包括最基本的print函數)。若是須要試用標準類庫的話,只要調用luaL_openlibs(L)函數就打開標準類庫就能夠了。標準類庫被分別封裝在不一樣的包中,當你須要使用的時候再引入到代碼中,這樣作的好處是可使Lua儘量的小(嵌入式語言必需要小),從而能夠方便嵌入到其餘語言中去。當Lua運行時狀態和標準類庫都準備完成後,就能夠調用luaL_dofile(L,"test.lua")函數來執行Lua腳本。運行結束後,須要調用lua_close(L)來關閉Lua運行時狀態。

 

被調用的test.lua文件:

-- test.lua

function add(x,y)
   return x + y 
end 

 

編譯命令,實際命令須要根據本身的lua環境調整

gcc test.c -o test -llua-5.1 -I /usr/local/include/ 

 

執行./test命令的輸出:

The sum is 25 

 

 

另一個操做table的例子:

int main()
{ lua_State
*L = luaL_newstate(); luaL_openlibs(L); lua_newtable(L); lua_pushstring(L, "i am key"); lua_pushstring(L, "i am value"); lua_settable(L, -3); lua_pushstring(L, "i am key"); lua_gettable(L, -2); const char *str = lua_tostring(L, -1); printf("%s", str); lua_close(L); return 0; }

 

 

 


 

Lua 調用 C 函數

當Lua調用C函數時,也使用了一個與C語言調用Lua時相同的棧。C函數從棧中獲取函數參數,並將結果壓入棧中。爲了在棧中將函數結果與其餘值區分開,C函數還應返回其壓入棧中的結果個數。
棧不是一個全局性的結構,每一個函數都有本身的局部私有棧。當Lua調用一個C函數時,第一個參數老是這個局部棧的索引1。

 

對於可被Lua調用的C函數而言,其接口必須遵循Lua要求的形式,即
typedef int (*lua_CFunction)(lua_State* L);

接收一個參數Lua_State*,即Lua的狀態,返回值表示壓入棧中的結果個數。

 

把要調用的C 函數註冊到lua狀態機中:
void lua_register (lua_State *L, const char *name, lua_CFunction f);

lua_register 是一個宏:#define lua_register(L,n,f) (lua_pushcfunction(L, f), lua_setglobal(L, n))

其中,參數name是lua中的函數名,f 是C中的函數。

從宏定義能夠看出,這個函數的做用是把C函數壓入堆棧,並在全局環境中設置Lua函數名;

 

Lua在require模塊的時候,除了搜索 "*.lua" 文件,也會搜索 "*.so" 文件,也就是說,Lua支持加載C/C++語言編譯的動態庫文件。

// foo.c

#include        "lua.h"
#include        "lualib.h"
#include        "lauxlib.h"

static int add(lua_State *L) 
{
    int n = lua_gettop(L);      /* number of arguments */

    lua_Number sum = 0;

    int i;

    for (i = 1; i <= n; i++) {
        if (!lua_isnumber(L, i)) {
            lua_pushstring(L, "incorrect argument");
            lua_error(L);
        }   

        sum += lua_tonumber(L, i); 
    }   

    lua_pushnumber(L, sum/n);    /* first result */
    lua_pushnumber(L, sum);      /* second result */

    return 2;                    /* number of results */
}

int luaopen_foo(lua_State *L) 
{
    lua_register(L, "add", add);
    return 1;
}

注意:luaopen_MODULE 函數的後綴是有規則的,必須是模塊名稱,而lua_register的第二個參數是供Lua代碼調用的函數名稱,第三個參數是當前C函數;

OK,如今把C代碼編譯成動態庫:

gcc foo.c -shared -fPIC -o foo.so  -llua-5.1 -I /usr/local/include/ 

 

而後在lua代碼裏面能夠加載該模塊:

require("foo")

這條命令會自動加載foo.so庫,並調用其中的 luaopen_foo 函數,而後執行裏面的函數註冊代碼,這樣接下來就能直接使用那些註冊到Lua狀態機裏面的C函數了。

print(add(14,25,15))

輸出結果:

18      54

 

 

 

 

 

參考文檔:

http://www.lua.org/manual/5.1/manual.html 

相關文章
相關標籤/搜索