上一篇文章簡單介紹了在Lua中如何調用C,其中的原理還是需要稍微深究一下。
文章參考自:Lua和C交互的簡易教程(HansChen的博客)
C/C++與Lua交互的基礎源於虛擬棧。在Lua中,Lua堆棧就是一個struct,堆棧索引的方式可是是正數也可以是負數,區別是:正數索引1永遠表示棧底,負數索引-1永遠表示棧頂
一個簡單的例子:
#include <lua.h>
#include <stdio.h>
#include "lauxlib.h"
int main()
{
lua_State *L = luaL_newstate();
lua_pushstring(L, "mick");
lua_pushnumber(L, 20);
printf("stack size: %d\n", lua_gettop(L));
const char *str = luaL_checklstring(L, 1, NULL);
int data = luaL_checknumber(L, 2);
printf("%s %d\n", str, data);
return 0;
}
不同於上次編譯動態庫,這次直接編譯可執行文件,缺少一些編譯選項就會報錯。我遇到的:
1、少 liblua.a 時:
/tmp/ccM31DcG.o: In function `main':
lua_test.c:(.text+0x9): undefined reference to `luaL_newstate'
lua_test.c:(.text+0x1e): undefined reference to `lua_pushstring'
lua_test.c:(.text+0x3d): undefined reference to `lua_pushnumber'
lua_test.c:(.text+0x53): undefined reference to `luaL_checklstring'
lua_test.c:(.text+0x68): undefined reference to `luaL_checknumber'
collect2: error: ld returned 1 exit status
2、少 -lm 時:
object.c:(.text+0xeb): undefined reference to `fmod'
lobject.c:(.text+0x111): undefined reference to `pow'
lobject.c:(.text+0x125): undefined reference to `floor'
/usr/local/lib/liblua.a(ltable.o): In function `luaH_get':
ltable.c:(.text+0x623): undefined reference to `floor'
/usr/local/lib/liblua.a(ltable.o): In function `luaH_newkey':
ltable.c:(.text+0xcc9): undefined reference to `floor'
/usr/local/lib/liblua.a(lvm.o): In function `tointeger_aux':
lvm.c:(.text+0x7a): undefined reference to `floor'
/usr/local/lib/liblua.a(lvm.o): In function `luaV_execute':
lvm.c:(.text+0x1a2e): undefined reference to `floor'
lvm.c:(.text+0x1ead): undefined reference to `pow'
lvm.c:(.text+0x2000): undefined reference to `fmod'
collect2: error: ld returned 1 exit status
3、少 -ldl 時:
/usr/local/lib/liblua.a(loadlib.o): In function `lookforfunc':
loadlib.c:(.text+0x502): undefined reference to `dlsym'
loadlib.c:(.text+0x549): undefined reference to `dlerror'
loadlib.c:(.text+0x576): undefined reference to `dlopen'
loadlib.c:(.text+0x5ed): undefined reference to `dlerror'
/usr/local/lib/liblua.a(loadlib.o): In function `gctm':
loadlib.c:(.text+0x781): undefined reference to `dlclose'
collect2: error: ld returned 1 exit status
所以可行的編譯爲:
gcc source.c -o target /usr/local/lib/liblua.a -lm -ldl
返回如下:
stack size: 2
mick 20
Lua調用C動態庫
對棧有了一定了解後,我們再次開始Lua調用C的例子,這回可以有返回值。
注意一點:所有的函數必須接收一個lua_State作爲參數,同時返回一個整數值,該整數值表示函數的返回值。這個函數使用Lua棧作爲參數,它可以從棧裏面讀取任意數量和任意類型的參數。當函數開始的時候, lua_gettop(L) 可以返回函數收到的參數個數。 第一個參數(如果有的話)在索引 1 的地方, 而最後一個參數在索引 lua_gettop(L) 處。 當需要向 Lua 返回值的時候, C 函數只需要把它們以正序壓到堆棧上(第一個返回值最先壓入), 然後返回這些返回值的個數。 在這些返回值之下的,堆棧上的東西都會被 Lua 丟掉。
例子如下:
#include <lua.h>
#include <lauxlib.h>
#include <stdio.h>
static int test(lua_State *L)
{
int num = luaL_checkinteger(L, 1);
const char* str = luaL_checklstring(L, 2, NULL);
printf("come from test: num = %d str = %s\n", num, str);
lua_pushnumber(L, 10);
lua_pushstring(L, "mick");
return 2;
}
int luaopen_myLib(lua_State *L)
{
luaL_Reg l[] =
{
{"test", test},
{NULL, NULL}
};
luaL_newlib(L, l);
return 1;
}
調用如下:
local reta, retb = myLib.test(666, "cxl")
print("ret val:", reta, retb)
打印如下:
rom test: num = 666 str = cxl
ret val: 10.0 mick
C調用Lua
事先準備lua文件:
str = "my lua lib"
person = {name = "cxl", age = 26}
function add(x, y)
return x + y
end
function addName(person, newName)
person.name = newName
return person
end
function display()
for k, v in pairs(person) do
print(k .. ": " .. v)
end
end
然後是 c 文件:
#include <lua.h>
#include <lauxlib.h>
#include <stdio.h>
lua_State* load_lua(char *luaPath) {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
//加載腳本並運行
if (luaL_loadfile(L, luaPath) || lua_pcall(L, 0, 0, 0)) {
printf("load Lua failed: %s\n", lua_tostring(L, -1)); //發生錯誤時,有關錯誤的提示信息會被壓入棧頂
return NULL;
}
return L;
}
int main() {
char *luaFile = "mylib.lua";
lua_State *L = load_lua(luaFile);
if (NULL == L) {
return -1;
}
printf("stack size: %d\n", lua_gettop(L));
lua_getglobal(L, "str");
printf("%s\n", luaL_checklstring(L, -1, NULL));
lua_getglobal(L, "person");
lua_getfield(L, -1, "name");
printf("name: %s\n", luaL_checklstring(L, -1, NULL));
lua_getfield(L, -2, "age"); // 由於將 "name" 對應的值壓棧,table的索引變成了-2
printf("age: %d\n", luaL_checkinteger(L, -1));
//=================== 棧頂 ===================
// 索引 類型 值
// 4 int: 26
// 3 string: cxl
// 2 table: person
// 1 string: cxl
//=================== 棧底 ===================
lua_getglobal(L, "add");
lua_pushnumber(L, 3);
lua_pushnumber(L, 4);
printf("stack size: %d\n", lua_gettop(L));
if (lua_pcall(L, 2, 1, 0)) { // 函數調用完畢,函數與參數出棧
printf("lua call failed: %s\n", luaL_checklstring(L, -1, NULL));
return -1;
}
int result = luaL_checkinteger(L, -1);
printf("result: %d\n", result);
printf("stack size: %d\n", lua_gettop(L));
lua_pushnumber(L, 165);
lua_setfield(L, 2, "height"); // 棧頂元素被設置到table的"height",同時出棧
printf("stack size: %d\n", lua_gettop(L));
lua_getglobal(L, "display");
if (lua_pcall(L, 0, 0, 0)) {
printf("lua call failed: %s\n", luaL_checklstring(L, -1, NULL));
return -1;
}
lua_getglobal(L, "addName");
lua_newtable(L); // 建立一個新表
lua_pushstring(L, "mzh");
if (lua_pcall(L, 2, 1, 0)) {
printf("lua call failed: %s\n", luaL_checklstring(L, -1, NULL));
return -1;
}
lua_getfield(L, -1, "name");
printf("name: %s\n", luaL_checklstring(L, -1, NULL));
lua_close(L);
return 0;
}
輸出顯示爲:
stack size: 0
my lua lib
name: cxl
age: 26
stack size: 7
result: 7
stack size: 5
stack size: 5
name: cxl
age: 26
height: 165.0
name: mzh
需要注意的是:棧操作是基於棧頂的,默認情況下它只會去操作棧頂的值。
舉個例子,函數調用流程是先將函數入棧,參數入棧,然後用lua_pcall調用函數,此時棧頂爲參數,棧底爲函數,所以棧過程大致會是:參數出棧->保存參數->函數出棧->調用函數->返回值入棧
類似的還有lua_setfield,設置一個表的值,首先將值出棧,保存,再去給表賦值。
有了以上基礎,我們嘗試在lua調用c,並將table傳入c,由c代碼處理。
c代碼:
#include <lua.h>
#include <lauxlib.h>
#include <stdio.h>
static int test(lua_State *L)
{
lua_pushnumber(L, 26);
lua_setfield(L, 1, "age");
return 1;
}
int luaopen_myLib(lua_State *L)
{
luaL_Reg l[] =
{
{"test", test},
{NULL, NULL}
};
luaL_newlib(L, l);
return 1;
}
lua調用:
package.cpath = "./?.so"
local myLib = require "myLib"
person = {name = "cxl"}
person = myLib.test(person)
for k, v in pairs(person) do
print(k .. ": " .. v)
end
結果如下:
age: 26.0
name: cxl
更多更詳細的函數介紹見:Lua5.3參考手冊