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)