Cpp Object Binding

lua-users home
wiki

Using Lua and C++ interchangably on C++ objects

Trying to gain access into your C++ Objects from Lua (without using any add-ons to your project)? You can expose your C++ methods to a Lua script and allow the script to call methods inside the object using lightuserdata to pass pointers around. Note that I am using luna.h from the * SimplerCppBinding page.

First, start off with an object you want to expose to Lua- this is a simple class with an integer attribute and a string.

object.cpp:


#include "object.h"



GameObject::GameObject(int x){

  attribute = x;

}



int GameObject::getAttr(){

  return (int)attribute;

}



void GameObject::setAttr(int set){

  attribute = set;

}



void GameObject::setMessage(const char* new_message){

  message.assign(new_message);

}



const char* GameObject::getMessage(){

  return message.c_str();

}

  

GameObject::~GameObject(){

  printf("deleted Object (%p)\n", this);

}

object.h:


/**

 * This is the main object that is actually used in the C++ code.

 * It is to be manipulated by Lua through the Lua wrapper object (which will

 * contain a pointer to this object).

 */

#ifndef _object_h_

#define _object_h_



// Notice that I don't need ANY Lua stuff in here...

#include <stdio.h>

#include <string>



class GameObject{

public:

  GameObject(int x);

  ~GameObject();



  int getAttr(void);

  void setAttr(int balance);



  void setMessage(const char* new_message);

  const char* getMessage(void);

private:

  int attribute;

  std::string message;

};

#endif

After you have your object that can now be modified through C++, you need to write a class to expose the object to Lua.

luaobject.cpp:


#include "luaobject.h"



LuaGameObject::LuaGameObject(lua_State *L){

  real_object = (GameObject*)lua_touserdata(L, 1);

}



void LuaGameObject::setObject(lua_State *L){

  real_object = (GameObject*)lua_touserdata(L, 1);

}



int LuaGameObject::setAttr(lua_State *L){

  real_object->setAttr((int)luaL_checknumber(L, 1));

  return 0;

}

int LuaGameObject::getAttr(lua_State *L){

  lua_pushnumber(L, real_object->getAttr());

  return 1;

}



int LuaGameObject::setMessage(lua_State *L){

  real_object->setMessage(lua_tostring(L, 1));

  return 0;

}

int LuaGameObject::getMessage(lua_State *L){

  lua_pushstring(L, real_object->getMessage());

  return 1;

}



LuaGameObject::~LuaGameObject(){

  printf("deleted Lua Object (%p)\n", this);

}

luaobject.h:


/**

 * This is the wrapper around the C++ object found in object.cc

 * Everything this object has done to it is passed on FROM Lua to the real C++

 * object through the pointer 'real_object'

 * Notice that I kept the function names the same for simplicity.

 */

#ifndef _luaobject_h_

#define _luaobject_h_



// Need to include lua headers this way

extern "C" {

#include "lua.h"

#include "lauxlib.h"

#include "lualib.h"

}



// I am using luna

#include "luna.h"

// The header file for the real C++ object

#include "object.h"



class LuaGameObject{

public:

  // Constants

  static const char className[];

  static Luna<LuaGameObject>::RegType methods[];



  // Initialize the pointer

  LuaGameObject(lua_State *L);

  ~LuaGameObject();

  void setObject(lua_State *L);



  // Methods we will need to use

  int getAttr(lua_State *L);

  int setAttr(lua_State *L);

  int getMessage(lua_State *L);

  int setMessage(lua_State *L);

private:

  // The pointer to the 'real object' defined in object.cc

  GameObject* real_object;

};

#endif

Alright, now we have a way for Lua to gain access to this object, let us bring it together. Notice the key here is to pass the pointer to Lua using lightuserdata.

main.cpp:


/**

 * Main program to actually run the Lua code against the C++ object

 */

#include "object.h"

#include "luaobject.h"



