tolua之wrap文件的原理与使用

什么是wrap文件

每个wrap文件都是对一个c#类的包装,在lua中,通过对wrap类中的函数调用,间接的对c#实例进行操作。

wrap类文件生成和使用的总体流程

生成一个wrap文件的流程

这部分主要通过分析类的反射信息完成。

wrap文件内容解析

使用UnityEngine_GameObjectWrap.cs进行举例。

注册部分

public static void Register(LuaState L)
{
    L.BeginClass(typeof(UnityEngine.GameObject), typeof(UnityEngine.Object));
    L.RegFunction("CreatePrimitive", CreatePrimitive);
    L.RegFunction("GetComponent", GetComponent);
    L.RegFunction("GetComponentInChildren", GetComponentInChildren);
    L.RegFunction("GetComponentInParent", GetComponentInParent);
    L.RegFunction("GetComponents", GetComponents);
    L.RegFunction("GetComponentsInChildren", GetComponentsInChildren);
    L.RegFunction("GetComponentsInParent", GetComponentsInParent);
    L.RegFunction("SetActive", SetActive);
    L.RegFunction("CompareTag", CompareTag);
    L.RegFunction("FindGameObjectWithTag", FindGameObjectWithTag);
    L.RegFunction("FindWithTag", FindWithTag);
    L.RegFunction("FindGameObjectsWithTag", FindGameObjectsWithTag);
    L.RegFunction("Find", Find);
    L.RegFunction("AddComponent", AddComponent);
    L.RegFunction("BroadcastMessage", BroadcastMessage);
    L.RegFunction("SendMessageUpwards", SendMessageUpwards);
    L.RegFunction("SendMessage", SendMessage);
    L.RegFunction("New", _CreateUnityEngine_GameObject);
    L.RegFunction("__eq", op_Equality);
    L.RegFunction("__tostring", ToLua.op_ToString);
    L.RegVar("transform", get_transform, null);
    L.RegVar("layer", get_layer, set_layer);
    L.RegVar("activeSelf", get_activeSelf, null);
    L.RegVar("activeInHierarchy", get_activeInHierarchy, null);
    L.RegVar("isStatic", get_isStatic, set_isStatic);
    L.RegVar("tag", get_tag, set_tag);
    L.RegVar("scene", get_scene, null);
    L.RegVar("gameObject", get_gameObject, null);
    L.EndClass();
}

这部分代码由GenRegisterFunction()生成,可以看到,这些代码分为了4部分: 1.BeginClass部分,负责类在lua中的初始化部分 2.RegFunction部分,负责将函数注册到lua中 3.RegVar部分,负责将变量和属性注册到lua中 4.EndClass部分,负责类结束注册的收尾工作

BeginClass部分

①用于创建类和类的元表,如果类的元表的元表(类的元表是承载每个类方法和属性的实体,类的元表的元表就是类的父类) ②将类添加到loaded表中。 ③设置每个类的元表的通用的元方法和属性,__gc,name,ref,__cal,__index,__newindex。

RegFunction部分

每一个RefFunction做的事都很简单,将每个函数转化为一个指针,然后添加到类的元表中去,与将一个c函数注册到lua中是一样的。

RegVar部分

每一个变量或属性或被包装成get_xxx,set_xxx函数注册添加到类的元表的gettag,settag表中去,用于调用和获取。

EndClass部分

做了两件事: ①设置类的元表 ②把该类加到所在模块代表的表中(如将GameObject加入到UnityEngine表中)

每个函数的实体部分

由于构造函数,this[],get_xxx,set_xxx的原理都差不多,都是通过反射的信息生成的,所以放在一起用一个实例讲一下(使用GameObject的GetComponent函数进行说明)。

