• lua绑定C++对象系列五——lunar模板进阶


    在系列第四篇通过luna绑定C++对象的机制,没有解决C++成员变量在lua中直接使用的问题。例如local car = Car(); car.x = 100的用法。本节介绍增强版的lunar模板类,主要有几点不同:

    1、  支持export C++变量和函数到lua中。通过重定义元方法__index和__newindex,当调用car.x 或者赋值 car.x = xxx时会分别触发元方法。

    2、  Luna版本的宏定义列表全是函数,直接注册成元表的cclosure类型,但lunar因为注册的类型可以是变量或者函数,methods列表必须包含类型和变量偏移信息。根据methods定义的信息,可以直接进行变量读写和函数调用。

    整体结构如下:

    代码文件lunar.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 enum class lua_member_type
     12 {
     13     member_none,
     14     member_func,
     15     member_int
     16 };
     17 
     18 #define DECLARE_LUNAR_CLASS(obj) 
     19     static const char *name;
     20     static lunar<obj>::TMethod methods[];
     21 
     22 #define EXPORT_LUNAR_FUNCTION_BEGIN(obj) 
     23     const char* obj::name = #obj;
     24     lunar<obj>::TMethod obj::methods[] = {
     25 
     26 #define EXPORT_LUNAR_MEMBER_INT(obj, member) 
     27     {#member, nullptr, lua_member_type::member_int, offsetof(obj, member)},
     28 
     29 #define EXPORT_LUNAR_FUNCTION(obj, func) 
     30     {#func, &obj::func, lua_member_type::member_func, 0},
     31 
     32 #define EXPORT_LUNAR_FUNCTION_END(obj) 
     33     {nullptr, nullptr, lua_member_type::member_none, 0}
     34     };
     35 
     36 template<typename T>
     37 class lunar
     38 {
     39     public:
     40         typedef struct {T* _u;} TObject;
     41         typedef int (T::*TPfn)(lua_State* L); 
     42         typedef struct {const char* name; TPfn pf; lua_member_type type; int offset;} TMethod;
     43     public:
     44         static int regist(lua_State* L); 
     45         static int create(lua_State* L); 
     46         static int call(lua_State* L); 
     47         static int gc(lua_State* L); 
     48         static int member_index(lua_State* L); 
     49         static int member_new_index(lua_State* L); 
     50 };
     51 
     52 template<typename T>
     53 int lunar<T>::member_index(lua_State* L)
     54 {
     55     int top = lua_gettop(L);
     56     lua_getmetatable(L, 1);
     57     lua_insert(L, -2);
     58     lua_rawget(L, -2);
     59     if (!lua_islightuserdata(L, -1))
     60     {
     61         lua_settop(L, top);
     62         return 0;
     63     }
     64 
     65     TMethod* l = (TMethod*)lua_topointer(L, -1);
     66     TObject* p = (TObject*)lua_topointer(L, 1);
     67 
     68     switch (l->type)
     69     {
     70         case lua_member_type::member_func:
     71             {
     72                 lua_settop(L, top);
     73                 lua_pushlightuserdata(L, (void*)l);
     74                 lua_pushlightuserdata(L, (void*)p);
     75                 lua_pushcclosure(L, &lunar<T>::call, 2);
     76                 break;
     77             }
     78         case lua_member_type::member_int:
     79             {
     80                 int val = *(int *)((char*)(p->_u) + l->offset);
     81                 lua_settop(L, top);
     82                 lua_pushinteger(L, val);
     83                 break;
     84             }
     85         default:
     86             {
     87                 cout<<"member index type error"<<endl;
     88                 break;
     89             }
     90     }
     91 
     92     return 1;
     93 }
     94 
     95 template<typename T>
     96 int lunar<T>::member_new_index(lua_State* L)
     97 {
     98     int top = lua_gettop(L);
     99     lua_getmetatable(L, 1);
    100     lua_pushvalue(L, 2);
    101     lua_rawget(L, -2);
    102     if (!lua_islightuserdata(L, -1))
    103     {
    104         lua_settop(L, top);
    105         return 0;
    106     }
    107 
    108     TMethod* l = (TMethod*)lua_topointer(L, -1);
    109     TObject* p = (TObject*)lua_topointer(L, 1);
    110 
    111     switch (l->type)
    112     {
    113         case lua_member_type::member_func:
    114             {
    115                 break;
    116             }
    117         case lua_member_type::member_int:
    118             {
    119                 int val = lua_tointeger(L, 3);
    120                 *(int *)((char*)(p->_u) + l->offset) = val;
    121                 lua_settop(L, top);
    122                 break;
    123             }
    124         default:
    125             {
    126                 cout <<"member new index type error"<<endl;
    127                 break;
    128             }
    129     }
    130 
    131     return 0;
    132 }
    133 
    134 template<typename T>
    135 int lunar<T>::regist(lua_State* L)
    136 {
    137     if (luaL_newmetatable(L, T::name))
    138     {
    139         lua_pushcfunction(L, &lunar<T>::member_index);
    140         lua_setfield(L, -2, "__index");
    141         lua_pushcfunction(L, &lunar<T>::member_new_index);
    142         lua_setfield(L, -2, "__newindex");
    143         lua_pushcfunction(L, lunar<T>::gc);
    144         lua_setfield(L, -2, "__gc");
    145     }
    146 
    147     //设置方法和成员
    148     for (auto* l = T::methods; l->name; l++)
    149     {
    150         lua_pushstring(L, l->name);
    151         lua_pushlightuserdata(L, (void*)l);
    152         lua_rawset(L, -3);
    153     }
    154 
    155     lua_getglobal(L, "lunar");
    156     if (!lua_istable(L, -1))
    157     {
    158         lua_pop(L, 1);
    159         lua_newtable(L);
    160         lua_pushvalue(L, -1);
    161         lua_setglobal(L, "lunar");
    162     }
    163 
    164     lua_pushcfunction(L, &lunar<T>::create);
    165     lua_setfield(L, -2, T::name);
    166     lua_pop(L, 2);
    167 
    168     return 0;
    169 }
    170 
    171 template<typename T>
    172 int lunar<T>::create(lua_State* L)
    173 {
    174     TObject* p = (TObject*)lua_newuserdata(L, sizeof(TObject));
    175     p->_u  = new T();
    176 
    177     luaL_getmetatable(L, T::name);
    178     lua_setmetatable(L, -2);
    179 
    180     return 1;
    181 }
    182 
    183 template<typename T>
    184 int lunar<T>::call(lua_State* L)
    185 {
    186     TMethod* v = (TMethod*)lua_topointer(L, lua_upvalueindex(1));
    187     cout<<"lunar<T>::call:"<<v->name<<endl;
    188 
    189     TObject* p = (TObject*)lua_topointer(L, lua_upvalueindex(2));
    190 
    191     return ((p->_u)->*(v->pf))(L);
    192 }
    193 
    194 template<typename T>
    195 int lunar<T>::gc(lua_State* L)
    196 {
    197     if (!lua_isuserdata(L, -1))
    198     {
    199         cout<<"gc cause error."<<endl;
    200     }
    201 
    202     TObject* p = (TObject*)lua_topointer(L, -1);
    203     delete p->_u;
    204     return 0;
    205 }

    这里有一点十分重要,__index = member_index,__newindex = member_new_index,因为mobile.version和mobile.getVersion都会触发member_index调用,但member_index的返回值很重要。例如针对mobile.version这种变量类型,需要最后push一个变量值到栈中;针对mobile.getVersion,需要push一个函数(闭包)到栈中,这样mobile.getVersion就返回一个函数,mobile.getVersion()就是调用这个返回的函数。

    这里还有一点需要注意一下,通过在全局表定义一个lunar表,在这个表中,T::name作为key,lunar<T>::create作为value,这样也可以把所有的构造函数全部统一管理起来。当后面有越来越多的不同类通过lunar绑定时,就可以在lunar全局统一管理而不至于混乱,造成名字冲突。例如针对类CarBusTrainAirplane注册后,效果如下:

    代码文件r_lunar.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 class Mobile
    16 {
    17     public:
    18         Mobile(){}
    19         ~Mobile(){cout<<"Delete Mobile,Ver:"<<version<<" Price:"<<price<<endl;}
    20 
    21         int getVersion(lua_State *L){
    22             lua_pushinteger(L, version);
    23             return 1;
    24         }   
    25         int getPrice(lua_State *L){
    26             lua_pushinteger(L, price);
    27             return 1;
    28         }   
    29         int setVersion(lua_State *L) 
    30         {   
    31             int val = lua_tointeger(L, -1);
    32             version = val;
    33             return 0;
    34         }   
    35         int setPrice(lua_State *L) 
    36         {   
    37             int val = lua_tointeger(L, -1);
    38             price = val;
    39             return 0;
    40         }   
    41         int print(lua_State *L) 
    42         {   
    43             cout <<"print version:"<<version<<" price:"<<price<<endl;
    44         }   
    45     public:
    46         DECLARE_LUNAR_CLASS(Mobile);
    47     public:
    48         int version = 100;
    49         int price = 200;
    50 };
    51 
    52 EXPORT_LUNAR_FUNCTION_BEGIN(Mobile)
    53 EXPORT_LUNAR_FUNCTION(Mobile, getVersion)
    54 EXPORT_LUNAR_FUNCTION(Mobile, getPrice)
    55 EXPORT_LUNAR_FUNCTION(Mobile, setVersion)
    56 EXPORT_LUNAR_FUNCTION(Mobile, setPrice)
    57 EXPORT_LUNAR_FUNCTION(Mobile, print)
    58 EXPORT_LUNAR_MEMBER_INT(Mobile, version)
    59 EXPORT_LUNAR_MEMBER_INT(Mobile, price)
    60 EXPORT_LUNAR_FUNCTION_END(Mobile)
    61 
    62 int main(int argc, char* argv[])
    63 {
    64     lua_State *L = luaL_newstate();
    65     luaL_openlibs(L);
    66 
    67     luaL_dofile(L, "tree.lua");
    68 
    69     //use lunar template and bind to object 
    70     lunar<Mobile>::regist(L);
    71 
    72     luaL_dofile(L, "r_lunar.lua");
    73     print_stack(L);
    74     lua_settop(L, 0);
    75     cout<<endl;
    76 
    77     lua_close(L);
    78     return 0;
    79 }

    对应的r_lunar.lua:

    do
    print_tree(lunar)
    print ""
    local mobile = lunar.Mobile();
    print_tree(mobile)
    print_metatable(mobile)
    
    print ""
    print(mobile.version, mobile.price);
    mobile.version = 101
    mobile.price = 4888
    mobile.y = 100
    print(mobile.version, mobile.price)
    print(mobile.x, mobile.y);
    print(mobile.getVersion, mobile.getPrice)
    mobile.print();
    
    print ""
    local mobile1 = lunar.Mobile();
    mobile1.setVersion(201);
    mobile1.setPrice(5888);
    mobile1.print();
    print(mobile1.getVersion(), mobile1.getPrice(), mobile1.version, mobile1.price);
    end
    collectgarbage("collect");

    运行结果:

    table: 0x216b830
    Mobile  function: 0x402f9c
    
    Mobile: 0x216c348
    not a table
    table: 0x216b8c0
    setPrice userdata: 0x641400
    __index function: 0x402c76
    getPrice userdata: 0x6413c0
    print   userdata: 0x641420
    getVersion userdata: 0x6413a0
    __newindex function: 0x402df2
    setVersion userdata: 0x6413e0
    price   userdata: 0x641460
    version userdata: 0x641440
    __name  Mobile
    __gc    function: 0x402f1c
    
    100     200
    101     4888
    nil     nil
    function: 0x216dc30     function: 0x216dc80
    lunar<T>::call:print
    print version:101 price:4888
    
    lunar<T>::call:setVersion
    lunar<T>::call:setPrice
    lunar<T>::call:print
    print version:201 price:5888
    lunar<T>::call:getVersion
    lunar<T>::call:getPrice
    201     5888    201     5888
    Delete Mobile,Ver:201 Price:5888
    Delete Mobile,Ver:101 Price:4888
    ==========Total:0==========
    ===========================

    可以看到,红色标记的部分变成userdata,只记录methods的元素地址,不再直接注册成一个函数。

    一个小问题:

    无论是luna还是lunar,都是通过userdata+metatable绑定C++对象的,那么能否通过table+metatable来绑定C++对象,如果可以,那应该怎么怎么做?有兴趣的可以手动试试看。

  • 相关阅读:
    geoserver显示shapefile中汉字呈乱码问题的可选解决方案
    C++回调函数示例
    也谈谈技术面试
    轻快好的c++实践
    向 Hacker 迈进 我的书单
    CMake是个好东西
    工作之中总有几日不在状态_你是怎样度过的?
    我是如何从煤矿工成为程序员的
    java多线程系列_用Thread类创建线程(2)
    java多线程系列_使用Runnable接口创建线程(3)
  • 原文地址:https://www.cnblogs.com/liao0001/p/9791557.html
Copyright © 2020-2023  润新知