分享

Sweet Lua

 quasiceo 2014-01-13

1. Overview

Sweet Lua is a C++ to Lua binding library. It compiles with Microsoft Visual Studio 2008 (MSVC 9.0), MinGW (GCC 4.6.2), and Xcode (LLVM-GCC 4.2.1).

Features:

  • Bind C++ functions as Lua functions and closures with out of order parameters.

  • Bind C++ objects as Lua tables with lifetime controlled by either C++ or Lua.

  • Coroutines.

  • Type safety.

  • Error handling.

  • Convert C++ iterator sequences to Lua iterators.

  • STL vector, list, set, and map integration.

  • Boost Filesystem integration.

1.1. Luabind vs Sweet Lua

Luabind is a C++/Lua binding library written by Daniel Wallin and Arvid Nordberg. Documentation is available at http://www./products/luabind/docs.html.

Luabind provides more infrastructure for object oriented programming than Sweet Lua does. Luabind provides explicit support for registering classes with the Lua environment while Sweet Lua provides support for calling member functions on objects but leaves the implementation of object orientation to the client. We believe this is a more flexible approach and allows the scripting environment to more closely match the problem domain.

Luabind supports overloaded functions stored in the same value in Lua and uses best match signature matching based on the types of parameter passed when the function is called. Sweet Lua supports overloaded functions but doesn’t do signature matching so overloaded functions must be stored in different values in the Lua virtual machine. If parameters of the wrong type are passed then Sweet Lua generates an error.

Luabind uses return value and parameter policies to affect the way in which return values and parameters are passed between C++ and Lua. Sweet Lua provides return value policies to generate raw, yielding, or weakening functions.

Luabind depends on Boost but Sweet Lua does not.

Luabind is tested on far more compilers while Sweet Lua is only tested and supported with Visual Studio 9.0.

In summary Luabind offers more out of the box support for C++ style object orientation with Lua - it provides a C++ like environment in Lua. Sweet Lua doesn’t provide as much out of the box support for object orientation - it provides a Lua like environment for C++ to host - with support for the style of object orientation and execution model more in control of the C++ application developer.

1.2. SWIG vs Sweet Lua

SWIG (simple wrapper interface generator) generates code to interface C++ programs with scripting languages. Documentation is available at http://www./index.php.

SWIG generates C++ source code to provide bindings from C++ header files and so requires an extra conversion step in addition to compilation. Sweet Lua is written in straight C++ and so compiles as part of your source code.

SWIG generates bindings for many scripting languages as well as for Lua. Sweet Lua only helps with binding to Lua.

In summary SWIG is probably better for you if you want to support binding to other languages in addition to Lua and if you don’t mind adding an additional generation step to your build.

2. Installation

Sweet Lua is built and installed by downloading the latest version http://www./, extracting the archive onto your computer, and then running "build.bat" (on Windows) or "sh ./build.sh" (on MacOSX) from the top level directory. This builds all variants and a Visual Studio solution or Xcode project to browse through the source.

You’ll need to add the top level directory (e.g. "c:\sweet\sweet_lua") and the library directory (e.g. "c:\sweet\sweet_lua\lib") to your compiler’s header and library search paths respectively.

If you want Sweet Lua built to another location or with different variants and/or settings then you’ll need to edit the settings in "sweet/build.lua".

3. Usage

Sweet Lua is a C++ library for providing Lua bindings to C++ variables, functions, and objects. It provides classes to represent the Lua virtual machine (Lua), a table or object (LuaObject), and a coroutine or thread (LuaThread).

The Lua class represents the Lua global environment and virtual machine. It provides functions to execute Lua scripts from files and memory and to define Lua bindings to C++ variables, functions, and objects.

The LuaObject class represents an object in the Lua virtual machine. It is typically used to create prototypes or metatables in Lua that have no equivalent C++ object to associate with non-intrusively. When a LuaObject is constructed a corresponding table is created in the Lua global environment. Both the C++ application and the Lua virtual machine can then set variables on and call member functions of that table.

The LuaThread class represents a Lua coroutine. It provides functions to start and resume execution in a separate yieldable thread in the Lua virtual machine. The calls can yield and return control from the virtual machine back to the program allowing asynchronous behaviour to be abstracted behind an interface that scripts can treat as synchronous.

