前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++反射深入浅出 - 3. function 实现分析

C++反射深入浅出 - 3. function 实现分析

作者头像
fangfang
发布2022-04-01 17:55:38
1.6K0
发布2022-04-01 17:55:38
举报
文章被收录于专栏:方方的杂货铺方方的杂货铺

在上篇中我们对反射中的Property实现做了相关的介绍, 本篇将深入Function这部分进行介绍. 主要内容是如何利用模板完成对C++函数的类型擦除, 以及如何在运行时调用类型擦除后的函数. 有的时候我们需要平衡类型擦除与性能的冲突, 所以本文也会以lua function wrapper这种功能为例, 简单介绍这部分.

1. Function示例代码

代码语言:javascript
复制
//-------------------------------------
//declaration
//-------------------------------------
class Vector3 {
 public:
  double x;
  double y;
  double z;
 public:
  Vector3() : x(0.0), y(0.0), z(0.0) {}
  Vector3(double _x, double _y, double _z) : x(_x), y(_y), z(_z) {}
  double DotProduct(const Vector3& vec) const;
};

//-------------------------------------
//register code
//-------------------------------------
__register_type<Vector3>("Vector3")
    .constructor()
    .constructor<Real, Real, Real>()
    .function("DotProduct", &Vector3::DotProduct);

//-------------------------------------
//use code
//-------------------------------------
auto* metaClass = __type_of<framework::math::Vector3>();
ASSERT_TRUE(metaClass != nullptr);

auto obj = runtime::CreateWithArgs(*metaClass, Args{1.0, 2.0, 3.0});
ASSERT_TRUE(obj != UserObject::nothing);

const reflection::Function* dotProductFunc = nullptr;
metaClass->TryGetFunction("DotProduct", dotProductFunc);
ASSERT_TRUE(dotProductFunc != nullptr);
math::Vector3 otherVec(1.0, 2.0, 3.0);
auto dotRet = runtime::Call(*dotProductFunc, obj, otherVec);
ASSERT_DOUBLE_EQ(dotRet.to<double>(), 14.0);

1.1 注册的代码

上述代码中, 我们通过 __register_type()创建的ClassBuilder提供的.function(name, func)函数来完成注册.

代码语言:javascript
复制
__register_type<Vector3>("Vector3").function("DotProduct", &Vector3::DotProduct);

上例中我们就将Vector3::DotProduct()函数注册到MetaClass中了.

1.2 使用的代码

运行时我们获取到的也是类型擦除后的Function对象, 如上例中的 dotProductFunc, 所以运行时我们需要通过runtime命名空间下提供的辅助设施 runtime::call()来完成对应函数的调用, c++的动态版函数类型擦除后的入口参数是统一的Args, 出口参数是Value, runtime::call()提供了任意输入参数到Args的转换, 如下所示, 我们即可完成对obj对象上的DotProduct函数的调用:

代码语言:javascript
复制
auto dotRet = runtime::Call(*dotProductFunc, obj, otherVec);

1.3 整体文章的展开思路

本篇文章的展开思路与Property那篇基本保持一致:

  1. 一些基本知识
  2. 运行时函数的表达 - Function类
  3. 反射函数的注册
  4. Lua版本反射函数的实现
  5. 反射函数的运行时分析

2. 基本知识

Function Traits和Type Traits在c++11推出后都逐渐变得成熟, 一个适配C++14/17的函数&类型萃取库对于像反射这种库也是至关重要的, 但Function Traits和Type Traits本质还是依赖SIFINAE做各种类型特化和推导, 属于细节非常多但真正的技巧比较少的部分, 本文就直接略过对Function Traits和Type Traits细节的分析推导, 假定Function Traits和Type Traits已经是成熟稳定的代码部分, 我们基于这部分稳定代码做上层的设计编码.

另外本文主要分析函数部分的处理过程, 所以主要关注Function Traits的提供的特性, 而不对每种函数的特化实现进行展开.

反射库所使用的TFunctionTratis包含的主要信息如下图所示:

2.1 TFunctionTraits<>::kind

FunctionKind枚举, 主要有以下值:

代码语言:javascript
复制
/**
 * \brief Enumeration of the kinds of function recognised
 *
 * \sa Function
 */
enum class FunctionKind
{
    kNone,               ///< not a function
    kFunction,           ///< a function
    kMemberFunction,     ///< function in a class or struct
    kFunctionWrapper,    ///< `std::function<>`
    kBindExpression,     ///< `std::bind()`
    kLambda              ///< lambda function `[](){}`
};

### 2.2 TFunctionTraits<>::ExposedType

