C和Lua之間的相互調用

前面的話

第一次接觸Lua是由於Unity遊戲中須要熱更,可是一直沒搞懂Lua是怎麼嵌入到別的語言中執行的,如何互相調用的。此次打算好好了解一下C跟lua是如何交互的html

那麼如何使用Lua語言?

lua是c語言編寫的,並且開源。能夠在https://www.lua.org官網上下載Lua的源碼,而後嘗試編譯它!是否是跟我同樣好激動,一直用集成環境,寫上層語言,今天竟然要碰編譯了!!~ 可怎麼編譯呢?前端

讓咱們召喚出編譯神器:gcc!【GNU編譯器套件(GNU Compiler Collection)包括C、C++、Objective-C、Fortran、Java、Ada和Go語言的前端,也包括了這些語言的庫(如libstdc++、libgcj等等)。】c++

在Mac上安裝GCC

若是你安裝了Homebrew的話,只要一行就能夠了。macos

brew install gcc

裝完後用curl

brew info gcc
或者
gcc -v

看一下是否是成功了
函數

編譯Lua

當你安裝好了編譯器後,編譯lua就變得很是簡單了

Lua官網的文檔裏有說編譯方式, 但MakeFile裏默認的是編譯成靜態連接庫,被這個坑了,後面再說性能

建議安裝在/opt目錄下ui

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編譯好了!~lua

下面正式開幹了~url

寫一個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庫中的函數,並將它們存儲在適當的位置。
 * 此函數的命名規則應遵循:
 * 一、使用"luaopen_"做爲前綴。
 * 二、前綴以後的名字將做爲"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

相關文章
相關標籤/搜索