HowTo: Call Lua from C++, C++ from Lua, and build with CMake

Here is a basic example (C++ file, Lua file, CMake file) of how to create a Lua environment inside C++, call a Lua function from C++, and have that Lua function make a function call to the C++ environment that contains it.

This demo covers functions with a variable number of arguments, multiple return values, and error handling.

I also include a sample CMake file to build it.

To build Lua projects on Ubuntu Lucid (current as of this writing), you’ll need the liblua5.1-dev package.

Here’s my interaction.cpp file, inline:
#include <lua5 .1/lua.hpp>
#include <string>
using namespace std;
 
lua_State* myLuaState;
lua_State* myLuaSandbox;
int myErrHandler;
 
//spell out type names
string lua_typeof(int index)
{
switch (index)
{
case LUA_TNIL: return "nil";
case LUA_TNUMBER: return "number";
case LUA_TBOOLEAN: return "boolean";
case LUA_TSTRING: return "string";
case LUA_TTABLE: return "table";
case LUA_TFUNCTION: return "function";
case LUA_TUSERDATA: return "userdata";
case LUA_TTHREAD: return "thread";
case LUA_TLIGHTUSERDATA: return "light userdata";
 
default: return "unknown type";
}
}
 
//this is the API function we expose to lua.
int HAPI_echo(lua_State* L)
{
int args_from_lua = lua_gettop(L);
 
//in this example, we'll take any number of args
printf("HAPI_echo() called with %d arguments\n",
args_from_lua);
for (int n = 1; n < = args_from_lua; ++n)
{
printf(" * arg %02d (%s):\t%s\n",
n,
lua_typeof(lua_type(L, n)).c_str(),
lua_tostring(L, n));
//note: lua_tostring coerces stack value!
}
 
//return (123, "abc") to lua
lua_pushnumber(L, 123);
lua_pushstring(L, "abc");
return 2; // 2 return values are on the stack
 
}
 
void InitLua(lua_State* &L)
{
//open new state
L = luaL_newstate();
luaL_openlibs(L);
 
//register a very minimal API
lua_register(L, "h_echo", HAPI_echo);
}
 
void HaltLua(lua_State* L)
{
lua_close(L);
}
 
bool LuaFuncExists(lua_State* L, string func)
{
//try to put the function on top of the stack
lua_getglobal(L, func.c_str());
 
//check that value on top of stack is not nil
bool ret = !lua_isnil(L, -1);
 
//get rid of the value we put on the stack
lua_pop(L, 1);
 
return ret;
}
 
string describe(int resultcode)
{
switch (resultcode)
{
case 0:
return "Success";
case LUA_ERRRUN:
return "Runtime error";
case LUA_ERRSYNTAX:
return "Syntax error";
case LUA_ERRERR:
return "Error with error alert mechanism.";
case LUA_ERRFILE:
return "Couldn't open or read file";
}
}
 
//call a lua function.
// you must specify the quantity of of params and returns.
int CallLua(lua_State* L, string func,
int num_params, int num_returns)
{
//on error, execute lua function debug.traceback
// debug is a table, put it on the stack
lua_getglobal(L, "debug");
 
//-1 is the top of the stack
lua_getfield(L, -1, "traceback");
 
//traceback is on top, remove debug from 2nd spot
lua_remove(L, -2);
 
myErrHandler = lua_gettop(L);
printf("lua's error handler at pos %d\n",
myErrHandler);
 
//get the lua function and execute it
lua_getglobal (L, func.c_str());
int ret = lua_pcall(L, num_params, num_returns,
myErrHandler);
if (ret)
{
printf("\nLua call failed (%s): %s\n",
describe(ret).c_str(),
lua_tostring(myLuaState, -1));
}
 
//remove the error handler from the stack
lua_pop(myLuaState, 1);
 
return ret;
}
 
int main(void)
{
std::string myfn = "Init";
std::string myfile = "go.lua";
 
printf("initializing lua environment\n");
InitLua(myLuaState);
 
printf("function '%s' exists in lua? %d\n",
myfn.c_str(),
LuaFuncExists(myLuaState, myfn));
 
printf("loading/executing lua file %s\n",
myfile.c_str());
luaL_dofile(myLuaState, myfile.c_str());
 
printf("function '%s' exists in lua? %d\n",
myfn.c_str(),
LuaFuncExists(myLuaState, myfn));
 
printf("calling %s in %s\n",
myfn.c_str(), myfile.c_str());
 
int res = CallLua(myLuaState, myfn, 0, 0);
printf("returned from call to %s in %s\n",
myfn.c_str(), myfile.c_str());
 
printf("destroying lua environment\n");
HaltLua(myLuaState);
}

This program is hard-coded to execute a Lua script called go.lua — so here are the contents of mine:
function Init()
h_echo("hello",
42,
true,
{},
nil,
function () return {} end,
"done")
 
SomethingElse()
 
-- find out what values get returned from h_echo
h_echo(h_echo())
 
-- make the error handler work
Fail()
end
 
function SomethingElse()
-- if you use a package, this is where lua looks
h_echo(package.path)
end
 
function Fail()
failure = nonexistent_variable.indexing.error
end

To build this project, you’ll need the following code in a file called CMakeLists.txt:
PROJECT(mylua)
 
#what files are needed?
SET(MYLUA_SRCS
interaction.cpp
)
# Add executable called myLua
ADD_EXECUTABLE( myLua ${MYLUA_SRCS})
 
# indicate how to link
TARGET_LINK_LIBRARIES(myLua lua5.1)

Build by executing cmake ./ followed by make.

Run the file by executing ./myLua.

This is the output I see on my machine:
initializing lua environment
function 'Init' exists in lua? 0
loading/executing lua file go.lua
function 'Init' exists in lua? 1
calling Init in go.lua
lua's error handler at pos 1
HAPI_echo() called with 7 arguments
* arg 01 (string): hello
* arg 02 (string): 42
* arg 03 (boolean): (null)
* arg 04 (table): (null)
* arg 05 (nil): (null)
* arg 06 (function): (null)
* arg 07 (string): done
HAPI_echo() called with 1 arguments
* arg 01 (string): ./?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?/init.lua;/usr/share/lua/5.1/?.lua;/usr/share/lua/5.1/?/init.lua
HAPI_echo() called with 0 arguments
HAPI_echo() called with 2 arguments
* arg 01 (string): 123
* arg 02 (string): abc
 
Lua call failed (Runtime error): go.lua:8: attempt to index global 'nonexistent_variable' (a nil value)
stack traceback:
go.lua:8: in function 'Fail'
go.lua:26: in function
returned from call to Init in go.lua
destroying lua environment

That should get you started.