1. 程式人生 > >C/C++與lua互調函式的方法

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

*/