99re热这里只有精品视频,7777色鬼xxxx欧美色妇,国产成人精品一区二三区在线观看,内射爽无广熟女亚洲,精品人妻av一区二区三区

8. Lua 與 C/C++ 交互

2018-02-24 16:10 更新

綁定Lua和C/C++的庫

Lua調(diào)用C/C++

簡介

Lua(念“魯啊”)作為一門發(fā)展成熟的腳本語言,正在變得越來越流行。它也可以作為和C/C++執(zhí)行腳本交互的語言。并且Lua的整個庫很小,Lua 5.1版本整個靜態(tài)鏈接的lua.dll才164KB,所以Lua很輕量,特別適合輕量級腳本嵌入。

這節(jié)要講Lua和C/C++的交互——Lua通過C/C++導出的dll來調(diào)用。

LUA調(diào)用C文件中的函數(shù)方法

  • C中注冊函數(shù)
lua_pushcfunction(l, l_sin); //注冊在lua中使用的c函數(shù)l_sin
lua_setglobal(l, "mysin");   //設定綁定到lua中的名字為mysin
  • C中提供的函數(shù)其定義要符合:
typedef int function(lua_State *L)

準備工作

安裝完Lua,需要在Visual Studio中配置Lua路徑,使得你的編譯器能搜尋到。關(guān)于VS2010的配置,見我的博文《VS2010 C++目錄配置》一文。完成后新建一個Dll工程便可以了。

我們用一個在Lua中顯示W(wǎng)indows對話框的程序來簡要介紹一下,程序雖小,但五臟俱全。程序如下:

// 將一些有用的Win32特性導出
// 以便在Lua中使用
extern "C"  
{  
#include <lua.h>  
#include <lualib.h>  
#include <lauxlib.h>  
#pragma comment(lib, "lua.lib")  
};

#include <Windows.h>  
#include <iostream>  
using namespace std;

static const char* const ERROR_ARGUMENT_COUNT = "參數(shù)數(shù)目錯誤!";  
static const char* const ERROR_ARGUMENT_TYPE  = "參數(shù)類型錯誤!";  

// 發(fā)生錯誤,報告錯誤
void ErrorMsg(lua_State* luaEnv, const char* const pszErrorInfo)  
{  
    lua_pushstring(luaEnv, pszErrorInfo);  
    lua_error(luaEnv);  
}  

// 檢測函數(shù)調(diào)用參數(shù)個數(shù)是否正常
void CheckParamCount(lua_State* luaEnv, int paramCount)  
{  
    // lua_gettop獲取棧中元素個數(shù).  
    if (lua_gettop(luaEnv) != paramCount)  
    {  
        ErrorMsg(luaEnv, ERROR_ARGUMENT_COUNT);  
    }  
}  

// 顯示W(wǎng)indows對話框.  
// @param [in] pszMessage string 1  
// @param [in] pszCaption string 2  
extern "C" int ShowMsgBox(lua_State* luaEnv)  
{  
    const char* pszMessage = 0;  
    const char* pszCaption = 0;  

    // 檢測參數(shù)個數(shù)是否正確.  
    CheckParamCount(luaEnv, 2);  

    // 提取參數(shù).  
    pszMessage = luaL_checkstring(luaEnv, 1);  
    pszCaption = luaL_checkstring(luaEnv, 2);  

    if (pszCaption && pszMessage)  
    {  
        ::MessageBox(  
            NULL,  
            pszMessage,  
            pszCaption,  
            MB_OK | MB_ICONINFORMATION  
            );  
    }  
    else  
    {  
        ErrorMsg(luaEnv, ERROR_ARGUMENT_TYPE);  
    }  

    // 返回值個數(shù)為0個.  
    return 0;  
}  

// 導出函數(shù)列表.  
static luaL_Reg luaLibs[] =  
{  
    {"ShowMsgBox", ShowMsgBox},  
    {NULL, NULL}  
};  

// Dll入口函數(shù),Lua調(diào)用此Dll的入口函數(shù).  
extern "C" __declspec(dllexport)  
int luaopen_WinFeature(lua_State* luaEnv)  
{  
    const char* const LIBRARY_NAME = "WinFeature";  
    luaL_register(luaEnv, LIBRARY_NAME, luaLibs);  

    return 1;  
}  

