在上一篇 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++中的函数。