返回值类型.

2.3 TFunctionTraits<>::Details::FunctionCallTypes

std::tuple<>类型, 函数所有参数的tuple<>类型, 注意类的成员函数首个参数是类对象本身.

3. 运行时函数的表达 - Function类

为了实现类中函数的动态调用过程, 我们需要对类的成员函数进行类型擦除, 形成统一的MetaFunction后, 方便运行时获取和调用, 以获得运行时的动态调用能力. 在framework反射库的实现中, Function是一个虚基类, 定义如下(节选):

代码语言:javascript
复制
class Function : public Type {
 public:
  inline IdReturn name() const { return name_; }

  inline IdReturn class_name() const { return class_name_; }

  FunctionKind kind() const { return kind_; }

  ValueKind return_type() const;

  policy::ReturnKind return_policy() const;

  virtual size_t GetParamCount() const = 0;

  virtual ValueKind GetParamType(size_t index) const = 0;

  virtual std::string_view GetParamTypeName(size_t index) const = 0;

  virtual TypeId GetParamTypeIndex(size_t index) const = 0;

  virtual TypeId GetParamBaseTypeIndex(size_t index) const = 0;

  virtual TypeId GetReturnTypeIndex() const = 0;

  virtual TypeId GetReturnBaseTypeIndex() const = 0;

  virtual bool ArgsMatch(const Args& arg) const = 0;
};

接口包括获取函数名, 父类名, 也包括像获取调用参数个数, 类型, 返回值类型这些常规方法, 不一一列举了. 需要注意的是并没有Invoke的方法, 这个主要是因为不同用途(如纯C++的调用, 和for lua的Invoke, 类型擦除后的调用方式会略有差异). c++的调用(依托Args和Value来完成调用参数和返回值类型的统一):

代码语言:javascript
复制
virtual Value Execute(const Args& args) const = 0;

lua的调用(依托Lua虚拟机的调用机制来完成函数类型的统一):

代码语言:javascript
复制
virtual int CallStraight(lua_State* L) const = 0;

4. 反射函数的注册

函数的注册过程本质上是类的成员函数, 经由类型擦除后, 变为统一的类型(上一节中Function对象)存入MetaClass中组织起来, 方便运行时动态使用的过程. 大致流程如下(略过declare<>获取ClassBuilder的这步)

4.1 从ClassBuilder创建一个function说起

代码语言:javascript
复制
template <typename T>
template <typename F, typename... P>
ClassBuilder<T>& ClassBuilder<T>::function(IdRef name, F function, P... policies)
{
    // Construct and add the metafunction
    return addFunction(detail::newFunction(name, function, policies...));
}

4.2 由newFunction() 到FunctionImpl(), 真正实现函数类型擦除的地方

代码语言:javascript
复制
// Used by ClassBuilder to create new function instance.
template <typename F, typename... P>
static inline Function* newFunction(IdRef name, F function, P... policies)
{
    typedef detail::FunctionTraits<F> FuncTraits;

    static_assert(FuncTraits::kind != FunctionKind::None, "Type is not a function");

    return new FunctionImpl<FuncTraits, F, P...>(name, function, policies...);
}

(注意此处对FuncTraits的使用, 另外框架相关单元测试里也给出了大量的Ponder Type Traits的测试代码.)

4.3 FunctionImpl()的具体实现

代码语言:javascript
复制
FunctionImpl(IdRef name, F function, P... policies) : Function(name)
{
    m_name = name;
    m_funcType = FuncTraits::kind;
    m_returnType = mapType<typename FuncTraits::ExposedType>();
    m_returnPolicy = ReturnPolicy<typename FuncTraits::ExposedType, P...> ::kind;
    m_paramInfo = FunctionApplyToParams<typename FuncTraits::Details::ParamTypes,
    FunctionMapParamsToValueKind<c_nParams>>::foreach();
    Function::m_usesData = &m_userData;

    processUses<uses::Uses::eRuntimeModule>(m_name, function);
    PONDER_IF_LUA(processUses<uses::Uses::eLuaModule>(m_name, function);)
}

注意ponder实现函数多用途的方式, 用了一个枚举的模板和相关的特化实现, 打开Lua支持后, 会执行两次processUses<>, 分别对应processUses< uses::Uses::eRuntimeModule >()和processUses< uses::Uses::eLuaModule >, 一个用来实现标准的C++反射支持, 另外一个则是用于Lua的导出支持.

这个地方的实现比较复杂, Ponder借助了一些辅助的设施来完成同一函数不同用途的注册方式的分离, 我们先来看一下这些辅助设施的定义, 再结合processUses<>()简单说明实现机制:

代码语言:javascript
复制
/**
 * \brief Global information on the compile-time type Uses.
 *
 *  - This can be extended for other modular uses
 */
struct Uses
{
    enum {
        eRuntimeModule,                 ///< Runtime module enumeration
        PONDER_IF_LUA(eLuaModule,)      ///< Lua module enumeration
        eUseCount
    };

     /// Metadata uses we are using.
    typedef std::tuple<RuntimeUse
                       PONDER_IF_LUA(,LuaUse)
                      > Users;

    /// Type that stores the per-function uses data
    typedef std::tuple<
            runtime::detail::FunctionCaller*
            PONDER_IF_LUA(,lua::detail::FunctionCaller*)
        > PerFunctionUserData;

    // Access note:
    //  typedef typename std::tuple_element<I, PerFunctionUserData>::type PerFunc_t;
    //  PerFunc_t* std::get<I>(getUsesData());
};

此处定义了两个tuple, 根据相关的定义也能大概猜到, 大致是通过定义的enum值去匹配相关tuple中不同位置type的一种做法, 能够比较好的实现基于enum-> tuple index -> types 的一种dispatcher, compiler阶段就能完成的匹配, 还是比较巧妙的, 后续会结合具体的代码说明这部分的详细使用.

4.4 processUse<> 的具体实现

processUses<>的代码实现如下:

代码语言:javascript
复制
uses::Uses::PerFunctionUserData m_userData;

template <int M>
void processUses(IdRef name, F function)
{
    typedef typename std::tuple_element<M, uses::Uses::Users>::type Processor;

    std::get<M>(m_userData) =
        Processor::template perFunction<F, T, FuncPolicies>(name, function);
}

主要是对上文中的Uses结构体中的两个tuple类型的使用(Uses::PerFunctionData, Uses::Users), 以枚举值 eRuntimeModule, eLuaModule作为processUses的非类型模板参数, 两次调用该模板函数, 我们即可得到两个不同类型的FunctionCaller存储至m_userData, 这部分只包含了对tuple的访问(std::tuple_element<>, std::get<>()), 通过Uses结构体的特殊构造和tuple的辅助函数, 可以借助不同的enum值来完成不同用途和不同类型的FunctionCaller的生成和存储. 大部分是编译期行为, 很值得借鉴的一种方式. 下面我们来具体看一下Ponder完成函数类型擦除的过程, 也就是上述Process::template perFunction<>()的具体实现 (注意此处template关键字的作用是告诉编译器perFunction本身也是模板函数, 不加在GCC等编译器上可能会报错).

4.5 C++版本反射函数的实现(RuntimeUse::perFunction())

我们先来看一下RuntimeUse::perFunction()的实现:

代码语言:javascript
复制
struct RuntimeUse
{
    /// Factory for per-function runtime data
    template <typename F, typename FTraits, typename Policies_t>
    static runtime::detail::FunctionCaller* perFunction(IdRef name, F function)
    {
        return new runtime::detail::FunctionCallerImpl<F, FTraits, Policies_t>(name, function);
    }
};

perFunction的作用主要是完成对不同函数(参数与返回值可能都不一样)的类型擦除, 形成统一类型的FunctionCaller. 下面我们具体来看一下FunctionCallerImpl<>的具体实现.

Ponder C++反射实现函数类型擦除的方式比较特殊, 不是通过得到一个统一类型的函数对象来实现的类型擦除, 而是通过类继承和虚函数的方式来实现的类型擦除, 代码如下:

代码语言:javascript
复制
//-----------------------------------------------------------------------------
// Base for runtime function caller

class FunctionCaller
{
public:
    FunctionCaller(const IdRef name) : m_name(name) {}
    virtual ~FunctionCaller() {}

    FunctionCaller(const FunctionCaller&) = delete; // no copying

    const IdRef name() const { return m_name; }

    virtual Value execute(const Args& args) const = 0;

private:
    const IdRef m_name;
};

// The FunctionImpl class is a template which is specialized according to the
// underlying function prototype.
template <typename F, typename FTraits, typename FPolicies>
class FunctionCallerImpl final : public FunctionCaller
{
public:

    FunctionCallerImpl(IdRef name, F function)
    :   FunctionCaller(name)
    ,   m_function(function)
    {}

private:

    typedef typename FTraits::Details::FunctionCallTypes CallTypes;
    typedef FunctionWrapper<typename FTraits::ExposedType, CallTypes> DispatchType;

    typename DispatchType::Type m_function; // Object containing the actual function to call

