Luapp C++ exports functions/classes to interact with Lua

I touched lua a few years ago and picked it up again recently.

I found it not very convenient to use, so I packaged it.

After encapsulation, the use no longer cares about the Lua state machine, nor does it care about the stack.

Various function types can be exported, including lambda, std::function.

Classes can be exported, and the way of exporting classes is similar to MFC’s message map, refer to the text below.

All codes have been uploaded to github: https://github.com/njzz/luapp

The author uses lua 5.3.5 version, which can be changed to 5.1 to use luajit.

Passed the test under win32/win64, without using windows-specific code, it can be directly transplanted.

Not much nonsense, the final effect is like this.

First write a simple lua test script hello.lua

print("lua file loaded")

function AddNum(num1,num2)
return (num1 + num2)
end

function AddString(str1,str2)
return (str1..str2)
end

function Rt3Value(v1,v2,v3)
return v1,v2,v3
end

utils={}
utils. math={}
function utils.math.add(n1,n2,n3)
return (n1 + n2 + n3);
end

Load script code when creating a new state machine

using namespace app;
//Initialization function, called when creating a new state machine
bool Lua_initfunc(lua::LuaWrap & amp;lvm) {
lvm.DoFile("hello.lua");
return true;
}

Then…

//Define a lua package object
lua::LuaWrap lw;
//Set the initialization function for initialization when creating a new lua state machine
//You can also set the global initialization function lua::SetGlobalInit
lw.Init( &Lua_initfunc);

Note that the LuaWrap life cycle of the encapsulated object is not the life cycle of the Lua state machine

When LuaWrap is destroyed, the lua state machine is cached, and the idle state machine will be used when the LuaWrap object is Init.

If you need to destroy the state machine, you can call the member function Destroy

Various ways to call Lua script functions:

