C++ 11 and LuaJIT Integration
Last weekend I was trying to integrate LuaJIT into my custom game engine but I was getting this strange lua runtime error:
[hammackj@taco:~/luajit_playground/luajit_static]$ ./static
Hello from Lua
Couldn't load file luatest.lua : luatest.lua:10: dlsym(RTLD_DEFAULT, test_ffi): symbol not found
After a bit of googling I couldn't really find an answer, all the examples I found just worked. So I started to look a little deeper.
[hammackj@taco:~/luajit_playground/luajit_static]$ nm -gU static
0000000100000000 T ____mh_execute_header
0000000100001110 T ____main
By looking at all of the final symbols from the binary I could see my test_ffi
function was missing. From here I was fairly certain clang was stripping all of the dead code that wasn't being directly called. Which was causing my problem as I was using the FFI interface to export my C++ wrappers to lua. After a few more hours of googling I found a stackoverflow question about dead code striping that led me to adding this -Wl,-force_load,libstatic.a
, to my linker command.
[hammackj@taco:~/luajit_playground/luajit_static]$ nm -gU static
0000000100000000 T ____mh_execute_header
0000000100001110 T ____main
0000000100001c40 T ____test_ffi
The symbol was now being correct "exported" from the static library to the host binary and not bring stripped. The output now worked correctly.
[hammackj@taco:~/luajit_playground/luajit_static]$ ./static
Hello from Lua
Hello from C++
Below is all the source code used. You can also use -Wl,-all_load
, which forces all symbols / functions to be imported even if unused. This will dramatically increase the size of the binary.
//Main.cpp
#include <iostream>
#include <luajit-2.0/lua.hpp>
int main(int argc, char const *argv[])
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
const char *path = "luatest.lua";
if (luaL_loadfile(L, path) || lua_pcall(L, 0, LUA_MULTRET, 0))
{
std::cerr << "Couldn't load file " << path << " : " << lua_tostring(L, -1) << std::endl;
}
lua_close(L);
return 0;
}
//static.hpp
#include <iostream>
extern "C"
{
void test_ffi();
}
//static.cpp
#include "static.hpp"
void test_ffi()
{
std::cout << "Hello from C++" << std::endl;
}
-- luatest.lua
print "Hello from Lua"
local ffi = require('ffi')
ffi.cdef([[
void test_ffi();
]])
local C = ffi.C
C.test_ffi()
CC=c++
CFLAGS=-g -Wall -I/usr/local/include -L/usr/local/lib -L. -std=c++11 -pagezero_size 10000 -image_base 100000000
LDFLAGS=-lluajit-5.1 -lstatic -Wl,-force_load,libstatic.a
#-Wl,-all_load
FILES=main.cpp
STATIC_FILES=static.cpp
OUTPUT=static
all: static standalone
static: $(STATIC_FILES)
$(CC) -MMD -MP -g -std=c++11 -c $(STATIC_FILES) -o static.o
ar -rcs libstatic.a static.o
standalone: $(static) $(FILES)
$(CC) $(CFLAGS) -o $(OUTPUT) $(FILES) $(LDFLAGS)
clean:
rm -rf *.dSYM static *.d *.a *.o
You can find all the source code on my github. Which includes a simple standalone single binary version, the above static library version and a dynamic library version. The code is written for OSX and a brew based install of LuaJIT. I didn't test if this was a issue on gcc or msvc++.