XLUA学习笔记之C#和Lua之间的相互调用

XLUA学习笔记之C#和Lua之间的相互调用

https://blog.csdn.net/xmx5166/article/details/90712888

感觉学习的笔记还是放在网上查询的时候比较方便,文章初看会感觉很难理清,需要耐心去看,对不起自己对不起翻到我文章的同学,不废话直接上代码。

1.C#调用Lua (耐心!耐心!耐心!)

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;

//这个示例涉及到的Lua脚本,将文本复制过来
/*
a = 100.1
str = "hahaha"
isDie = false

person = {
name = "xumeixi";
age1 = 26,108,18,"haha",true,2.34;

add = function(self,a,b)
print(a+b)
end

--[[  
--这是注释掉的两种方法实现方式

function person:add(a,b)--默认带一个self的参数,代表当前table
print(a+b)
end

function person.add(self,a,b)
print(a+b)
end

--这是注释掉的两种方法实现方式
--]]

}

function add()
    print("调用全局Function:add")
end

function cut(a,b)
    print("相减获得:"..(a-b))
    return a - b
end

function add_cut_mul_div(a,b)
    return a+b,a-b,a*b,a/b
end
 */

/// <summary>
/// C#调用Lua代码示例
/// </summary>
public class HelloXLua_CSharpCallLua : MonoBehaviour {
    //创建Lua虚拟机
    LuaEnv luaenv = new LuaEnv();
    // Use this for initialization
    void Start () {
        //普通加载lua脚本的形式
        luaenv.DoString("require 'Lua/CSharpCallLua'");

        //直接加载文本形式的lua代码
        //luaenv.DoString(script);

        //1.C#访问Lua中的全局基本数据类型
        //GetGlobalData();

        //2.1.1.读取lua脚本中的数据映射到普通class或struct(比较耗费性能)
        //GetTable();

        //2.1.2.读取文本形式的lua代码映射到普通class或struct(比较耗费性能)
        //GetTableScript();

        //2.2.映射到一个interface(接口)
        //GetOrSetData();

        //2.3.更轻量级(代码量较少)的by value方式:映射到Dictionary<>、List<>
        GetDataByDictionatyOrList();

        //2.4.另外一种by ref方式:映射到LuaTable类(不推荐,一般用2.2映射到一个interface的方法)
        //GetDataByLUaTable();

        //3.1使用Get方法,不同的是类型映射
        //GetDataByFunction();

        //3.2.映射到LuaFunction(性能较慢)
        //GetDataByLuaFunction();
    }

    #region 1.C#访问Lua中的全局基本数据类型
    void GetGlobalData()
    {
        double a = luaenv.Global.Get<double>("a");
        print("a = " + a);//a = 100.1
        string str = luaenv.Global.Get<string>("str");
        print("str = " + str);//str = hahaha
        bool isDie = luaenv.Global.Get<bool>("isDie");
        print("isDie = " + isDie);//isDie = False

        a = 10;
        a = luaenv.Global.Get<double>("a");
        print(a);//a = 100.1  说明在这种直接获取的情况下不能改变Lua脚本中的变量值,只能获取
    }
    #endregion

    #region 2.C#访问Lua中全局的table类型
    //定义一个class,有对应于table的字段的public属性,而且有无参数构造函数即可,比如对于{f1 = 100, f2 =100}可以定义一个包含public int f1;public int f2;的class。
    //这种方式下xLua会帮你new一个实例,并把对应的字段赋值过去。
    //table的属性可以多于或者少于class的属性(没有对应的属性就不会映射)。可以嵌套其它复杂类型。
    //要注意的是,这个过程是值拷贝,如果class比较复杂代价会比较大。而且修改class的字段值不会同步到table,反过来也不会。
    //这个功能可以通过把类型加到GCOptimize生成降低开销

    class Person
    {
        public string name;
        public int age1;
        public int age2;
    }

    class DTable
    {
        public int f1;
        public int f2;
    }