3.1. Initialization

Initializing the library is as simple as constructing a Lua object to represent the global environment and virtual machine and registering the variables, functions, and objects in the interface being bound.

The library has no static state. Creating multiple Lua objects poses no problems. However, like Lua itself, objects are not thread-safe. Only one thread can call into a Lua virtual machine at once.

LuaObjects and LuaThreads may only be used only with the Lua object that they were created in (as they can only represent a table or coroutine in a single Lua virtual machine).

Once a Lua object has been created the values of variables can be set to the values of C++ booleans, integers, floats, strings, functions, and objects.

Setting a variable to the value of a C++ function sets the value of that variable to a C++ function that decodes the arguments expected by the target C++ function from the Lua stack, makes the call to the target C++ function, and pushes any return value back onto the Lua stack.

Parameters to a C++ function can be bound as upvalues when the function is registered. Simply passing a value at the time of registration stores that value as an upvalue of the registered function in the Lua virtual machine. The upvalue is then provided as a parameter when the C++ function is called by Lua. Any other parameters expected by the C++ function are mapped as per normal after the bound upvalues.

Parameters to a C++ function can be bound out of order using one of the out of order parameter values _1, _2, _3, _4, _5, or _6. The number of the out of order parameter specifies the index of the Lua parameter that is to be provided to the C++ parameter at the position that it appears in (the same as Boost Bind).

Out of order parameters and upvalue binding can be used together to provide any combination of bound values and out of order parameters.

#include <sweet/lua/Lua.hpp>
#include <sweet/error/ErrorPolicy.hpp>
#include <stdio.h>
#include <string.h>

using namespace std;
using namespace sweet;
using namespace sweet::lua;

static void print( const string& message )
{
    printf( "%s", message.c_str() );
}

void lua_hello_world_example()
{
    error::ErrorPolicy error_policy;
    Lua lua( error_policy );
    lua.globals()
        ( "print", &print )
    ;

    const char* script = "print('Hello World!\\n')";
    lua.call( script, script + strlen(script), "hello_world" ).end();
}

3.2. Scripts and Functions

Scripts and functions are executed by calling Lua::call(), LuaThread::call(), or LuaThread::resume().

All of these functions return a helper object that provides a convenient syntax for supplying the parameters to the call and getting the return value via AddParameter::end(). It is important to call AddParameter::end() even if no parameters are passed and no return value is expected as this call does the actual execution. If AddParameter::end() is not called then nothing will be executed.

#include <sweet/lua/Lua.hpp>
#include <sweet/error/ErrorPolicy.hpp>
#include <string.h>

using namespace sweet;
using namespace sweet::lua;

void lua_factorial_example()
{
    error::ErrorPolicy error_policy;
    Lua lua( error_policy );

    const char* script =
        "function factorial(n) \n"
        "   if n <= 1 then \n"
        "       return 1 \n"
        "   else \n"
        "       return n * factorial(n - 1) \n"
        "   end \n"
        "end"
    ;
    lua.call( script, script + strlen(script), "factorial" )
    .end();

    int factorial = 0;
    lua.call( "factorial" )
        ( 4 )
    .end( &factorial );
    SWEET_ASSERT( factorial == 24 );
}

3.3. Objects

By default C++ objects are stored in Lua as user data. C++ objects passed to Lua are copy constructed into user data values. When these user data values are garbage collected the destructor for the C++ object is automatically called and the memory allocated for them is freed.

More usefully C++ objects can be stored in Lua as tables. C++ objects stored as tables are automatically converted to and from their equivalent table when passed to and from Lua.

The C++ class of objects to be represented by reference must have its storage type trait defined to LuaReference using the SWEET_LUA_TYPE_CONVERSION() macro so that the correct conversions are performed when passing objects of that class between C++ and Lua.

Lua tables that are associated with C++ objects are explicitly created and destroyed by users of the library through calls to Lua::create(), Lua::create_with_existing_table(), and Lua::destroy().

Once the table has been created its fields can be set to the values of C++ booleans, integers, floats, strings, objects, and functions. The fields can be cleared by setting their value to nil. See Lua::members() and LuaObject::members().

Two important fields that must always be set are the __type and __this fields that store the type information and this pointer for the C++ object respectively. This must be done explicitly by calling the AddMember::type() and AddMember::this_pointer() functions.

