Lua + C++ 交互的奇技淫巧

By | 2017-12-11

前言

费了好大的劲儿, 终于给 ZFFramework
加上了 lua 的动态自动绑定功能, 作为一个为了实现各种高级功能而用尽了各种奇技淫巧的非主流框架,
在此期间积累了不少好玩的伎俩, 留文纪念一下

首先介绍下 ZF 里面最终实现的, lua 动态自动绑定的效果: (各位有兴趣也可以猜测下为何这些功能需要后文列到的奇技淫巧)

  • 利用 C艹 端的反射, 自动将 class 和 function 绑定到 lua
    (property 通过 function 的 setter/getter 实现)
  • 通过动态库加载进来的 C艹 代码, 也能够自动绑定到 lua,
    并且能够绑定到多个 lua_State

扯之前, 先从我们的最终目标出发, 来分锅一下各种麻烦事:

  • class 的绑定

    这个最容易, 只要做好 C艹 端的反射,
    然后搞好 class name 和 constructor 在 lua 中对应的处理即可,
    当然, 也有麻烦事:

    • 带参数的构造函数怎么玩
  • function 的绑定

    这个咋一看也不难, 但仔细一想就会发现坑无比的多

    • 静态的好搞, 动态绑定, 怎么知道参数类型?
    • 引用, 指针, 输出参数?
    • 函数重载怎么处理?
    • lua 类型怎么隐式转换到 C艹 类型?
  • 动态绑定, 多 lua_State 绑定

    这个其实只要有了上述动态绑定基础, 要处理起来也只需些小技巧而已, 本文不表

好, 下面开始各种用到的好玩伎俩:

细节

类型的绑定

带动态特性的框架一般都会有个显著的特点, 单根继承,
因为这样才好搞 metadata, 有了 metadata 才能搞参数类型校验,
才能搞函数重载判断等等

然而 C艹 里要真正实现单根继承还是不现实的 (否则真变成又造了个 Java 或脚本语言出来了),
因此最直接的方式就是, 把所有类型都用单根继承体系里的类包装一下 (怎样? 有没想起 Java 里的 IntegerLong?)

有了这基本思路, 基本的定义方式也就明了了

// class WrapperObject : public Object
class v_int : public WrapperObject
{
public:
    int v;
    virtual bool fromString(const char *s) {...}
    virtual bool toString(string &s) {...}
};
  • 继承自单根继承体系, 并使用某个特定的父类, 这样反射时才好区分哪个是普通类哪个是 wrapper 类
  • 类名按照固定的某种格式, 以便进行反射
  • 使用字符串作为中间载体

于是, 所有类型的绑定可以简化为 基类 Object 的绑定 + Object 与各个子类的动态判断 + 类型与字符串的转换,
在 lua 端, 实际上所有交互的对象都是基类 Object,
类型的判断, 类型的转换/隐式转换, 函数分发, 全都在 C艹 端完成

具体怎么实现呢? 当然还得用模板:

template<typename T>
class TypeWrapper
{
public:
    static bool fromString(sharedPtr<WrapperObject> &ret, const char *s)
    {
        v_int v;
        if(v.fromString(s))
        {
            ret = v;
        }
        else
        {
            return false;
        }
    }
    static bool canAccess(Object *obj)
    {
        return obj->to<v_int *>() != NULL;
    }
    static int access(Object *obj)
    {
        return obj->to<v_int *>()->v;
    }
    static sharedPtr<WrapperObject> store(T const &v)
    {
        v_int ret;
        ret.v = v;
        return sharedPtr(ret);
    }
};

指针/引用

C艹 这么多变种类型, 每个都特化一遍的话繁琐且不现实,
好在我们还有神奇的模板