    #region 2.1.1.读取lua脚本中的数据映射到普通class或struct(比较耗费性能)
    void GetTable()
    {
        Person p = luaenv.Global.Get<Person>("person");
        print(p.name + "-" + p.age1 + "-" + p.age2);
        p.name = "xudada";
        luaenv.DoString("print(person.name)");
    }
    #endregion

    #region 2.1.2.读取文本形式的lua代码映射到普通class或struct(比较耗费性能)
    string script =
        @"
       person = {
           name = 'xumeixi';
           age1 = 26,108,18,'haha',true;
                }

       d = {
               f1 = 10;
               f2 = 20;
               add = function(self,a,b)
                  print('d.add called')
                  return a + b;
               end
        }";

    void GetTableScript()
    {
        Person p = luaenv.Global.Get<Person>("person");
        print(p.name + "-" + p.age1 + "-" + p.age2);
        p.name = "xudada";
        luaenv.DoString("print(person.name)");

        DTable d = luaenv.Global.Get<DTable>("d");
        print("f1+f2=" + d.f1 + d.f2);
    }
    #endregion

    #region 2.2.映射到一个interface(接口)
    //这种方式依赖于生成代码(如果没生成代码会抛InvalidCastException异常),代码生成器会生成这个interface的实例,如果get一个属性,
    //生成代码会get对应的table字段,如果set属性也会设置对应的字段。甚至可以通过interface的方法访问lua的函数。
    //使用[CSharpCallLua]标记后就会生成代码,不适用标记就会报错InvalidCastException异常
    [CSharpCallLua]
    interface IPerson
    {
        string name { get; set; }
        int age1 { get; set; }
        void add(int a, int b);
    }

    //可以获取和设置数据,双向的,改变C#这边的值Lua那边的也会改变
    void GetOrSetData()
    {
        IPerson p = luaenv.Global.Get<IPerson>("person");
        print(p.name + "-" + p.age1);
        p.name = "xudada";
        luaenv.DoString("print(person.name)");
        p.add(28, 30);
    }

    #endregion

    #region 2.3.更轻量级(代码量较少)的by value方式:映射到Dictionary<>、List<>
    //不想定义class或者interface的话,可以考虑用这个,前提table下key和value的类型都是一致的。
    //映射成Dictionary指只会射有key值的,而List只能映射无Key值的,两者互补可以得到全部数据(这里的有无Key值只lua代码中的一些值没有指定Key,age1 = 26,108,18,"haha",true,2.34;这里只有26是有key值的)
    void GetDataByDictionatyOrList()
    {
        //因为value类型不确定,因此用object代替,只会映射有key的(有变量名的,包括函数)
        Dictionary<string, object> dict = luaenv.Global.Get<Dictionary<string, object>>("person");
        foreach(string key in dict.Keys)
        {
            print("dic:" + key + "-" + dict[key]);
        }

        //只会映射没有key(没有变量名)的数据,包括所有类型(int,string,bool等)
        List<object> listObj = luaenv.Global.Get<List<object>>("person");//只会映射没有key的,也就是数值
        foreach (var item in listObj)
        {
            print("listObj:" + item);
        }

        //只会映射没有key(没有变量名)的int类型数据
        List<int> listInt = luaenv.Global.Get<List<int>>("person");
        foreach (var item in listInt)
        {
            print("listInt:" + item);
        }
    }
    #endregion

    #region 2.4.另外一种by ref方式:映射到LuaTable类(不推荐,一般用2.2映射到一个interface的方法)
    //这种方式的好处是不需要生成代码,但也有一些问题,比如慢,比方式2.2要慢一个数量级,比如没有类型检查。
    void GetDataByLUaTable()
    {
        LuaTable tab = luaenv.Global.Get<LuaTable>("person");
        print(tab.Get<string>("name"));
        print(tab.Get<int>("age1"));
        print(tab.Length);//lua中会将没有key值的数据自动添加对应的下标,下标从1开始,lua中计算table的长度是根据下标来的,遇到中断就停止计算长度了,哪怕你长度不止这么多
    }
    #endregion