The value of a variable can be set to a member function. The first parameter of these calls is always assumed to provide the this pointer of the object to make the call on. This works with Lua’s mechanism of implicitly providing the self parameter to calls made using the ":" syntax. The Lua table passed as the implicit self parameter is converted into its equivalent C++ object using its __type and __this fields.

Member functions that have been defined in a Lua script (using the ":" syntax or that take an explicit self parameter) on an object that corresponds to a C++ object can be called using the overload of the Lua::call() function that takes an object as its second parameter. This looks up the function as a member of the object’s table but doesn’t pass the object’s table as the implicit first parameter to the call (the self parameter) - this must be done by the application itself. This gives the application the opportunity to provide a different value for the self parameter.

#include <sweet/lua/Lua.hpp>
#include <sweet/error/ErrorPolicy.hpp>
#include <stdio.h>
#include <string.h>

using namespace std;
using namespace sweet;
using namespace sweet::lua;

enum LogMask
{
    LOG_NULL = 0x00,
    LOG_ERROR = 0x01,
    LOG_WARN = 0x02
};

class Log
{
    int level_;

    public:
        Log( int level )
        : level_( level )
        {
        }

        void set_level( int level )
        {
            level_ = level;
        }

        int get_level() const
        {
            return level_;
        }

        void log( int level, const std::string& format )
        {
            if ( level_ & level )
            {
                printf( "%s\n", format.c_str() );
            }
        }
};

SWEET_LUA_TYPE_CONVERSION( Log, LuaByReference );

void lua_logging_example()
{
    error::ErrorPolicy error_policy;
    Lua lua( error_policy );

    Log log( LOG_NULL );
    lua.create( log );
    lua.members( log )
        .type( SWEET_STATIC_TYPEID(Log) )
        .this_pointer( &log )
        ( "LOG_NULL", int(LOG_NULL) )
        ( "LOG_ERROR", int(LOG_ERROR) )
        ( "LOG_WARN", int(LOG_WARN) )
        ( "set_level", &Log::set_level )
        ( "get_level", &Log::get_level )
        ( "log", &Log::log )
    ;

    lua.globals()
        ( "log", log )
    ;

    const char* script =
        "log:set_level( log.LOG_ERROR + log.LOG_WARN );\n"
        "log:log( log.LOG_ERROR, 'This is an error' );\n"
        "log:log( log.LOG_WARN, 'This is a warning' );\n"
    ;
    lua.call( script, script + strlen(script), "logging" ).end();
}

3.4. Classes and Prototypes

Lua is a prototype based language.

A prototype defines the behaviour of an object in a prototype based language. The relationship between an object and its prototype in a prototype based language is similar to the relationship between an object and a class in C++.

The prototype of an object is another object that index operations that fail on the first object are resolved on. For example if a script calls the get_value function on an object but that doesn’t define a get_value function then the get_value function will then be looked up on that object’s prototype. Because prototypes are themselves objects they can also have prototypes allowing for multiple levels of inheritance.

For more descriptions of object oriented programming in Lua and prototype based languages in general have a look at:

Prototypes can be provided using the library by creating a LuaObject to use as the prototype table and a LuaObject to use as the metatable that redirects failed index operations to the prototype. One or more objects are then set to use that prototype by setting their metatable to be the metatable created by the second LuaObject. See AddMember::metatable().

Note that in the following example the prototype table stores the __type field while the object itself stores the __this field. This is correct. It is the prototype that is representing the type in this scenario while the object itself must provide the this pointer in order to resolve back to the correct C++ object.

#include <sweet/lua/Lua.hpp>
#include <sweet/lua/LuaObject.hpp>
#include <sweet/error/ErrorPolicy.hpp>
#include <stdio.h>
#include <string.h>

using namespace std;
using namespace sweet;
using namespace sweet::lua;

enum LogMask
{
    LOG_NULL = 0x00,
    LOG_ERROR = 0x01,
    LOG_WARN = 0x02
};

class Log
{
    int level_;

    public:
        Log( int level )
        : level_( level )
        {
        }

        void set_level( int level )
        {
            level_ = level;
        }

        int get_level() const
        {
            return level_;
        }

