git.guelker.eu LuaWrap / master README.md
master

Tree @master (Download .tar.gz)

README.md @masterview markup · raw · history · blame

LuaWrap -- wrap C++ classes more easily

\mainpage

LuaWrap is a small project helping you to bridge C++ classes over to Lua. As Lua already provides a quite usable C API, this project doesn’t aim at replacing all this with a complete different API for now, it’s merely an automation of the tedious task to map instance methods to specific objects. If you want, you can see LuaWrap as a collection of lua* and luaL* calls that do the class/instance method table assigning for you, nothing more.

As the project evolves, more advanced facilities may be added in the future, but for now this is a 1-header-1-cpp-file library you can just drop into your project to simplify wrapping of C++ classes. It’s based solely on standard C++ templates (not even fancy preprocessor directives used exagerrately by other Lua/C++ wrappers), so it should compile anywhere.

Installation

LuaWrap doesn’t depend on any external libraries beside Lua itself, which must be version 5.2 or higher. Older versions won’t work and I can’t guarantee for newer ones.

How to use

First, you have to add the luawrap.hpp header file to your project directory and then #include it where you want to wrap some classes, then place the implementation file luawrap.cpp next to the header file and ensure it gets compiled and linked.

#include "luawrap.hpp"

Everything defined by this library (except the header guard macro) resides inside the LuaWrap namespace and everything you need to know for starting out with the lib is directly defined on that namespace. There are two other namespaces, LuaWrap::InternalC and LuaWrap::InternalLua which may come in handy when the standard allocator/finalizer used by LuaWrap doesn’t serve you well (see below).

Suppose you have this class you want to bridge to Lua:

// foo.hpp
#include <iostream>

class Foo
{
public:
  Foo(); // Constructor
  int get_val();
  void some_method();
private:
  int m_val;
};

Now you can create a file foo_wrapper.cpp and fill it with the following contents:

#include "foo.hpp"
#include "luawrap.hpp"

/* This bridges the get_val() C++ method to a
 * Lua method val(). It’s just an ordinary Lua
 * C function, nothing special here.
 */
static int luafoo_val(lua_State* p_state)
{
  // First check if we were called with a valid receiver,
  // i.e. with the colon notation.
  if (!lua_isuserdata(p_state, 1))
    return luaL_error(p_state, "Invalid receiver.");

  // Here we unwrap the pointer to the original object
  // from the Lua argument.
  Foo* p_foo = (Foo*) lua_touserdata(p_state, 1);

  // Work with the pointer, and put a result onto the Lua stack.
  lua_pushnumber(p_state, p_foo->get_val());

  // We put 1 extra result onto the stack.
  return 1;
}

// This wraps the some_method() C++ method. Works
// the same way as the method above.
static int luafoo_some_method(lua_State* p_state)
{
  if (!lua_isuserdata(p_state, 1))
    return luaL_error(p_state, "Invalid receiver.");

  Foo* p_foo = (Foo*) lua_touserdata(p_state, 1);
  p_foo->some_method();

  return 0;
}

/* This is a standard Lua method table as defined by
 * the Lua manual. It maps the Lua method names to their
 * C++ equivalents, which must be static member functions.
 * Remember to place the {NULL, NULL} sentinel at the end!
 */
static luaL_Reg s_foo_imethods[] = {
  {"val", luafoo_val},
  {"some_method", luafoo_some_method},
  {NULL, NULL}
};

/* This method is exposed to the public. When it is called, it
 * loads the wrapped Foo class into the given Lua state.
 */
void luafoo_open(lua_State* p_state)
{
  /* The register_class function is the main function you’ll have
   * to deal with when using LuaWrap. It’s a template function that
   * takes the C++ class you want to wrap, the name to expose to
   * the Lua side (these are not required to match) and the
   * instance method table for objects of this class. It also
   * supports an optional fourth parameter that allows you to
   * pass a class method table the same way you pass an instance
   * method table.
   */
  LuaWrap::register_class<Foo>(p_state, "Foo", s_foo_imethods);
}