[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int GetComponent(IntPtr L)
{
    try
    {
        //获取栈中参数的个数
        int count = LuaDLL.lua_gettop(L);
        //根据栈中元素的个数和元素的类型判断该使用那一个重载
        if (count == 2 && TypeChecker.CheckTypes<string>(L, 2))
        {
            //将栈底的元素取出来,这个obj在栈中是一个fulluserdata,需要先将这个fulluserdata转化成对应的c#实例,也就是调用这个GetComponent函数的GameObject实例
            UnityEngine.GameObject obj = (UnityEngine.GameObject)ToLua.CheckObject(L, 1, typeof(UnityEngine.GameObject));
            //将栈底的上一个元素取出来,也就是GetComponent(string type)的参数
            string arg0 = ToLua.ToString(L, 2);
            //通过obj,arg0直接第调用GetCompent(string type)函数
            UnityEngine.Component o = obj.GetComponent(arg0);
            //将调用结果压栈
            ToLua.Push(L, o);
            //返回参数的个数
            return 1;
        }
        //另一个GetComponent的重载,跟上一个差不多,就不详细说明了
        else if (count == 2 && TypeChecker.CheckTypes<System.Type>(L, 2))
        {
            UnityEngine.GameObject obj = (UnityEngine.GameObject)ToLua.CheckObject(L, 1, typeof(UnityEngine.GameObject));
            System.Type arg0 = (System.Type)ToLua.ToObject(L, 2);
            UnityEngine.Component o = obj.GetComponent(arg0);
            ToLua.Push(L, o);
            return 1;
        }
        //参数数量或类型不对,没有找到对应的重载,抛出错误
        else
        {
            return LuaDLL.luaL_throw(L, "invalid arguments to method: UnityEngine.GameObject.GetComponent");
        }
    }
    catch (Exception e)
    {
        return LuaDLL.toluaL_exception(L, e);
    }
}

可以看到,GetComponent函数的内容,其实就是通过反射分析GetComponent的重载个数,每个重载的参数个数,类型生成的。具体内容和lua调用c函数差不多。

每个函数实际的调用过程

假如说在lua中有这么一个调用:

local tempGameObject = UnityEngine.GameObject("temp")
local transform = tempGameObject.GetComponent("Transform")

第二行代码对应的实际调用过程是: 1.先去tempGameObject的元表GameObject元表中尝试去取GetComponent函数,取到了。 2.调用取到的GetComponent函数,调用时会将tempGameObject,"Transform"作为参数先压栈,然后调用GetComponent函数。 3.接下来就进入GetComponent函数内部进行操作,因为生成了新的ci,所以此时栈中只有tempGameOjbect,"Transfrom"两个元素。 4.根据参数的数量和类型判断需要使用的重载。 5.通过tempGameObject代表的c#实例的索引,在objects表中找到对应的实例。同时取出"Transform"这个参数,准备进行真正的函数调用。 6.执行obj.GetComponent(arg0),将结果包装成一个fulluserdata后压栈,结束调用。 7.lua中的transfrom变量赋值为这个压栈的fulluserdata。 8.结束。 其中3-7的操作都在c#中进行,也就是wrap文件中的GetComponent函数。

一个类通过wrap文件注册进lua虚拟机后是什么样子的

使用GameObjectWrap进行举例

可以看到GameObject的所有功能都是通过一个元表实现的,通过这个元表可以调用GameObjectWrap文件中的各个函数来实现对GameObject实例的操作,这个元表对使用者来说是不可见的,因为我们平时只会在代码中调用GameObject类,GameObject实例,并不会直接引用到这个元表,接下来来分析一下GameObject类,GameObject实例与这个元表的关系: ①GameObject类:其实只是一个放在_G表中供人调用的一个充当索引的表,我们通过它来触发GameObject元表的各种元方法,实现对c#类的使用。 ②GameObject的实例:是一个fulluserdata,内容为一个整数,这个整数代表了这个实例在objects表中的索引(objects是一个用list实现的回收链表,lua中调用的c#类实例都存在这个里面,后面会讲这个objects表),每次在lua中调用一个c#实例的方法时,都会通过这个索引找到这个索引在c#中对应的实例,然后进行操作,最后将操作结果转化为一个fulluserdata(或lua的内建类型,如bool等)压栈,结束调用。

在lua中调用一个c#实例中的函数或变量的过程

local tempGameObject = UnityEngine.GameObject("temp")
local instanceID = tempGameObject.GetInstanceID()

在了解了GameObject元表后,这些只是一些基础的元表操作,就不多做解释。

lua中c#实例的真正存储位置

前面说了每一个c#实例在lua中是一个内容为整数索引的fulluserdata,在进行函数调用时,通过这个整数索引查找和调用这个索引代表的实例的函数和变量。 生成或使用一个代表c#实例的lua变量的过程大概是这样的。 还用这个例子来说明:

local tempGameObject = UnityEngine.GameObject("temp")
local transform = tempGameObject.GetComponent("Transform")

所以说lua中调用和创建的c#实例实际都是存在c#中的objects表中,lua中的变量只是一个持有该c#实例索引位置的fulluserdata,并没有直接对c#实例进行引用。 对c#实例进行函数的调用和变量的修改都是通过元表调用操作wrap文件中的函数进行的。

以上就是c#类如何通过wrap类在lua中进行使用的原理。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏机器学习和数学

[Tensorflow] TensorFlow之Hello World!(2)

TensorFlow入门的第一篇和大家聊了?graph图,op操作,node节点。对TensorFlow有了一个简单的认识,今天主要和大家分享的是TensorF...

3917
来自专栏章鱼的慢慢技术路

排序算法的实现与比较

1758
来自专栏尾尾部落

希尔排序【Shellsort】

希尔排序是基于插入排序的以下两点性质而提出改进方法的: – 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率 – 但插入排序一般来说...

963
来自专栏逸鹏说道

博客园LaTex的测试,附带开启方法

单位矩阵是个方阵,从左上角到右下角的对角线(称为主对角线)上的元素均为1。其他全都为0,eg:

1003
来自专栏地方网络工作室的专栏

原生JS自己构建一个0-1之间的随机小数

原生JS自己构建一个0-1之间的随机小数 前言 我们都知道使用Math.random()来得到一个随机数。但是很多人都没有深入的思考过,如何这个随机数是怎么来的...

2375
来自专栏C语言及其他语言

[每日一题]C语言程序设计教程(第三版)课后习题6.5

题目描述 求以下三数的和,保留2位小数 1~a之和 1~b的平方和 1~c的倒数和 输入 a b c 输出 1+2+...+a + 1^2+2^2+...+b^...

3035
来自专栏机器学习从入门到成神

数据库闭包和候选码求解方法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sinat_35512245/articl...

1K1
来自专栏温安适的blog

union/find--不相交集合

3677
来自专栏老九学堂

【干货】小白如何熟练掌握C语言随机数!

随机数的使用,是不少小伙伴在学C语言过程中都会遇到的一个坎,今天老九为大家讲解如何在C语言中使用随机数。 通常情况下,使用最多的方法的就是使用rand函数随机生...

4767
来自专栏数据结构与算法

P1115 最大子段和

题目描述 给出一段序列,选出其中连续且非空的一段使得这段和最大。 输入输出格式 输入格式: 输入文件maxsum1.in的第一行是一个正整数N,表示了序列的长度...

2915

扫码关注云+社区

领取腾讯云代金券