    #endregion

    #region 3.访问全局的function
    #region 3.1使用Get方法,不同的是类型映射
    //这种是建议的方式,性能好很多,而且类型安全。缺点是要生成代码(如果没生成代码会抛InvalidCastException异常)。
    //多返回值要怎么处理?从左往右映射到c#的输出参数,输出参数包括返回值,out参数,ref参数。
    //参数、返回值类型支持哪些呢?都支持,各种复杂类型,out,ref修饰的,甚至可以返回另外一个delegate。
    [CSharpCallLua]
    public delegate int CutFunc(int a, int b);

    [CSharpCallLua]
    public delegate void Add_cut_mul_div(int a, int b,out int resAdd,out int resCut,out int resMul,out float resDiv);

    void GetDataByFunction()
    {
        //没有参数没有返回值
        Action act1 = luaenv.Global.Get<Action>("add");
        act1();
        act1 = null;

        //有参数一个返回值
        CutFunc cut1 = luaenv.Global.Get<CutFunc>("cut");
        int result = cut1(10, 5);
        print(result);
        cut1 = null;

        //有参数,多返回,通过out返回
        Add_cut_mul_div func = luaenv.Global.Get<Add_cut_mul_div>("add_cut_mul_div");
        int resAdd = 0, resCut = 0, resMul = 0;
        float resDiv = 0;
        func(10, 4,out resAdd,out resCut,out resMul,out resDiv);
        print(resAdd + "--" + resCut + "--" + resMul + "--" + resDiv);
    }
    #endregion

    #region 3.2.映射到LuaFunction(性能较慢)
    //这种方式的优缺点刚好和3.1方法相反。
    //使用也简单,LuaFunction上有个变参的Call函数,可以传任意类型,任意个数的参数,返回值是object的数组,对应于lua的多返回值。
    void GetDataByLuaFunction()
    {
        LuaFunction func = luaenv.Global.Get<LuaFunction>("add_cut_mul_div");
        object[] os = func.Call(1, 2);
        foreach (object o in os)
        {
            print(o);
        }
    }

    #endregion
    #endregion

    #region 使用建议
    /*
     1、访问lua全局数据,特别是table以及function,代价比较大,建议尽量少做,比如在初始
     化时把要调用的lua function获取一次(映射到delegate)后,保存下来,后续直接调用
     该delegate即可。table也类似。

     2、如果lua测的实现的部分都以delegate和interface的方式提供,使用方可以完全和xLua解
     耦:由一个专门的模块负责xlua的初始化以及delegate、interface的映射,然后把这
     些delegate和interface设置到要用到它们的地方。
    */
    #endregion

    #region 其他常态函数
    // Update is called once per frame
    void Update()
    {
        if (luaenv != null)
        {
            //清除Lua未手动释放的LuaBase对象(这个操作是未处理过的,太频繁了,可以增加定时清理)
            luaenv.Tick();
        }
    }

    private void OnDestroy()
    {
        luaenv.Dispose();
    }
    #endregion
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308

2.Lua调用C# (耐心!耐心!耐心!)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
using System;

//添加了[LuaCallCSharp]标签的类才能被Lua调用
[LuaCallCSharp]
public class BaseClass
{
    public static int BSF = 1;
    public static void BSFunc()
    {
        Debug.Log("Driven Static Func,BSF = " + BSF);
    }

    public int BMF { get; set; }
    public void BMFunc()
    {
        Debug.Log("Driven Member Func, BMF = " + BMF);
    }
}

public struct Paraml
{
    public int x;
    public string y;
}

public enum TestEnum
{
    E1,
    E2
}

[LuaCallCSharp]
public class DrivenClass:BaseClass
{
    public int DMF { get; set; }
    public void DMFunc()
    {
        Debug.Log("Driven Member Func, DMF = " + DMF);
    }