The above code is the pattern you can follow to wrap about 60% of all C++ code you’re faced with. The register_class function does all the necessary magic to define the method tables and will be the main interaction you have with LuaWrap, as the actual wrapper functions you wrote are nothing else than normal Lua C functions.

Wait! What about memory allocation?

register_class not only keeps track of mapping the class and instance method tables to your Lua userdata objects. It also defines an object allocator and finalizer for you that are suitable for standard usage. When you call Foo:new() in Lua, LuaWrap asks the Lua interpreter to allocate the necessary memory and places a new instance of Foo inside this memory, creating a Lua full userdata object. As the memory is managed by Lua, you don’t have to manually call delete on your Foo instance; if you do, you will get segmentation faults as Lua is going to free() this memory later. Lua doesn’t know about the C++ delete operator and during this process the Foo class’ destructor wouldn’t get called, but LuaWrap hooks into Lua’s garbage collection mechanism (by defining the __gc() metamethod on the userdata objects it returns) and calls the destructor from there, however without calling delete as the memory is freed by Lua itself.

Custom allocators/finalizers

As pointed out above, LuaWrap’s standard usage covers around 60% of C++ code you want to wrap. The rest requires custom allocators and finalizers for your Lua objects, so it would be nice if you could define your own allocator/finalizer for these cases. Now, you actually can do so! But before I go into depth, I need to explain a bit about LuaWrap’s internal structure so your custom allocators and finalizers work well with the rest of the library.

Allocators

An allocator, as interpreter by LuaWrap, is a function that does two things:

  1. Ask the Lua interpreter for memory where to store the object. The allocator can either ask for a light or a full userdata object (see the Lua manual), just as you need. Briefly, a "light userdata" object just takes a pointer to whatever you want to wrap -- these kinds of objects cannot be used with finalizers as Lua doesn’t call the __gc() metamethod on them; on the other hand, a "full userdata" object is completely allocated by the Lua interpreter and you’re asked to place your object into the memory Lua hands to you. Light userdata is created by lua_pushlightuserdata(), full userdata by lua_newuserdata().
  2. Create/reference an instance of the C++ class you want to wrap and place it inside the userdata object.

The userdata object is then pushed onto the Lua stack given to the allocator and the allocator exits by returning 1 to Lua, indicating it returned one value (which is the userdata object we pushed onto the stack).

That said, LuaWrap comes with a default allocator called LuaWrap::InternalLua::default_new() that’s defined as follows:

template<typename WrappedClass>
int default_new(lua_State* p_state)
{
  // Ensure we got a class table
  if (!lua_istable(p_state, 1))
    return luaL_error(p_state, "No class table given.");

  // Let Lua allocate memory and place the new object into that memory
  WrappedClass* p_wrapped = new (lua_newuserdata(p_state, sizeof(WrappedClass))) WrappedClass;
  if (!p_wrapped)
    return luaL_error(p_state, "Failed to allocate an instance!");

  InternalC::set_imethod_table(p_state);

  return 1;
}

