自动化 Lua ⟷ C API功能模块(ygluu)

关键词:C/C++、模板、template、反射、Lua、API、绑定、自动化、开发、效率、成本

痛点:

C/C++没有完美的反射功能(如golang/delphi),对开发 Lua ⟷ C API 来说是比较繁琐和耗工时的,并且人工编码的出错率也非常高。

方案

C++模板编译期自动化推导。

优点

•性能高(相比于反射)

•提高开发效率(相比与人工)

•出错率极低(相比与人工)

示例

示例1:注册 C++ 函数到 Lua

示例2:从 C++ 调用 Lua 函数(无函数指针方式)

示例3:从 C++ 调用 Lua 函数(函数指针方式)

代码

复制代码
/**********************************************************************************
 * 
 *  自动化 Lua ⟷ C API功能模块
 * 
 *  author: ygluu
 * 
 *  1、Lua->C: 自动推导和注册C-Func参数和返回值给Lua调用
 *      函数 alua::reg_lua<c-func>(L, mod_name, func_name)
 * 
 *  2、C->Lua: 自动推导C-Call语句的参数和返回值并调用Lua函数
 *
 *      无返回值调用(常用)
 *          alua::call_lua(L, mod_name, func_name, lua-args...);
 * 
 *      显式返回类型调用(常用)
 * 
 *          int ret = alua::call_lua(L, mod_name, func_name, lua-args...);
 * 
 *          std::tuple<int, bool, string> ret = alua::call_lua(L, mod_name, func_name, lua-args...);
 * 
 *          std::map<string, int> ret = alua::call_lua(L, mod_name, func_name, lua-args...);
 * 
 *      隐式返回类型调用(需声明与LUA函数一致的c-func声明)
 *          auto ret = alua::call_lua<c-func>(...);
 * 
 * 参数和返回值类型支持:
 *      1、基础类型:int/uint32/int64/uint64/string(const char*)/bool
 *      2、数组:std::vector<基础类型>
 *      3、元组:std::tuple<基础类型,基础类型,...>
 *      4、字典:std::map/std::unordered_map<基础类型, 基础类型>
 * 
 * 
**********************************************************************************/

#pragma once

#include "lua.hpp"

#include <vector>
#include <string>
#include <unordered_map>
#include <tuple>
#include <type_traits>
#include <utility>
#include <source_location>

namespace alua
{
    // 工具:把 __PRETTY_FUNCTION__ 里的裸函数名取出来
    namespace
    {
        // 判断是否是 tuple
        template<typename T> inline constexpr bool is_tuple_v = false;
        template<typename... Ts> inline constexpr bool is_tuple_v<std::tuple<Ts...>> = true;

        // 计算期望返回数量
        template<typename R>
        constexpr int expected_returns() noexcept
        {
            if constexpr (std::is_same_v<R, void>) return 0;
            else if constexpr (is_tuple_v<R>) return std::tuple_size_v<R>;
            else return 1;
        }

        constexpr std::string_view raw_name(std::string_view pretty) noexcept
        {
            std::size_t tail = pretty.rfind('(');
            if (tail == std::string_view::npos) { return pretty; }
            std::size_t head = pretty.rfind(' ', tail);
            return (head == std::string_view::npos)
                   ? pretty.substr(0, tail)
                   : pretty.substr(head + 1, tail - head - 1);
        }

        // 单值读取 / 压栈
        template<typename T> struct popper;
        template<typename T> struct pusher;

        // 数值
        template<> struct popper<int>           { static int           get(lua_State* l,int i){return static_cast<int>(luaL_checkinteger(l,i));}};
        template<> struct popper<float>         { static float         get(lua_State* l,int i){return static_cast<float>(luaL_checknumber(l,i));}};
        template<> struct popper<double>        { static double        get(lua_State* l,int i){return luaL_checknumber(l,i);}};
        template<> struct popper<bool>          { static bool          get(lua_State* l,int i){return lua_toboolean(l,i)!=0;}};
        template<> struct popper<std::uint32_t> { static std::uint32_t get(lua_State* l,int i){return static_cast<std::uint32_t>(luaL_checkinteger(l,i));}};
        template<> struct popper<std::uint64_t> { static std::uint64_t get(lua_State* l,int i){return static_cast<std::uint64_t>(luaL_checkinteger(l,i));}};
        template<> struct popper<std::int64_t>  { static std::int64_t  get(lua_State* l,int i){return static_cast<std::int64_t>(luaL_checkinteger(l,i));}};