//Template function parameter is Lua return value type Call AddNum in the script
printf("AddNum(20,30) = %d \\
", lw. Call<int>("AddNum", 20, 30));
//Call utils.math.add in the script
printf("utils.math.add(20,30,100) = %d \\
", lw.Call<int>("utils.math.add", 20, 30, 100));
//Call AddString in the script and print
auto str = lw. Call<std::string>("AddString", "string is ", "added");
printf("AddString(string is ,added)=%s\\
",str.c_str());

//When lua returns multiple return values in the script, the return value uses std::tuple
auto rtuple = lw.Call<std::tuple<std::string, int,std::string>>("Rt3Value", "v1", 2 , "v3");
printf("Rt3Value('v1',2,'v3')=%s,%d,%s\\
", std::get<0>(rtuple).c_str(), std::get<1>(rtuple), std::get<2>(rtuple).c_str());

//lua returns multiple return values
auto rtuple2 = lw.Call<std::tuple<int, float, std::string>>("Rt3Value", 2, 2.f, "second");
printf("Rt3Value(2,2.f,'second')=%d,%f,%s\\
", std::get<0>(rtuple2), std::get<1 >(rtuple2), std::get<2>(rtuple2).c_str());

//Call the lua function print
lw.Call<void>("print", "call lua self function:print\\
");

//Define a string function and call it
lw.RunString<void>("function PrintParam(vv) print("param:"..vv) end ", "PrintParam", "pass by string");
// directly call the string function
lw.RunString<void>("print("run sample string code") ");

operation result

Export functions to Lua:

 //export a member function
 class KKK {
 public:
void print(int a) {
printf("Class member function KKK::print v:%d\\
",a);
}
 };

 //export common functions
 static std::string NormalFunc(std::string p) {
return p + " : cpp added.";
 }

//Register function, the function name can be different
lw.RegFunction("func_addstr", & amp;NormalFunc);
//Call functions
lw.RunString<void>("print(func_addstr('call normal function'))");

//Export lambda and return two values
auto exLam = [](int a) {
return std::tuple<int,int>(a + 1,a + 2);
};

//Register xxx class function and call
lw.RegFunction("exLamabc", exLam);
lw.RunString<void>("print('exLamabc(5)=',exLamabc(5))");

//Register xxx.yyy class function and call
lw.RegFunction("exLam.abcd", exLam);
lw.RunString<void>("print('exLam.abcd(6)=',exLam.abcd(6))");

//Register any aaa.bbb.ccc.ddd function and call
lw.RegFunction("exLam.kk.cc.bb", exLam);
lw.RunString<void>("print('exLam.kk.cc.bb(7)=',exLam.kk.cc.bb(7))");

KKK k;
// Note the use of std::function to determine the type
std::function<void(int)> ff = std::bind( & amp;KKK::print, & amp;k , std::placeholders::_1);
lw.RegFunction("kkk.print", ff);//Pay attention to the life cycle of k
lw.RunString<void>("kkk.print(6)");

operation result:

Export classes to LUA:

Export classes use an MFC-like design:

 //Export classes to Lua, similar to MFC framework
 class TestClass {
 public:
TestClass() {
printf("TestClass Constructor!\\
");
}

~TestClass() {
printf("TestClass Destructor!\\
");
}

int add(int a, int b) {
return a + b;
}

void setv(int value) {
m_value = value;
}

int getv() {
return m_value;
}

int m_value;
//export statement
LUA_CLASS_EXPORT_DECLARE;
 };

 LUA_CLASS_EXPORT_BEGIN(TestClass) //Export implementation start
LUA_CLASS_EXPORT_FUNC("add", & amp;TestClass::add) //Set the exported member function name and function address, support the export of base class functions, only need to specify the class
LUA_CLASS_EXPORT_FUNC("getvalue", & amp;TestClass::getv) //Change the name in lua
LUA_CLASS_EXPORT_FUNC("setv", &TestClass::setv)
LUA_CLASS_EXPORT_END() //End

Then register using:

//Call the LuaClassRegister function of the exported class to export the class to the current state machine
TestClass::LuaClassRegister(lw. get());

//Test the exported class
lw.RunString<void>(
R"(
local t1 = TestClass() --new object
             local v = t1:add(1, 4) -- 5
             t1:setv(6)
             local t2 = TestClass() --new object
t2:setv(v)
             print('t1:getvalue()='..t1:getvalue()) --6
             print('t2:getvalue()='..t2:getvalue()) --5
)"
);

Finally, look at the overall test code:

#include 
#include "LuaWrap.h"

using namespace app;

 bool Lua_initfunc(lua::LuaWrap & amp;lvm) {
lvm.DoFile("hello.lua");
return true;
}

 //Test call lua function
 void RunLuaCaller() {
lua::LuaWrap lw;
//Set the initialization function, you can also set the global initialization function lua::SetGlobalInit
lw.Init( &Lua_initfunc);

//Call the initialization function Lua_initfunc to load the functions in hello.lua
printf("AddNum(20,30) = %d \\
", lw. Call("AddNum", 20, 30));
printf("utils.math.add(20,30,100) = %d \\
", lw.Call("utils.math.add", 20, 30, 100));
auto str = lw. Call("AddString", "string is ", "added");
printf("AddString(string is ,added)=%s\\
",str.c_str());

//lua returns multiple return values
auto rtuple = lw.Call>("Rt3Value", "v1", 2 , "v3");
printf("Rt3Value('v1',2,'v3')=%s,%d,%s\\
", std::get<0>(rtuple).c_str(), std::get<1>(rtuple), std::get<2>(rtuple).c_str());

//lua returns multiple return values
auto rtuple2 = lw.Call>("Rt3Value", 2, 2.f, "second");
printf("Rt3Value(2,2.f,'second')=%d,%f,%s\\
", std::get<0>(rtuple2), std::get<1 >(rtuple2), std::get<2>(rtuple2).c_str());

//Call the lua function print
lw.Call("print", "call lua self function:print\\
");

//Define a string function and call it
lw.RunString("function PrintParam(vv) print("param:"..vv) end ", "PrintParam", "pass by string");
// directly call the string function
lw.RunString("print("run sample string code") ");
 }

 // export a member function
 class KKK {
 public:
void print(int a) {
printf("Class member function KKK::print v:%d\\
",a);
}
 };

 //export common functions
 static std::string NormalFunc(std::string p) {
return p + " : cpp added.";
 }

 //Test export function to lua
 void RunLuaExportor(){
lua::LuaWrap lw;
lw.Init();

lw.RegFunction("func_addstr", & amp;NormalFunc);
lw.RunString("print(func_addstr('call normal function'))");

//Export lambda and return two values
auto exLam = [](int a) {
return std::tuple(a + 1,a + 2);
};

//Register xxx class function and call
lw.RegFunction("exLamabc", exLam);
lw.RunString("print('exLamabc(5)=',exLamabc(5))");

//Register xxx.yyy class function and call
lw.RegFunction("exLam.abcd", exLam);
lw.RunString("print('exLam.abcd(6)=',exLam.abcd(6))");

//Register any aaa.bbb.ccc.ddd function and call
lw.RegFunction("exLam.kk.cc.bb", exLam);
lw.RunString("print('exLam.kk.cc.bb(7)=',exLam.kk.cc.bb(7))");

KKK k;
// Note the use of std::function to determine the type
std::function ff = std::bind( & amp;KKK::print, & amp;k , std::placeholders::_1);
lw.RegFunction("kkk.print", ff);//Pay attention to the life cycle of k
lw.RunString("kkk.print(6)");
}

 //Export classes to Lua, similar to the MFC framework
 class TestClass {
 public:
TestClass() {
printf("TestClass Constructor!\\
");
}

~TestClass() {
printf("TestClass Destructor!\\
");
}

int add(int a, int b) {
return a + b;
}

void setv(int value) {
m_value = value;
}

int getv() {
return m_value;
}

int m_value;
//export statement
LUA_CLASS_EXPORT_DECLARE;
 };

 LUA_CLASS_EXPORT_BEGIN(TestClass) //Export implementation start
LUA_CLASS_EXPORT_FUNC("add", & amp;TestClass::add) //Set the exported member function name and function address, support the export of base class functions, only need to specify the class
LUA_CLASS_EXPORT_FUNC("getvalue", & amp;TestClass::getv) //Change the name in lua
LUA_CLASS_EXPORT_FUNC("setv", &TestClass::setv)
LUA_CLASS_EXPORT_END() //End

//Test exporting classes to lua
 void RunLuaExportClass() {
lua::LuaWrap lw;
lw.Init();

//Call the LuaClassRegister function of the exported class to export the class to the current state machine
TestClass::LuaClassRegister(lw. get());

//Test the exported class
lw.RunString<void>(
R"(
local t1 = TestClass() --new object
             local v = t1:add(1, 4) -- 5
             t1:setv(6)
             local t2 = TestClass() --new object
t2:setv(v)
             print('t1:getvalue()='..t1:getvalue()) --6
             print('t2:getvalue()='..t2:getvalue()) --5
)"
);
 }

int main(void)
{
RunLuaCaller();
RunLuaExportor();
RunLuaExportClass();
lua::CleanAll();//Clean all
system("pause");
return 0;
}

final result:

In conclusion:

There is nothing to say about calling lua, it is nothing more than passing the c++11 variable parameter template, pushing the parameters on the stack, and then calling. According to the return value type, to get the return value. There is an empty structure emp in the implementation, which means an empty return value, which is used for laziness. . To avoid further specialization of a void

To export a function/class, in order to keep the function prototype, a class template is used. Then the required parameters are obtained from lua and stored in std::tuple, and the function is called with tuple and the return value is pushed onto the stack. Since a new class is generated with a template, a pcb base class is written for type normalization, and virtual functions are used for actual calls.

There is nothing else to say, there are only a few files in total, and the code is full of comments.

Python also has a similar package, which I haven’t done recently, and will release it when I have time.