There are two remarkable aspects in this method. The first is that we’re using the so-called "placement new operator" to instruct C++ to use a given block of memory (namely that one returned by lua_newuserdata() to place the newly created instance in. The second is that obscure method call to InternalC::set_imethod_table() in line 13. It is a helper method that does the heavy work of finding out which Lua class belongs to the object we’re wrapping and maps the instance method metatable registered for this class to the new object. To achieve this task, set_imethod_table() expects two Lua objects on the stack given to it:

|            |
+------------+
|  userdata  |
+------------+
| classtable |
+============+

The classtable object is the class method table for the class you want to instanciate. Every class you create with LuaWrap is automatically given a method called classname() that returns the class’ name as a string (via Lua’s C closure mechanism). set_imethod_table() uses this method it extracts from the classtable to find the correct metatable to assign to the userdata object, the second element on the stack. It also keeps the stack balanced, i.e. after set_imethod_table() returns the stack looks the same way it looked prior to the call.

Just in case you didn’t realise why I’m explaining this method that lengthy: When you write your own allocator, you have to use this method, because otherwise your object wouldn’t get an instance method table and would be quite useless from the Lua side.

Let’s revisit the example class I showed to you above. I’ve now modified it slightly, so that the Foo() constructor now requires you to pass an argument which is beyond the automatic capabilities of LuaWrap as it cannot know what you want to pass. The perfect case for a custom allocator.

// foo.hpp
#include <iostream>

class Foo
{
public:
  Foo(int val); // Constructor
  int get_val();
  void some_method();
private:
  int m_val;
};

Here’s how the allocator would look:

static int luafoo_alloc(lua_State* p_state)
{
  /* Ask Lua for a full userdata object. Note that
   * p_foo points to an *empty* block of memory after this.
   * If a memory allocation failure occured, p_foo would be
   * NULL after this. */
  Foo* p_foo = lua_newuserdata(p_state, sizeof(Foo));

  // Use placement new to place the actual Foo instance
  // into the memory Lua gave us.
  p_foo = new (p_foo) Foo(33);

  /* Tell LuaWrap to attach the appropriate instance method
   * table to the userdata object on top of the stack
   * (lua_newuserdata() places the new object on the stack) */
  LuaWrap::InternalC::set_imethod_table(p_state);

  // Tell Lua we return one object.
  return 1;
}

// ...

void luafoo_open(lua_State* p_state)
{
  // register_class() takes a 5th parameter indicating the
  // custom allocator.
  LuaWrap::register_class<Foo>(p_state, "Foo", s_foo_imethods, NULL, luafoo_alloc);
}

Note this example doesn’t do any error checks to simplify the code. In reality, you want to test whether lua_newuserdata() returned a valid pointer (it could have ran out of memory) and whether the placement new didn’t return NULL which it may do on allocation failure.

Finalizers

As with the allocators, some theory first. As already explained, Lua knows about two kinds of userdata objects: So-called "full" and "light" userdata objects. Light userdata is merely a wrapper around a pointer and doesn’t get garbage collected at all, so you cannot use finalizers with light userdata. Therefore this section solely deals with full userdata objects which can have a special Lua metamethod named __gc() which is invoked just before the memory occupied by the object is freed.

LuaWrap’s default finalizer just calls the wrapped C++ object’s destructor and is implemented like this:

    template<typename WrappedClass>
    int default_gc(lua_State* p_state)
    {
      WrappedClass* p_wrapped = (WrappedClass*) lua_touserdata(p_state, 1);
      /* Call the object’s destructor. You CANNOT call `delete'
       * here, because the memory occupied by the object is
       * managed by Lua (you remember the placement new for
       * the allocators, do you?), including any free operation.
       * If you free it here and Lua tries to do so when this
       * method returns, you’ll get a segmentation fault. */
      p_wrapped->~WrappedClass();
      return 0;
    }

This is usually enough for wrapping standard C++ classes, but sometime you may want to tweak the finalization process a bit more. Maybe you want to write the object destruction to some kind of log file or just to the standard output. In this case you’ll need your own finalizer. Nothing easier than that:

static int luafoo_finalize(lua_State* p_state)
{
  Foo* p_wrapped = (Foo*) lua_touserdata(p_state, 1);
  std::cout << "Finalizing an instance of Foo." << std::endl;
  p_wrapped->~Foo();
  return 0;
}

// ...

void luafoo_open(lua_State* p_state)
{
  // Not really surprsingly our super-hero `register_class()' also
  // takes a parameter for your custom finalizer.
  LuaWrap::register_class<Foo>(p_state,
                               "Foo",
                               s_foo_imethods,
                               NULL, // No special class methods
                               luafoo_alloc,
                               luafoo_finalize);
}

There may be some rare usecases where you need a custom finalizer, but not a custom constructor. You can achieve this by just instanciating the standard constructor template function with your class:

LuaWrap::register_class<CrazyClass>(p_state,
                                    s_crazy_class_imethods,
                                    NULL,
                                    LuaWrap::InternalLua::default_new<CrazyClass>,
                                    luacrazyclass_finalize);

License

LuaWrap is licensed under the 2-clause BSD license.

Copyright © 2012 Pegasus Alpha

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.