        // 字符串
        template<> struct popper<std::string>
        {
            static std::string get(lua_State* l,int i)
            {
                std::size_t len = 0;
                const char* s = luaL_checklstring(l,i,&len);
                return std::string{s,len};
            }
        };
        template<> struct popper<const char*> { static const char* get(lua_State* l,int i){return luaL_checkstring(l,i);}};

        // 轻量 userdata
        template<> struct popper<void*> { static void* get(lua_State* l,int i){return lua_touserdata(l,i);}};

        // 压栈
        template<> struct pusher<int>           { static void put(lua_State* l,int v){lua_pushinteger(l,v);}};
        template<> struct pusher<float>         { static void put(lua_State* l,float v){lua_pushnumber(l,v);}};
        template<> struct pusher<double>        { static void put(lua_State* l,double v){lua_pushnumber(l,v);}};
        template<> struct pusher<bool>          { static void put(lua_State* l,bool v){lua_pushboolean(l,v);}};
        template<> struct pusher<std::uint32_t> { static void put(lua_State* l,std::uint32_t v){lua_pushnumber(l,v);}};
        template<> struct pusher<std::uint64_t> { static void put(lua_State* l,std::uint64_t v){lua_pushnumber(l,v);}};
        template<> struct pusher<std::int64_t>  { static void put(lua_State* l,std::int64_t v){lua_pushnumber(l,v);}};
        template<> struct pusher<std::string>   { static void put(lua_State* l,const std::string& v){lua_pushlstring(l,v.data(),v.size());}};
        template<> struct pusher<const char*>   { static void put(lua_State* l,const char* v){lua_pushstring(l,v);}};
        template<> struct pusher<void*>         { static void put(lua_State* l,void* v){lua_pushlightuserdata(l,v);}};

        // vector
        template<typename T> struct popper<std::vector<T>>
        {
            static std::vector<T> get(lua_State* l,int idx)
            {
                luaL_checktype(l,idx,LUA_TTABLE);
                std::vector<T> v;
                lua_pushnil(l);
                while(lua_next(l,idx)!=0)
                {
                    v.emplace_back(popper<T>::get(l,-1));
                    lua_pop(l,1);
                }
                return v;
            }
        };
        template<typename T> struct pusher<std::vector<T>>
        {
            static void put(lua_State* l,const std::vector<T>& v)
            {
                lua_createtable(l,static_cast<int>(v.size()),0);
                for(std::size_t i=0;i<v.size();++i)
                {
                    pusher<T>::put(l,v[i]);
                    lua_rawseti(l,-2,static_cast<int>(i+1));
                }
            }
        };

        // unordered_map
        template<typename K,typename V> struct popper<std::unordered_map<K,V>>
        {
            static std::unordered_map<K,V> get(lua_State* l,int idx)
            {
                luaL_checktype(l,idx,LUA_TTABLE);
                std::unordered_map<K,V> m;
                lua_pushnil(l);
                while(lua_next(l,idx)!=0)
                {
                    K k=popper<K>::get(l,-2);
                    V v=popper<V>::get(l,-1);
                    m.emplace(std::move(k),std::move(v));
                    lua_pop(l,1);
                }
                return m;
            }
        };
        template<typename K,typename V> struct pusher<std::unordered_map<K,V>>
        {
            static void put(lua_State* l,const std::unordered_map<K,V>& m)
            {
                lua_createtable(l,0,static_cast<int>(m.size()));
                for(const auto& [k,v]:m)
                {
                    pusher<K>::put(l,k);
                    pusher<V>::put(l,v);
                    lua_settable(l,-3);
                }
            }
        };