    Value execute(const Args& args) const final
    {
        return DispatchType::template
            call<decltype(m_function), FTraits, FPolicies>(m_function, args);
    }
};

如上所示, 特化的FunctionCallerImpl<>会实现基类的Value excute(const Args& args)方法, 基类的excute方法的参数和返回值是固定的, 这样我们针对不同的函数会最终得到一个有统一excute()函数的FunctionCaller对象, 间接完成了函数的类型擦除. (另外一种方式是通过模板推导存储一个固定参数表和返回值的lambda, 也可以完成函数的类型擦除.)

我们上述仅介绍了ponder内部最终存储函数的方式和基本的使用形式( 统一的excute()接口), 具体的函数到最终存储形式的过程被忽略了, 这里基于前文提到的成熟的Function Traits功能展开一下中间的处理部分.

4.5.1 FunctionWrapper<>模板类

通过FunctionWrapper<>模板类完成std::function<>函数对象的生成以及统一参数和返回值的call<>()方法的支持. 注意FunctionCallerImpl中对FunctionWrapper类的使用:

代码语言:javascript
复制
typedef typename FTraits::Details::FunctionCallTypes CallTypes;
    typedef FunctionWrapper<typename FTraits::ExposedType, CallTypes> DispatchType;

注意此处使用Function Traits直接为FunctionWrapper提供参数列表和返回值(FunctionTraits<>::Details::FunctionCallTypes 和 FunctionTraits<>::ExposedType).

FunctionWrapper的代码以及使用到的CallHelper的实现代码如下:

代码语言:javascript
复制
template <typename R, typename FTraits, typename FPolicies>
class CallHelper
{
public:

    template<typename F, typename... A, size_t... Is>
    static Value call(F func, const Args& args, std::index_sequence<Is...>)
    {
        typedef typename ChooseCallReturner<FPolicies, R>::type CallReturner;
        return CallReturner::value(func(ConvertArgs<A>::convert(args, Is)...));
    }
};
//-----------------------------------------------------------------------------
// Convert traits to callable function wrapper. Generic for all function types.
template <typename R, typename A> struct FunctionWrapper;
template <typename R, typename... A> struct FunctionWrapper<R, std::tuple<A...>>
{
    typedef typename std::function<R(A...)> Type;

    template <typename F, typename FTraits, typename FPolicies>
    static Value call(F func, const Args& args)
    {
        typedef std::make_index_sequence<sizeof...(A)> ArgEnumerator;
        return CallHelper<R, FTraits, FPolicies>::template
            call<F, A...>(func, args, ArgEnumerator());
    }
};

此处重点关注 std::make_index_sequence<>和std::index_sequence<>的使用, 借助index_sequence相关的函数, 我们可以很方便的对varidic template进行处理, 此处通过index_sequence的使用, 我们可以很好的完成args中包含的arg到函数需要的正确类型参数的转换:

代码语言:javascript
复制
ConvertArgs<A>::convert(args, Is)...

ConvertArgs<>和ChooseCallReturner<>一个是将从args中取到的Value置换为具体类型的参数, 一个是将具体类型的返回值置换为Value, 通过这种方式, 最终实现了函数的调用参数和返回值的统一, 通过这段代码, 我们也能看到在C++14/17后, 相关的函数类型擦除的代码对比原来的实现会简化非常多, 已经很容易理解了.

另外, 对于没有返回值的函数, 也有专门特化的CallHelper, 代码如下:

代码语言:javascript
复制
// Specialization of CallHelper for functions returning void
template <typename FTraits, typename FPolicies>
class CallHelper<void, FTraits, FPolicies>
{
public:

    template<typename F, typename... A, size_t... Is>
    static Value call(F func, const Args& args, PONDER__SEQNS::index_sequence<Is...>)
    {
        func(ConvertArgs<A>::convert(args,Is)...);
        return Value::nothing;
    }
};

对比有返回值的版本, 差异主要是直接返回Value::nothing, 所以我们也可以简单的通过call的返回值是否为Value::nothing来判断反射函数是否有返回值, 这也是Rpc库使用的方式.

上面我们有提到ConvertArgs<>和ChooseCallReturner<>, 通过这两者我们很好的实现了调用函数的参数统一以及返回值统一, 这里我们也对其实现做一下具体的拆解, 当然, 主要的类型转换的实现其实更多的是依赖Value和UserObject本身的实现, 此处我们不对这两者做具体的展开, 与Function Traits一样, 我们把这两者当成即有成熟功能, 来方便理清函数类型擦除相关的核心代码.

4.5.2 ConvertArgs<> 模板类

CovertArgs<>整体实现代码如下:

代码语言:javascript
复制
//-----------------------------------------------------------------------------

/*
 * Helper function which converts an argument to a C++ type
 *
 * The main purpose of this function is to convert any BadType error to
 * a BadArgument one.
 */
template <int TFrom, typename TTo>
struct ConvertArg
{
    typedef typename std::remove_reference<TTo>::type ReturnType;
    static ReturnType
    convert(const Args& args, size_t index)
    {
        try {
            return args[index].to<typename std::remove_reference<TTo>::type>();
        }
        catch (const BadType&) {
            PONDER_ERROR(BadArgument(args[index].kind(), mapType<TTo>(), index, "?"));
        }
    }
};

// Specialisation for returning references.
template <typename TTo>
struct ConvertArg<(int)ValueKind::User, TTo&>
{
    typedef TTo& ReturnType;
    static ReturnType
    convert(const Args& args, size_t index)
    {
        auto&& uobj = const_cast<Value&>(args[index]).ref<UserObject>();
        if (uobj.pointer() == nullptr)
            PONDER_ERROR(NullObject(&uobj.getClass()));
        return uobj.ref<TTo>();
    }
};

// Specialisation for returning const references.
template <typename TTo>
struct ConvertArg<(int)ValueKind::User, const TTo&>
{
    typedef const TTo& ReturnType;
    static ReturnType
    convert(const Args& args, size_t index)
    {
        auto&& uobj = args[index].cref<UserObject>();
        if (uobj.pointer() == nullptr)
            PONDER_ERROR(NullObject(&uobj.getClass()));
        return uobj.cref<TTo>();
    }
};

//-----------------------------------------------------------------------------
// Object function call helper to allow specialisation by return type. Applies policies.

template <typename A>
struct ConvertArgs
{
    typedef typename ponder::detail::DataType<A>::Type Raw;
    static constexpr ValueKind kind = ponder_ext::ValueMapper<Raw>::kind;
    typedef ConvertArg<(int)kind, A> Convertor;

    static typename Convertor::ReturnType convert(const Args& args, size_t index)
    {
        return Convertor::convert(args, index);
    }
};

首先是template struct ConvertArg的实现, 前面的TForm是ValueKind值, 后面的TTo是目标类型, 对于非User类型的Value, 模板推导出的是最前面的实现, 最后直接执行Value::to<>()模板函数来完成Value到目标类型的转换, 注意此处对于Covert错误的处理是直接抛异常. 后续的两个特化实现分别针对reference和const reference, 主要依赖UserObject的ref<>()和cref<>()模板函数, 最后就是CallHelper<>模板类使用到的的template struct ConvertArgs 实现, 其实就是对template struct ConvertArg的简单包装.

4.5.3 ChooseCallReturner<> 模板类

ChooseCallReturner<>的具体实现代码如下:

代码语言:javascript
复制
//-----------------------------------------------------------------------------
// Handle returning copies

template <typename R, typename U = void> struct CallReturnCopy;

template <typename R>
struct CallReturnCopy<R, typename std::enable_if<!ponder::detail::IsUserType<R>::value>::type>
{
    static inline Value value(R&& o) {return Value(o);}
};

template <typename R>
struct CallReturnCopy<R, typename std::enable_if<ponder::detail::IsUserType<R>::value>::type>
{
    static_assert(!std::is_pointer<R>::value, "Cannot return unowned pointer. Use ponder::policy::ReturnInternalRef?");
    static inline Value value(R&& o) {return Value(UserObject::makeCopy(std::forward<R>(o)));}
};

//-----------------------------------------------------------------------------
// Handle returning internal references

template <typename R, typename U = void> struct CallReturnInternalRef;

template <typename R>
struct CallReturnInternalRef<R,
    typename std::enable_if<
        !ponder::detail::IsUserType<R>::value
        && !std::is_same<typename ponder::detail::DataType<R>::Type, UserObject>::value
    >::type>
{
    static inline Value value(R&& o) {return Value(o);}
};

template <typename R>
struct CallReturnInternalRef<R,
    typename std::enable_if<
        ponder::detail::IsUserType<R>::value
        || std::is_same<typename ponder::detail::DataType<R>::Type, UserObject>::value
    >::type>
{
    static inline Value value(R&& o) {return Value(UserObject::makeRef(std::forward<R>(o)));}
};

//-----------------------------------------------------------------------------
// Choose which returner to use, based on policy
//  - map policy kind to actionable policy type

template <typename Policies_t, typename R> struct ChooseCallReturner;

template <typename... Ps, typename R>
struct ChooseCallReturner<std::tuple<policy::ReturnCopy, Ps...>, R>
{
    typedef CallReturnCopy<R> type;
};

