C/C++與lua互調函式的方法
1,在lua指令碼中呼叫C/C++程式碼中的函式
在C++中定義函式時必須以lua_State為引數, 以int為返回值才能被Lua所呼叫。
/*
typedef int (*lua_CFunction) (lua_State*L);
C 函式的型別。
為了正確的和 Lua 通訊,C 函式必須使用下列定義了引數以及返回值傳遞方法的協議: C 函式通過 Lua 中的堆疊來接受引數,引數以正序入棧(第一個引數首先入棧)。因此,當函式開始的時候, lua_gettop(L) 可以返回函式收到的引數個數。第一個引數(如果有的話)在索引 1 的地方,而最後一個引數在索引 lua_gettop(L) 處。當需要向 Lua 返回值的時候,C 函式只需要把它們以正序壓到堆疊上(第一個返回值最先壓入),然後返回這些返回值的個數。在這些返回值之下的,堆疊上的東西都會被 Lua 丟掉。和 Lua 函式一樣,從 Lua 中呼叫 C 函式也可以有很多返回值。
*/
下面這個例子中的函式將接收若干數字引數,並返回它們的平均數與和:
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#include <lua.h> // lua是用純c語言寫的
#include <lualib.h>
#include <lauxlib.h>
#ifdef __cplusplus
}
#endif /* __cplusplus */
int c_average(lua_State* L)
{
int n = lua_gettop(L); /* 返回棧頂元素的索引。因為索引是從1開始編號的(1表示棧底,-1表示棧頂),所以這個結果等於堆疊上的元素個數(返回0表示堆疊為空)。這裡棧中元素的個數就是傳入的引數個數 */
double sum = 0;
int i;
for (i = 1; i <= n; i++)
{
if (!lua_isnumber(L, i))
{
lua_pushstring(L, "Incorrect argument to 'average'"); // 將錯誤資訊壓入棧中
lua_error(L); // 丟擲棧頂的錯誤
/*
int lua_error (lua_State *L);
產生一個 Lua 錯誤。錯誤資訊(實際上可以是任何型別的 Lua 值)必須被置入棧頂。這個函式會做一次長跳轉,它不會再返回。
*/
}
sum += lua_tonumber(L, i); // lua_tonumber 將棧中指定index的值轉換成數值型別的值,注意並不會從棧中彈出這個值
}
double avg = sum / n;
lua_pushnumber(L, avg); // 將avg壓入棧中,第1個返回值是avg
lua_pushnumber(L, sum); // 將sum壓入棧中,第2個返回值是sum
return 2; /* return the number of results,該函式有2個返回值,即上面入棧的avg和sum */
}
int main(int argc, char* argv[])
{
lua_State* L = luaL_newstate(); // 建立一個新的獨立的狀態機
/* register our function,告訴lua指令碼其中呼叫的average函式(lua中的變數名)對應的是一個叫c_average的c語言函式 */
lua_register(L, "average", c_average);
/*
void lua_register (lua_State *L,
const char *name,
lua_CFunction f);
把 C 函式 f 設到全域性變數 name 中。它通過一個巨集定義:
#define lua_register(L,n,f) \
(lua_pushcfunction(L, f), lua_setglobal(L, n))
*/
/* run the script */
luaL_dofile(L, "e1.lua"); // 載入指令碼,指令碼中的可執行語句將會得到執行
/* Loads a file as a Lua chunk and then 以保護模式呼叫一個函式,等價於(luaL_loadfile(L,filename) || lua_pcall(L, 0, LUA_MULTRET, 0)) */
lua_getglobal(L, "avg"); // 將全域性變數avg的值入棧,等價於lua_getfield(L,LUA_GLOBALSINDEX, "name")
printf("avg is: %d\n", lua_tointeger(L, -1)); // 讀棧頂的int值,注意不會從棧中彈出這個元素
lua_pop(L, 1); // 彈出棧頂的一個元素
lua_getglobal(L, "sum"); // 將全域性變數sum的值入棧
printf("sum is: %d\n", lua_tointeger(L, -1));
lua_pop(L, 1); // 彈出棧頂的一個元素
/* cleanup Lua */
lua_close(L);
return 0;
}
/*
-- e1.lua
avg, sum = average(10, 20, 30, 40, 50)
print("The average is ", avg)
print("The sum is ", sum)
*/
/*
輸出:
The average is 30
The sum is 150
avg is: 30
sum is: 150
*/
2,在C/C++程式碼中呼叫lua指令碼中的函式
在C/C++程式碼中通過棧將引數傳遞給lua指令碼中的函式,引數按照正序入棧,返回值由lua按正序壓入棧中供C/C++訪問。(函式呼叫成功後函式名和引數都出棧,返回值入棧)
示例:
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#include <lua.h> // lua是用純c語言寫的
#include <lualib.h>
#include <lauxlib.h>
#ifdef __cplusplus
}
#endif /* __cplusplus */
lua_State* L = NULL;
int luaAdd(int x, int y)
{
int sum;
/* the function name,lua指令碼中的函式名 */
lua_getglobal(L, "add"); // lua函式名入棧
int savedTop = lua_gettop(L); // 儲存棧中的元素個數,後面要還原
/* the first argument */
lua_pushnumber(L, x); // 引數x入棧
/* the second argument */
lua_pushnumber(L, y); // 引數y入棧
// 此時棧中有3個值,棧頂是引數y,棧底是函式名add
/* call the function with 2 arguments, return 1 result */
lua_call(L, 2, 1);
/*
void lua_call (lua_State *L, int nargs, int nresults);
呼叫一個函式。
要呼叫一個函式請遵循以下協議:首先,要呼叫的函式應該被壓入堆疊;接著,把需要傳遞給這個函式的引數按正序壓棧;這是指第一個引數首先壓棧。最後呼叫一下 lua_call; nargs 是你壓入堆疊的引數個數。當函式呼叫完畢後,所有的引數以及函式本身都會出棧。而函式的返回值這時則被壓入堆疊。返回值的個數將被調整為 nresults 個,除非 nresults 被設定成 LUA_MULTRET。在這種情況下,所有的返回值都被壓入堆疊中。 Lua 會保證返回值都放入棧空間中。函式返回值將按正序壓棧(第一個返回值首先壓棧),因此在呼叫結束後,最後一個返回值將被放在棧頂。
lua_pcall (lua_State *L, int nargs, int nresults, int errfunc);
以保護模式呼叫一個函式。
nargs 和 nresults 的含義與 lua_call 中的相同。如果在呼叫過程中沒有發生錯誤, lua_pcall 的行為和 lua_call 完全一致。但是,如果有錯誤發生的話, lua_pcall 會捕獲它,然後把單一的值(錯誤資訊)壓入堆疊,然後返回錯誤碼。同 lua_call 一樣, lua_pcall 總是把函式本身和它的引數從棧上移除。
如果 errfunc 是 0 ,返回在棧頂的錯誤資訊就和原始錯誤資訊完全一致。否則,errfunc 就被當成是錯誤處理函式在棧上的索引。(在當前的實現裡,這個索引不能是偽索引。)在發生執行時錯誤時,這個函式會被呼叫而引數就是錯誤資訊。錯誤處理函式的返回值將被 lua_pcall 作為出錯資訊返回在堆疊上。
典型的用法中,錯誤處理函式被用來在出錯資訊上加上更多的除錯資訊,比如棧跟蹤資訊 (stack traceback) 。這些資訊在 lua_pcall 返回後,因為棧已經展開 (unwound) ,所以收集不到了。
lua_pcall 函式在呼叫成功時返回 0 ,否則返回以下(定義在 lua.h 中的)錯誤程式碼中的一個:
LUA_ERRRUN: 執行時錯誤。
LUA_ERRMEM: 記憶體分配錯誤。對於這種錯,Lua 調用不了錯誤處理函式。
LUA_ERRERR: 在執行錯誤處理函式時發生的錯誤。
*/
// 因為add只有一個返回值,所以此時棧中只有1個值(如果有多個返回值,也是按正序入棧)
/* get the result */
sum = (int) lua_tonumber(L, -1); // -1是棧頂
lua_pop(L, 1); // 返回值出棧
/* 取出指令碼中的變數z的值 */
lua_getglobal(L, "z"); // 變數z的值入棧
int z = (int) lua_tonumber(L, -1);
printf("z = %d\n", z);
lua_pop(L, 1); // 變數z出棧
lua_pushnumber(L, 4); // 4入棧
lua_setglobal(L, "r");
/*
void lua_setglobal (lua_State *L, const char *name);
從堆疊上彈出一個值,並將其設到全域性變數 name 中。它由一個巨集定義出來:
#define lua_setglobal(L,s) lua_setfield(L, LUA_GLOBALSINDEX, s)
*/
lua_getglobal(L, "r"); // 變數r的值入棧
int r = (int)lua_tonumber(L, -1);
printf("r = %d\n", r);
lua_pop(L, 1); // 變數r出棧
lua_settop(L, savedTop); // 函式退出之前恢復原來的棧
return sum;
}
int main(int argc, char* argv[])
{
L= luaL_newstate(); // 建立lua執行環境
/* load the script,載入指令碼,為後面讀取其中的變數做準備 */
luaL_dofile(L, "e2.lua");
/* call the add function */
int sum = luaAdd(10, 15);
/* print the result */
printf("The sum is %d\n", sum);
/* cleanup Lua */
lua_close(L);
return 0;
}
/*
-- e2.lua
-- add two numbers
function add(x, y)
return x + y
end
z = 6
*/
/*
輸出:
z = 6
r = 4
The sum is 25
*/
3,綜合例子
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#include <lua.h> // lua是用純c語言寫的
#include <lualib.h>
#include <lauxlib.h>
#ifdef __cplusplus
}
#endif /* __cplusplus */
#define err_exit(num,fmt,args...) \
do{printf("[%s:%d]"fmt"\n",__FILE__,__LINE__,##args);exit(num);}while(0)
#define err_return(num,fmt,args...) \
do{printf("[%s:%d]"fmt"\n",__FILE__,__LINE__,##args);return(num);}while(0)
// lua中呼叫的c函式定義,實現加法
int c_sum(lua_State* L)
{
int a = lua_tointeger(L, 1);
int b = lua_tointeger(L, 2);
lua_pushinteger(L, a + b);
return 1;
}
int main(int argc, char* argv[])
{
lua_State* L = luaL_newstate(); //建立lua執行環境
if (L == NULL)
err_return(-1, "luaL_newstat() failed");
int ret = 0;
ret = luaL_loadfile(L, "e3.lua"); // Loads a file as a Luachunk. 底層呼叫lua_load,功能是:載入一個 Lua chunk 。如果沒有錯誤, lua_load 把一個編譯好的 chunk 作為一個 Lua 函式壓入堆疊。否則,壓入出錯資訊。
if (ret != 0)
err_return(-1, "luaL_loadfile failed");
ret = lua_pcall(L, 0, 0, 0); // 以保護模式呼叫一個函式。 即剛剛載入的檔案
if (ret != 0)
err_return(-1, "lua_pcall failed: %s", lua_tostring(L, -1));
lua_getglobal(L, "width"); // 將全域性變數width的值入棧
lua_getglobal(L, "height");// 將全域性變數height的值入棧
printf("height: %ld, width: %ld\n", (long) lua_tointeger(L,-1), (long) lua_tointeger(L, -2));
lua_pop(L, 1); // 從棧中彈出一個值,從棧頂開始
int a = 11;
int b = 12;
lua_getglobal(L, "mySum1"); // 將全域性變數mySum1入棧,注意這裡的mySum1是一個函式
lua_pushinteger(L, a); // 第1個整型引數入棧,從左往右的順序
lua_pushinteger(L, b); // 第2個整型引數入棧,從左往右的順序
ret = lua_pcall(L, 2, 1, 0); // 呼叫函式,2個引數,1個返回值。呼叫成功後,函式和引數均出戰,返回值入棧。如果有多個返回值,則第一個返回值先入棧
if (ret != 0)
err_return(-1, "lua_pcall failed: %s", lua_tostring(L, -1));// 呼叫函式失敗後棧頂存放失敗原因字串
printf("sum: %d + %d = %ld\n", a, b, (long) lua_tointeger(L,-1)); // sum只有一個返回值,呼叫成功後棧頂存放函式的返回值
lua_pop(L, 1);
const char str1[] = "hello";
const char str2[] = "world";
lua_getglobal(L, "myStrcat"); // 呼叫lua中的函式myStrcat
lua_pushstring(L, str1);
lua_pushstring(L, str2);
ret = lua_pcall(L, 2, 1, 0);
if (ret != 0)
err_return(-1, "lua_pcall failed: %s", lua_tostring(L, -1));
printf("mystrcat: %s %s = %s\n", str1, str2, lua_tostring(L,-1));
lua_pop(L, 1);
lua_pushcfunction(L, c_sum); // 將一個 C 函式壓入堆疊
lua_setglobal(L, "mySum2");
/*
void lua_setglobal (lua_State *L, const char *name);
從堆疊上彈出一個值,並將其設到全域性變數 name 中。它由一個巨集定義出來:
#define lua_setglobal(L,s) lua_setfield(L, LUA_GLOBALSINDEX, s)
*/
// 以上兩句可以用lua_register(L, "mySum2", c_sum);來代替
/* register our function,告訴lua指令碼其中呼叫的average函式(也是一個變數名)其實對應的是一個叫c_averaged的c程式碼函式 */
// 呼叫lua中的mySum2函式,該函式呼叫c_sum函式實現加法
lua_getglobal(L, "mySum2");
lua_pushinteger(L, a);
lua_pushinteger(L, b);
ret = lua_pcall(L, 2, 1, 0);
if (ret != 0)
err_return(-1, "lua_pcall failed: %s", lua_tostring(L, -1));
printf("mysum: %d + %d = %ld\n", a, b, (long) lua_tointeger(L,-1));
lua_pop(L, 1);
lua_close(L); // 釋放lua執行環境
return 0;
}
/*
-- e3.lua
-- 變數定義
width = 1
height = 2
-- lua函式定義,實現加法
function mySum1(a, b)
return a + b
end
-- lua函式定義,實現字串相加
function myStrcat(a, b)
return a.. "_" .. b
end
-- lua函式定義,通過呼叫c程式碼中的csum函式實現加法
function mysum(a, b)
return csum(a, b)
end
*/
/*
輸出:
height: 2, width: 1
sum: 11 + 12 = 23
mystrcat: hello world = hello_world
mysum: 11 + 12 = 23
*/