Contents

  1. Using Lua with C++
  2. Running Lua programs
  3. Calling C functions from Lua
  4. Loading and running bytecode
  5. Next steps
  6. Using RAII
  7. Comments

License

Creative Commons License
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License.
Valid XHTML 1.0 Strict

Using Lua with C++ — A short tutorial

Written by Christian Stigen Larsen

License

Creative Commons License
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License.

Using Lua is easy! In this short tutorial we'll show how to write a fully working host-program in C++ with Lua callbacks.

Since the static Lua libraries are written in C, you must import them as such:

extern "C" {
#include "lua.h"
}

int main()
{
  lua_State *L = lua_open();
  lua_close(L);
  return 0;
}

Update 2007-05-14: It has been reported that on some systems you need to include the headers lualib.h and lauxlib.h to compile the above example:

extern "C" {
#include "lualib.h"
#include "lauxlib.h"
}

Compiling and linking with GNU g++:

g++ host.cpp -o host -Ilua-5.0.2/include/ -Llua-5.0.2/lib/ -llua

Including lualib.h and lauxlib.h makes it easy to write a fully working host:

#include <iostream>

extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

void report_errors(lua_State *L, int status)
{
  if ( status!=0 ) {
    std::cerr << "-- " << lua_tostring(L, -1) << std::endl;
    lua_pop(L, 1); // remove error message
  }
}

int main(int argc, char** argv)
{
  for ( int n=1; n<argc; ++n ) {
    const char* file = argv[n];

    lua_State *L = lua_open();

    luaopen_io(L); // provides io.*
    luaopen_base(L);
    luaopen_table(L);
    luaopen_string(L);
    luaopen_math(L);
    luaopen_loadlib(L);

    std::cerr << "-- Loading file: " << file << std::endl;

    int s = luaL_loadfile(L, file);

    if ( s==0 ) {
      // execute Lua program
      s = lua_pcall(L, 0, LUA_MULTRET, 0);
    }

    report_errors(L, s);
    lua_close(L);
    std::cerr << std::endl;
  }

  return 0;
}

Compilation and linking:

g++ host.cpp -o host -Ilua-5.0.2/include/ -Llua-5.0.2/lib/ -llua -llualib

Running Lua programs

Let's test this with some Lua programs. The files here are from the distribution, hello.lua is simply:

-- the first program in every language
io.write("Hello world, from ",_VERSION,"!\n")

Executing a couple of Lua programs with our host program produces:

[csl@eris:~/dev/lua/lua-5.0.2]$ ./host test/hello.lua test/printf.lua
-- Loading file: test/hello.lua
Hello world, from Lua 5.0.2!

-- Loading file: test/printf.lua
Hello csl from Lua 5.0.2 on Wed Mar  2 13:13:05 2005

Calling C functions from Lua

It gets very interesting when Lua programs call your own functions. In the following program, we define a function my_function() and register it with the Lua environment using lua_register(). Our function prints its arguments as strings and returns the integer value of 123.

#include <iostream>

extern "C"{
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

int my_function(lua_State *L)
{
  int argc = lua_gettop(L);

  std::cerr << "-- my_function() called with " << argc
    << " arguments:" << std::endl;

  for ( int n=1; n<=argc; ++n ) {
    std::cerr << "-- argument " << n << ": "
      << lua_tostring(L, n) << std::endl;
  }

  lua_pushnumber(L, 123); // return value
  return 1; // number of return values
}

void report_errors(lua_State *L, int status)
{
  if ( status!=0 ) {
    std::cerr << "-- " << lua_tostring(L, -1) << std::endl;
    lua_pop(L, 1); // remove error message
  }
}

int main(int argc, char** argv)
{
  for ( int n=1; n<argc; ++n ) {
    const char* file = argv[n];

    lua_State *L = lua_open();

    luaopen_io(L); // provides io.*
    luaopen_base(L);
    luaopen_table(L);
    luaopen_string(L);
    luaopen_math(L);
    luaopen_loadlib(L);

    // make my_function() available to Lua programs
    lua_register(L, "my_function", my_function);

    std::cerr << "-- Loading file: " << file << std::endl;

    int s = luaL_loadfile(L, file);

    if ( s==0 ) {
      // execute Lua program
      s = lua_pcall(L, 0, LUA_MULTRET, 0);
    }

    report_errors(L, s);
    lua_close(L);
    std::cerr << std::endl;
  }

  return 0;
}

Let's write a small Lua program test.lua to call my_function():

io.write("Running ", _VERSION, "\n")
a = my_function(1, 2, 3, "abc", "def")
io.write("my_function() returned ", a, "\n")

With the new host program above, running test.lua produces:

[csl@eris:~/dev/lua/lua-5.0.2]$ ./host test.lua
-- Loading file: test.lua
Running Lua 5.0.2
-- my_function() called with 5 arguments:
-- argument 1: 1
-- argument 2: 2
-- argument 3: 3
-- argument 4: abc
-- argument 5: def
my_function() returned 123

Loading and running bytecode

The luaL_loadfile() function loads both source programs as well as compiled bytecode, so the following works as a charm:

$ ./bin/luac -s -o test.bytecode test.lua
$ ls -lka test.bytecode
-rw-r--r--    1 csl csl   307 mar  2 13:46 test.bytecode
$ ./host test.bytecode
-- Loading file: test.bytecode
Running Lua 5.0.2
-- my_function() called with 5 arguments:
-- argument 1: 1
-- argument 2: 2
-- argument 3: 3
-- argument 4: abc
-- argument 5: def
my_function() returned 123

Omitting our host program's log-messages produces clean output:

[csl@eris:~/dev/lua/lua-5.0.2]$ ./main test.bytecode 2>/dev/null
Running Lua 5.0.2
my_function() returned 123

Next steps

Suggestions for next steps would be to investigate how to have Lua's closures integrate neatly with your host program.

If you're writing programs without consoles, then you'd probably want to trap io.write(). I did that by copying the code from lualib.c and changing io_write to point to my own function. This can be useful for game programming or plain X/Windows applications where you want to catch output.

Using RAII

Also I'd recommend using the resource-acquisition-is-initialization (RAII) technique in which resources are allocated in a constructor and freed in the destructor. Using operator overloading, we can elegantly hide the details:

#include <iostream>

extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

class Lua_State
{
  lua_State *L;
public:
  Lua_State() : L(lua_open()) { }

  ~Lua_State() {
    lua_close(L);
  }

  // implicitly act as a lua_State pointer
  inline operator lua_State*() {
    return L;
  }
};

static void open_libs(lua_State *L)
{
  luaopen_io(L);
  luaopen_base(L);
  luaopen_table(L);
  luaopen_string(L);
  luaopen_math(L);
  luaopen_loadlib(L);
}

static int execute_program(lua_State *L)
{
  // make a short function to make program behaviour more clear
  return lua_pcall(L, 0, LUA_MULTRET, 0);
}

static void report_errors(lua_State *L, const int status)
{
  if ( status!=0 ) {
    std::cerr << "-- " << lua_tostring(L, -1) << std::endl;
    lua_pop(L, 1); // remove error message
  }
}

int main(int argc, char** argv)
{
  for ( int n=1; n<argc; ++n ) {

    Lua_State L;
    open_libs(L);

    int s = luaL_loadfile(L, argv[n]);

    if ( s==0 )
      s = execute_program(L);

    report_errors(L, s);

    // lua_close(L) automatically called here
  }

  return 0;
}

 

blog comments powered by Disqus