        // tuple
        template<typename...> struct tuple_popper;
        template<> struct tuple_popper<> { static std::tuple<> get(lua_State*,int){return {};}; };
        template<typename H,typename... T> struct tuple_popper<H,T...>
        {
            static auto get(lua_State* l,int idx)
            {
                return std::tuple_cat(std::make_tuple(popper<H>::get(l,idx)),
                                      tuple_popper<T...>::get(l,idx+1));
            }
        };

        // 返回值包装
        template<typename R> struct ret_helper
        {
            static int push(lua_State* l,const R& v){pusher<R>::put(l,v);return 1;}
        };
        template<> struct ret_helper<void>{ static int push(lua_State*,...){return 0;} };
        template<typename... Ts> struct ret_helper<std::tuple<Ts...>>
        {
            static int push(lua_State* l,const std::tuple<Ts...>& tup)
            {
                std::apply([&l](const Ts&... vs){(pusher<Ts>::put(l,vs),...);},tup);
                return static_cast<int>(sizeof...(Ts));
            }
        };

        // C 函数 → Lua 包装
        template<auto f> struct lua_to_c_wrapper;
        template<typename R,typename... A,R(*f)(A...)> struct lua_to_c_wrapper<f>
        {
            static int call(lua_State* l)
            {
                constexpr int k_expect=sizeof...(A);
                const int argc=lua_gettop(l);
                if(argc!=static_cast<int>(k_expect))
                {
                    luaL_error(l,"expect %d args, got %d",k_expect,argc);
                }
                try
                {
                    auto args=tuple_popper<A...>::get(l,1);
                    if constexpr(std::is_same_v<R,void>)
                    {
                        std::apply(f,args);
                        return 0;
                    }
                    else
                    {
                        R ret=std::apply(f,args);
                        return ret_helper<R>::push(l,ret);
                    }
                }
                catch(const std::exception& e)
                {
                    luaL_where(l,1);
                    lua_pushfstring(l,"C++ exception: %s",e.what());
                    lua_concat(l,2);
                    lua_error(l);
                }
                catch(...)
                {
                    luaL_where(l,1);
                    lua_pushstring(l,"unknown C++ exception");
                    lua_concat(l,2);
                    lua_error(l);
                }
                return 0;
            }
        };

        // 已知函数指针的调用实现
        template<auto f> struct lua_caller;
        template<typename R,typename... A,R(*f)(A...)> struct lua_caller<f>
        {
            static R call(lua_State* L,const char* mod,const char* fn,const A&... args)
            {
                constexpr int k_argc=sizeof...(A);
                constexpr int k_ret = expected_returns<R>();
                constexpr bool k_void=std::is_same_v<R,void>;
                const int base=lua_gettop(L);

                lua_getglobal(L,"debug");
                lua_getfield(L,-1,"traceback");

                // 找函数
                if(mod && *mod)
                {
                    lua_getglobal(L,mod);
                    if(!lua_istable(L,-1)) luaL_error(L,"module '%s' not found",mod);
                    lua_getfield(L,-1,fn);
                    lua_remove(L,-2);
                }
                else
                {
                    lua_getglobal(L,fn);
                }
                if(!lua_isfunction(L,-1))
                {
                    auto full=mod?(std::string(mod)+'.'+fn):std::string(fn);
                    alog_print(get_log_file(),LogLevel::LEVEL_ERROR,full.c_str(),full.size());
                }

                (pusher<std::decay_t<A>>::put(L,args),...);

                lua_inc_call_count();
                if(lua_pcall(L,k_argc,k_ret,base+2)!=LUA_OK)
                {
                    size_t sz; auto str=lua_tolstring(L,-1,&sz);
                    alog_print(get_log_file(),LogLevel::LEVEL_ERROR,str,sz);
                }

                if constexpr(std::is_same_v<R,void>)
                {
                    lua_settop(L,base);
                    return;
                }
                else if constexpr(is_tuple_v<R>)
                {
                    // 读取 tuple 返回值
                    R ret = [&L,k_ret] {
                        return popper<R>::get(L,-k_ret);
                    }();
                    lua_settop(L,base);
                    return ret;
                }
                else
                {
                    R ret=popper<R>::get(L,-1);
                    lua_settop(L,base);
                    return ret;
                }
            }
        };