程序解析

首先我們包含Lua的頭文件,并鏈入庫文件。注意:Lua的頭文件為C風格,所以用external “C”來含入。在此例中,我們最終的導出函數(shù)為“ShowMsgBox”。

每一個導出函數(shù)的格式都為:

extern “C”int Export_Proc_Name(luaState* luaEnv);  

其中,luaState*所指的結(jié)構(gòu)中包含了Lua調(diào)用此Dll時必備的Lua環(huán)境。那么Lua向函數(shù)傳遞參數(shù)該怎么辦呢?實際上是用luaL_check[type]函數(shù)來完成的。如下:

const char* pHelloStr = luaL_checkstring(luaEnv, 1);  
double value = luaL_checknumber(luaEnv, 2);  
int ivalue = luaL_checkint(luaEnv, 3);  

luaL_check系列函數(shù)的第二個參數(shù)是Lua調(diào)用該函數(shù)時傳遞參數(shù)從坐到右的順序(從1開始)。

然后我們看到,static的一個luaL_Reg的結(jié)構(gòu)數(shù)組中包含了所有要導出的函數(shù)列表。最后通過luaopen_YourDllName的一個導出函數(shù)來完成一系列操作。YourDllName就是你最終的Dll的名字(不含擴展名)。因為你在Lua中調(diào)用此Dll時,Lua會根據(jù)此Dll名字找luaopen_YourDllName對應的函數(shù),然后從此函數(shù)加載該Dll。

Dll入口函數(shù)格式如下:

extern "C" __declspec(dllexport)  
int luaopen_WinFeature(lua_State* luaEnv)  
{  
    const char* const LIBRARY_NAME = "WinFeature";  
    luaL_register(luaEnv, LIBRARY_NAME, luaLibs);  

    return 1;  
} 

我們通過luaL_register將LIBRARY_NAME對應的庫名,以及l(fā)uaL_Reg數(shù)組對應的導出列表來注冊到lua_State*對應的Lua環(huán)境中。

Lua調(diào)用

那么我們要如何調(diào)用該Dll呢?首先,把該Dll放到你的Lua能搜尋到的目錄——當前目錄、Lua安裝目錄下的clibs目錄,然后通過require函數(shù)導入。

因為Lua中如果你的函數(shù)調(diào)用參數(shù)只有一個,并且該參數(shù)為字符串的話,函數(shù)調(diào)用時的括號是可以省略的,所以:require(“YourLibName”)和requir“YourLibName”都是合法的。我們把剛剛生成的WinFeature.dll文件拷貝到C盤下,然后在C盤啟動Lua。示例如下:

> require "WinFeature"
> for k, v in pairs(WinFeature) do
>>      print(k, v)
>> end
ShowMsgBox functon:0028AB90
>

可以看到,函數(shù)調(diào)用方式都是“包名.函數(shù)名”,而包名就是你的Dll的名字。我們可以用下面的方式查看一個包中的所有函數(shù):

for k, v in pairs(PackageName) do  
    print(k, v)  
end  

然后我們調(diào)用WinFeature.ShowMsgBox函數(shù):

> WinFeature.ShowMsgBox("Hello, this is a msgBox", "Tip")

會彈出對話框顯示內(nèi)容為"Hello, this is a msgBox"和標題為"Tip"。

Lua堆棧詳解

唔,那么lua_State結(jié)構(gòu)如何管理Lua運行環(huán)境的呢?Lua又是如何將參數(shù)傳遞到C/C++函數(shù)的呢?C/C++函數(shù)又如何返回值給Lua呢?……這一切,都得從Lua堆棧講起。

Lua在和C/C++交互時,Lua運行環(huán)境維護著一份堆?!皇莻鹘y(tǒng)意義上的堆棧,而是Lua模擬出來的。Lua與C/C++的數(shù)據(jù)傳遞都通過這份堆棧來完成,這份堆棧的代表就是lua_State*所指的那個結(jié)構(gòu)。

