0

I have a series of functions and objects in C++ that will be called/invoked by Lua, I already have existing code that does exactly this (using the MFC COM dispatch) for VBScript (see the bottom of this post), so I could recreate/create wrappers for all of it in C++ using Lua's existing C API. But given the number of functions and classes involved that would take much longer than if I could just create something similar to the COM dispatch and just forward the registered Lua functions straight to the existing C++ functions. Note that all Lua functions in the C API basically have the same signature:

// accepts a Lua state with the input/s in the stack
// returns the number of outputs, pushes "actual" outputs onto the stack
int Func(lua_State* L)

Imagine I have, for example, functions that look like this:

int Add(int x, int y)
double Divide(double x, double y)
void DoStuff(int x, double y, CString abc)

I would need a wrapper (let's call it LuaWrapper::GenericFunction) that would look like the above Lua C API call, read the various typed inputs from the stack, call the corresponding function with those inputs, and return the correctly typed output.

Lua normally does dispatches using this syntax to register a function. Here I am roughly outlining the syntax of how LuaWrapper::GenericFunction would be used:

// have the Lua state redirect scripted calls to "Add" to this function:
// LuaWrapper::GenericFunction takes the Lua state, the function itself, the output type, and a list/string/whatever of the input types; then calls the function on those inputs and pushes the output onto the Lua state's stack
lua_register(L, "Add", Add); // vs:
lua_register(L, "Add", [](lua_State* L) { return LuaWrapper::GenericFunction(L, RealFunctions::Add, INT_TYPE, "INT INT"; });

The issue is that I can't figure out how to get the inputs to work and how to call a completely generic function that could have basically any number of inputs of any type (they are guaranteed not to be references unless they are class objects, which I have a do plan for). I know it is possible since the MFC COM API is basically doing exactly this for VBScript.

Side note, the existing COM dispatch code for VBScript integration basically works like this: a dispatch map is written in the code for the C++ class,

// "Function" outputs an int, and takes two strings and a bool as inputs
// VT_I4 is basically an enum, VTS_??? are strings that get concatenated in this syntax
DISP_FUNCTION(CParentClass, "Function", Function, VT_I4, VTS_BSTR VTS_BSTR VTS_BOOL)

Then is written as a standard function of that class; allowing VBScript to invoke it.

int CParentClass::Function(CString str1, CString str2, bool true_or_false)

7
  • At the bottom, COM IDispatch stuff uses DispCallFunc. This function takes a list of VARIANTs and a description of the target function's signature, and packs the contents of those VARIANTs into a stack frame appropriate for the calling convention. I imagine there's some pretty hairy machine-dependent assembly in there. You could try and press it into service; for that, you'd have to repackage parameters from lua_State into VARIANTs. Not familiar with Lua, no idea what goes into lua_State Commented May 13 at 0:16
  • Using libffi would obviate the need for writing the hairy assembly yourself. It should also be possible to write a function that translates between C++ types and libffi type descriptors (forget the name of the actual type). And then it would be something like tx_type<Types>(args).... where tx_type is a function that takes one type/value pair and returns the libffi type pointer. Commented May 13 at 3:25
  • Have you had a look at lua binding libraries, such as LUAA for example ? These libraries allow you to register a function to lua via a C++ code as simple as register_function("name_in_lua", &your_cpp_function);. Thank to C++ templates, the wrapper playing with the lua stack is automatically generated for you and you have nothing else to do. Commented May 13 at 4:16
  • @QuentinC Is this the library you are referring to? Commented May 13 at 7:20
  • I just came across sol2 that solves the same problem, but is written and maintained by a well-known C++ template juggler. Commented May 13 at 7:37

2 Answers 2

0

You might use variadic template, something along:

// helpers to have unified (same name) interface
template <typename T>
void my_lua_push(lua_State* L, T value)
{
    if constexpr (std::is_same_v<bool, T>) {
        return lua_pushboolean(L, value);
    } /* Handle other types .. */
}

template <typename T>
T my_lua_get(lua_State* L, int index)
{
    if constexpr (std::is_same_v<bool, T>) {
        return lua_toboolean(L, index) != 0;
    } /* Handle other types .. */
}

// variadic helper
template <typename... Ts>
std::tuple<Ts...> my_lua_pop(lua_State* L)
{
    auto res = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
        // Care with stack order
        return std::tuple<Ts...>{
            my_lua_get<Ts>(L,
                           -static_cast<int>(sizeof...(Is)) + static_cast<int>(Is) + 1)...
        };
    }(std::index_sequence_for<Ts...>());
    lua_pop(L, sizeof...(Ts))
    return res;
}

template <auto f>
struct GenericWrapper;

template <typename Ret, typename... Ts, Ret (*f) (Ts...)>
struct GenericWrapper<f>
{
    static int call(lua_State* L)
    {
        std::tuple<Ts...> tuple = my_lua_pop<Ts...>(L);
        if constexpr (std::is_same_v<void, Ret>) {
            std::apply([](Ts... args){ return f(args...); }, tuple);
            return 0;
        } else {
            Ret ret = std::apply([](Ts... args){ return f(args...); }, tuple);
            my_lua_push(L, ret);
            return 1;
        }
    }
};
Sign up to request clarification or add additional context in comments.

2 Comments

Do you have an alternative that doesn't use std::apply? I'd considered it before, and this looks like it could work, but the project I am working on is stuck in C++14.
std::apply can been rewritten for C++14, more direct way would be to use std::index_sequence. As mentionned in comment, lua binding libraries exist and would be more complete than my answer.
0

I ended up using LuaBridge to resolve this issue. For the most part I got away with using the standard pattern of

getGlobalNamespace(L)
    .beginClass<CClass>("Class")
        .addData("number", &CClass::number)
        .addProperty("string", CSTRING_GETTER(CClass, CClass::string), CSTRING_SETTER(CClass, CClass::string))
        .addFunction("func", &CClass::func)
    .endClass();

Though as you can see I needed to add a conversion macro (basically an overcomplicated type-cast style getter/setter that LuaBridge can understand) to switch between CString and std::string for the string variables. I also needed to do a few functions using the Lua C API standard using the "addCFunction()" component of LuaBridge for functions that had too many arguments, or multiple inputs. The thing that took the most time was simple writing wrappers for the functions I had that took or returned variables that were BSTR, BOOL, DATE, LPDISPATCH, etc. But that was more tedious than problematic.

In the end though, everything is working as expected so thanks to everyone else for their help/advice.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.