用LuaBridge为Lua绑定C/C++对象

最近为了总结Lua绑定C/C++对象的各种方法、第三方库和原理,学习了LuaBridge库为Lua绑定C/C++对象,下面是学习笔记,实质是对该库的Reference Manual基本上翻译了一遍,学习过程中测试代码,放在我的github上。

LuaBridge的主要特点

源码只有头文件,没有.cpp文件,没有MakeFile,使用时只需一个#include即可。

支持不同的对象生命周期管理模式。

对Lua栈访问方便并且是类型安全的(type-safe)。

Automatic function parameter type binding.

Easy access to Lua objects like tables and functions.

LuaBridge的API是基于C++模板元编程(template metaprogramming)的。在编译时这些模板自动生成各种Lua API调用,从而可以再Lua脚本中使用C++程序中的类和函数。
为了能在C++中使用Lua的数据,比如number,string,table以及方便调用Lua的函数,使用LuaBridge中的LuaRef类,可以方便做到。

LuaBridge设计原则

由于LuaBridge的设计目标尽可能方便使用,比如只有头文件、没有用到高级C++的语法、不需要配置。因此LuaBridge性能虽足够好,但并不是最好的,比如
OOLua(https://code.google.com/p/oolua/)执行效率就比它好,并且它也不像LuaBind(http://www.rasterbar.com/products/luabind.html)那样功能全面。
LuaBridge不支持下面特性:

枚举型常量

不支持8个以上的函数或方法的调用

重载函数、方法和构造函数(Overloaded functions, methods, or constructors)

全局变量(变量必须被包装在命名空间里)

自动地转换STL容器类型和Table

在Lua中继承C++类(Inheriting Lua classes from C++ classes)。

Passing nil to a C++ function that expects a pointer or reference

Standard containers like std::shared_ptr

在Lua访问C++

为了在Lua中使用C++中的数据和函数,LuaBridge要求任何需要使用的数据的都需要注册。LuaBridge可以注册下面五种类型数据:

Namespaces  一个Lua table包含了其他注册信息

Data  全局变量或静态变量、数据成员或静态数据成员

Functions  一般函数、成员函数或静态成员函数

CFunctions  A regular function, member function, or static member function that uses the lua_CFunction calling convention

Properties  Global properties, property members, and static property members. These appear like data to Lua,
but are implemented in C++ using functions to get and set the values.

Data和Properties在注册时被标记为只读(read-only)。这不同于const,这些对象的值能在C++中修改,但不能在Lua脚本中修改。

Namespaces

LuaBridge索引的注册都是在一个namespace中,namespace是从lua角度来看的,它实质上就是table,注意这里的namespace不是C++中的namespace,C++的namespace
不是一定需要的。LuaBridge的namespace是对Lua脚本来说的,它们被作为逻辑组合工具(logical grouping tool)。为了访问Lua的全局命名空间(global namespace),可以在C++
中,这样调用:

[cpp] 
view plain
copy

  1. getGlobalNamespace (L);

上面的调用会返回一个对象(实质是table)可用来进一步注册,比如:

[cpp] 
view plain
copy

  1. getGlobalNamespace (L)
  2. .beginNamespace ("test")

上面的调用就会在Lua的_G中创建一个名为"test"的table,现在这个table还是空的。LuaBridge保留所有以双下划线开头命名的标识,因此__test是无效的命名,
尽管这样命名LuaBridge不会报错。我们可以进一步扩展上面的注册:

[cpp] 
view plain
copy

  1. getGlobalNamespace (L)
  2. .beginNamespace ("test")
  3. .beginNamespace ("detail")
  4. .endNamespace ()
  5. .beginNamespace ("utility")
  6. .endNamespace ()
  7. .endNamespace ();

这样注册后,我们就可以在Lua中使用test, test.detail,和test.utility。这里的引入的endNamespace函数,也会返回一个对象(实质也是table),该对象实质就是上一层namespace,
表示当前namespace注册完成。 All LuaBridge functions which create registrations return an object upon which subsequent registrations can be made,
allowing for an unlimited number of registrations to be chained together using the dot operator。在一个namespace中,注册相同命名的对象,对于LuaBridge来说是没有
定义的行为。一个namespace可以多次使用增加更多的成员。比如下面两段代码是等价的:

[cpp] 
view plain
copy

  1. getGlobalNamespace (L)
  2. .beginNamespace ("test")
  3. .addFunction ("foo", foo)
  4. .endNamespace ();
  5. getGlobalNamespace (L)
  6. .beginNamespace ("test")
  7. .addFunction ("bar", bar)
  8. .endNamespace ();

[cpp] 
view plain
copy

  1. getGlobalNamespace (L)
  2. .beginNamespace ("test")
  3. .addFunction ("foo", foo)
  4. .addFunction ("bar", bar)
  5. .endNamespace ();

Data, Properties, Functions, and CFunctions

Data, Properties, Functions, and CFunctions可以依次使用addVariable,, addProperty, addFunction, and addCFunction来注册。在Lua脚本中调用注册的函数时,
LuaBridge会自动地传入相应的参数,并对参数类型转和检查。同样,函数的返回值也会自动处理。当前LuaBridge最多可处理8个参数。Pointers, references, and objects
of class type as parameters are treated specially。如果我们在C++中有以下定义:

[cpp] 
view plain
copy

  1. int globalVar;
  2. static float staticVar;
  3. std::string stringProperty;
  4. std::string getString () { return stringProperty; }
  5. void setString (std::string s) { stringProperty = s; }
  6. int foo () { return 42; }
  7. void bar (char const*) { }
  8. int cFunc (lua_State* L) { return 0; }

为了在Lua使用这些变量和函数,我们可以按以下方式注册它们:

[cpp] 
view plain
copy

  1. getGlobalNamespace (L)
  2. .beginNamespace ("test")
  3. .addVariable ("var1", &globalVar)
  4. .addVariable ("var2", &staticVar, false)     // read-only
  5. .addProperty ("prop1", getString, setString)
  6. .addProperty ("prop2", getString)            // read only
  7. .addFunction ("foo", foo)
  8. .addFunction ("bar", bar)
  9. .addCFunction ("cfunc", cFunc)
  10. .endNamespace ();

Variables在注册时,可以通过传递第二个参数为false,确保Variables不会在Lua被修改,默认第二个参数是true。Properties在注册时,若不传递set函数,则在脚本中是read-only。

通过上面注册后,则下面表达式在Lua是有效的:

[cpp] 
view plain
copy

  1. test        -- a namespace,实质就是一个table,下面都是table中的成员
  2. test.var1   -- a lua_Number variable
  3. test.var2   -- a read-only lua_Number variable
  4. test.prop1  -- a lua_String property
  5. test.prop2  -- a read-only lua_String property
  6. test.foo    -- a function returning a lua_Number
  7. test.bar    -- a function taking a lua_String as a parameter
  8. test.cfunc  -- a function with a variable argument list and multi-return

注意test.prop1和test.prop2引用的C++中同一个变量,然后test.prop2是read-only,因此在脚本中对test.prop2赋值,会导致运行时错误(run-time error)。在Lua按以下方式使用:

[cpp] 
view plain
copy

  1. test.var1 = 5         -- okay
  2. test.var2 = 6         -- error: var2 is not writable
  3. test.prop1 = "Hello"  -- okay
  4. test.prop1 = 68       -- okay, Lua converts the number to a string.
  5. test.prop2 = "bar"    -- error: prop2 is not writable
  6. test.foo ()           -- calls foo and discards the return value
  7. test.var1 = foo ()    -- calls foo and stores the result in var1
  8. test.bar ("Employee") -- calls bar with a string
  9. test.bar (test)       -- error: bar expects a string not a table

Class Objects

类的注册是以beginClass或deriveClass开始,以endClass结束。一个类注册完后,还可以使用beginClass重新注册更多的信息,但是deriveClass只能被使用一次。为了给已经用deriveClass注册的类,注册更多的信息,可以使用beginClass。

[cpp] 
view plain
copy

  1. class A {
  2. public:
  3. A() { printf("A constructor\n");}
  4. static int staticData;
  5. static int getStaticData() {return staticData;}
  6. static float staticProperty;
  7. static float getStaticProperty () { return staticProperty; }
  8. static void setStaticProperty (float f) { staticProperty = f; }
  9. static int staticCFunc (lua_State *L) { return 0; }
  10. std::string dataMember;
  11. char dataProperty;
  12. char getProperty () const { return dataProperty; }
  13. void setProperty (char v) { dataProperty = v; }
  14. void func1 () {printf("func1 In Class A\n"); }
  15. virtual void virtualFunc () {printf("virtualFunc In Class A\n");  }
  16. int cfunc (lua_State* L) { printf("cfunc In Class A\n");  return 0; }
  17. };
  18. class B : public A {
  19. public:
  20. B() { printf("B constructor\n");}
  21. double dataMember2;
  22. void func1 () {printf("func1 In Class B\n"); }
  23. void func2 () { printf("func2 In Class B\n"); }
  24. void virtualFunc () {printf("virtualFunc In Class B\n");  }
  25. };
  26. int A::staticData = 3;
  27. float A::staticProperty = 0.5;

按下面方式注册:

[cpp] 
view plain
copy

  1. getGlobalNamespace (L)
  2. .beginNamespace ("test")
  3. .beginClass<A>("A")
  4. .addConstructor <void (*) (void)> ()
  5. .addStaticData ("staticData", &A::staticData)
  6. .addStaticProperty ("staticProperty", &A::getStaticData)
  7. .addStaticFunction ("getStaticProperty", &A::getStaticProperty) //read-only
  8. .addStaticCFunction ("staticCFunc", &A::staticCFunc)
  9. .addData ("data", &A::dataMember)
  10. .addProperty ("prop", &A::getProperty, &A::setProperty)
  11. .addFunction ("func1", &A::func1)
  12. .addFunction ("virtualFunc", &A::virtualFunc)
  13. .addCFunction ("cfunc", &A::cfunc)
  14. .endClass ()
  15. .deriveClass<B, A>("B")
  16. .addConstructor <void (*) (void)> ()
  17. .addData ("data", &B::dataMember2)
  18. .addFunction ("func1", &B::func1)
  19. .addFunction ("func2", &B::func2)
  20. .endClass ()
  21. .endNamespace ();

注册后,可以再Lua脚本中按一下方式使用:

[cpp] 
view plain
copy

  1. local AClassObj = test.A ()  --create class A instance
  2. print("before:",test.A.staticData) -- access class A static member
  3. test.A.staticData = 8  -- modify class A static member
  4. print("after:",test.A.staticData)
  5. print("before:", test.A.getStaticProperty())
  6. --test.A.staticProperty = 1.2 --error:can not modify
  7. print("staticCFunc")
  8. test.A.staticCFunc()
  9. AClassObj.data = "sting"
  10. print("dataMember:",AClassObj.data)
  11. AClassObj.prop = ‘a‘
  12. print("property:",AClassObj.prop)
  13. AClassObj:func1()
  14. AClassObj:virtualFunc()
  15. AClassObj:cfunc()
  16. BClassObj = test.B()
  17. BClassObj:func1()
  18. BClassObj:func2()
  19. BClassObj:virtualFunc()

其输出结果为:

[cpp] 
view plain
copy

  1. A constructor
  2. before: 3
  3. after:  8
  4. before: 0.5
  5. staticCFunc
  6. dataMember:     sting
  7. property:       a
  8. func1 In Class A
  9. virtualFunc In Class A
  10. cfunc In Class A
  11. A constructor
  12. B constructor
  13. func1 In Class B
  14. func2 In Class B
  15. virtualFunc In Class B

类的方法注册类似于通常的函数注册,虚函数也是类似的,没有特殊的语法。在LuaBridge中,能识别const方法并且在调用时有检测的,因此如果一个函数返回一个const object或包含指向const object的数据给Lua脚本,则在Lua中这个被引用的对象则被认为是const的,它只能调用const的方法。对于每个类,析构函数自动注册的。无须在继承类中重新注册已在基类中注册过的方法。If a class has a base class that is **not** registeredwith Lua, there is no need to declare it as a subclass.

Constructors

为了在Lua中,创建类的对象,必须用addConstructor为改类注册构造函数。并且LuaBridge不能自动检测构造函数的参数个数和类型(这与注册函数或方法能自动检测是不同的),因此在用注册addConstructor时必须告诉LuaBridge在Lua脚本将用到的构造函数签名,例如:

[cpp] 
view plain
copy

  1. struct A {
  2. A ();
  3. };
  4. struct B {
  5. explicit B (char const* s, int nChars);
  6. };
  7. getGlobalNamespace (L)
  8. .beginNamespace ("test")
  9. .beginClass <A> ("A")
  10. .addConstructor <void (*) (void)> ()
  11. .endClass ()
  12. .beginClass <B> ("B")
  13. .addConstructor <void (*) (char const*, int)> ()
  14. .endClass ();
  15. .endNamespace ()

在Lua中,就可以一些方式,创建A和B的实例:

[cpp] 
view plain
copy

  1. a = test.A ()           -- Create a new A.
  2. b = test.B ("hello", 5) -- Create a new B.
  3. b = test.B ()           -- Error: expected string in argument 1

lua_State*

有时候绑定的函数或成员函数,需要lua_State*作为参数来访问栈。使用LuaBridge,只需要在将要绑定的函数最后添加lua_State*类型的参数即可。比如:

[cpp] 
view plain
copy

  1. void useStateAndArgs (int i, std::string s, lua_State* L);
  2. getGlobalNamespace (L).addFunction ("useStateAndArgs", &useStateAndArgs);

在Lua中,就可按以下方式使用:

[cpp] 
view plain
copy

  1. useStateAndArgs(42,"hello")

在脚本中,只需传递前面两个参数即可。注意 lua_State*类型的参数就放在定义的函数最后,否则结果是未定义的。

Class Object Types

一个注册的类型T,可能以下方式传递给Lua脚本:

[cpp] 
view plain
copy

  1. `T*` or `T&`:       Passed by reference, with _C++ lifetime_.
  2. `T const*` or `T const&`:   Passed by const reference, with _C++ lifetime_.
  3. `T` or `T const`:       Passed by value (a copy), with _Lua lifetime_.

C++ Lifetime

对于C++ lifetime的对象,其创建和删除都由C++代码控制,Lua GC不能回收这些对象。当Lua通过lua_State*来引用对象时,必须确保该对象还没删除,否则将导致未定义的行为。例如,可按以下方法给Lua传递

C++ lifetime的对象:

[cpp] 
view plain
copy

  1. A a;
  2. push (L, &a);             // pointer to ‘a‘, C++ lifetime
  3. lua_setglobal (L, "a");
  4. push (L, (A const*)&a);   // pointer to ‘a const‘, C++ lifetime
  5. lua_setglobal (L, "ac");
  6. push <A const*> (L, &a);  // equivalent to push (L, (A const*)&a)
  7. lua_setglobal (L, "ac2");
  8. push (L, new A);          // compiles, but will leak memory
  9. lua_setglobal (L, "ap");

Lua Lifetime

当C++通过值传递给Lua一个对象时,则该对象是Lua lifetime。在值传递时,该对象将在Lua中以userdata形式保存,并且当Lua不再引用该对象时,该对象可以被GC回收。当userdata被回收时,其相应对象的

析构函数也会被调用。在C++中应用lua lifetime的对象时,必须确保该对象还没被GC回收,否则其行为是未定义的。例如,可按以下方法给Lua传递的是Lua lifetime的催下:

[cpp] 
view plain
copy

  1. B b;
  2. push (L, b);                    // Copy of b passed, Lua lifetime.
  3. lua_setglobal (L, "b");

当在Lua中调用注册的构造函数创建一个对象时,该对象同样是Lua lifetime的,当该对象不在被引用时,GC会自动回收该对象。当然你可以把这个对象引用作为参数传递给C++,但需要保证C++在通过引用使用该对象时,

改对还没有被GC回收。

 Pointers, References, and Pass by Value

当C++对象作为参数从Lua中传回到C++代码中时,LuaBridge会尽可能做自动转换。比如,向Lua中注册了以下C++函数:

[cpp] 
view plain
copy

  1. void func0 (A a);
  2. void func1 (A* a);
  3. void func2 (A const* a);
  4. void func3 (A& a);
  5. void func4 (A const& a);

则在Lua中,就可以按以下方式调用上面的函数:

[cpp] 
view plain
copy

  1. func0 (a)   -- Passes a copy of a, using A‘s copy constructor.
  2. func1 (a)   -- Passes a pointer to a.
  3. func2 (a)   -- Passes a pointer to a const a.
  4. func3 (a)   -- Passes a reference to a.
  5. func4 (a)   -- Passes a reference to a const a.

上面所有函数,都可以通过a访问对象的成员以及方法。并且通常的C++的继承和指针传递规则也使用。比如:

[cpp] 
view plain
copy

  1. void func5 (B b);
  2. void func6 (B* b);

在lua中调用:

[cpp] 
view plain
copy

  1. func5 (b)   - Passes a copy of b, using B‘s copy constructor.
  2. func6 (b)   - Passes a pointer to b.
  3. func6 (a)   - Error: Pointer to B expected.
  4. func1 (b)   - Okay, b is a subclass of a.

当C++给Lua传递的指针是NULL时,LuaBridge会自动转换为nil代替。反之,当Lua给C++传递的nil,相当于给C++传递了一个NULL指针。

时间: 2024-08-06 06:29:30

用LuaBridge为Lua绑定C/C++对象的相关文章

c++对象与lua绑定

2015.1.29 wqchen. 转载请注明出处 http://www.cnblogs.com/wqchen/ 本文主要探讨c++的类对象和lua脚本的绑定使用,读者需要有一定的lua以及lua的c api接口知识:). 如果你使用过c/c++和lua混合编程,那么肯定会熟悉宿主(c/c++)与脚本(lua)之间函数的注册与调用.userdata等等方面知识.宿主对象与脚本的绑定使用,其实可以看作是userdata与注册函数的整合,只不过多了一层语法糖.下面我们一起来分析一下这层语法糖是怎样实

lua绑定C++对象系列一——基础知识

本文主要介绍lua绑定C++对象的原理和方法,并能在C/C++定义类和方法,在lua中创建C++类的句柄实例,像面向对象一样去使用C++类实例.为了便于大家理解,系列文章会从基础知识讲解,并通过多个版本的进化,一步步完成从基础到多版本实践的完美结合和深入,彻底理解lua绑定C++对象的原理方法.在阅读本系列文章前,需要具备一定的lua开发经验以及lua与C/C++相互调用操作的知识. 1.基础C/C++和Lua的相互引用调用 我们知道C和lua相互调用,是通过虚拟栈进行数据传递通信的,基础介绍介

cocos2dx v3.x lua绑定分析

打算新项目转到cocos2dx v3上了,下载代码浏览过后发现改动真是非常大,结构性调整很多. 比如tolua绑定这一块,就几乎全翻新了. 胶水代码的生成,改成了全自动式的,通过clang来分析c++代码,可以准确的知道每一个类.函数.参数的信息,再也不用手动写pkg文件了. 运行期对象管理这块,似乎也有了不少改动,至少我原来的一些扩展代码运行不了了,还没来得及细看,待看完再一一录下. 先记录一下目前已看清楚的[类名表.类元表.对象实例]之间的关系: 1.类元表:最核心的表,在lua代码里是不可

cocos2d-x lua绑定解析

花了几天时间看了下cocos2d-x lua绑定那块,总算是基本搞明白了,下面分三部分解析lua绑定: 一.lua绑定主要用到的底层函数 lua绑定其本质就是有一个公用的lua_Stack来进行C和Lua之间的值传递,在路径[项目根目录]\frameworks\cocos2d-x\external\lua\luajit\include下有个lua.h文件,大部分lua绑定底层函数以及相关的常量都在这里. 1.lua堆栈常量 #define LUA_REGISTRYINDEX (-10000) /

cocos2dx lua 绑定之二:手动绑定自定义类中的函数

cococs2dx 3.13.1 + vs2013 + win10 1.首先按照<cocos2dx lua 绑定之一:自动绑定自定义类>绑定Student类 2.在Student类中增加一个用于测试手动绑定的函数manual_call ①Student.h中增加函数 //手动绑定调用函数 void manual_call(); ②Student.cpp中增加函数实现 //和自动绑定相比,只增加了这个函数 void Student::manual_call() { std::cout <&

使用cocos2d脚本生成lua绑定

这几天要老大要求把DragonBones移到cocos2dx 3.0 里边,并且绑定lua使用接口.因为刚学lua,使用的引擎也刚从2.2改为3.0,各种不熟悉,折腾了好几天才弄完,有空了总结一下 这篇先说一下cocos2d生成lua绑定的修改,有空的话再写一篇lua中注册回调到c++中方法 我的目录结构 假设我的目录名称是DragonBones -Cocosdx目录 -DragonBones  -c代码 -c代码头 -tools  db_DragonBones.ini genbindings.

【转】如何做dragonbones的lua绑定(VisualStudio)

原文:<如何做dragonbones的lua绑定(VisualStudio)>(不完善和错误的地方我已做红字修改) 最近好多同学在QQ群里问怎么在lua项目中使用DB(DrgonBones,龙骨),为了帮助更多的人,同时也好让更多的人跟容易使用DB,这里详细记录coco2dx-3.2版本对应DB的lua绑定. 首先要说明下,本文章对应的cocos2dx-3.2版本,其他cocos2dx-3.x版本跟3.2版本类似.这里假设自己使用cocos命令行创建的lua项目,而且没有修改过目录结构,如果修

【转】如何做dragonbones的lua绑定(Android)

这篇写dragonbones的lua绑定之Android部分,不知道怎么在VS(Visual Studio)中绑定的话请看如何在lua项目中使用dragonbones.有了上篇的基础,这次再做Android就比较简单了.注意:ndk9b不能编译通过,我这里使用的是ndk9d,其他版面没有测试. 修改 Application.mk 文件路径:MyLuaGame/frameworks/runtime-src/proj.android/jni/Application.mk 添加预定义宏 APP_CPP

如何做dragonbones的lua绑定(mac)

部分文件需要如何做dragonbones的lua绑定(VisualStudio) 支持 a. 将dragonbones(cocos2d_libs), lua_dragonbones_auto.cpp,lua_dragonbones_auto.hpp(cocos2d_lua_bindings/auto)添加到工程 b.  cocos2d_libs/project/Build Settings/Search Paths/User Header Search Paths增加 $(SRCROOT)/..