lua是擴展性非常良好的語言,雖然核心非常精簡,但是用戶可以依靠lua庫來實現大部分工作。除此之外,lua還可以通過與C函數相互調用來擴展程序功能。在C中嵌入lua腳本既可以讓用戶在不重新編譯代碼的情況下修改lua代碼更新程序,也可以給用戶提供一個自由定制的接口,這種方法遵循了機制與策略分離的原則。在lua中調用C函數可以提高程序的運行效率。lua與C的相互調用在工程中相當實用,本文就來講解lua與C相互調用的方法。
Lua與C相互調用的首要問題是如何交換數據,lua API使用了一個抽象的棧與C語言交換數據,提供了壓入元素,查詢元素和彈出元素等功能的API操作棧,這里可以查看lua5.2中每個函數的詳細文檔,棧中的元素可以通過索引訪問,從棧底向上是從1開始遞增的正整數,從棧頂向下是從-1開始遞減的負整數,棧的元素按照FIFO的規則進出。
一、 C調用lua
先通過一個簡單的例子了解C是怎么調用lua的,
//test.lua width = 10 height = 20 //test.c #include <stdio.h> #include <lua.h> #include <lualib.h> #include <lauxlib.h> int main() { lua_State *L = luaL_newstate(); luaL_openlibs(L); if(luaL_loadfile(L, "test.lua") || lua_pcall(L, 0,0,0)){ printf("error %s\n", lua_tostring(L,-1)); return -1; } lua_getglobal(L,"width"); lua_getglobal(L,"length"); printf("width = %d\n", lua_tointeger(L,-2)); printf("length = %d\n", lua_tointeger(L,-1)); lua_close(L); return 0; }
luaL_newstate創建一個新的lua_State,C和lua的所有操作都要依賴這個lua環境, luaL_openlibs將lualib.h中定義的lua標准庫加載到進lua_State。
luaL_loadfile從文件中加載lua代碼並編譯,編譯成功后的程序塊被壓入棧中,
lua_pcall會將程序塊彈出並在保護模式下解釋執行。代碼中調用lua_pcall就在lua_State中定義了 width和 length兩個全局變量。
lua_getglobal將全局變量的值壓入棧中,width先入棧,在-2的位置,length在棧頂。
除了變量,C代碼還可以直接調用lua中定義的函數
//test.lua function add(x, y) return x+y end //test.c #include <stdio.h> #include <lua.h> #include <lualib.h> #include <lauxlib.h> #include <math.h> int main() { lua_State *L = luaL_newstate(); luaL_openlibs(L); if(luaL_loadfile(L, "test.lua") || lua_pcall(L, 0,0,0)){ printf("error %s\n", lua_tostring(L,-1)); return -1; } lua_getglobal(L,"add"); lua_pushnumber(L, 10); lua_pushnumber(L, 20); if(lua_pcall(L, 2, 1, 0) != 0){ printf("error %s\n", lua_tostring(L,-1)); return -1; } double z = lua_tonumber(L, -1); printf("z = %f \n", z); lua_pop(L, 1); lua_close(L); return 0; }
lua_pcall(L, 2, 1, 0)表示,傳入兩個參數,期望得到一個返回值,0表示錯誤處理函數在棧中的索引值,壓入結果前會彈出函數和參數,所以z的索引是-1.
二、 lua調用C
lua可以將C函數注冊到lua中,C函數必須遵循統一的原型,這個原型定義在lua.h中,
typedef int (*) (lua_State *)
用C函數擴展lua時,一般將所有的C函數編譯成一個獨立的模塊,方便增加新的函數。
//mylib.c #include <stdio.h> #include <lua.h> #include <lualib.h> #include <lauxlib.h> #include <math.h> static int myadd(lua_State *L){ int a = luaL_checknumber(L, 1); int b = luaL_checknumber(L, 2); lua_pushnumber(L, a+b); return 1; } static const struct luaL_Reg mylib [] = { {"add", myadd}, {NULL, NULL} }; int luaopen_mylib(lua_State *L){ luaL_newlib(L, mylib); return 1; } //call.lua #!/usr/local/bin/lua lib=require "mylib" print(lib.add(1, 2))
每個被lua調用的C函數都有自己的私有棧,壓入參數的索引從1開始遞增,結果值也是直接壓入棧中,函數返回時會將壓入的參數全部刪除,只留下結果值。mylib[]聲明了模塊中所有C函數列表,每一項映射了C函數在lua中的命名,比如上面代碼中myadd函數在lua中用add表示,列表必須用{NULL, NULL}結束。 luaL_newlib在棧中創建一個table,將mylib數組中的C函數注冊進這個table中。 luaopen_mylib將這個table中的函數加載進lua環境中。
先將C代碼編譯成動態鏈接庫,
gcc -shared -fPIC -o mylib.so mylib.c -llua -lm -ldl
lua代碼中,require會查找 mylib.so,並調用該鏈接庫中的 luaopen_mylib,luaopen_的后綴必須與動態鏈接庫名字一樣,這是由require查找函數方式決定的。