    //复杂函数:C#和Lua相互调用
    public double ComplexFunc(Paraml p1,ref int p2, out string p3, Action luaFunc,out Action csFunc)
    {
        Debug.Log("p1 = {x = " + p1.x + ", y = " + p1.y + "},  p2 = " + p2);
        luaFunc();
        p2 = p2 * p1.x;
        p3 = "hello" + p1.y;
        csFunc = () =>
        {
            Debug.Log("csharp callback invoked!");
        };
        return 1.23;
    }

    //重载方法
    public void TestFunc(int i)
    {
        Debug.Log("TestFunc(int i)");
    }
    public void TestFunc(string i)
    {
        Debug.Log("TestFunc(string i)");
    }

    //操作符
    public static DrivenClass operator +(DrivenClass a, DrivenClass b)
    {
        DrivenClass ret = new DrivenClass();
        ret.DMF = a.DMF + b.DMF;
        return ret;
    }

    //函数包含默认参数
    public void DefaultValueFunc(int a = 100, string b = "bbbbbb", string c = null)
    {
        Debug.Log("DefaultValueFunc: a=" + a + ",b=" + b + ",c=" + c);
    }

    //可变参数
    public void VariableParamsFunc(int a, params string[] strs)
    {
        UnityEngine.Debug.Log("VariableParamsFunc: a =" + a);
        foreach (var str in strs)
        {
            UnityEngine.Debug.Log("str:" + str);
        }
    }

    //通过Extension methods实现访问泛化方法
    public void GenericMethod<T>()
    {
        Debug.Log("GenericMethod<" + typeof(T) + ">");
    }

    //枚举类型
    public TestEnum EnumTestFunc(TestEnum e)
    {
        Debug.Log("EnumTestFunc: e=" + e);
        return TestEnum.E2;
    }
    public enum TestEnumInner
    {
        E3,
        E4
    }

    //委托调用:下面是直接用lambda表达式实现了带有一个string类型参数的无返回值委托
    public Action<string> TestDelegate = (param) =>
    {
        Debug.Log("TestDelegate in c#:" + param);
    };

    //事件回调
    //定义一个事件成员,在lua层那边可以通过特殊方式添加和删除事件回调,然后启动事件
    public event Action TestEvent;
    //启动事件的函数
    public void CallEvent()
    {
        TestEvent();
    }

    //支持lua64位计算
    public ulong TestLong(long n)
    {
        return (ulong)(n + 1);
    }

    //强转
    class InnerCalc:ICalc
    {
        public int add(int a, int b)
        {
            return a + b;
        }
        public int id = 100;
    }
    public ICalc GetCalc()
    {
        return new InnerCalc();
    }

}

[LuaCallCSharp]
public interface ICalc
{
    int add(int a, int b);
}


//扩展方法:在不改变类的情况下扩展类对象的方法
//实现多种类型的扩展方法,静态类,静态方法,参数为类类型前面需要加上this关键字,调用的时候使用参数类型的对象
[LuaCallCSharp] //必须加上标签,不然lua会找不到这个扩展方法类
public static class DrivenClassExtensions
{
    public static int GetSomeData(this DrivenClass obj)
    {
        Debug.Log("GetSomeData ret = " + obj.DMF);
        return obj.DMF;
    }

    public static int GetSomeBaseData(this BaseClass obj)
    {
        Debug.Log("GetSomeBaseData ret = " + obj.BMF);
        return obj.BMF;
    }

    public static void GenericMethodOfString(this DrivenClass obj)
    {
        obj.GenericMethod<string>();
    }
}

/// <summary>
/// 研究普通类中的函数在lua中的调用
/// </summary>
public class TestClass
{
    [LuaCallCSharp]
    public void Test()
    {
        Debug.Log("我是普通类(既不是静态类也继承于MonoBehaviour,也没有添加[LuaCallCSharp])中加了[LuaCallCSharp]的函数");
    }
}

public class HelloXLua_LuaCallCSharp : MonoBehaviour {
    LuaEnv luaenv = null;

