前面的話
第一次接觸Lua是因為Unity游戲中需要熱更,但是一直沒搞懂Lua是怎么嵌入到別的語言中執行的,如何互相調用的。這次打算好好了解一下C跟lua是如何交互的
那么如何使用Lua語言?
lua是c語言編寫的,而且開源。可以在https://www.lua.org官網上下載Lua的源碼,然后嘗試編譯它!是不是跟我一樣好激動,一直用集成環境,寫上層語言,今天居然要碰編譯了!!~ 可怎么編譯呢?
讓我們召喚出編譯神器:gcc!【GNU編譯器套件(GNU Compiler Collection)包括C、C++、Objective-C、Fortran、Java、Ada和Go語言的前端,也包括了這些語言的庫(如libstdc++、libgcj等等)。】
在Mac上安裝GCC
如果你安裝了Homebrew的話,只要一行就可以了。
brew install gcc
裝完后用
brew info gcc
或者
gcc -v
看一下是不是成功了
編譯Lua
當你安裝好了編譯器后,編譯lua就變得非常簡單了
Lua官網的文檔里有說編譯方式, 但MakeFile里默認的是編譯成靜態鏈接庫,被這個坑了,后面再說
建議安裝在/opt目錄下
sudo su
cd /opt
curl -R -O http://www.lua.org/ftp/lua-5.3.4.tar.gz
tar zxf lua-5.3.4.tar.gz
cd lua-5.3.4
make macosx test
make macosx install
安裝好后用lua -v查看下如果有信息, 恭喜你,Lua編譯好了!~
下面正式開干了~
寫一個C調用Lua的Demo編譯運行
add.c內容
//你需要include這幾個lua頭文件
#include <stdio.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
lua_State* L;
int
luaadd(int x, int y)
{
int sum;
/*函數名*/
lua_getglobal(L,"add");
/*參數入棧*/
lua_pushnumber(L, x);
/*參數入棧*/
lua_pushnumber(L, y);
/*開始調用函數,有2個參數,1個返回值*/
lua_call(L, 2, 1);
/*取出返回值*/
sum = (int)lua_tonumber(L, -1);
/*清除返回值的棧*/
lua_pop(L,1);
return sum;
}
int
main(int argc, char *argv[])
{
int sum;
L = luaL_newstate(); /* 創建lua狀態機 */
luaL_openlibs(L); /* 打開Lua狀態機中所有Lua標准庫 */
/*加載lua腳本*/
luaL_dofile(L, "add.lua");
/*調用C函數,這個里面會調用lua函數*/
sum = luaadd(99, 10);
printf("The sum is %d \n",sum);
/*清除Lua*/
lua_close(L);
return 0;
}
add.lua放到與C同級的目錄下,里面寫一個簡單的函數,讓C調用
function add(x,y)
return x + y
end
好了,終於到了用GCC編譯的階段了,直接gcc add.c一下看看行不行。
果然報錯了!
這是因為沒有把add.c里面的函數鏈接到我們前面編譯出來的lua庫里導致的。怎么讓他指定鏈接哪個庫呢?看GCC的文檔得知-l參數可以指定要鏈接的庫
-l參數和-L參數
-l參數就是用來指定程序要鏈接的庫,-l參數緊接着就是庫名,那么庫名跟真正的庫文
件名有什么關系呢?
就拿數學庫來說,他的庫名是m,他的庫文件名是libm.so,很容易看出,把庫文件名的
頭lib和尾.so去掉就是庫名了
那我們再試一下,gcc add.c -llua,這次編譯出來了: a.out
執行成功!
如何讓Lua調用C?
Lua調用C,我了解到的有3種方式
1.通過在C中注冊函數給lua調用
2.封裝成c動態鏈接庫,在lua中require
3.在LuaJIT里面可以使用ffi高性能的調用C(但是IOS上不支持LuaJIT。。)
1.在C中注冊函數給Lua
lua提供了lua_register函數注冊C函數給lua端調用
hello.c
#include <stdio.h>
#include <string.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
static int l_SayHello(lua_State *L)
{
const char *d = luaL_checkstring(L, 1);//獲取參數,字符串類型
int len = strlen(d);
char str[100] = "hello ";
strcat(str, d);
lua_pushstring(L, str); /* 返回給lua的值壓棧 */
return 1;
}
int
main(int argc, char *argv[])
{
lua_State *L = luaL_newstate(); /* 創建lua狀態機 */
luaL_openlibs(L); /* 打開Lua狀態機中所有Lua標准庫 */
lua_register(L, "SayHello", l_SayHello);//注冊C函數到lua
const char* testfunc = "print(SayHello('lijia'))";//lua中調用c函數
if(luaL_dostring(L, testfunc)) // 執行Lua命令。
printf("Failed to invoke.\n");
/*清除Lua*/
lua_close(L);
return 0;
}
gcc -o hello hello.c -llua編譯執行
2.調用C動態鏈接庫
創建一個mylib.c的文件,然后我們把它編譯成動態鏈接庫
#include <stdio.h>
#include <math.h>
#include <stdarg.h>
#include <stdlib.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
/* 所有注冊給Lua的C函數具有
* "typedef int (*lua_CFunction) (lua_State *L);"的原型。
*/
static int l_sin(lua_State *L)
{
// 如果給定虛擬棧中索引處的元素可以轉換為數字,則返回轉換后的數字,否則報錯。
double d = luaL_checknumber(L, 1);
lua_pushnumber(L, sin(d)); /* push result */
/* 這里可以看出,C可以返回給Lua多個結果,
* 通過多次調用lua_push*(),之后return返回結果的數量。
*/
return 1; /* number of results */
}
/* 需要一個"luaL_Reg"類型的結構體,其中每一個元素對應一個提供給Lua的函數。
* 每一個元素中包含此函數在Lua中的名字,以及該函數在C庫中的函數指針。
* 最后一個元素為“哨兵元素”(兩個"NULL"),用於告訴Lua沒有其他的函數需要注冊。
*/
static const struct luaL_Reg mylib[] = {
{"mysin", l_sin},
{NULL, NULL}
};
/* 此函數為C庫中的“特殊函數”。
* 通過調用它注冊所有C庫中的函數,並將它們存儲在適當的位置。
* 此函數的命名規則應遵循:
* 1、使用"luaopen_"作為前綴。
* 2、前綴之后的名字將作為"require"的參數。
*/
extern int luaopen_mylib(lua_State* L)
{
/* void luaL_newlib (lua_State *L, const luaL_Reg l[]);
* 創建一個新的"table",並將"l"中所列出的函數注冊為"table"的域。
*/
luaL_newlib(L, mylib);
return 1;
}
使用gcc -o mylib.so -fPIC -shared mylib.c -llua -ldl編譯成so
然后創建一個lua文件,把我們編譯出來的c庫引入進來
--[[ 這里"require"的參數對應C庫中"luaopen_mylib()"中的"mylib"。
C庫就放在"a.lua"的同級目錄,"require"可以找到。]]
local mylib = require "mylib"
-- 結果與上面的例子中相同,但是這里是通過調用C庫中的函數實現。
print(mylib.mysin(3.14 / 2)) --> 0.99999968293183
執行a.lua文件,后報錯,說Lua存在多個虛擬機!
lua: multiple Lua VMs detected
為什么呢?查了一些資料發現因為lua默認編譯的是靜態鏈接庫,這樣會導致鏈接多個VM沖突。
那么我們自己再編譯個lua解釋器動態鏈接一下。
mylua.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, "a.lua") || lua_pcall(L, 0, 0, 0)) {
printf("%s", lua_tostring(L, -1));
}
}
gcc -o mylua mylua.c -llua -ldl -lm -Wall
這樣就能編譯出mylua可執行文件
在命令行./mylua執行,成功打印出0.99999968293183
總結
gcc命令,編譯lua,編譯C動態鏈接庫這些之前都接觸的比較少。所以也爬了不少坑,哈哈哈。接下來要好好研究下怎么在c中解析二進制協議給lua調用,在c中怎么封裝好luatable
參考資料:
https://www.cnblogs.com/pied/archive/2012/10/26/2741601.html
http://blog.csdn.net/vermilliontear/article/details/50947379
http://blog.csdn.net/casularm/article/details/316149