在上一篇 C++混合編程之idlcpp教程(一) 中介紹了 idlcpp 工具的使用。現在對 idlcpp 所帶的示例教程進行講解,這裡針對的 Lua 語言的例子。首先看第一個示例程序 LuaTutorial0。像很多語言的第一個例子一樣,是一個打印 Hello world 的程序。用Visual Studio 2015打開解決方案文件 tutorials\LuaTutorials\LuaTutorials.sln,其下已經有多個工程文件。
在工程LuaTutorial0中,已經加入了三個文件,分別是 LuaTutorial0.cpp, Tutorial0.i, tutorial0.lua。首先看Tutorial0.i內容如下:
//tutorial $$#include <stdio.h> namespace tutorial { struct Test { static void Run(); }; $* inline void Test::Run() { printf("Hello World!"); } *$ }
看起來和C++代碼較為相似。編譯Tutorial0.i,將會生成Tutorial0.h,Tutorial0.mh,Tutorial0.ic,Tutorial0.mc四個文件。其中Tutorial0.h內容如下:
//DO NOT EDIT THIS FILE, it is generated by idlcpp //http://www.idlcpp.org #pragma once #include <stdio.h> #line 4 "D:/GitHub/idlcpp/tutorials/Common/Tutorial0.i" namespace tutorial #line 5 "D:/GitHub/idlcpp/tutorials/Common/Tutorial0.i" { #line 6 "D:/GitHub/idlcpp/tutorials/Common/Tutorial0.i" struct Test #line 7 "D:/GitHub/idlcpp/tutorials/Common/Tutorial0.i" { public: #line 8 "D:/GitHub/idlcpp/tutorials/Common/Tutorial0.i" static void Run(); #line 9 "D:/GitHub/idlcpp/tutorials/Common/Tutorial0.i" }; inline void Test::Run() { printf("Hello World!"); } #line 16 "D:/GitHub/idlcpp/tutorials/Common/Tutorial0.i" }
因為在編譯.i文件時指定了-ld選項,所以生成的.h文件中其中有許多#line指令,這是為下一步C++編譯時能夠定位錯誤到.i中的位置,而不是定位到.h上。修改編譯選項,去掉-ld選項,重新編譯,得到的結果如下:
//DO NOT EDIT THIS FILE, it is generated by idlcpp //http://www.idlcpp.org #pragma once #include <stdio.h> namespace tutorial { struct Test { public: static void Run(); }; inline void Test::Run() { printf("Hello World!"); } }
這樣看起來比較清爽了,請和上面的Tutorial0.i內容對照一下,基本上內容差不多。下面詳細解釋一下,首先是第一行
//tutorial
這是注釋,idlcpp和C++一樣用//表示單行注釋,用/**/表示一塊注釋。
$$#include <stdio.h>
idlcpp只分析接口的聲明,而C++頭文件中一般還會出現其他的內容。此處idlcpp提供了將.i文件中的部分內容直接復制到.h的方法,一共有三種
這一行表示將#include <stdio.h>復制到.h中,下面的printf要用到這個頭文件。
namespace tutorial 以及對應的{}。
namespace 和C++中的概念是一樣的,會原樣輸出到.h中。
struct Test 以及對應的{}; 。
這個也和C++中概念類似,會原樣輸出到.h中。
下面.h文件中多了一行
public:
在idlcpp中聲明的數據成員以及成員函數都被認為是public的,所以此處無腦加了這一行。
static void Run(); 這一行兩邊也是一樣的,聲明一個靜態成員函數。
$*
inline void Test::Run()
{
printf("Hello World!");
}
*$
如上所述,idlcpp將$*和*$之間的內容復制到.h中。因為idlcpp只處理函數聲明,不能處理其實現代碼,所以無法向C++一樣將其實現代碼放在類的聲明中,只能寫在外面。此處為了少寫一個.cpp文件,就用內聯函數的方式寫在頭文件中。
文件Tutorial0.ic中沒有實質性的內容。
文件Tutorial0.mh和Tutorial0.mc用於構建對應的元數據信息,具體內容牽涉太多,暫時不做解釋。
再來看一下LuaTutorial0.cpp的內容
#include <tchar.h> #include <string> #include <windows.h> #include "lua.hpp" #include "../../../paf/src/paflua/LuaWrapper.h" #include "../../Common/Tutorial0.h" #include "../../Common/Tutorial0.mh" #include "../../Common/Tutorial0.ic" #include "../../Common/Tutorial0.mc" #if defined(_DEBUG) #pragma comment(lib,"pafcore_d.lib") #pragma comment(lib,"paflua_d.lib") #pragma comment(lib,"lua53_d.lib") #else #pragma comment(lib,"pafcore.lib") #pragma comment(lib,"paflua.lib") #pragma comment(lib,"lua53.lib") #endif void GetExePath(std::string& path) { char fileName[MAX_PATH]; GetModuleFileName(0, fileName, sizeof(fileName)); const char* end = _tcsrchr(fileName, '\\'); path.assign(fileName, end + 1); } int _tmain(int argc, _TCHAR* argv[]) { int error; lua_State *L = luaL_newstate(); luaL_openlibs(L); luaopen_paflua(L); std::string path; GetExePath(path); path += "tutorial0.lua"; error = luaL_loadfile(L, path.c_str()) || lua_pcall(L, 0, 0, 0); if (error) { fprintf(stderr, "%s\n", lua_tostring(L, -1)); lua_pop(L, 1); } lua_close(L); return 0; }
#include "lua.hpp"
這一行引入lua頭文件
#include "../../../paf/src/paflua/LuaWrapper.h"
這一行引入lua插件頭文件
#include "../../Common/Tutorial0.h"
#include "../../Common/Tutorial0.mh"
#include "../../Common/Tutorial0.ic"
#include "../../Common/Tutorial0.mc"
這幾行將由Tutorial0.i編譯的結果包含進來,這樣編譯後就會將對應的元數據信息注冊到系統中,從而能夠讓腳步語言訪問到。
main()函數中是一個運行一個lua腳步的基本過程。其中
luaopen_paflua(L);
這一行在lua虛擬機中加載插件。
最後看一下tutorial0.lua文件的內容
paf.tutorial.Test.Run();
這句代碼表示調用C++中的::tutorial::Test::Run();
所有由idlcpp生成的數據類型都是在paf名字下,可以理解為lua中的名字paf等價於C++中的全局名字空間。在C++中,Run函數的全名可以認為是::tutorial::Test::Run,在lua中即為paf.tutorial.Test.Run。
編譯鏈接後,執行結果如下圖:
可以看到Lua正確調用了C++中的函數。