template <typename... Ps, typename R>
struct ChooseCallReturner<std::tuple<policy::ReturnInternalRef, Ps...>, R>
{
    typedef CallReturnInternalRef<R> type;
};

template <typename R>
struct ChooseCallReturner<std::tuple<>, R> // default
{
    typedef CallReturnCopy<R> type;
};

template <typename P, typename... Ps, typename R>
struct ChooseCallReturner<std::tuple<P, Ps...>, R> // recurse
{
    typedef typename ChooseCallReturner<std::tuple<Ps...>, R>::type type;
};

此处注意注意Return Policy的实现, 通过policy::ReturnCopy和policy::ReturnInternalRef我们可以控制Value的创建方式, 默认是Copy方式创建Value, 其余的主要是Value本身支持从不同类型T构造的特性来完成的.

Value对不同类型T的支持特性可以自行查阅Value的实现, 目前版本的Value的内部通过ponder自己实现的variants来完成对不同类型T的存取, 但其实第一版的ponder重度依赖boost, 所以第一版的实现也是直接使用的boost::variants, 后续V2版本解除了对boost的依赖, 但variants的实现也大量参考了boost的实现, 所以对这部分细节感兴趣的可以直接查阅boost::variants相关的文档和源码, 更容易理解其中的细节.

5. Lua版本反射函数的实现 - LuaUse::perFunction()

LuaUse::perFunction()的目的与C++反射函数的目的一致, 也是完成对普通函数的类型擦除, 形成统一的函数对象类型, 只是生成的统一FunctionCaller对象不同.

代码语言:javascript
复制
struct LuaUse
{
    /// Factory for per-function runtime data
    template <typename F, typename FTraits, typename Policies_t>
    static lua::detail::FunctionCaller* perFunction(IdRef name, F function)
    {
        return new lua::detail::FunctionCallerImpl<F, FTraits, Policies_t>(name, function);
    }
};

具体的实现与上一节的很多地方都一样, 我们主要关注针对Lua的那部分特性.

代码语言:javascript
复制
// Base for runtime function caller
class FunctionCaller
{
public:
    FunctionCaller(const IdRef name, int (*fn)(lua_State*) = nullptr)
        :   m_name(name)
        ,   m_luaFunc(fn)
    {}

    FunctionCaller(const FunctionCaller&) = delete; // no copying
    virtual ~FunctionCaller() {}

    const IdRef name() const { return m_name; }

    void pushFunction(lua_State* L)
    {
        lua_pushlightuserdata(L, (void*) this);
        lua_pushcclosure(L, m_luaFunc, 1);
    }

private:
    const IdRef m_name;
    int (*m_luaFunc)(lua_State*);
};

// The FunctionImpl class is a template which is specialized according to the
// underlying function prototype.
template <typename F, typename FTraits, typename FPolicies>
class FunctionCallerImpl : public FunctionCaller
{
public:
    FunctionCallerImpl(IdRef name, F function)
    :   FunctionCaller(name, &call)
    ,   m_function(function)
    {}
private:
    typedef FunctionCallerImpl<F, FTraits, FPolicies> ThisType;

    typedef typename FTraits::Details::FunctionCallTypes CallTypes;
    typedef FunctionWrapper<typename FTraits::ExposedType, CallTypes> DispatchType;

    typename DispatchType::Type m_function; // Object containing the actual function to call

    static int call(lua_State *L)
    {
        lua_pushvalue(L, lua_upvalueindex(1));
        ThisType *self = reinterpret_cast<ThisType*>(lua_touserdata(L, -1));
        lua_pop(L, 1);

        return DispatchType::template
            call<decltype(m_function), FTraits, FPolicies>(self->m_function, L);
    }
};

首先看到的差异点是FunctionCaller对象上的m_luaFunc成员:

代码语言:javascript
复制
int (*m_luaFunc)(lua_State*);

以及pushFunction()成员函数:

代码语言:javascript
复制
void pushFunction(lua_State* L)
    {
        lua_pushlightuserdata(L, (void*) this);
        lua_pushcclosure(L, m_luaFunc, 1);
    }

先忽略类型擦除的过程, 我们先来看Lua版的FunctionCaller, 对比C++的FunctionCaller, 差异之处为所有函数会被处理为标准Lua C函数的类型(lua_CFunction类型, int为返回值, lua_State*作为入口参数), 另外通过额外多出来的pushFunction()函数可以将m_luaFunc作为c closure 入栈, 当然FunctionCaller本身的this指针被当成light userdata作为这个c closure的up value被传入lua虚拟机中.

