在 Lua 里使用 C++ 的 Class

  在 Lua 里调用 C/C++ 里面的函数比较简单,需要被 Lua 调用的 C/C++ 函数都长一个德性:

  1. int CFunc( lua_State *pLua );

Lua script 传过来的参数都能从 pLua 里通过 stack 拿到,需要返回给 script 的数据也需要入栈,之后通过 C function 的返回值告诉 script 一共返回了几个值。

把上面这个函数注册一下在 Lua script 里就能用啦:

  1. lua_pushcfunction( pLua, CFunc );
  2.  lua_setglobal( pLua, "CFunc" );

这个操作也可以用 Lua.h 里面定义的 lua_register() 宏一步搞定。

  为了在 Lua 里使用 C++ 的类还真是让我这个 Lua 初学者费了一番周折,记录一把跟大家分享一下。在 Lua 里构造一个 class 需要用到 Lua 的 metatable,这个东西很灵活,基本的语法什么的就不说啦,简单的说 metatable 可以改变“宿主”的一些默认行为。还要用到 userdata,要在 Lua 里保存和使用 C/C++ 的结构啊对象什么的肯定要用到这个东西。

下面就是在 Lua 里构造一个 class 的代码:

  1. Foo = {}
  2.  
  3.  function Foo:new()
  4.      local object = {}
  5.      object.object = NewObject()  -- object.object is a userdata
  6.      setmetatable( object, self )
  7.      self.__index = self
  8.      return object
  9.  end
  10.  
  11.  function Foo:Bar( By2ndboy )
  12.      FooFunc( "Bar", self.object, By2ndboy )
  13.  end

具体的原理可以参考《Programming in Lua》,上面的 NewObject() 和 FooFunc() 都是 C function,其中 NewObject() 用来实际创建 C++ object,FooFunc() 是这个 C++ object 的使用接口。我们来看看 NewObject() 的实现:

  1. typedef struct
  2.  {
  3.      Foo *m_pObject;
  4.  } FooObject;
  5.  
  6.  int
  7.  NewObject( lua_State *pLua )
  8.  {
  9.      FooObject *pObject = (FooObject *)lua_newuserdata( pLua, sizeof( FooObject ) );
  10.      pObject->m_pObject = new Foo;
  11.      return( 1 );
  12.  }

实现很简单,就是用一个 struct 来保存 Foo 对象实例的指针,但是这样没办法实现 Foo 对象的析构,我们来改造一下上述代码:

  1. int
  2.  NewObject( lua_State *pLua )
  3.  {
  4.      FooObject *pObject = (FooObject *)lua_newuserdata( pLua, sizeof( FooObject ) );
  5.      pObject->m_pObject = new Foo;
  6.  
  7.      // Create/Get metatable in registry and push it onto stack
  8.      if( 1 == luaL_newmetatable( pLua, "forGc" ) )
  9.      {
  10.          lua_pushstring( pLua, "__gc" );
  11.          lua_pushcfunction( pLua, DeleteObject );
  12.          lua_settable( pLua, -3 );
  13.      }
  14.  
  15.      // Set metatable for userdata(used by gc)
  16.      lua_setmetatable( pLua, -2 );
  17.  
  18.      return( 1 );
  19.  }
  20.  
  21.  int
  22.  DeleteObject( lua_State *pLua )
  23.  {
  24.      FooObject *pObject = (FooObject *)lua_touserdata( pLua, 1 );
  25.      delete pObject->m_pObject;
  26.      return( 0 );
  27.  }

原理就是通过给 userdata 设置 metatable 来改变 Lua GC 在回收这个 userdata 时的行为,转而调用我们设定的 C function 来删除 Foo object 实例。Lua 里规定 userdata 的 metatable 不能在 script 里面设定,一定要在 C/C++ 里面做。这样改过之后在 Lua 里写下如下代码就会触发 DeleteObject() 这个函数:

  1. function test()
  2.      local a = Foo:new()
  3.  end

最后来看一下 FooFunc() 的实现,也非常简单:

  1. int
  2.  FooFunc( lua_State *pLua )
  3.  {
  4.      string strAction( lua_tostring( pLua, 1 ) );
  5.      FooObject *pObject = (FooObject *)lua_touserdata( pLua, 2 );
  6.      if( strAction == "Bar" )
  7.          pObject->m_pObject->Bar( lua_tonumber( pLua, 3 ) );
  8.  
  9.      return( 0 );
  10.  }

Leave a Reply