// Define the Lua ClassName

const char LuaGameObject::className[] = "LuaGameObject";



// Define the methods we will expose to Lua

// Check luaobject.h for the definitions...

#define method(class, name) {#name, &class::name}

Luna<LuaGameObject>::RegType LuaGameObject::methods[] = {

   method(LuaGameObject, setAttr),

   method(LuaGameObject, getAttr),

   method(LuaGameObject, getMessage),

   method(LuaGameObject, setMessage),

   {0,0}

};



int main(int argc, char *argv[]){

  // Init Lua

  lua_State *L = lua_open();

  luaopen_base(L);

  luaopen_table(L);

  luaopen_io(L);

  luaopen_string(L);

  luaopen_math(L);

  luaopen_debug(L);



  // Register the LuaGameObject data type with Lua

  Luna<LuaGameObject>::Register(L);



  // In C++ - Create a GameObject for use in our program

  GameObject temp(20);

  temp.setMessage("I'm set in C++");

  

  // Push a pointer to this GameObject to the Lua stack

  lua_pushlightuserdata(L, (void*)&temp);

  // And set the global name of this pointer

  lua_setglobal(L,"gameobject");



  printf("In C: %p => %d, %s\n", &temp, temp.getAttr(), temp.getMessage());



  printf("Loading lua----------\n");

  luaL_loadfile(L, argv[1]);

  printf("lua is loaded--------\n");



  printf("Running lua----------\n");

  lua_pcall(L, 0, 0, 0);

  printf("Lua is done----------\n");

  //lua_setgcthreshold(L, 0);  // collected garbage



  luaL_loadfile(L, argv[1]);

  printf("Running lua2---------\n");

  lua_pcall(L, 0, 0, 0);

  printf("Lua is done2---------\n");



  // GC + Close out Lua

  lua_close(L);



  printf("In C++: %p => %d, %s\n", &temp, temp.getAttr(), temp.getMessage());

  return 0;

}

Now, lets run a test program like this:

gameobject.lua:

--[[ 

   gameobject comes from the global parameters and is a pointer to

   the REAL C++ data.  This is saved as lightuserdata in Lua and we preserve

   this pointer in the LuaGameObject.  From there, we can manipulate

   ANYTHING from this pointer.

--]]



-- Lua Functions

function printf(...) io.write(string.format(unpack(arg))) end



-- This function uses the getX() methods in our target class

function LuaGameObject:show()

  printf("LuaGameObject attribute = %d - %s\n", self:getAttr(), self:getMessage())

end



-- Start up a new LuaGameObject wrapper class and pass the global gameobject

-- C++ lightuserdata pointer into it

b = LuaGameObject(gameobject)



-- Call a Lua function on this object

b:show()



print('Now to work on the C++ object')

-- Modify some of the parameters (gameobject->modify)

b:setAttr(120)

b:setMessage('Hey - Lua changes a string!')

print('Lua is done changing...')

b:show()

Finally, here's what happens:


./main gameobject.lua

In C: 0xbfb1d630 => 20, I'm set in C

Loading lua----------

lua is loaded--------

Running lua----------

LuaGameObject balance = 20 - I'm set in C

Now to work on the C++ object

Lua is done changing...

LuaGameObject attribute = 120 - Hey - Lua changes a string!

Lua is done----------

Running lua2---------

LuaGameObject attribute = 120 - Hey - Lua changes a string!

Now to work on the C++ object

Lua is done changing...

LuaGameObject attribute = 120 - Hey - Lua changes a string!

Lua is done2---------

deleted Lua Object (0x80642d0)

deleted Lua Object (0x8064c40)

In C: 0xbfb1d630 => 120, Hey - Lua changes a string!

deleted Object (0xbfb1d630)

I hope you find this example useful- Lua is awesome!


RecentChanges · preferences
edit · history
Last edited May 6, 2009 7:33 am GMT (diff)