If configuration files don’t work, you have to rely on scripts. Lua is almost always the first choice for C++ programs that want to embed scripts.

Lua source code comes with a Makefile, can compile a static library, interpreter, compiler three object files, as the host C++ program, in addition to Lua header file, should also link to the static library.

If C++ programs are built with CMake, it’s not difficult to create a static library for Lua using CMake. CMake solves the cross-platform problem nicely.

There are only two problems with script extensions: how do I get Lua to access C++ objects? How can C++ access Lua objects? Object, of course, is a broad concept that includes variables, functions, classes, and so on.

With LuaBridge, you can easily solve both of these problems.

The header file

I’ll give you the header file first, and I won’t talk about it later. I will include a few Lua header files first. Since this is C code, I can only mix them with C++ programs after placing them in extern “C”.

extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}  // extern "C"Copy the code

LuaBridge, like STL, has only header files, which are directly included for use.

#include "LuaBridge/LuaBridge.h"Copy the code

Lua access to c + +

function

C++ function SayHello “exports” to Lua function SayHello, and then executes Lua code through luaL_dostring to call this function.

void SayHello() {
  std::cout << "Hello, World!" << std::endl;
}Copy the code
int main() {
  lua_State* L = luaL_newstate();
  luaL_openlibs(L);

  luabridge::getGlobalNamespace(L)
    .addFunction("sayHello", SayHello);

  luaL_dostring(L, "sayHello()");

  lua_close(L);
}Copy the code

Output:

Hello, World!Copy the code

Add an argument to SayHello:

void SayHello(const char* to) {
  std::cout << "Hello, " << to << "!" << std::endl;
}Copy the code
luabridge::getGlobalNamespace(L)
  .addFunction("sayHello", SayHello);

luaL_dostring(L, "sayHello('Lua')");Copy the code

Output:

Hello, Lua!Copy the code

class

C++ classes export tables as Lua, and the member functions of the class correspond to the members of the table. Suppose we have a class Line that represents a Line in a text file:

class Line {
public:
  Line(const std::string& data)
      : data_(data) {
  }

  size_t Length() const {
    return data_.length();
  }

private:
  std::string data_;
};Copy the code

The constructor

AddConstructor (); addFunction ();

luabridge::getGlobalNamespace(L)
  .beginClass("Line")
    .addConstructor()
    .addFunction("getLength", &Line::Length)
  .endClass();Copy the code

The constructor cannot be addressed; addConstructor is called by passing a template parameter to indicate the type.

Testing:

const char* str =
  "line = Line('test')\n"
  "print(line:getLength())\n";

luaL_dostring(L, str);Copy the code

Output:

4Copy the code

If there are multiple constructors, such as one default constructor:

Line::Line();Copy the code

Only one can be exported. The second will override the first:

Luabridge: : getGlobalNamespace (L). BeginClass (" Line "). AddConstructor () / / covered by next addConstructor () endClass ();Copy the code

You can’t have the same name for two things.

A member function

Consider a slightly more complex member function, StartWith determines whether a line of text begins with a string and ignore_spaces determines whether the space at the beginning of the line is ignored. Those not interested in the implementation can be completely ignored.

bool Line::StartWith(const std::string& str, bool ignore_spaces) const { size_t i = 0; if (ignore_spaces && ! IsSpace(str[0])) { for (; i < data_.size() && IsSpace(data_[i]); ++i) { } } if (data_.size() < i + str.size()) { return false; } if (strncmp(&data_[i], &str[0], str.size()) == 0) { return true; } return false; }Copy the code

Export to Lua via addFunction:

addFunction("startWith", &Line::StartWith)Copy the code

Testing:

const char* str = "line = Line(' if ... ')\n" "print(line:startWith('if', false))\n" "print(line:startWith('if', true))\n";Copy the code

Output:

false
trueCopy the code

Output parameters

Now add an optional output parameter to StartWith so that ignore_spaces returns offset information (the subscript of the first non-null character) when set to true:

bool Line::StartWith(const std::string& str, bool ignore_spaces, int* off = NULL) const { size_t i = 0; if (ignore_spaces && ! IsSpace(str[0])) { for (; i < data_.size() && IsSpace(data_[i]); ++i) { } } if (data_.size() < i + str.size()) { return false; } if (strncmp(&data_[i], &str[0], str.size()) == 0) { if (off ! = NULL) { *off = static_cast(i); } return true; } return false; }Copy the code

Output parameters are a common use in C/C++, allowing a function to return multiple values. But StartWith exported with addFunction cannot be called by Lua because Lua has no output parameters. Fortunately, Lua functions can have multiple return values, so in order for StartWith to return multiple values, we need to wrap Lua CFunction.

// Lua CFunction wrapper for startWith. int Line::Lua_StartWith(lua_State* L) {int n = lua_getTop (L); If (n! = 3) { luaL_error(L, "incorrect argument number"); } // Validate the argument type if (! lua_isstring(L, 2) || ! lua_isboolean(L, 3)) { luaL_error(L, "incorrect argument type"); } // get STD ::string STR (lua_tostring(L, 2)); bool ignore_spaces = lua_toboolean(L, 3) ! = 0; StartWith int off = 0; bool result = StartWith(str, ignore_spaces, &off); Luabridge ::push(L, result); luabridge::push(L, off); return 2; // Return two values}Copy the code

A function of type int (*) (lua_State*) is called Lua CFunction. Use addCFunction to export Lua_StartWith:

addCFunction("startWith", &Line::Lua_StartWith)Copy the code

Testing:

const char* str = "line = Line(' if ... ')\n" "ok, off = line:startWith('if', true)\n" "print(ok, off)\n";Copy the code

Output:

true   2Copy the code

varargs

Now that we’ve wrapped CFunction, we might as well do it more thoroughly. Given Lua’s good support for variables, we’ll let startWith support variables, such as whether to startWith ‘if’ :

line:startWith(true, 'if')Copy the code

We can also determine whether the prefix is ‘if’ or ‘else’ :

line:startWith(true, 'if', 'else')Copy the code

To do this, ignore_spaces becomes the first argument, followed by a string argument, which is implemented as follows:

int Line::Lua_StartWith(lua_State* L) { int n = lua_gettop(L); if (n < 3) { luaL_error(L, "incorrect argument number"); } if (! lua_isboolean(L, 2)) { luaL_error(L, "incorrect argument type"); } bool ignore_spaces = lua_toboolean(L, 2) ! = 0; bool result = false; int off = 0; // Compare string arguments one by one and break out of the loop if they match. for (int i = 3; i <= n; ="" ++i)="" {="" if="" (! lua_isstring(l,="" i))="" break; ="" }="" std::string="" str(lua_tostring(l,="" i)); ="" (startwith(str,="" ignore_spaces,="" &off))="" result="true;" luabridge::push(l,="" result); ="" off); ="" return="" 2; ="" }<="" code="">Copy the code

Testing:

const char* str = "line = Line(' else ... ')\n" "ok, off = line:startWith(true, 'if', 'else')\n" "print(ok, off)\n";Copy the code

Output:

true   2Copy the code

Execute the Lua file

The previous example uses luaL_dostring to execute all Lua codes. In actual projects, Lua codes mainly exist in the form of files, so luaL_dofile is required.

Testing:

luaL_dofile(L, "test.lua);Copy the code

The contents of the test.lua file are:

line = Line(' else ... ') ok, off = line:startWith(true, 'if', 'else') print(ok, off)Copy the code

Output:

true   2Copy the code

C + + access Lua

Use getGlobal to get a “global” Lua object of type LuaRef.

int main() { lua_State* L = luaL_newstate(); luaL_openlibs(L); {// Const char* STR = "world = 'world '\n" "sayHello = function(to)\n" "print('Hello, '.. to .. '! ')\n" "end\n"; luaL_dostring(L, str); using namespace luabridge; LuaRef world = getGlobal(L, "world"); LuaRef say_hello = getGlobal(L, "sayHello"); say_hello(world.cast()); } lua_close(L); }Copy the code

Output:

Hello, World!Copy the code

string

Lua has no character types and no Unicode strings (specifically, wchar_t*).

bool IsSpace(char c) {
  return c == ' ' || c == '\t';
}Copy the code
luabridge::getGlobalNamespace(L)
  .addFunction("isSpace", IsSpace);
    
luaL_dostring(L, "print(isSpace(' '))");
luaL_dostring(L, "print(isSpace('    '))");
luaL_dostring(L, "print(isSpace('c'))");Copy the code

Output:

true
true
falseCopy the code

If IsSpace is wchar_t:

bool IsSpace(wchar_t c) {
  return c == L' ' || c == L'\t';
}Copy the code

When isSpace(“) is called in Lua, LuaBridge fails to assert:

Assertion failed: lua_istable (L, -1), file e:\proj\lua_test\third_party\include\luabridge\detail/Us
erdata.h, line 189Copy the code

The middle ground is to provide a wrapper for IsSpace(wchar_t c) just for Lua.

bool Lua_IsSpace(char c) {
  return IsSpace((wchar_t)c);
}Copy the code
luabridge::getGlobalNamespace(L)
  .addFunction("isSpace", Lua_IsSpace);Copy the code

The premise, of course, is that Lua code calls isSpace with only ASCII characters passed in.

Error handling

To facilitate problem diagnosis and error handling, some encapsulation of built-in functions or macros is necessary.

luaL_dostring

bool DoLuaString(lua_State* L, const std::string& str, std::string* error = NULL) { if (luaL_dostring(L, str.c_str()) ! = LUA_OK) { if (error ! = NULL) {// Get error messages from the top of the stack. if (lua_gettop(L) ! = 0) { *error = lua_tostring(L, -1); } } return false; } return true; }Copy the code

Test: Deliberately call a nonexistent function SayHello (should be SayHello).

std::string error; if (! DoLuaString(L, "SayHello('Lua')", &error)) { std::cerr << error << std::endl; }Copy the code

Output (trying to call a null value) :

[string "SayHello('Lua')"]:1: attempt to call a nil value (global 'SayHello')Copy the code

luaL_dofile

Similar to the encapsulation of luaL_dostring.

bool DoLuaFile(lua_State* L, const std::string& file, std::string* error = NULL) { if (luaL_dofile(L, file.c_str()) ! = LUA_OK) { if (error ! = NULL) {// Get error messages from the top of the stack. if (lua_gettop(L) ! = 0) { *error = lua_tostring(L, -1); } } return false; } return true; }Copy the code

luabridge::LuaRef

LuaRef world = getGlobal(L, "world"); if (! world.isNil() && world.isString()) { // ... }Copy the code
LuaRef say_hello = getGlobal(L, "sayHello"); if (! say_hello.isNil() && say_hello.isFunction()) { // ... }Copy the code

luabridge::LuaException

LuaBridge will raise LuaException if there is a problem with Lua code, which is best placed in a try… The catch.

try {
  // ...
} catch (const luabridge::LuaException& e) {
  std::cerr << e.what() << std::endl;
}Copy the code