堆棧結(jié)構(gòu)解析

堆棧通過lua_push系列函數(shù)向堆棧中壓入值,通過luaL_check系列從堆棧中獲取值。而用luaL_check系列函數(shù)時傳遞的參數(shù)索引,比如我們調(diào)用WinFeature.ShowMsgBox(“Hello”, “Tip”)函數(shù)時,棧結(jié)構(gòu)如下:

棧頂 "Tip" 2或者-1 "Hello" 1或者-2 棧底

其中,參數(shù)在棧中的索引為參數(shù)從左到右的索引(從1開始),棧頂元素索引也可以從-1記起。棧中元素個數(shù)可以用lua_gettop來獲得,如果lua_gettop返回0,表示此棧為空。(lua_gettop這個函數(shù)名取得不怎么樣?。?/p>

提取參數(shù)

luaL_check系列函數(shù)在獲取值的同時,檢測這個值是不是符合我們所期望的類型,如果不是,則拋出異常。所有這個系列函數(shù)如下:

luaL_checkany          —— 檢測任何值(可以為nil)  
luaL_checkint          —— 檢測一個值是否為number(double),并轉(zhuǎn)換成int  
luaL_checkinteger      —— 檢測一個值是否為number(double),并轉(zhuǎn)換成lua_Integer(prtdiff_t),在我的機子上,ptrdiff_t被定義為int  
luaL_checklong         —— 檢測一個值是否為number(double),并轉(zhuǎn)換成long  
luaL_checklstring      —— 檢測一個值是否為string,并將字符串長度傳遞在[out]參數(shù)中返回  
luaL_checknumber       —— 檢測一個值是否為number(double)  
luaL_checkstring       —— 檢測一個值是否為string并返回  
luaL_checkudata        —— 檢測自定義類型  

傳遞返回值

當我們要傳遞返回值給Lua時,可以用lua_push系列函數(shù)來完成。每一個導出函數(shù)都要返回一個int型整數(shù),這個整數(shù)是你的導出函數(shù)的返回值的個數(shù)。而返回值通過lua_push系列函數(shù)壓入棧中。比如一個Add函數(shù):

extern “C” int Add(lua_State* luaEnv)  
{  
    CheckParamCount(luaEnv, 2);  

    double left = luaL_checknumber(luaEnv, 1);  
    double right = luaL_checknumber(luaEnv, 2);  

    double result = left + right;  
    lua_pushnumber(luaEnv, result);  

    return 1;  
}

可以看出,我們用lua_pushnumber把返回值壓入棧,最后返回1——1代表返回值的個數(shù)。lua_push系列函數(shù)如下:

lua_pushboolean        —— 壓入一個bool值  
lua_pushcfunction      —— 壓入一個lua_CFunction類型的C函數(shù)指針  
lua_pushfstring        —— 格式化一個string并返回,類似于sprintf  
lua_pushinteger        —— 壓入一個int  
lua_pushlightuserdata  —— 壓入自定義數(shù)據(jù)類型  
lua_pushliteral        —— 壓入一個字面值字符串  
lua_pushlstring        —— 壓入一個規(guī)定長度內(nèi)的string  
lua_pushnil            —— 壓入nil值  
lua_pushnumber         —— 壓入lua_Number(double)值  
lua_pushstring         —— 壓入一個string  
lua_pushthread         —— 壓入一個所傳遞lua_State所對應的線程,如果此線程是主線程,則返回1  
lua_pushvalue          —— 將所傳遞索引處的值復制一份壓入棧頂  
lua_pushvfstring       —— 類似lua_pushfstring  

通過這些函數(shù),我們可以靈活的使用C/C++的高性能特性,來導出函數(shù)供Lua調(diào)用。

C/C++調(diào)用Lua腳本

簡介

C調(diào)用LUA文件中的函數(shù)方法

lua_getglobal(L, <function name>) //獲取lua中的函數(shù)
lua_push*() //調(diào)用lua_push系列函數(shù),把輸入?yún)?shù)壓棧。例如lua_pushnumber(L, x)
lua_pcall(L, <arguments nums>, <return nums>, <錯誤處理函數(shù)地址>)

例如:

lua_settop(m_pLua,0);
lua_getglobal(m_pLua,"mainlogic");
lua_pushlstring(m_pLua,(char*)msg.getBuf(),msg.size());
int ret = 0;
ret = lua_pcall(m_pLua,1,4,0);

上一節(jié)介紹了如何在Lua中調(diào)用C/C++代碼,本節(jié)介紹如何在C/C++中調(diào)用Lua腳本。本節(jié)介紹一個例子,通過Lua來生成一個XML格式的便簽。便簽格式如下:

<?xml version="1.0" encoding="utf-8" ?>  
<note>  
    <fromName>發(fā)送方姓名</fromName>  
    <toName>接收方姓名</toName>  
    <sendTime>發(fā)送時間</sendTime>  
    <msgContent>便簽內(nèi)容</msgContent>  
</note>  

我們通過C/C++來輸入這些信息,然后讓Lua來生成這樣一個便簽文件。

Lua代碼

xmlHead = '<?xml version="1.0" encoding="utf-8" ?>\n'  

-- Open note file to wriet.  
function openNoteFile(fileName)  
    return io.open(fileName, "w")  
end  

-- Close writed note file.  
function closeNoteFile(noteFile)  
    noteFile:close()  
end  

function writeNestedLabel(ioChanel, label, nestCnt)  
    if nestCnt == 0 then  
        ioChanel:write(label)  
        return  
    end  

    for i = 1, nestCnt do  
        ioChanel:write("\t")  
    end  

    ioChanel:write(label)  
end  

function generateNoteXML(fromName, toName, msgContent)  
    local noteFile = openNoteFile(fromName .. "_" .. toName .. ".xml")  
    if not noteFile then  
        return false  
    end  

    noteFile:write(xmlHead)  
    noteFile:write("<note>\n")  

    local currNestCnt = 1  
    writeNestedLabel(noteFile, "<fromName>", currNestCnt)  
    noteFile:write(fromName)  
    writeNestedLabel(noteFile, "</fromName>\n", 0)  

    writeNestedLabel(noteFile, "<toName>", currNestCnt)  
    noteFile:write(toName)  
    writeNestedLabel(noteFile, "</toName>\n", 0)  

    local sendTime = os.time()  
    writeNestedLabel(noteFile, "<sendTime>", currNestCnt)  
    noteFile:write(sendTime)  
    writeNestedLabel(noteFile, "</sendTime>\n", 0)  

    writeNestedLabel(noteFile, "<msgContent>", currNestCnt)  
    noteFile:write(msgContent)  
    writeNestedLabel(noteFile, "</msgContent>\n", 0)  

    noteFile:write("</note>\n")  
    closeNoteFile(noteFile)  

    return true  
end  

我們通過openNoteFile和closeNoteFile來打開/關(guān)閉XML文件。generateNoteXML全局函數(shù)接受發(fā)送方姓名、接收方姓名、便簽內(nèi)容,生成一個XML便簽文件。便簽發(fā)送時間通過Lua標準庫os.time()函數(shù)來獲取。writeNestedLabel函數(shù)根據(jù)當前XML的縮進數(shù)目來規(guī)范XML輸出格式。此文件很好理解,不再贅述。

C++調(diào)用Lua腳本

extern "C"  
{  
#include <lua.h>  
#include <lualib.h>  
#include <lauxlib.h>  
#pragma comment(lib, "lua.lib")  
};  

#include <iostream>  
#include <string>  
using namespace std;  

// 初始化Lua環(huán)境.  
lua_State* initLuaEnv()  
{  
    lua_State* luaEnv = lua_open();  
    luaopen_base(luaEnv);  
    luaL_openlibs(luaEnv);  

    return luaEnv;  
}  

// 加載Lua文件到Lua運行時環(huán)境中
bool loadLuaFile(lua_State* luaEnv, const string& fileName)  
{  
    int result = luaL_loadfile(luaEnv, fileName.c_str());  
    if (result)  
    {  
        return false;  
    }  

    result = lua_pcall(luaEnv, 0, 0, 0);  
    return result == 0;  
}  

// 獲取全局函數(shù)
lua_CFunction getGlobalProc(lua_State* luaEnv, const string& procName)  
{  
    lua_getglobal(luaEnv, procName.c_str());  
    if (!lua_iscfunction(luaEnv, 1))  
    {  
        return 0;  
    }  

    return lua_tocfunction(luaEnv, 1);  
}  

int main()  
{  
    // 初始化Lua運行時環(huán)境.  
    lua_State* luaEnv = initLuaEnv();  
    if (!luaEnv)  
    {  
        return -1;  
    }  

    // 加載腳本到Lua環(huán)境中.  
    if (!loadLuaFile(luaEnv, ".\\GenerateNoteXML.lua"))  
    {  
        cout<<"Load Lua File FAILED!"<<endl;  
        return -1;  
    }  

    // 獲取Note信息.  
    string fromName;  
    string toName;  
    string msgContent;  

    cout<<"Enter your name:"<<endl;  
    cin>>fromName;  

    cout<<"\nEnter destination name:"<<endl;  
    cin>>toName;  

    cout<<"\nEnter message content:"<<endl;  
    getline(cin, msgContent);  
    getline(cin, msgContent);  

    // 將要調(diào)用的函數(shù)和函數(shù)調(diào)用參數(shù)入棧.  
    lua_getglobal(luaEnv, "generateNoteXML");  
    lua_pushstring(luaEnv, fromName.c_str());  
    lua_pushstring(luaEnv, toName.c_str());  
    lua_pushstring(luaEnv, msgContent.c_str());  

    // 調(diào)用Lua函數(shù)(3個參數(shù),一個返回值).  
    lua_pcall(luaEnv, 3, 1, 0);  

    // 獲取返回值.  
    if (lua_isboolean(luaEnv, -1))  
    {  
        int success = lua_toboolean(luaEnv, -1);  
        if (success)  
        {  
            cout<<"\nGenerate Note File Successful!"<<endl;  
        }  
    }  

    // 將返回值出棧.  
    lua_pop(luaEnv, 1);  

    // 釋放Lua運行時環(huán)境.  
    lua_close(luaEnv);  

    system("pause");  
    return 0;  
}  

代碼解析

初始化Lua運行時環(huán)境

lua_State*所指向的結(jié)構(gòu)中封裝了Lua的運行時環(huán)境。我們用initLuaEnv這個函數(shù)來初始化。lua_open函數(shù)用來獲取一個新環(huán)境,然后我們用luaopen_base打開Lua的基礎(chǔ)庫,然后用luaL_openlibs打開Lua的io、string、math、table等高級庫。

加載Lua文件

然后我們用luaL_loadfile和lua_pcall來將一個Lua腳本加載到對應的Lua運行時環(huán)境中——注意:并不自動執(zhí)行,只是加載。這兩個函數(shù)如果返回非0,表示加載失敗——你的Lua腳本文件可能未找到或Lua腳本有語法錯誤……

加載Lua函數(shù)

我們用lua_getglobal函數(shù)將Lua腳本中的全局函數(shù)、全局變量等入棧,放在棧頂。

壓入Lua函數(shù)調(diào)用參數(shù)

我們用lua_push系列函數(shù)來將要調(diào)用函數(shù)所需參數(shù)全部入棧,入棧順序為Lua函數(shù)對應參數(shù)從左到右的順序。

調(diào)用Lua函數(shù)

最后用lua_pcall來調(diào)用函數(shù)。Lua_pcall函數(shù)原型如下:

int lua_pcall(lua_State* L, int nargs, int nresults, int errfunc);  

其中,L為此函數(shù)執(zhí)行的Lua環(huán)境,nargs為此函數(shù)所需的參數(shù)個數(shù),nresults為此函數(shù)的返回值個數(shù),errfunc為發(fā)生錯誤時錯誤處理函數(shù)在堆棧中的索引。

獲取Lua函數(shù)返回值

然后,我們可以通過檢測棧頂?shù)奈恢茫◤?1開始),來獲取返回值。獲取返回值后,用lua_pop將棧頂元素出?!鰲€數(shù)為返回值個數(shù)。

