When C + + and Lua collaborate, mutual invocation is a topic that cannot be bypassed. Normally, we can use the LUA/C API directly to complete the normal parameter transfer process. But it's easy to write cumbersome and repetitive code by manipulating the LUA stack directly in your code. At this time we tend to use tolua++ and other libraries, the parameters of the work of automation, reduce the burden.
Further, because Lua's parameter passing is very flexible in number and type (any one function can pass any number and type of arguments), sometimes we want to retain this flexibility when interoperating with C + +, such as when C + + sends a message to Lua, if it can be a message ID It is convenient (and vice versa) to take any number and type of arguments. Since C + + can implement type-safe parameter passing through the template function of variable parameters, we can achieve greater cross-language freedom on one interface when combined with the dynamic parameter list of Lua.
There are a number of third-party libraries that simplify mutual invocation between C + + and Lua, and this time we use Luabridge to do our work. Before we begin, let's start by introducing how common calls are made.
First, the functions of Lua are tuned from C + +:
--Lua side
function foo (str, I, f)
Return String.Format ("%s,%d,%f", str, I, f)
End
C side
luabridge::luaref foo = Luabridge::getglobal (L, "foo");
Auto retstring = foo ("Bar", 0.25f); Ignore error handling here first
Then there's the Lua tuning C + +:
C side
int CallMe (const std::string& arg1, const std::string& arg2)
{
Return Std::stoi (arg1) + std::stoi (arg2); Also first, regardless of the error handling
}
Luabridge::getglobalnamespace (L)
. Beginnamespace ("native")
. AddFunction ("Call_me", CallMe)
. Endnamespace ();
--Lua side
sum = Native.call_me (",")--sum = 35
Well, as you can see, with the help of Luabridge, the arguments and return values invoked by both sides are consistent with each other's habits, without any additional code.
All right, warm up. Now let's look at the variable parameter interface for C + + calling Lua.
--Lua side
function G_post (MsgID, ...)
_queue:appendmsg ({id=msgid, args={...}})
End
We do a simple encapsulation on the luabridge::luaref that can be used as functor, as follows:
Template<class Tret, class ... U>
Tret PostMessage (u&&. U)
{
Get the corresponding function
Auto Reffunc = Getglobal ("G_post");
if (!reffunc.isfunction ())
Return Luabridge::luaref (L);
Generate functor that carry all the parameters
Auto Func = Std::bind (Reffunc, std::forward<u> (U) ...);
Implcallglobal () implements a bit, uses try/catch to handle errors, and returns the return value to the desired type
Return Implcallglobal (name, func);
}
With such an interface, you can use the following way to tune in C + +:
C side
PostMessage (msgtype_a, "foo", "Bar");
PostMessage (Msgtype_b, 0.25f, std::string ("std::string goes as");
Any combination of parameters ...
And in the Lua queue, you can get
--Lua side
{id=msgtype_a, args={' foo ', ' Bar '}}
{id=msgtype_b, args={100, 0.25, "std::string goes As."}}
--args can accommodate any parameters passed in the table.
For a particular message type, Lua only needs to detect if the parameters they care about are matched.
This extends the flexibility of dynamic language to the host language to some extent.
In turn, Lua is slightly more cumbersome to tune C + + in any parameterized way, because C + + is inherently static, and the function's parameter types need to be fully determined at compile time.
We can do this:
--Simple encapsulation at the Lua end
function g_post_native (MsgID, ...)
Native.post (MsgID, {...})
End
C side
int Post (int msgID, luabridge::luaref args)
{
The switch here can use template <int n> to avoid branching and eliminate duplicate code in each case. Specific implementation of temporary, here to clear direct handwriting
Switch (MsgID)
{
Case MSGA:
{
Auto T = tuple_cast<std::string, std::string> (args);
Return Processa (std::get<0> (t), std::get<1> (t));
}
Case MSGB:
{
Auto T = tuple_cast<int, float, float> (args);
Return Processb (std::get<0> (t), std::get<1> (t), std::get<2> (t));
}
}
return failed_bad_id;
}
The advantage of using tuple_cast here is that it is easy to extend all types of duplicate code to one place, extending to custom types. Essentially, the tuple_cast () function expands a luaref according to the desired type (specified by the template parameter) into a std::tuple, and recursively completes the expansion at compile time for any given type of group. The specific technology in the previous blog mentioned, here no longer repeat.
OK, now you can do this on the Lua side:
--Lua side
G_post_native (msgtype_a, "foo", "Bar");
G_post_native (Msgtype_b, 100, 0.1, 12.5);
Then directly define functions that accept explicit argument lists on C + + side
C side
int Processa (const std::string& s1, const std::string& s2);
int processb (int arg1, float arg2, float arg3);
The biggest advantage of this is that both scripting script programmers and host language engineers can write in their own language habits, especially C + + programmers, can always use tuple_cast into their desired parameters list, so that all the interface functions Self-documenting.