        void log( int level, const std::string& format )
        {
            if ( level_ & level )
            {
                printf( "%s\n", format.c_str() );
            }
        }
};

SWEET_LUA_TYPE_CONVERSION( Log, LuaByReference );

void lua_logging_prototype_example()
{
    error::ErrorPolicy error_policy;
    Lua lua( error_policy );

    LuaObject log_prototype( lua );
    log_prototype.members()
        .type( SWEET_STATIC_TYPEID(Log) )
        ( "LOG_NULL", static_cast<int>(LOG_NULL) )
        ( "LOG_ERROR", static_cast<int>(LOG_ERROR) )
        ( "LOG_WARN", static_cast<int>(LOG_WARN) )
        ( "set_level", &Log::set_level )
        ( "get_level", &Log::get_level )
        ( "log", &Log::log )
    ;

    LuaObject log_metatable( lua );
    log_metatable.members()
        ( "__index", log_prototype )
    ;

    Log log( LOG_NULL );
    lua.create( log );
    lua.members( log )
        .metatable( log_metatable )
        .this_pointer( &log )
    ;

    lua.globals()
        ( "log", log )
    ;

    const char* script =
        "log:set_level( log.LOG_ERROR + log.LOG_WARN );\n"
        "log:log( log.LOG_ERROR, 'This is an error' );\n"
        "log:log( log.LOG_WARN, 'This is a warning' );\n"
    ;

    lua.call( script, script + strlen(script), "logging" ).end();
}

3.5. Weak Objects

Often an application will give Lua script ownership of the lifetime of the objects that it creates. The simplest way to do this is to store an object by value in the Lua table to manage the lifetime of the C++ object (for example a reference counted pointer back to the original C++ object). This unfortunately causes a memory leak because the C++ object is associated with the table through a strong reference stored in the Lua registry at the same time as the table is referencing the C++ object via a reference counted smart pointer or some other mechanism that maintains a strong reference. This cycle allows the C++ object and the Lua table to live forever.

To break the cycle the table can be stored in a special weak objects table. This weakens the reference from the C++ object to the Lua table allowing Lua to garbage collect the table when it is no longer referenced (because the C++ side is no longer holding a strong reference to it only the references that Lua itself holds are counted towards keeping it alive) and when this happens, provided that the lifetime management object (e.g. the reference counted pointer) has an appropriate destructor, the C++ object will also be destroyed.

Objects are weakened when returned from C++ functions that have been registered using the weaken() policy. The weakened table is moved from the Lua registry to the weak objects table while a strong reference is still held to it from the returned value on the Lua stack. Weakening is implemented this way so that there is always a strong reference to the object to prevent it being garbage collected at least up until the point where user code can maintain its own strong reference to it. This prevents the weak object being garbage collected before user code has had an opportunity to do something with it.

The typical implementation of this uses shared_ptrs with a custom deleter to ensure that Lua::destroy() is called to remove the dangling weak reference from the weak objects table.

#if defined SWEET_BOOST_ENABLED

#include <sweet/lua.hpp>
#include <sweet/lua/shared_ptr.hpp>

using namespace boost;
using namespace sweet::lua;

class Log;
SWEET_LUA_TYPE_CONVERSION( Log, LuaReference );

enum LogMask
{
    LOG_NULL = 0x00,
    LOG_ERROR = 0x01,
    LOG_WARN = 0x02
};


class Log
{
    int level_;

public:
    Log( int level )
    : level_( level )
    {
    }

    void set_level( int level )
    {
        level_ = level;
    }

    int get_level() const
    {
        return level_;
    }

    void log( int level, const std::string& format )
    {
        if ( level_ & level )
        {
            printf( "%s\n", format.c_str() );
        }
    }
};


struct LogCreator
{
    Lua* lua_;
    LuaObject* log_metatable_;

    LogCreator( Lua* lua, LuaObject* log_metatable )
    : lua_( lua ),
      log_metatable_( log_metatable )
    {
    }