我们接下来看看FunctionCallerImpl, 对比C++版的实现, 区别最大的是call函数, 此处的call函数也是个lua_CFunction类型的函数, 同时我们也很容易观察到生成的静态call函数被当成构造函数的参数, 最终赋值给了FunctionCaller内的m_luaFunc, 我们知道Lua与C++的交互主要是通过lua_State来完成的, 要在Lua中调用C++函数, 我们需要间接的通过lua_State来传入参数和输出返回值, 所以对应的FunctionWrapper对比C++版本也是特殊实现的, 并且都带入了lua_State作为额外的参数. 类同2.4.1, 我们也深入分析FunctionWrapper的实现以及从Lua虚拟机上传入参数以及传出返回值的过程.

5.1 FunctionWrapper<>模板类

代码语言:javascript
复制
template <typename R, typename FTraits, typename FPolicies>
class CallHelper
{
public:

    template<typename F, typename... A, size_t... Is>
    static int call(F func, lua_State* L, std::index_sequence<Is...>)
    {
        typedef typename ChooseCallReturner<FPolicies, R>::type CallReturner;
        return CallReturner::value(L, func(ConvertArgs<A>::convert(L, Is)...));
    }
};
//-----------------------------------------------------------------------------
// Convert traits to callable function wrapper. Generic for all function types.
template <typename R, typename P> struct FunctionWrapper;
template <typename R, typename... P> struct FunctionWrapper<R, std::tuple<P...>>
{
    typedef typename std::function<R(P...)> Type;

    template <typename F, typename FTraits, typename FPolicies>
    static int call(F func, lua_State* L)
    {
        typedef std::make_index_sequence<sizeof...(P)> ArgEnumerator;

        return CallHelper<R, FTraits, FPolicies>::template call<F, P...>(func, L, ArgEnumerator());
    }
};

与C++版本一致的部分我们不再展开讲解, 首先我们注意到与C++版本一样, FunctionCallerImpl中存储的std::function函数对象类型与C++版本实现一致, 同样, CallHelper也有无返回值的版本, 主要差别是CovertArgs<>()和ChooseCallReturner<>()的实现, 都变成了带lua_State参数的版本, 原因也是显而意见的, 需要通过lua_State来交换需要的数据, Lua版与C++版本的实现主要的差异也在这里, 我们接下来具体看看这两个模板函数的实现.

5.2 CovertArgs<>模板类

代码语言:javascript
复制
//-----------------------------------------------------------------------------
// Object function call helper to allow specialisation by return type. Applies policies.
template <typename P>
struct ConvertArgs
{
    typedef LuaValueReader<P> Convertor;

    static typename Convertor::ParamType convert(lua_State* L, size_t index)
    {
        return Convertor::convert(L, index+1);
    }
};

很容易发现Lua版的ConvertArgs仅是对LuaValueReader<>的简单包装和使用, 而阅读LuaValueReader的实现发现是对各种数据类型的特化实现, 包含了各种lua c api的访问, 比较特殊的是对lua table, c++侧的UserObject等的处理, 熟悉lua c api的话这些代码都比较容易读懂, 此处不再展开了, 仅给出string_view的实现供参考:

代码语言:javascript
复制
template <>
struct LuaValueReader<ponder::detail::string_view>
{
    typedef ponder::detail::string_view ParamType;
    static inline ParamType convert(lua_State* L, size_t index)
    {
        return ParamType(luaL_checkstring(L, (int)index));
    }
};

5.3 ChooseCallReturner<>模板类

代码语言:javascript
复制
// Handle returning copies
template <typename R, typename U = void> struct CallReturnCopy;
template <typename R>
struct CallReturnCopy<R, typename std::enable_if<!ponder::detail::IsUserType<R>::value>::type>
{
    // "no member named push" error here means the type returned is not covered.
    static inline int value(lua_State *L, R&& o) {return LuaValueWriter<R>::push(L, o);}
};

ChooseCallReturner<>因为Policy的存在, 实现版本较多, 此处仅贴出其中一个实现供参考. 与CovertArgs一样, ChooseCallRetruner<>也是对LuaValueWriter<>模板类的包装和使用, 我们同样给出其中一个LuaValueWriter的实现供参考:

代码语言:javascript
复制
template <typename T>
struct LuaValueWriter<T, typename std::enable_if<std::is_floating_point<T>::value>::type>
{
    static inline int push(lua_State *L, T value)
    {
        return lua_pushnumber(L, value), 1;
    }
};

5.4 小结

其实对于c++ -> lua的Wrapper, 我们当然可以复用第4节的设施, 直接针对:

代码语言:javascript
复制
virtual Value Execute(const Args& args) const = 0;

包装lua c function, 也是极简单的, 但考虑到性能, ponder的做法是复用了相关的Traits实现, 重新包装了第5节的Function实现, 这样可以得到更高性能的跨语言调用设施. 所以很多时候, 我们应该是在整个系统不同层面去衡量性价比, 像上述代码实现不那么繁复, 又能够得到更好的性能的实现, 我们肯定会更多考虑.

通过上述C++版和Lua版的函数反射实现, 我们其实可以发现在Ponder已有的设施下, 实现不同目的反射函数变得相当的简单, 基于C++版本反射函数的实现思路, 可以非常方便的实现其他目的版本的反射函数(如Lua版), 这也是Ponder本身实现的完备和强大之处.

另外对于lua bridge来说, 光一个function的实现肯定是不够的, 下一篇的 [[4. c++反射深入浅出 - 基于反射的Lua中间层实现]] 会相对完整的介绍怎么基于已有的c++反射特性来实现一个项目级的lua bridge.

6. 反射函数的运行时分析

6.1 c++::function的执行分析

与Property篇类同, 我们也给出一个运行时的分析, 方便大家更好的了解整个Function机制的运转方式. 运行时的测试选用的依然是最前面示例的代码:

代码语言:javascript
复制
math::Vector3 otherVec(1.0, 2.0, 3.0);
auto dotRet = runtime::Call(*dotProductFunc, obj, otherVec);

简洁起见, 仅给出最顶层call stack的展开:

相关的最顶层代码:

最终执行的模板实例格式化后如下所示:

代码语言:javascript
复制
framework::reflection::runtime::detail::TCallHelper<
    double /*return value type*/ ,framework::reflection::detail::TFunctionTraits<   //deduced TFunctionTraits<>
        double (__cdecl framework::math::Vector3::*)(framework::math::Vector3 const &)const>, //Vector3::DotProduct() type
    std::tuple<>
>::Call<
    std::function<double __cdecl(framework::math::Vector3 const &,framework::math::Vector3 const &)>,
    framework::math::Vector3 const &,
    framework::math::Vector3 const &,0,1
>(  //argument list
    const std::function<double __cdecl(framework::math::Vector3 const &,framework::math::Vector3 const &)> & func, 
    const framework::reflection::Args & args, 
    std::integer_sequence<unsigned __int64,0,1> __formal)

通过层层嵌套的模板特化, 我们最后完成了运行时函数的动态调用.

6.2 lua::function的执行分析

lua::function的执行与c++::function的执行过程非常类同, 这里不重复展开, 有兴趣的同学可以自行尝试.

7. 总结

至此整体反射的实现的理论介绍已经靠一段路, 本系列文章年前本篇就是最后一篇了, 年后会继续剩下更侧重应用的几篇:

  • [[4. c++反射深入浅出 - 基于反射的Lua中间层实现]]
  • [[5. C++反射深入浅出 - 反射信息的自动生成]]
  • [[6. C++反射深入浅出 - 反射的其他应用]]
  • [[7. C++反射深入浅出 - c++20 concept 改造]]

8. 参考

  1. github ponder库
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. Function示例代码
    • 1.1 注册的代码
      • 1.2 使用的代码
        • 1.3 整体文章的展开思路
        • 2. 基本知识
          • 2.1 TFunctionTraits<>::kind
            • 2.3 TFunctionTraits<>::Details::FunctionCallTypes
            • 3. 运行时函数的表达 - Function类
            • 4. 反射函数的注册
              • 4.1 从ClassBuilder创建一个function说起
                • 4.2 由newFunction() 到FunctionImpl(), 真正实现函数类型擦除的地方
                  • 4.3 FunctionImpl()的具体实现
                    • 4.4 processUse<> 的具体实现
                      • 4.5 C++版本反射函数的实现(RuntimeUse::perFunction())
                        • 4.5.1 FunctionWrapper<>模板类
                          • 4.5.2 ConvertArgs<> 模板类
                            • 4.5.3 ChooseCallReturner<> 模板类
                            • 5. Lua版本反射函数的实现 - LuaUse::perFunction()
                              • 5.1 FunctionWrapper<>模板类
                                • 5.2 CovertArgs<>模板类
                                  • 5.3 ChooseCallReturner<>模板类
                                    • 5.4 小结
                                    • 6. 反射函数的运行时分析
                                      • 6.1 c++::function的执行分析
                                        • 6.2 lua::function的执行分析
                                        • 7. 总结
                                        • 8. 参考
                                        领券
                                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档