程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> Lua與C/C++交互——C/C++導出Dll

Lua與C/C++交互——C/C++導出Dll

編輯:C++入門知識

 

0.簡介

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

 

        看文章名就知道,我們要講Lua和C/C++的交互,這期講Lua如何使用C/C++的東西——Lua通過C/C++導出的dll來調用。OK,Let’s Go!首先,你得去Lua官網http://www.lua.org獲取最新版本的Lua。

 

1.准備工作

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

 

       我們用一個在Lua中顯示Windows對話框的程序來簡要介紹一下,程序雖小,但五髒俱全。程序如下:

 

// 

// 將一些有用的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 = "參數數目錯誤!"; 

static const char* const ERROR_ARGUMENT_TYPE  = "參數類型錯誤!"; 

 

 

// 

// 發生錯誤,報告錯誤. 

// 

void ErrorMsg(lua_State* luaEnv, const char* const pszErrorInfo) 

    lua_pushstring(luaEnv, pszErrorInfo); 

    lua_error(luaEnv); 

 

 

// 

// 檢測函數調用參數個數是否正常. 

// 

void CheckParamCount(lua_State* luaEnv, int paramCount) 

    // lua_gettop獲取棧中元素個數. 

    if (lua_gettop(luaEnv) != paramCount) 

    { 

        ErrorMsg(luaEnv, ERROR_ARGUMENT_COUNT); 

    } 

 

 

// 

// 顯示Windows對話框. 

// @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; 

 

    // 檢測參數個數是否正確. 

    CheckParamCount(luaEnv, 2); 

 

    // 提取參數. 

    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); 

    } 

 

    // 返回值個數為0個. 

    return 0; 

 

 

// 

// 導出函數列表. 

// 

static luaL_Reg luaLibs[] = 

    {"ShowMsgBox", ShowMsgBox}, 

    {NULL, NULL} 

}; 

 

 

// 

// Dll入口函數,Lua調用此Dll的入口函數. 

// 

extern "C" __declspec(dllexport) 

int luaopen_WinFeature(lua_State* luaEnv) 

    const char* const LIBRARY_NAME = "WinFeature"; 

    luaL_register(luaEnv, LIBRARY_NAME, luaLibs); 

 

    return 1; 

 

 

//:-) 

2.程序解析

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

 

        每一個導出函數的格式都為:

 

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

其中,luaState*所指的結構中包含了Lua調用此Dll時必備的Lua環境。那麼Lua向函數傳遞參數該怎麼辦呢?實際上是用luaL_check[type]函數來完成的。如下:

 

const char* pHelloStr = luaL_checkstring(luaEnv, 1); 

 

double value = luaL_checknumber(luaEnv, 2); 

int ivalue = luaL_checkint(luaEnv, 3); 

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

 

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

 

Dll入口函數格式如下:

 

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對應的庫名,以及luaL_Reg數組對應的導出列表來注冊到lua_State*對應的Lua環境中。

 

3.Lua調用

        那麼我們要如何調用該Dll呢?首先,把該Dll放到你的Lua能搜尋到的目錄——當前目錄、Lua安裝目錄下的clibs目錄……然後通過require函數導入。

 

        因為Lua中如果你的函數調用參數只有一個,並且該參數為字符串的話,函數調用時的括號是可以省略的,所以:

require(“YourLibName”)和requir“YourLibName”都是合法的。我們把剛剛生成的WinFeature.dll文件拷貝到C盤下,然後在C盤啟動Lua。示例如下:

\

 

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

 

 

for k, v in pairs(PackageName) do 

    print(k, v) 

end 

然後我們調用WinFeature.ShowMsgBox函數:

 

\

4.Lua堆棧詳解

 

       唔,那麼lua_State結構如何管理Lua運行環境的呢?Lua又是如何將參數傳遞到C/C++函數的呢?C/C++函數又如何返回值給Lua呢?……這一切,都得從Lua堆棧講起。

 

       Lua在和C/C++交互時,Lua運行環境維護著一份堆棧——不是傳統意義上的堆棧,而是Lua模擬出來的。Lua與C/C++的數據傳遞都通過這份堆棧來完成,這份堆棧的代表就是lua_State*所指的那個結構。

 

4.1.堆棧結構解析

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

\       

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

 

 

4.2.提取參數

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

 

luaL_checkany          ——檢測任何值(可以為nil) 

luaL_checkint          ——檢測一個值是否為number(double),並轉換成int 

luaL_checkinteger      ——檢測一個值是否為number(double),並轉換成lua_Integer(prtdiff_t),在我的機子上,ptrdiff_t被定義為int 

luaL_checklong         ——檢測一個值是否為number(double),並轉換成long 

luaL_checklstring      ——檢測一個值是否為string,並將字符串長度傳遞在[out]參數中返回 

luaL_checknumber       ——檢測一個值是否為number(double) 

luaL_checkstring       ——檢測一個值是否為string並返回 

luaL_checkudata        ——檢測自定義類型 

 

4.3.傳遞返回值

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

 

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代表返回值的個數。lua_push系列函數如下:

 

lua_pushboolean        ——壓入一個bool值 

lua_pushcfunction      ——壓入一個lua_CFunction類型的C函數指針 

lua_pushfstring        ——格式化一個string並返回,類似於sprintf 

lua_pushinteger        ——壓入一個int 

lua_pushlightuserdata  ——壓入自定義數據類型 

lua_pushliteral        ——壓入一個字面值字符串 

lua_pushlstring        ——壓入一個規定長度內的string 

lua_pushnil            ——壓入nil值 

lua_pushnumber         ——壓入lua_Number(double)值 

lua_pushstring         ——壓入一個string 

lua_pushthread         ——壓入一個所傳遞lua_State所對應的線程,如果此線程是主線程,則返回1 

lua_pushvalue          ——將所傳遞索引處的值復制一份壓入棧頂 

lua_pushvfstring       ——類似lua_pushfstring 

通過這些函數,我們可以靈活的使用C/C++的高性能特性,來導出函數供Lua調用

摘自:Arnozhang的專欄

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved