zoukankan      html  css  js  c++  java
  • lua绑定C++对象系列四——luna模板

    在系列文章二三中描述的绑定C++对象基础篇和进阶篇,都有一个很大的问题,就是每个类需要写大量的代码,从类的元表创建、方法注册到实例创建,都需要自己重复写类似的代码。如果涉及N个不同类,会有大量重复的代码,能否创建一个模板类,把这些重复的代码进行简化,通过模板的方式绑定成不同的类?下面的luna<T>就是完成这样一个壮举,例如针对Car类,只需要luna<Car>::regist(L)即可完成注册。在lua层面 local car = Car()就能自动创建Car对象,然后方便的通过car.xxx()调用成员方法。

    代码文件luna.h

      1 #include <iostream>
      2 #include <cstring>
      3 extern "C" {
      4 #include <lua.h>
      5 #include <lualib.h>
      6 #include <lauxlib.h>
      7 }
      8 
      9 using namespace std;
     10 
     11 #define DECLARE_LUNA_CLASS(obj) 
     12     static const char *name;
     13     static luna<obj>::TMethod methods[];
     14 
     15 #define EXPORT_LUNA_FUNCTION_BEGIN(obj) 
     16     const char* obj::name = #obj;
     17     luna<obj>::TMethod obj::methods[] = {
     18 
     19 #define EXPORT_LUNA_MEMBER_INT(obj, member) 
     20     {#member, nullptr},
     21 
     22 #define EXPORT_LUNA_FUNCTION(obj, func) 
     23     {#func, &obj::func},
     24 
     25 #define EXPORT_LUNA_FUNCTION_END(obj) 
     26     {nullptr, nullptr}
     27     };
     28 
     29 template<typename T>
     30 class luna
     31 {
     32     public:
     33         typedef struct {T* _u;} TObject;
     34         typedef int (T::*TPfn)(lua_State* L); 
     35         typedef struct {const char* name; TPfn pf;} TMethod;
     36     public:
     37         static int regist(lua_State* L); 
     38         static int create(lua_State* L); 
     39         static int call(lua_State* L); 
     40         static int gc(lua_State* L); 
     41 };
     42 
     43 template<typename T>
     44 int luna<T>::regist(lua_State* L)
     45 {
     46     //原表Shape
     47     if (luaL_newmetatable(L, T::name))
     48     {   
     49         //注册Shape到全局
     50         lua_newtable(L);
     51         lua_pushvalue(L, -1);
     52         lua_setglobal(L, T::name);
     53 
     54         //设置Shape的原表,主要是__call,使其看起来更像C++初始化
     55         lua_newtable(L);
     56         lua_pushcfunction(L, luna<T>::create);
     57         lua_setfield(L, -2, "__call");
     58         lua_setmetatable(L, -2);
     59         lua_pop(L, 1); //这时候栈只剩下元表
     60 
     61         //设置元表Shape index指向自己
     62         lua_pushvalue(L, -1);
     63         lua_setfield(L, -2, "__index");
     64         lua_pushcfunction(L, luna<T>::gc);
     65         lua_setfield(L, -2, "__gc");
     66     }
     67     return 0;
     68 }
     69 
     70 template<typename T>
     71 int luna<T>::create(lua_State* L)
     72 {
     73     lua_remove(L, 1);
     74     TObject* p = (TObject*)lua_newuserdata(L, sizeof(TObject));
     75     p->_u = new T();
     76 
     77     luaL_getmetatable(L, T::name);
     78     lua_setmetatable(L, -2);
     79 
     80     luaL_getmetatable(L, T::name);
     81     for (auto* l = T::methods; l->name; l++)
     82     {
     83         lua_pushlightuserdata(L,(void*)l);
     84         lua_pushlightuserdata(L,(void*)p);
     85         lua_pushcclosure(L, luna<T>::call, 2);
     86         lua_setfield(L, -2, l->name);
     87     }
     88 
     89     lua_pop(L, 1);
     90 
     91     return 1;
     92 }
     93 
     94 template<typename T>
     95 int luna<T>::call(lua_State* L)
     96 {
     97     TMethod* v = (TMethod*)lua_topointer(L, lua_upvalueindex(1));
     98     cout<<"luna<T>::call:"<<v->name<<endl;
     99 
    100     TObject* p = (TObject*)lua_topointer(L, lua_upvalueindex(2));
    101 
    102 
    103     return ((p->_u)->*(v->pf))(L);
    104 }
    105 
    106 template<typename T>
    107 int luna<T>::gc(lua_State* L)
    108 {
    109     TObject* p = (TObject*)lua_touserdata(L, 1);
    110     (p->_u)->~T();
    111     return 0;
    112 }

    通过上述代码发现:luna<T>模板类,把一些行为固化下来了。主要改进有几点:

    1、  通过EXPORT_LUNA_XXX系列宏定义,把每个业务类需要name和methods列表固化下来,尽可能减少业务层工作量,避免出错。

    2、  通过模板的方式,抽象出regist、create、call、gc几个公共接口,流程极为简单。所有不同类都遵循这样的原则。其中:

    • Luna<T>::regist: 注册T::name元表和T::name全局表,成员函数注册到元表,统一通过闭包的方式注册到一个公共的调用函数call进行分发调用。全局表T::name只保留__call方法,只是为了保留类似local car = Car() 这种类C++的初始化方式。
    • Luna<T>::create:在lua层使用local car = Car()创建对象实例时,注册成员函数,并且通过闭包的形式把成员method地址和对象指针都通过pushcclosure绑定在一起
    • Luna<T>::call: 当通过car.xxx()调用成员函数时,通过触发call函数,因为闭包的upvalue不同,通过upvalue包含的不同的method信息,也能取到实例句柄,就能触发不同的成员函数调用,相当于统一通过call进行派发。
    • Luna<T>::gc:注册元表的__gc方法,当跟对象实例绑定的userdata被gc回收时,会触发gc调用。

    整体结构如如下:

    代码文件r_luna.cpp

     1 #include <iostream>
     2 #include <cstring>
     3 #include <stdlib.h>
     4 extern "C" {
     5 #include <lua.h>
     6 #include <lualib.h>
     7 #include <lauxlib.h>
     8 }
     9 #include "comm.h"
    10 #include "luna.h"
    11 #include "lunar.h"
    12 
    13 using namespace std;
    14 
    15 
    16 class Car 
    17 {
    18     public:
    19         Car(){}
    20         ~Car(){cout<<"Delete Car,Length:"<<length<<" Width:"<<width<<endl;}
    21 
    22         int getLength(lua_State *L){
    23             lua_pushinteger(L, length);
    24             return 1;
    25         }   
    26         int getWidth(lua_State *L){
    27             lua_pushinteger(L, width);
    28             return 1;
    29         }   
    30         int setLength(lua_State *L) 
    31         {   
    32             int val = lua_tointeger(L, -1);
    33             length = val;
    34             return 0;
    35         }   
    36         int setWidth(lua_State *L) 
    37         {   
    38             int val = lua_tointeger(L, -1);
    39             width = val;
    40             return 0;
    41         }   
    42         int print(lua_State *L) 
    43         {   
    44             cout <<"print length:"<<length<<" "<<width<<endl;
    45         }   
    46     public:
    47         DECLARE_LUNA_CLASS(Car);
    48     public:
    49         int length = 100;
    50         int width = 200;
    51 };
    52 
    53 EXPORT_LUNA_FUNCTION_BEGIN(Car)
    54 EXPORT_LUNA_FUNCTION(Car, getLength)
    55 EXPORT_LUNA_FUNCTION(Car, getWidth)
    56 EXPORT_LUNA_FUNCTION(Car, setLength)
    57 EXPORT_LUNA_FUNCTION(Car, setWidth)
    58 EXPORT_LUNA_FUNCTION(Car, print)
    59 EXPORT_LUNA_FUNCTION_END(Car)
    60 
    61 int main(int argc, char* argv[])
    62 {
    63     lua_State *L = luaL_newstate();
    64     luaL_openlibs(L);
    65 
    66     luaL_dofile(L, "tree.lua");
    67 
    68     //use luna template and bind to object
    69     luna<Car>::regist(L);
    70 
    71     luaL_dofile(L, "r_luna.lua");
    72     print_stack(L);
    73     lua_settop(L, 0);
    74     cout << endl;
    75 
    76     lua_close(L);
    77     return 0;
    78 }

    可以看到,定义一个业务类Car,把一些共性的东西通过EXPORT_LUNA_XXX宏固化下来,这样定义一个业务类就只用关注自己的成员函数,并且把自己需要导出到lua的成员函数通过EXPORT_LUNA_FUNCTION宏处理一下即可,方便快捷,代码极其简洁。

    代码文件r_luna.lua

     1 do
     2 local car = Car();
     3 print_tree(car)
     4 print_metatable(car)
     5 print “”
     6 car.print();
     7 car.setLength(11);
     8 car.setWidth(22);
     9 car.print();
    10 print(car.getLength(), car.getWidth(), car.x, car.y);
    11 end
    12 collectgarbage("collect");

    运行结果:

    Car: 0xec8d68
    not a table
    table: 0xec88c0
    print   function: 0xec8f20
    setWidth function: 0xec6c60
    getLength function: 0xec7e10
    getWidth function: 0xec8b70
    __gc    function: 0x402cce
    __index table: 0xec88c0+
    setLength function: 0xec8bc0
    __name  Car
    
    luna<T>::call:print
    print length:100 200
    luna<T>::call:setLength
    luna<T>::call:setWidth
    luna<T>::call:print
    print length:11 22
    luna<T>::call:getLength
    luna<T>::call:getWidth
    11      22      nil     nil
    Delete Car,Length:11 Width:22
    ==========Total:1==========
    idx:-1 type:5(table) 0xec88c0
    ===========================

    由上,红色标记的部分是元表。print(car.getLength(), car.getWidth(), car.x, car.y);这行的输出是11      22      nil     nil,因为变量x和变量y没有定义,所以打印出来是nil。那么能不能继续扩展luna框架,使其不仅能够导出成员函数,也能导出成员变量?这个就是下一节加强版的lunar的特性。

  • 相关阅读:
    C#2.0 对AD的简单操作
    启用sqlclr
    项目管理杂谈
    使用目录服务和 Visual C# .NET 向本地系统添加用户
    CSS HACK
    Web2.0设计师工具箱资源网站集锦
    Cookie 的基本知识
    精妙sql语句二
    分页存储过程
    js常用函数
  • 原文地址:https://www.cnblogs.com/liao0001/p/9791495.html
Copyright © 2011-2022 走看看