template<typename T>
class TypeWrapper
{
    ...
public:
    typedef v_int Wrapper;
public:
    template<typename T_Access, int isPointer>
    class Value
    {
    public:
        static T_Access access(Object *obj)
        {
            return obj->to<v_int *>()->v;
        }
        static sharedPtr<WrapperObject> store(T_Access v)
        {
            v_int ret;
            ret.v = v;
            return sharedPtr(ret);
        }
    };
    template<>
    class Value<T_Access, 1>
    {
    public:
        static T_Access access(Object *obj)
        {
            return &(obj->to<v_int *>()->v);
        }
        static sharedPtr<WrapperObject> store(T_Access v)
        {
            v_int ret;
            ret.v = *v;
            return sharedPtr(ret);
        }
    };
};

访问时:

T t = TypeWrapper<Traits<T>::Traits>
    ::Value<T, IsPointer<T>::value>
    ::access(obj);

(关于 Traits 和 IsPointer 可以参考各种 type traits 相关的文章)

generic invoker

有了类型绑定, 我们终于可以来做函数的绑定了,
不过在此之前, 还得为我们需要反射的函数生成一个泛化的 invoker, 大约长这样:

class Owner : public Object
{
public:
    int myFunc(int param0, int param1) {...}
    static bool canInvoke(Object *obj, Object *param0, Object *param1)
    {
        return (TypeWrapper::xxx::canAccess(param0)
            && TypeWrapper::xxx::canAccess(param1));
    }
    static sharedPtr<WrapperObject> invoker(Object *obj, Object *param0, Object *param1)
    {
        int ret = obj->to<Owner *>()->myFunc(
                TypeWrapper::xxx::access(param0),
                TypeWrapper::xxx::access(param1)
            );
        return TypeWrapper::xxx::store(ret);
    }
    static Method metadata_myFunc()
    {
        static Method m("myFunc", canInvoke, invoker);
        return m;
    }
};

由此, 我们已经得到一个类型无关的 invoker 了, 转为函数指针丢给 lua 环境愉快的玩耍吧

函数重载

开始动手前得先对主流语言如何处理函数重载有个了解, 关于这些, 最好的资料是去找找 C艹 的 name mangling

于是, 我们也得在函数分发时模拟实现重载函数, 但是, 由于是动态绑定 (外加还需要实现隐式转换),
没法像编译器一样根据参数类型来指定函数名, 因此得用更简单粗暴的方式:
找到所有同名函数, 然后一个个参数校验过去看能否调用成功, 直到找到一个可以成功调用的

Object params[MAX_PARAM];
list<Method> methods = cls.findMethod("myFunc");
foreach(method in methods)
{
    if(method.canInvokeCallback(params[0], params[1], ...))
    {
        return method.invokeCallback(obj, params[0], params[1], ...);
    }
}

隐式转换

经历了前面的种种, 我们终于搞起了 lua 与 C艹 的单根继承体系的交互逻辑,
然而一个简单的 func(123) 在 lua 里却每次要 func(int(123)) 实在不方便,
因此还得搞个相对 "智能" 的 lua 到 C艹 类型的隐式转换

首先, 当然是给 "未知类型" 也包装一下, 让它也属于单根继承体系,
包含的唯一数据就是我们作为载体的字符串

class UnknownType : public Object
{
public:
    string data;
};

然后, 函数调用分发的地方, 逐个尝试是否可以调用

static sharedPtr<WrapperObject> invoker(Object *obj, Object *param0, Object *param1)
{
    list<Method> methods = xxx;
    for(method in methods)
    {
        WrapperObject paramConverted[MAX_PARAM];
        for(xxx)
        {
            if(paramN->isTypeOf(UnknownType))
            {
                paramConverted[i] = method->paramTypeIdAt(i)->fromString(paramN);
            }
            else
            {
                paramConverted[i] = paramN;
            }
        }
        if(method.canInvokeCallback(paramConverted[0], paramConverted[1], ...))
        {
            return method.invokeCallback(obj, paramConverted[0], paramConverted[1], ...);
        }
    }
    error("no matching method to call");
}

转载请注明来自: http://zsaber.com/blog/p/200

既然都来了, 有啥想法顺便留个言呗? (无奈小广告太多, 需审核, 见谅)

One thought on “Lua + C++ 交互的奇技淫巧

发表评论

您的电子邮箱地址不会被公开。