    //使用文本形式的代码代替Lua脚本
    string script = @"
        function demo()
            --new出来的C#对象:也会出现在场景中,对象的名称没有给定就会显示默认的new GameObject
            local newGameObj = CS.UnityEngine.GameObject()
            local newGameObj2 = CS.UnityEngine.GameObject('helloWorld')
            print(newGameObj,newGameObj2)
            
            --访问静态属性和方法:使用点号
            local GameObject = CS.UnityEngine.GameObject
            print('UnityEngine.Time.deltaTime:',CS.UnityEngine.Time.deltaTime) --读静态属性
            CS.UnityEngine.Time.timeScale = 0.5 --写静态属性
            print('hello world',GameObject.Find('Main Camera')) --静态方法调用
            
            --访问成员属性和方法:成员属性使用点号,成员方法使用冒号
            local DrivenClass = CS.DrivenClass; --在实际项目中,可以先用一个局部变量将C#那边的对象引用一下,今后只需要使用这个变量就好了,避免多次映射访问C#那边的对象增加消耗
            local testObj = DrivenClass() --创建一个对象时不需要new关键字
            testObj.DMF = 1024 --设置成员属性使用类对象+点号
            print(testObj.DMF) --调用成员属性使用类对象+点号
            testObj:DMFunc() --调用成员方法使用类对象+冒号
            --testObj.DMFunc() --成员方法使用点号访问会报错
            
            --基类属性和方法
            print(DrivenClass.BSF) --读取基类静态属性使用类名+点号
            DrivenClass.BSF = 2048 --写基类静态属性使用类名+点号
            DrivenClass.BSFunc() --调用基类静态方法使用类名+点号
            print(testObj.BMF) --读基类成员属性使用类对象+点号
            testObj.BMF = 4096 --写基类成员属性使用类对象+点号
            testObj:BMFunc() --基类方法调用使用类对象+冒号
            
            --复杂方法调用:接收多个返回值,如果C#函数有返回值那么这个返回值排在第一位,其余以ref和out方式返回的按照从左到右依次排序
            local ret,p2,p3,csfunc = testObj:ComplexFunc({x=3,y='john'},100,function() print('I am lua callBack') end)
            print('ComplexFunc ret:',ret,p2,p3,csfunc)
            csfunc()
            
            --重载方法调用:只需要传入不同类型的参数就可以了
            testObj:TestFunc(100);
            testObj:TestFunc('haha')
            
            --操作符
            local testObj2 = DrivenClass()
            testObj2.DMF = 2048
            print('(testObj + testObj2).DMF = ',(testObj + testObj2).DMF)
            
            --函数包含默认参数
            testObj:DefaultValueFunc(1)
            testObj:DefaultValueFunc(3,'hello','john')
            
            --可变参数:调用的C#成员函数的参数是不固定的,lua这边也只需要传入对应类型的参数即可,也可以不传
            testObj:VariableParamsFunc(5, 'hello', 'john')
            
            --Extension methods扩展方法,扩展方法是静态方法,使用对象+冒号访问
            print(testObj:GetSomeData())
            print(testObj:GetSomeBaseData()) --访问基类的扩展方法
            testObj:GenericMethodOfString()  --通过扩展方法访问泛型方法
            
            --枚举类型的操作:如果枚举类加入到生成代码的话,枚举类将支持__CastFrom方法,可以实现从一个整数或者字符串到枚举值的转换
            local e = testObj:EnumTestFunc(CS.TestEnum.E1)
            print(e, e == CS.TestEnum.E2)
            print(CS.TestEnum.__CastFrom(1), CS.TestEnum.__CastFrom('E1'))
            print(CS.DrivenClass.TestEnumInner.E3)
            assert(CS.BaseClass.TestEnumInner == nil)
            