釋放Lua環(huán)境

最后,通過lua_close函數(shù)來關(guān)閉Lua環(huán)境并釋放資源。

運行結(jié)果

我們將GenerateNoteXML.lua腳本和最終的C++生成的GenerateNoteXML.exe放在同一路徑下,并運行GenerateNoteXML.exe,在此目錄下會生成一個XML文件。如下:

Enter your name:
Jack

Enter destnation name:
Joe

Enter message content:
Hello, Can you help me?

Generate Note File Successful!

生成的arnozhang_YaFengZhang.xml文件如下:

<?xml version="1.0" encoding="utf-8" ?>
<note>
  <fromName>Jack</fromName>
  <toName>Joe</toName>
  <sendTime>1317971623</sendTime>
  <msgContent>Hello, Can you help me?</msgContent>
</note>

C 作為動態(tài)庫文件被 Lua 調(diào)用

C/C++中的入口函數(shù)定義

一定是要定義成: luaopen_(dll或so文件的文件名稱),(dll或so文件的文件名稱)必須和dll或so文件名稱保持一致。

例如(C++ windows情況):

#ifdef _WIN32
#define _EXPORT extern "C" __declspec(dllexport)
#else //unix/linux
#define _EXPORT extern "C"
#endif
_EXPORT int luaopen_capi_mytestlib(lua_State *L)
{
    struct luaL_reg driver[] = {
        {"average", average1},
        {NULL, NULL},};
    luaL_register(L, "mylib", driver);
    //luaL_openlib(L, "mylib", driver, 0);
    return 1;
}

