把C++类成员函数集成到lua

       有时我们会把C++类导入到lua,这样方便在lua里面对C++类进行操作,这在游戏编程里面经常使用,本文只是简单介绍一种实现。

      

1.       lua里面面向对象的实现

      
       在lua里面,我们可以这样操作表,如下:

 

       Account = {balance = 0}

       function Account:withdraw (v)

              self.balance = self.balance - v

       end

 

       在这里,我们定义了一个table并且有一个withdraw的函数,我们可以这样使用

      

       a = Account

       a:withdraw(100)

 

       这样就跟C++的类形式一样了。但是在lua里面没有类的概念,但是每个对象都有一个prototype(原型),当调用不属于对象的某些操作时,会最先会到prototype中查找这些操作。所以如果我们又两个对象a和b,如果我们把b作为a的prototype,我们就实现了最基本的继承。在lua中只需如下实现

 

       setmetatable(a, {__index = b})

 

       这样a调用任何不存在的成员都会到对象b里面去查找。在这里我们使用了__index metamethod,至于什么是metatable和metamethod lua相关文档有详细说明。(另外特别推荐programming in lua这本书,里面几乎全部介绍了这些知识)

      

 

2.       C++类在lua中的表现形式
在lua中,我们假设C++类的操作形式如下,假设有一个类A

 

              obj = A:new()

              obj:dosomething()

              obj:delete()

      

       这里我们没考虑成员变量的操作,因为对于我们来说只是对成员函数进行操作,而且考虑到封装原则不建议对类成员变量进行直接操作,如果要操作我们会在类里面定义相关的成员函数。

      

 