    shared_ptr<Log> create()
    {
        struct LogDeleter
        {
            Lua* lua_;

            LogDeleter( Lua* lua )
            : lua_( lua )
            {
            }

            void operator()( Log* log ) const
            {
                lua_->destroy( *log );
                delete log;
                log = NULL;
            }
        };

        shared_ptr<Log> log( new Log(LOG_NULL), LogDeleter(lua_) );
        lua_->create( *log );
        lua_->members( *log )
            .metatable( *log_metatable_ )
            .this_pointer( log.get() )
            ( "ptr", value(log) )
        ;
        return log;
    }
};

void lua_logging_owned_by_lua_example()
{
    Lua lua;

    LuaObject log_prototype( lua );
    log_prototype.members()
        .type( SWEET_STATIC_TYPEID(Log) )
        ( "LOG_NULL", static_cast<int>(LOG_NULL)  )
        ( "LOG_ERROR", static_cast<int>(LOG_ERROR) )
        ( "LOG_WARN", static_cast<int>(LOG_WARN)  )
        ( "set_level", &Log::set_level )
        ( "get_level", &Log::get_level )
        ( "log", &Log::log       )
    ;

    LuaObject log_metatable( lua );
    log_metatable.members()
        .type( SWEET_STATIC_TYPEID(LuaObject) )
        ( "__index", log_prototype )
    ;

    LogCreator log_creator( &lua, &log_metatable );
    lua.globals()
        ( "Log", weaken(&LogCreator::create), &log_creator )
    ;

    const char* script =
        "local log = Log();\n"
        "log:set_level( log.LOG_ERROR + log.LOG_WARN );\n"
        "log:log( log.LOG_ERROR, 'This is an error' );\n"
        "log:log( log.LOG_WARN, 'This is a warning' );\n"
    ;
    lua.call( script, script + strlen(script), "logging", 0 ).end();
}

#else

void lua_logging_owned_by_lua_example()
{
}

#endif

3.6. Coroutines

The library provides support for coroutines through the LuaThread object and the yield() policy that is used to generate a C++ function that yields after it has been called.

The LuaThread object is constructed just like a LuaObject. Once constructed it then behaves similarly to a Lua object except that it can resume functions as well as call them.

The yield() function is used to wrap the function pointer passed in when setting the value of C++ functions. It marks the function as needing to generate a yield on return instead of returning control to Lua.

3.7. Standard Template Library Integration

There is a small amount of integration with the Standard Template Library. The standard containers vector, list, set, multiset, map, and multimap can all be implicitly converted into Lua iterators that iterates over their [begin, end) sequences.

There is also a lua_push() implementation that will take two STL iterators ( first and last) and convert them into a Lua iterator that iterates over the [first, last) sequence. This makes it simple to convert other C++ sequences to Lua iterators by providing the right overloaded lua_push() function and converting the sequence into a Lua iterator.

#include <sweet/lua/Lua.hpp>
#include <sweet/lua/vector.hpp>
#include <sweet/rtti/macros.hpp>
#include <sweet/error/ErrorPolicy.hpp>
#include <string>
#include <string.h>

using std::string;
using std::vector;
using namespace sweet;
using namespace sweet::lua;

struct Target
{
    vector<string> values_;

    Target()
    : values_()
    {
    }

    void add_value( const string& value )
    {
        values_.push_back( value );
    }

    const vector<string>& get_values() const
    {
        return values_;
    }
};

SWEET_LUA_TYPE_CONVERSION( Target, LuaByReference );

void lua_vector_example()
{
    Target target;
    target.add_value( "one" );
    target.add_value( "two" );
    target.add_value( "three" );

    error::ErrorPolicy error_policy;
    Lua lua( error_policy );
    lua.create( target );
    lua.members( target )
        .type( SWEET_STATIC_TYPEID(Target) )
        .this_pointer( &target )
        ( "get_values", &Target::get_values )
    ;
    lua.globals()
        ( "target", target )
    ;

    const char* script = "for v in target:get_values() do print(v) end;";
    lua.call( script, script + strlen(script), "log" ).end();
}

3.8. Boost Integration

There is a small amount of integration with the Boost Filesystem library. The basic_directory_iterator<> and basic_recursive_directory_iterator<> iterators are implicitly converted into Lua iterators that iterate from the specified iterator to the default constructed "end" iterator. The basic_directory_enrty<> class template is implicitly converted into a string.

#include <sweet/lua/filesystem.hpp>
Copyright (C) 2006 - 2013 Charles Baker. All rights reserved.

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多