在C++中使用Lua(三)
例六,使用C++包装类
尽管用Lua的C API已经可以方便地写出与Lua交互的程序了,不过对于用惯C++的人来说还是更愿意用C++的方式来解决问题。于是开源社区就出现了不少Lua C API的C++的wrap,比如:LuaBind,LuaPlus,toLua
这里介绍的是LuaBind库,下载
它在Windows下貌似只能支持MSVC和ICC,好在Lua可以支持动态库的载入,所以用VC+LuaBind写Lua库,再用C++Builder调用也是个好主意。
在VC使用LuaBind的方法是把LuaBind的src目录下的cpp文件加入工程(当然也可以先编译成静态库),加入Lua库,设置LuaBind,Lua和Boost的头文件路径。
头文件:
1. //Lua头文件
2. extern "C"
3. {
4. #include <lua.h>
5. #include <lualib.h>
6. #include <lauxlib.h>
7. }
8. //LuaBind头文件
9. #include <luabind/luabind.hpp>
在C++中调用Lua函数
调用Lua函数那是最简单不过的事情了,用LuaBind的call_function()模板函数就可以了:
1. int main(
2. // 建立新的Lua环境
3. lua_State *myLuaState = luaL_newstate();
4.
5. // 让LuaBind“认识”这个Lua环境
6. luabind::open(myLuaState);
7.
8. // 定义一个叫add的Lua函数
9. luaL_dostring(
10. myLuaState,
11. "function add(first, second) "
12. " return first + second "
13. "end "
14. );
15.
16. //调用add函数
17. cout << "Result: "
18. << luabind::call_function<int>(myLuaState, "add", 2, 3)
19. << endl;
20.
21. lua_close(myLuaState);
22.}
在本例中我们先使用Lua C API产生一个Lua线程环境,然后调用luabind::open()让LuaBind关联这个线程环境,在使用LuaBind之前这步是必须做的,它要在Lua环境中注册一些LuaBind专用的数据。
在执行完Lua代码之后,我们使用luabind::call_function<int>调用了Lua里的add函数,返回值是int。
在Lua代码中调用C++函数
从前面的文章里我们知道在Lua调用C函数需要经常操作栈,而LuaBind帮我们做了这些工作,下面的例子把print_hello函数送给Lua脚本调用:
1. void print_hello(int number) {
2. cout << "hello world " << number << endl;
3. }
4.
5. int main(
6. // 建立新的Lua环境
7. lua_State *myLuaState = lua_open();
8.
9. // 让LuaBind“认识”这个Lua环境
10. luabind::open(myLuaState);
11.
12. // 添加print_hello函数到Lua环境中
13. luabind::module(myLuaState) [
14. luabind::def("print_hello", print_hello)
15. ];
16.
17. // 现在Lua中可以调用print_hello了
18. luaL_dostring(
19. myLuaState,
20. "print_hello(123) "
21. );
22.
23. lua_close(myLuaState);
24.}
向Lua环境加入函数或其它东东的方法是:
luabind::module(lua_State* L, char const* name = 0) [
...
];
其中module函数中的第二个指定要加入的东东所处的名空间(其实就是table),如果为0,则处于全局域之下。
在中括号里的luabind::def把print_hello函数提供给Lua环境,第一个参数是Lua中使用的函数名。
如果要定义多个函数,可以使用逗号分隔。
在Lua代码中使用C++类
如果我们直接使用Lua C API向Lua脚本注册一个C++类,一般是使用userdata+metatable的方法,就象我们在例五中做的一样。这样做尽管难度不大,却非常繁琐而且不方便维护。
使用LuaBind我们就可以更方便地向Lua脚本注册C++类了,例:
1. class NumberPrinter {
2. public:
3. NumberPrinter(int number) :
4. m_number(number) {}
5.
6. void print() {
7. cout << m_number << endl;
8. }
9.
10. private:
11. int m_number;
12.};
13.
14.int main() {
15. lua_State *myLuaState = lua_open();
16. luabind::open(myLuaState);
17.
18. // 使用LuaBind导出NumberPrinter类
19. luabind::module(myLuaState) [
20. luabind::class_<NumberPrinter>("NumberPrinter")
21. .def(luabind::constructor<int>())
22. .def("print", &NumberPrinter::print)
23. ];
24.
25. // 现在Lua中可以使用NumberPinter类了
26. luaL_dostring(
27. myLuaState,
28. "Print2000 = NumberPrinter(2000) "
29. "Print2000:print() "
30. );
31.
32. lua_close(myLuaState);
33.}
为了注册一个类,LuaBind提供了class_类。它有一个重载过的成员函数 def() 。这个函数被用来注册类的成员函数、操作符、构造器、枚举和属性。
它将返回this指针,这样我们就可以方便地直接注册更多的成员。
属性
LuaBind 也可以导出类成员变量:
1. template<typename T>
2. struct Point {
3. Point(T X, T Y) :
4. X(X), Y(Y) {}
5.
6. T X, Y;
7. };
8.
9. template<typename T>
10.struct Box {
11. Box(Point<T> UpperLeft, Point<T> LowerRight) :
12. UpperLeft(UpperLeft), LowerRight(LowerRight) {}
13.
14. Point<T> UpperLeft, LowerRight;
15.};
16.
17.int main() {
18. lua_State *myLuaState = lua_open();
19. luabind::open(myLuaState);
20.
21. // 使用LuaBind导出Point<float>类和Box<float>类
22. luabind::module(myLuaState) [
23. luabind::class_<Point<float> >("Point")
24. .def(luabind::constructor<float, float>())
25. .def_readwrite("X", &Point<float>::X)
26. .def_readwrite("Y", &Point<float>::Y),
27.
28. luabind::class_<Box<float> >("Box")
29. .def(luabind::constructor<Point<float>, Point<float> >())
30. .def_readwrite("UpperLeft", &Box<float>::UpperLeft)
31. .def_readwrite("LowerRight", &Box<float>::LowerRight)
32. ];
33.
34. // 现在Lua中可以使用为些类了
35. luaL_dostring(
36. myLuaState,
37. "MyBox = Box(Point(10, 20), Point(30, 40)) "
38. "MyBox.UpperLeft.X = MyBox.LowerRight.Y "
39. );
40.
41. lua_close(myLuaState);
42.}
本例中使用def_readwrite定义类成员,我们也可以用def_readonly把类成员定义成只读。
LuaBind还可以把C++类导出成支持getter和setter的属性的Lua类:
1. struct ResourceManager {
2. ResourceManager() :
3. m_ResourceCount(0) {}
4.
5. void loadResource(const string &sFilename) {
6. ++m_ResourceCount;
7. }
8. size_t getResourceCount() const {
9. return m_ResourceCount;
10. }
11.
12. size_t m_ResourceCount;
13.};
14.
15.int main() {
16. lua_State *myLuaState = lua_open();
17. luabind::open(myLuaState);
18.
19. // 导出类,在Lua中调用ResourceCount属性会调用C++中的ResourceManager::getResourceCount
20. // 属性定义有点象C++Builder里的__property定义,呵呵
21. luabind::module(myLuaState) [
22. luabind::class_<ResourceManager>("ResourceManager")
23. .def("loadResource", &ResourceManager::loadResource)
24. .property("ResourceCount", &ResourceManager::getResourceCount)
25. ];
26.
27. try {
28. ResourceManager MyResourceManager;
29.
30. // 把MyResourceManager定义成Lua的全局变量
31. luabind::globals(myLuaState)["MyResourceManager"] = &MyResourceManager;
32.
33. // 调用
34. luaL_dostring(
35. myLuaState,
36. "MyResourceManager:loadResource(\"abc.res\") "
37. "MyResourceManager:loadResource(\"xyz.res\") "
38. " "
39. "ResourceCount = MyResourceManager.ResourceCount "
40. );
41.
42. // 读出全局变量
43. size_t ResourceCount = luabind::object_cast<size_t>(
44. luabind::globals(myLuaState)["ResourceCount"]
45. );
46. cout << ResourceCount << endl;
47. }
48. catch(const std::exception &TheError) {
49. cerr << TheError.what() << endl;
50. }
51.
52. lua_close(myLuaState);
53.}
附: Lua语法简介
1.语法约定
Lua语句用分号结尾,不过如果不写分号,Lua也会自己判断如何区分每条语句
如:
a=1 b=a*2 --这样写没有问题,但不太好看。
建议一行里有多个语句时用分号隔开
变量名、函数名之类的命名规则与C语言一样:由字母,下划线和数字组成,但第一个字符不能是数字。并且不能和Lua的保留字相同。
Lua是大小写敏感的
使用两个减号--作为单行注释符,多行注释使用--[[...--]]
2.类型
Lua是动态类型语言,变量不要类型定义。Lua中有8个基本类型分别为:nil、boolean、number、string、userdata、function、thread和table。
同一变量可以随时改变它的类型,如:
1. a = 10 --number
2. a = "hello" --string
3. a = false --boolean
4. a = {10,"hello",false} --table
5. a = print --function
使用type函数可以得到变量当前的类型,如print(type(a));
nil 所有没有被赋值过的变量默认值为nil,给变量赋nil可以收回变量的空间。
boolean 取值false和true。但要注意Lua中所有的值都可以作为条件。在控制结构的条件中除了false和nil为假,其他值都为真。所以Lua认为0和空串都是真。(注意,和C不一样哦)
number 表示实数,Lua中没有整数。不用担心实数引起的误差,Lua的numbers可以处理任何长整数。
string 字符串,Lua中的字符串可以存放任何包括0在内的二进制数据。可以使用单引号或双引号表示字符串,和C一样使用\作为转义符。也可以使用或 [[...]]表示字符串,它可以表示多行,而且不解释转义符(也可以是[=[...]=]、[==[]==]、...用于适应各种类型字符串)。另外要注意的是Lua中字符串是不可以修改的。
function 函数,Lua中的函数也可以存储到变量中,可以作为其它函数的参数,可以作为函数的返回值。
table 表,表是Lua特有的功能强大的东东,它是Lua中唯一的一种数据结构,它可以用来描述数组,结构,map的功能。
userdata userdata类型用来将任意C语言数据保存在Lua变量中。例如:用标准I/O库来描述文件。
thread 线程。由coroutine表创建的一种数据类型,可以实现多线程协同操作。
3.表达式
算术运行符: 加+、减-、乘*、除/、幂^
关系运算符:小于<、大于>、小于等于<=、大于等于>=、等于==、不等~=
逻辑运算符:与and、或or、非not
and和or的运算结果返回值是其中的操作数:
a and b -- 如果a为false,则返回a,否则返回b
a or b -- 如果a为true,则返回a,否则返回b
所以C中的三元运算符a?b:c在Lua中可以这样写:(a and b) or c
连接运算符:连续两个小数点..,如:
"hello" .. "world" 结果是 "helloworld"
0 .. 1 结果是 "01",当在一个数字后面写..时,必须加上空格以防止被解释错。
取长度操作符:一元操作 #
字符串的长度是它的字节数,table 的长度被定义成一个整数下标 n,它满足 t[n] 不是 nil 而 t[n+1] 为nil。
4.基本语法
赋值
a = a + 1
Lua里的赋值还可以同时给多个变量赋值。变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量。如:
a, b = 10, 2*x --相当于a=10; b=2*x
x, y = y, x --交换x和y
如果赋值符左右个数不同时,Lua会自动丢弃多余值或以nil补足
局部变量
local i = 10
使用local声明局部变量,局部变量只在所在的代码块内有效。
如果不声明,默认为全局变量,这个变量在所有Lua环境中有效。
代码块是指一个控制结构内,一个函数体,或者一个chunk(变量被声明的那个文件或者文本串),也可以直接使用do...end(相当于C中的{})。
条件
1. if 条件 then
2. then-part
3. elseif 条件n then
4. elseif-part
5. .. --->多个elseif
6. else
7. else-part
8. end;
循环
Lua中的循环有:while循环,repeat-until循环,for循环和for in循环。
循环中可以用break跳出,Lua语法要求break和return只能是代码块的最后一句(放心,正常的代码都是满足这个要求的,break和 reuturn后面即使有代码也是执行不到的,再说了,大不了自己加个do...end好了^_^)
如:
1. local i = 1
2. while a[i] do
3. if a[i] == v then break end
4. i = i + 1
5. end
while循环
1. while condition do
2. statements;
3. end;
repeat-until循环:
1. repeat
2. statements;
3. until conditions;
for循环
1. for var=exp1,exp2,exp3 do
2. loop-part
3. end
for将用exp3作为step从exp1(初始值)到exp2(终止值),执行loop-part。其中exp3可以省略,默认step=1
for in循环
1. for 变量 in 集合 do
2. loop-part
3. end
实际上,
for var_1, ..., var_n in explist do block end
等价于
1. do
2. local _f, _s, _var = explist
3. while true do
4. local var_1, ... , var_n = _f(_s, _var)
5. _var = var_1
6. if _var == nil then break end
7. block
8. end
9. end
如:
1. a = {"windows","macos","linux",n=3}
2. for k,v in pairs(a) do print(k,v) end
5.函数
1. function 函数名 (参数列表)
2. statements-list;
3. end;
函数也可以一次返回多个值,如:
1. function foo() return 'a','b','c'; end
2. a,b,c = foo()
在Lua中还可以直接定义匿名函数,如
print((function() return 'a','b','c' end)())