動態(tài)庫要供LUA調(diào)用的function

其定義要符合:

typedef int function(lua_State *L)

在動態(tài)庫調(diào)用LUA注冊

將要調(diào)用的函數(shù)放到這個結(jié)構(gòu)體里:

struct luaL_Reg lib[] ={}

在動態(tài)庫的入口函數(shù)里調(diào)用luaL_register將這個結(jié)構(gòu)體注冊,在這個入口函數(shù)注冊結(jié)構(gòu)體時,要注冊成:

luaL_register(L,"XXX",lib);

在寫腳本的時候,使用require("XXX")

就是入口函數(shù)的luaopen_后面的XXX,注意大小寫敏感

編譯生成的動態(tài)庫命令成XXX.so或XXX.dll(win)

同入口函數(shù)的luaopen_后面的XXX一致

示例:

C文件如下:

#include <stdio.h>
#include "lua/lua.h"
#include "lua/lualib.h"
#include "lua/lauxlib.h"
static int add(lua_State *L)
{
    int a,b,c;
    a = lua_tonumber(L,1);
    b = lua_tonumber(L,2);
    c = a+b;
    lua_pushnumber(L,c);
    printf("test hello!!!\r\n");
    return 1;
}
static const struct luaL_Reg lib[] =
{
    {"testadd",add},
    {NULL,NULL}
};

int luaopen_testlib(lua_State *L)
{
    luaL_register(L,"testlib",lib);
    return 1;
}

編譯: gcc test.c -fPIC -shared -o testlib.so

lua腳本編寫:

require("testlib")
c = testlib.testadd(15,25)
print("The result is ",c);

示例:

int lua_createmeta (lua_State *L, const char *name, const luaL_reg *methods) {
    if (!luaL_newmetatable (L, name))
        return 0;

    luaL_openlib (L, NULL, methods, 0);

    lua_pushliteral (L, "__gc");
    lua_pushcfunction (L, methods->func);
    lua_settable (L, -3);
    lua_pushliteral (L, "__index");
    lua_pushvalue (L, -2);
    lua_settable (L, -3);
    lua_pushliteral (L, "__metatable");
    lua_pushliteral (L, "you're not allowed to get this metatable");
    lua_settable (L, -3);
    return 1;
}

示例中的luaopen_testlib函數(shù)替換為:

int luaopen_testlib(lua_State *L)
{
    lua_createmeta(L,"testlib",lib);
    return 1;
}
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號