lua 調用 c 函數
lua 調用 c 函數與 c 調用 lua 函數一樣,都是通過 CAPI 和一個棧來實現,lua 調用 c 函數有以下幾個步驟
1. 在 c 代碼中定義函數
lau.h 中定義了要註冊到 lua 中的 c 函數的原型,雖然我測試的時候不使用這個原型也不會出錯,但還是要遵循規範比較好
int FunctionName(lua_State* L)
返回值表示此函數有幾個返回值,lua 執行完些函數之後根據這個返回值就知道需要從棧中取幾個返回值,而 lua_State* 參數使得 c 函數內可以使用 CAPI 來操作棧,與 lua 交互
c 函數的實現流程:
(1)從棧中取函數的參數。lua 調用 c 函數之前會把參數壓入棧中,每個 c 函數都有一個獨立的私有棧,所以函數第一個參數在棧中的索引是 1。
(2)實現函數功能。
(3)把返回值逐個壓入棧中,第一個返回值最先壓入,也就是在棧底;把結果壓入棧之前不用手動清除棧中原來的參數和函數,lua 會自動幫我們清除。
(4)返回一個整數表示該函數有幾個返回值。
2. 把 c 函數註冊到 lua 環境,在 lua 中創建一個全局變量保存這個函數
使用 lua_pushcfunction(L, c_fun_name)
把函數壓入棧中
使用 lua_setglobal(L, l_fun_name)
把函數賦給一個全局變量
3. 在 lua 程序中使用上面創建的全局變量了
main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
int sayHello(lua_State* L)
{
printf("hello world in c!\n");
return 0;
}
int l_sin(lua_State* L)
{
//double angle = lua_tonumber(L, 1);
double angle = luaL_checknumber(L, 1);
lua_pushnumber(L, sin(angle));
return 1;
}
int main(void)
{
lua_State* L = luaL_newstate();
luaL_openlibs(L);
lua_pushcfunction(L, sayHello);
lua_setglobal(L, "c_say_hello");
lua_pushcfunction(L, l_sin);
lua_setglobal(L, "c_sin");
int error = luaL_loadfile(L, "main.lua") | lua_pcall(L, 0, 0, 0);
if (error != 0)
{
printf("main.lua run error!\n");
}
system("pause");
return 0;
}
加載 main.lua 之前先把兩個 c 函數 sayHello 和 l_sin 註冊到 lua 環境中,然後再加載運行 main.lua 文件,這時 lua 環境中已經有兩個全局變量 c_say_hello 和 c_sin 了,main.lua 中的代碼塊直接使用這兩個全局函數就不會找不到了
main.lua
print "hello world in lua!"
c_say_hello()
print("PI = 3.14")
local angle
repeat
angle = tonumber(io.read())
local s, c = c_sin(angle)
print(string.format("sin(%g) = %.2f cos(%g) = %.2f", angle, s, angle, c))
until angle == -1
c 模塊
我們已經知道了如何在執行 lua 代碼塊之前註冊 c 函數,但前面的程序還是一個 c 程序,只不過實現了 lua 調 c 而已。如果我們想要在使用 lua 解釋器來運行 lua 代碼時能夠讓 lua 調用 c 函數,那就得事先將 c 函數導出來。將要導出的 c 函數導出爲一個模塊,lua 代碼可以像加載 lua 模塊一樣加載這個模塊;lua 不僅能加載以 .lua 爲後綴的 lua 文件,還能加載以 .dll 或 .so 爲後綴的二進制文件,我們就是要將 c 函數導出爲 .dll 或 .so 的動態鏈接庫。
導出 c 函數模塊的流程(以 windows 下導出 dll 爲例):
1. 定義要導出給 lua 調用的 c 函數,加上 static 定義成私有的,因爲這些函數的註冊在模塊內部完成,不需要模塊外部訪問
2. 把這些 c 函數組織成一個數組,類型爲 const luaL_Reg[],同樣爲私有的
3. 定義一個外部函數,將所有 c 函數註冊到 lua 環境,這個函數是外部調用的接口,lua 通過這個函數來註冊上面組織的所有函數。爲了讓外部模塊能夠訪問到這個函數,函數除了要 定義成公有之外,還需要將函數導出,dll 導出函數的方式是在函數前面加上 __declspec(dllexport)
註冊 c 函數:
lua 5.1: luaL_register(L, "mylib", mylib)
lua 5.2: luaL_newlib(L, mylib)
等價於
luaL_checkversion(L);
lua_newlibtable(L, mylib);
lua_setfuncs(L, mylib, 0);
其中 luaL_checkversion 函數會導致在 lua 解釋器程序中無法加載正常 c 模塊,會出現 multiple Lua VMs detected 錯誤,這是打開了兩個 lua 環境造成的;所以如果要導出供純 lua 程序使用的 c 模塊,要把 luaL_checkversion 這條語句去掉,即不要直接使用 luaL_newlib 函數
下面用實例演示一下整個過程,創建一個 c 動態鏈接庫程序,這個鏈接庫註冊了一個 clib 模塊,然後分別在 lua 解釋器和 c 程序中使用這個模塊
新建一個動態鏈接庫項目
使用 visual studio 創建一個控制檯項目 clib,勾選 dll 項目選項和空項目選項。新建一個文件 lib.c
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
//定義私有C函數
static int sayHello(lua_State* L)
{
printf("hello world!\n");
lua_pushstring(L, "hello world!\n");
return 1;
}
//定義私有函數數組
static const struct luaL_Reg myLibs[] =
{
{"sayHello",sayHello},
{NULL,NULL}
};
//定義外部函數
__declspec(dllexport) int luaopen_clib(lua_State* L)
{
//5.1
//luaL_register(L, "myLibs", myLibs);
//5.2
//luaL_newlib(L, myLibs);
luaL_newlibtable(L, myLibs);
luaL_setfuncs(L, myLibs, 0);
return 1;
}
然後編譯項目就會生成一個 clib.dll,這個文件就可以直接在 lua 程序中加載使用了。但如果要在 c 項目中使用的話,還需要創建一個頭文件 lib.h,不然的話 c 項目在使用時會找不到導出的外部函數
#pragma once
#include "lua.h"
__declspec(dllexport) int luaopen_clib(lua_State* L);
在 lua 解釋器中使用 c 模塊
把上面導出的 clib.dll 庫文件放入到 lua 程序運行時的 cpath 中去,然後在 lua 代碼中直接使用 require 加載模塊名即可,加載後要使用一個變量來保存模塊生成的 table
local clib = require("clib")
print(clib.sayHello())
程序到這裏就可以運行測試一下了,但運行之後很可能會崩潰,主要有下面幾點方要注意:
1、註冊的模塊名和 dll 文件名要保持一致,因爲 require 函數是根據名字找到 dll 文件,再從 dll 文件中加載同名的模塊
2、require 函數會自動在模塊內查找函數 luaopen_模塊名,然後運行這個函數完成註冊工作,因此模塊定義的外部函數名並不是可以隨便起的,而是必須使用 luaopen_模塊名,比如上面的
luaopen_clib
,其註冊的模塊名就是 clib3、
__declspec(dllexport)
是必須的4、 導出 c 模塊的時候需要用到 lua 鏈接庫,如果直接鏈接靜態鏈接庫,則生成的 c 模塊 dll 文件比較大,可直接加載;如果是鏈接靜態鏈接庫的 lib 文件,則生成的 c 模塊 dll 文件比較小,但不能直接加載,需要把 lua.dll 跟這個 dll 放在一起
在 c 程序中使用 c 模塊
我們也可以在 c 程序中打開一個 lua 環境,然後加載上面的 c 模塊到 lua 環境,最後執行 lua 代碼塊,讓 lua 代碼調用 c 函數。這種方法其實跟最上面講的 lua 調用 c 函數
一樣,不同的是前面都是一個函數一個函數手動註冊,而現在是通過一個外部模塊批量註冊所有函數。使用 lua_requiref 在 c 程序中加載 c 模塊完成函數註冊,函數原型如下
luaL_requiref(lua_State, "模塊名", 模塊外部接口, 1);
#include "lib.h"
#include "lualib.h"
#include "lauxlib.h"
int main()
{
lua_State* L = luaL_newstate();
//打開默認模塊
luaL_openlibs(L);
//打開自定義模塊
luaL_requiref(L, "clib", luaopen_clib, 1); //1
char buff[] = "clib.sayHello()";
if (luaL_loadbuffer(L, buff, strlen(buff), 0))
{
printf("error load");
}
if (lua_pcall(L, 0, 0, 0))
{
printf("error run");
}
lua_close(L);
getchar();
return 0;
}