3.       设计思想

       参考上面的表现形式,我们会把A设定成一个metatable,然后把相关函数与它关联,   也就是把new等函数通过lua_pushfunction使其作为改metatable的函数。而对于obj来说,他是一个A的实例,我们会把它的指针传给lua供lua使用,而这个指针值在lua里面就是用userdata来表示。由于只涉及到函数的操作,所以我们会把metatable的__index与相关处理函数进行关联。

       另外,假设A继承于类B,而obj为A的实例,那么obj的metatable为A的metatable,而A的metatable的metatable为B的metatable,这样就保证了继承性。我们通过obj调用函数,obj首先会在A里面查找,如果A里面没有,A会在B里面进行查找。

 

       基于上面的思路,我们首先定义函数:

       int reg_type(lua_State *L, const char *name);

     这个函数把我们类注册进lua中,即是把我们的类当成一个metatable,代码如下:

     //registry type to lua

     int reg_type(lua_State *L, const char *name)

     {

         //创建metatable,如果成功返回1,失败返回0

          int r = luaL_newmetatable(L, name);

 

          if(r)

          {       

              //把该metatable注册为全局的

              lua_pushstring(L, name);

              lua_pushvalue(L, 1);

              lua_settable(L, LUA_GLOBALSINDEX);  

          }

 

          if(r)

          {

              classevent(L);

          }

 

          lua_pop(L, 1);

 

          return r;

     }

 

     对于classevent(),我们实现如下:

     void classevent(lua_State *L)

     {

          lua_pushstring(L,"__index");

          lua_pushcfunction(L,class_index_event);

          lua_rawset(L,-3);

     }

     这里就是关联metatable的__index域,我们所有的操作都会转换到函数class_index_event进行。

     int class_index_event(lua_State *L)

     {

          int t = lua_type(L,1);

          if (t == LUA_TUSERDATA)

          {

              //这里我们只考虑了函数类型,所以简单起见没有进行很多其他的处理

              //我们会得到metatable,然后找到该metatable对应的函数,进行调用

               lua_getmetatable(L, 1);

               lua_pushvalue(L, 2);

               lua_rawget(L, -2);

               if(!lua_isnil(L, -1))

               {

                   return 1;

               }

          }

 

          return 1;

     }

 

     同时我们提供了设定继承关系的函数

     int reg_class(lua_State *L, const char *name, const char *base)

     {

          luaL_getmetatable(L, name);          //stack: mt

    

         //如果有基类,我们就把基类作为该类metatable的metatable

          if(base && *base)

          {

              luaL_getmetatable(L, base);     //stack: mt basemt

              lua_setmetatable(L, -2);        //stack: mt

          }

 

          lua_pop(L, 1);

    

          return 0;

     }

 

     注册函数的函数,使函数与特定的metatable关联:

     int reg_function(lua_State* L, const char * type, const char* name, lua_CFunction func)

     {   

          luaL_getmetatable(L, type);

          lua_pushstring(L, name);

          lua_pushcfunction(L, func);

          lua_rawset(L, -3);

          lua_pop(L, 1);

          return 0;

     }

 

     注册userdata的函数,在这里我们必须明确,对于我们自定义的类型的实例,比如obj,我们是通过指针传递给lua的,这其实是一个light userdata,而在lua里面,light userdata不具有meta机     制,但是我们又要设定该类型的meta为类A的meta,这样我们就要实现一下包装,我们把这个obj 首先赋值给一个full userdata,在lua里面full userdata具有meta属性,然后存贮在一个注册table里面,这样其实我们返回的不是这个obj的指针了,而是包装了这个obj的一个full userdata 指针。在lua5.0里面定义了lua_unboxpointer和lua_boxpointer宏,但不知怎么5.1里面没有了,这两个宏如下:

    

     #define lua_boxpointer(L,u)

     (*(void **)(lua_newuserdata(L, sizeof(void *))) = (u))

 

     #define lua_unboxpointer(L,i)    (*(void **)(lua_touserdata(L, i)))

     当我们需要取出obj指针时候,我们通过(*(void **)转换得到。

 

     int reg_userdata(lua_State* L, void* value, const char* type)

     {

          luaL_getmetatable(L, type);

    

          lua_pushstring(L, "script_ubox");

          lua_rawget(L, LUA_REGISTRYINDEX);

    

          lua_pushlightuserdata(L,value);

          lua_rawget(L,-2);                       /* stack: mt ubox ubox[u] */

          if (lua_isnil(L,-1))

          {

               lua_pop(L,1);                          /* stack: mt ubox */

               lua_pushlightuserdata(L,value);

               *(void**)lua_newuserdata(L,sizeof(void *)) = value;   /* stack: mt ubox u newud */

               lua_pushvalue(L,-1);                   /* stack: mt ubox u newud newud */

               lua_insert(L,-4);                      /* stack: mt newud ubox u newud */

               lua_rawset(L,-3);                      /* stack: mt newud ubox */

               lua_pop(L,1);                          /* stack: mt newud */

 

               lua_pushvalue(L, -2);            /* stack: mt newud mt */

               lua_setmetatable(L,-2);          /* stack: mt newud */

          }

 

          lua_remove(L, -2);

 

          return 1;

     }

 

     然后我们定义:

     这个函数只是注册一个table供我们操作。

     int script_open(lua_State *L)

     {

          lua_pushstring(L,"script_ubox");

          lua_newtable(L);  

          lua_rawset(L,LUA_REGISTRYINDEX);

 

          return 0;

     }

 

     接下来就是我们的实现:

     首先定义类以及类成员函数对应的注册函数:

     class test

     {

     public:

          test(){}

          ~test(){}

          void myprint() { printf("test::Hello Worldn"); }

     };

 

     int test_new(lua_State *L)

     {

          test *t =  new test();

          reg_userdata(L, (void*)t, "test");

          return 1;

     }

 

     int test_delete(lua_State *L)

     {

          test *t = (test*)(*(void**)lua_touserdata(L, 1));

          delete t;

          lua_settop(L, 0);

          return 0;

     }

 

     int test_myprint(lua_State *L)

     {

          test *t = (test*)(*(void**)lua_touserdata(L, 1));

          t->myprint();

          return 0;

     }

 

     然后实现:

     script_open(L);

 

     reg_type(L, "test");

     reg_class(L, "test", NULL);

     reg_function(L, "test", "new", test_new);

     reg_function(L, "test", "delete", test_delete);

     reg_function(L, "test", "myprint", test_myprint);

 

     这样在lua里面,我们就可以这样调用了:

     obj = test:new()

     obj:myprint()

     obj:delete()

 

     注意,这只是一个非常简单的实现,有很多缺陷

     首先,它把类直接注册进GLOBALSINDEX,这样可能会导致命名冲突,其次每个类的成员函数都要单独对应一个注册函数,这样工作量很大,虽然我以前写过关于通过一个代理方法将不同的C类型与类成员函数注册进lua的方法,但是该方法对现在这种实现并不怎么适用。再就是这个例子很多方面没有考虑完全,只是单纯的把成员函数导入,功能很有限。看以后能不能写出一套更好的绑定类的东西吧。

时间: 2024-09-15 21:24:18

把C++类成员函数集成到lua的相关文章

谈函数指针(全局/类成员函数)和函数对象

函数指针(全局函数/类成员函数).函数对象(Function object) 一. 函数指针类型为全局函数. #include "stdafx.h"#include <iostream>using namespace std;class TestAction;typedef void (*fp)(int); void Drink(int i){ cout<<"No. "<<i<<" drink..."

直接调用类成员函数地址

一.成员函数指针的用法 在C++中,成员函数的指针是个比较特殊的东西.对普通的函数指针来说,可以视为一个地址,在需要的时候可以任意转换并直接调用.但对成员函数来说,常规类型转换是通不过编译的,调用的时候也必须采用特殊的语法.C++专门为成员指针准备了三个运算符: "::*"用于指针的声明,而"->*"和".*"用来调用指针指向的函数.比如: class tt { public: void foo(int x){ printf("\

类成员函数作为多线程的入口函数如何访问对话框资源

问题描述 类成员函数作为多线程的入口函数如何访问对话框资源 我申请了一个类成员函数作为多线程的入口函数,如何在该函数中访问对话框资源(分割后的对话框) 解决方案 需要把类的实例作为线程函数参数传递进去.

c++,关于类成员函数作为线程的入口函数

问题描述 c++,关于类成员函数作为线程的入口函数 class Map {public: Bird *pB; Pig *pP; ..........}class Grav {public: Map *pM; ...... void runBird(Bird &b); void runPig(Pig &p); void run(Map &m);}void Grav::run(Map &m) { thread t[2]; t[0] = thread(&Grav::run

【C/C++学院】(8)全局函数和类成员函数转化/友元/操作符重载

1.全局函数和类成员函数转化     全局函数和成员函数的相互转化:只需要修改一个指向本类的this指针: #include <iostream> using namespace std; class Test { public: Test(int a, int b) { this->a = a; this->b = b; } //成员函数 Test &Gadd2(Test &t2) { this->a = this->a + t2.a; this-&g

c++-【析构函数写法】如何使用析构函数释放类成员函数申请的堆内存

问题描述 [析构函数写法]如何使用析构函数释放类成员函数申请的堆内存 标题:[析构函数写法]如何使用析构函数释放类成员函数申请的堆内存 环境:win7 64位/AMD CPU/C++ GCC4.7.2/Codeblocks 详细描述:如下所示"代码块1",每次在调用encrypt()函数时均会申请一次内存,如何在析构函数中一次性销毁所有产生的内存,"代码块2"的方法不符合要求 扩展:如果上述问题无较好答案,是否有其他方法可以避免在类函数中使用new,但也能达到目的

c++基础-为什么不合法,基类的类成员函数在最下面定义

问题描述 为什么不合法,基类的类成员函数在最下面定义 解决方案 是不是有些变量在上面已经定义过了. 解决方案二: 把错误粘出来看一下吧~ 解决方案三: 函数定义跟申明的原型是否一致 解决方案四: #include 了么? 解决方案五: include fstream了么? 解决方案六: if(xxx){....}; if需要"{}"的吧... 解决方案七: 另外没有判断shape[]的大小,如果shape[]小于4,就溢出了.

类成员函数指针区别于用法

通常的函数指针大家已经非常熟悉了.但我们今天讨论一下类成员函数指针的用法. 今天我们来看一下成员函数指针,加入我们想要声明一个 void CTest::Show()成员函数指针类型,那么我们一般的做法是: typedef void(CTest::*pShow)(); 从上面可以看出一些和一般函数指针类型不同的地方.让我们把他和一般的函数声明比较一下.下面是一般的函数声明: typedef void(*pShow)(); 我们可以看出成员函数指针和一般函数指针的不同,那就是成员函数指针声明时加上类

C++ 如何获取类成员函数地址?

C语言中可以用函数地址直接调用函数:     void print ()       {          printf ("function print");       }       typdef void (*fun)();       fun f = print;       f(); C++中类非静态成员函数必须通过实例去调用,C++中类成员函数调用:     class test       {       public:       void print ()