            --委托调用Delegate
            testObj.TestDelegate('hello') --直接调用
            local function lua_delegate(str)
                print('TestDelegate in lua:', str)
            end
            testObj.TestDelegate = lua_delegate + testObj.TestDelegate --combine,这里演示的是C#delegate作为右值,左值也支持
            testObj.TestDelegate('hello')
            testObj.TestDelegate = testObj.TestDelegate - lua_delegate --remove
            testObj.TestDelegate('hello')
            
            --事件:相当于一个函数的集合,访问方式同函数一样使用类对象+冒号
            local function lua_event_callback1() print('lua_event_callback1') end
            local function lua_event_callback2() print('lua_event_callback2') end
            testObj:TestEvent('+', lua_event_callback1) --给事件添加回调函数
            testObj:CallEvent() --启动事件
            testObj:TestEvent('+', lua_event_callback2) --给事件移除回调函数
            testObj:CallEvent()
            testObj:TestEvent('-', lua_event_callback1)
            testObj:CallEvent()
            testObj:TestEvent('-', lua_event_callback2)
            
            --64位计算支持
            local num = testObj:TestLong(11)
            print(type(num),num,num+100,num+10000)
            
            --typeof:使用typeof如以下的方式给一个Unity的GameObject对象添加组件
            newGameObj:AddComponent(typeof(CS.UnityEngine.ParticleSystem))--添加粒子系统
            
            --cast强转函数,将参数一类型强转为参数二类型
            local calc = testObj:GetCalc()
            print('assess instance of InnerCalc via reflection', calc:add(1, 2))--直接映射访问消耗大
            assert(calc.id == 100)
            print(calc.id)
            calc.id = 50
            print(calc.id)
            cast(calc, typeof(CS.DrivenClass.InnerCalc)) --用CS.Calc的生成代码来访问calc对象,消耗减小
            --cast(calc, typeof(CS.Calc)) --使用CS.Calc为目标转换类型,由于此类型里面没有id这个变量,所以calc.id的值会变为nil
            print('cast to interface ICalc', calc:add(1, 2))
            print(calc.id)
            assert(calc.id ~= nil) --断言括号中的语句成立,如果不成立就报错

            --继承MonoBehavior的类Lua中调用静态函数:可以直接CS.类名.静态函数名调用
            CS.HelloXLua_LuaCallCSharp.TestLuaCallCS();
            --继承MonoBehavior的类Lua中调普通成员函数,需要生成对象后调用,使用冒号
            --以下两种方式都可以
            --local testObj = CS.HelloXLua_LuaCallCSharp():TestLuaCallCS111();
            local testObj = CS.HelloXLua_LuaCallCSharp();
            testObj:TestLuaCallCS111();

            --研究普通类中的函数在lua中的调用
            CS.TestClass():Test();
        end
        
        --函数的普通调用
        --demo()

        --协程下使用
        local co = coroutine.create(function()
            print('------------------------------------------------------')
            demo()
        end)
        assert(coroutine.resume(co))
    ";

    public static void TestLuaCallCS()
    {
        print("继承于MonoBehaviour的类,可以直接CS.类名.静态函数名调用");
    }

    public void TestLuaCallCS111()
    {
        print("继承于MonoBehaviour的类,调用里面的普通成员函数需要生成对象后调用");
    }

    // Use this for initialization
    void Start()
    {
        luaenv = new LuaEnv();
        luaenv.DoString(script);
    }

    // Update is called once per frame
    void Update()
    {
        if (luaenv != null)
        {
            //清除Lua未手动释放的LuaBase对象(这个操作是未处理过的,太频繁了,可以增加定时清理)
            luaenv.Tick();
        }
    }

    void OnDestroy()
    {
        luaenv.Dispose();
    }
}

————————————————
版权声明:本文为CSDN博主「xmx5166」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xmx5166/article/details/90712888

发布了64 篇原创文章 · 获赞 36 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/kuangben2000/article/details/104079298