        // 无需函数指针的调用实现
        template<typename R>
        struct free_caller
        {
            template<typename... Args>
            static R call(lua_State* L,const char* mod,const char* fn,Args&&... args)
            {
                constexpr int k_ret = expected_returns<R>();
                constexpr bool k_void=std::is_same_v<R,void>;
                const int base=lua_gettop(L);

                lua_getglobal(L,"debug");
                lua_getfield(L,-1,"traceback");

                // 找函数
                if(mod && *mod)
                {
                    lua_getglobal(L,mod);
                    if(!lua_istable(L,-1)) luaL_error(L,"module '%s' not found",mod);
                    lua_getfield(L,-1,fn);
                    lua_remove(L,-2);
                }
                else
                {
                    lua_getglobal(L,fn);
                }
                if(!lua_isfunction(L,-1))
                {
                    auto full=mod?(std::string(mod)+'.'+fn):std::string(fn);
                    alog_print(get_log_file(),LogLevel::LEVEL_ERROR,full.c_str(),full.size());
                }

                (pusher<std::decay_t<Args>>::put(L,std::forward<Args>(args)),...);

                lua_inc_call_count();
                if(lua_pcall(L,sizeof...(Args),k_ret,base+2)!=LUA_OK)
                {
                    size_t sz; auto str=lua_tolstring(L,-1,&sz);
                    alog_print(get_log_file(),LogLevel::LEVEL_ERROR,str,sz);
                }

                if constexpr(std::is_same_v<R,void>)
                {
                    lua_settop(L,base);
                    return;
                }
                else if constexpr(is_tuple_v<R>)
                {
                    R ret = popper<R>::get(L,-k_ret);
                    lua_settop(L,base);
                    return ret;
                }
                else
                {
                    R ret=popper<R>::get(L,-1);
                    lua_settop(L,base);
                    return ret;
                }
            }
        };

        // tuple 读取器
        template<typename... Ts>
        struct popper<std::tuple<Ts...>>
        {
            static std::tuple<Ts...> get(lua_State* l,int idx)
            {
                constexpr std::size_t N=sizeof...(Ts);
                if(lua_gettop(l)-idx+1<static_cast<int>(N))
                    luaL_error(l,"not enough return values for tuple");

                return [&l,idx]<std::size_t... I>(std::index_sequence<I...>)
                {
                    return std::make_tuple(popper<Ts>::get(l,idx+I)...);
                }(std::make_index_sequence<N>{});
            }
        };
    } // namespace

    // 已知函数原型,两参
    template<auto f,typename... Args>
    auto call_lua(lua_State* L,const char* fn,Args&&... args)
    {
        return lua_caller<f>::call(L,nullptr,fn,std::forward<Args>(args)...);
    }
    // 已知函数原型,三参
    template<auto f,typename... Args>
    auto call_lua(lua_State* L,const char* mod,const char* fn,Args&&... args)
    {
        return lua_caller<f>::call(L,mod,fn,std::forward<Args>(args)...);
    }
    // 仅指定返回值,两参(默认 void)
    template<typename R=void,typename... Args>
    auto call_lua(lua_State* L,const char* fn,Args&&... args)
    {
        return free_caller<R>::call(L,nullptr,fn,std::forward<Args>(args)...);
    }
    // 仅指定返回值,三参
    template<typename R,typename... Args>
    auto call_lua(lua_State* L,const char* mod,const char* fn,Args&&... args)
    {
        return free_caller<R>::call(L,mod,fn,std::forward<Args>(args)...);
    }

    // 注册函数
    template<auto f>
    inline void reg_lua(lua_State* L,const char* module_name,const char* lua_name=nullptr)
    {
        lua_getglobal(L,module_name);
        if(!lua_istable(L,-1))
        {
            lua_pop(L,1);
            luaL_error(L,"module '%s' not found",module_name);
            return;
        }
        const char* real_name=lua_name?lua_name:raw_name(std::source_location::current().function_name()).data();
        lua_pushcfunction(L,lua_to_c_wrapper<f>::call);
        lua_setfield(L,-2,real_name);
        lua_pop(L,1);
    }
} // namespace alua