热 更新的 基本原理
C#脚本不能直接热更新的原因:
下载的图片与模型都没有问题,如果是Unity逻辑代码,无论是以前的
Mono AOT 还是后面的il2cpp , 都是编译成native code, IOS下是运行不起
来的。
解决方法
就是不用native code ,改为解释执行。包括Xlua在内所有热更新就是这
个思路来实现。
Xlua插件的GitHub 下载地址
https://github.com/Tencent/xLua
作者对于Xlua插件的整体介绍性文章
http://www.gad.qq.com/article/detail/24967
具体准备开发环境:
1:下载Xlua插件,且导入工程。
2: 把Xlua中的Xlua与Plugins 目录直接拷贝到项目所在文件夹。成功拷贝后,
在Unity菜单中可以看到出现"XLua"的菜单项。
1.lua中使用冒号“:”表示成员方法的调用。它自动完成当前对象作为一个参数,传入方法;lua中使用点“.”,则表示静态属性与方法调用,它需要手工往方法中传递当前对象。
2.冒号定义方法,默认会接受self参数,而点号定义的时候,默认不会接受self参数
3.lua中 classObj:Method()与 classObj.Method(classObj)一样
1.lua 的字段或者函数之间要用 ,(逗号)隔开
2.lua里面的类或者table可以映射到C#里面的Interface,C#里面的Interface要用public修饰,并且要添加[CSharpCallLua]特性标签
3.加载Resources文件里面的lua文件:
a. env = new LuaEnv();
env.DoString("require 'LuaCallCSharp'");
b. env = new LuaEnv();
TextAsset txtAsset = Resources.Load<TextAsset>("simpleLua.lua");
env.DoString(txtAsset.text);
c. LuaEnv env = null;
// Use this for initialization
string fileName = "CustomDIRLuaFile";
void Start()
{
env = new LuaEnv();
env.AddLoader(CustomMyLoader);
env.DoString("require 'CustomDIRLuaFile'");
}
byte[] CustomMyLoader(ref string fileName)
{
string luaPath = Application.dataPath + "/Scripts/LuaScripts/" + fileName + ".lua";
string strLuaContent = File.ReadAllText(luaPath);
byte[] back=null;
back = System.Text.Encoding.UTF8.GetBytes(strLuaContent);
return back;
}
uv:告诉贴图如何贴在模型上
反转法线
高亮 unlit/texture
使用
1. 最基本是直接用LuaEnv.DoString执行一个字符串,当然,字符串得符合Lua语法
比如:luaenv.DoString("print('hello world')")
2. 加载 lua 文件
使用TextAsset 方式加载文件:
Eg:
TextAsset ta=Resources.Load<TextAsset>("HelloWorld.lua");
LuaEnv env=new LuaEnv();
env.DoString(ta.text);
3. 加载 lua 文件
Require函数进行加载 (常用方式)
require 就是一个个的调用Loader ,查找出匹配的lua文件,然后执行
该文件。
注意事项:
1:因为Resource 只支持有限的后缀,放Resource下的lua文件需要加上
txt后缀。
2:使用lua开发项目推荐的方式是:整个程序就一个
DoString(“require‘main’”), 然后在main.lua中加载其他脚本。(这里
的Main文件名称可以任意)
3. 在xLua加自定义loader是很简单的,只涉及到一个接口:
public delegate byte[] CustomLoader(ref string filepath);
public void LuaEnv.AddLoader(CustomLoader loader)
通过AddLoader可以注册个回调,该回调参数是字符串,lua代码里头调用require时,参数将会透传给回调,回调中就可以根据这个参数去加载指定文件,如果需要支持调试,需要把filepath修改为真实路径传出。该回调返回值是一个byte数组,如果为空表示该loader找不到,否则则为lua文件的内容。如下:
//lua环境(官方建议全局唯一)
LuaEnv env = null;
private void Start()
{
env = new LuaEnv();
env.AddLoader(CustomMyLoader);
env.DoString("require 'CustomDIRLuaFile'");
}
/// <summary>
/// 定义回调方法
/// 功能:
/// 本方法主要功能是自定义lua文件路径。
/// </summary>
/// <param name="fileName">文件名称</param>
/// <returns></returns>
private byte[] CustomMyLoader(ref string fileName)
{
byte[] byArrayReturn = null; //返回数据
//定义lua路径
string luaPath = Application.dataPath + "/Scripts/LuaScripts/" + fileName + ".lua";
//读取lua路径中指定lua文件内容
string strLuaContent = File.ReadAllText(luaPath);
//数据类型转换
byArrayReturn = System.Text.Encoding.UTF8.GetBytes(strLuaContent);
return byArrayReturn;
}
不同lua文件的加载方式分析:
使用Require 方式加载lua文件,必须放置在Resources特殊目录下,否则
查找不到无法加载。
自定义Loader 可以进一步扩展前面两种方法的弊端,可以按照自己的方
式进行加载lua与执行lua代码。(即:可以把*.lua文件放置到任意合法文
件夹下,且lua文件不用后缀增加txt标识。)
require 本质是按照既定的查找顺序,找到需要的lua程序,否则返回
nil ,然后报错。演示当查找一个错误的lua文件时候,控制台详细打印的
查找路径。
C#调用lua
Lua类:
--定义一个综合表(lua中的OOP思想)属性或方法中以逗号分隔
gameUser={
name="崔永元",
age=40,
ID="18011112222",
Speak=function()
print("lua玩家在讨论中")
end,
Walking=function()
print("lua玩家在健身中")
end,
Calulation=function(age,num1,num2)--说明:age 这里命名可以任意,表示当前对象(即:gameUser)
return age.age+num1+num2
end
}
1. 获取 一个全局基本 数据类型
访问LuaEnv.Global就可以了,上面有个模版Get方法,可指定返回的类型。
env = new LuaEnv();
env.DoString("require 'CsharpCallLua'");
string str1=env.Global.Get<string>("str"); //字符串类型
int num = env.Global.Get<int>("number");//数字类型
2. 访问一个全局 table
方式1: 映射到普通class或struct
定义一个class,有对应于table的字段的public属性,而且有无参数构
造函数即可,比如对于{f1 = 100, f2 = 100}可以定义一个包含
public int f1;
public int f2; 的class。
这种方式下xLua会帮你new一个实例,并把对应的字段赋值过去。
table的属性可以多于或者少于class的属性。可以嵌套其它复杂类型。
方式2:映射到一个interface [*推荐方式]
这种方式依赖于生成代码(如果没生成代码会抛InvalidCastException异
常),代码生成器会生成这个interface的实例。 如果get一个属性,生成代
码会get对应的table字段,如果set属性也会设置对应的字段。甚至可以通过
interface的方法访问lua的函数。
注意点与性能分析:
A: 接口需要添加特性标签[CSharpCallLua],否则无法生成实例代码。
B: 为引用拷贝,适合用在复杂表,一般商业项目推荐使用本方式。
如下:
//定义接口
[CSharpCallLua]
public interface IGameLanguage
{
string str1 { get; set; }
string str2 { get; set; }
string str3 { get; set; }
string str4 { get; set; }
}
//lua环境(官方建议全局唯一)
LuaEnv env = null;
private void Start()
{
env = new LuaEnv();
env.DoString("require 'CsharpCallLua'");
//得到lua中的表信息
IGameLanguage gameLan = env.Global.Get<IGameLanguage>("gameLanguage");
//输出显示
Debug.Log("[使用接口]gameLan.str1="+ gameLan.str1);
Debug.Log("[使用接口]gameLan.str2=" + gameLan.str2);
Debug.Log("[使用接口]gameLan.str3=" + gameLan.str3);
Debug.Log("[使用接口]gameLan.str4=" + gameLan.str4);
//演示接口的引用拷贝原理
gameLan.str1 = "我是修改后的内容";
env.DoString("print('修改后gameLanguage.str1='..gameLanguage.str1)");
}
Example: 通过interface ,调用lua中一个复杂一些的Table
public class CallLuaTableByInterfaceComp:MonoBehaviour
{
//lua环境(官方建议全局唯一)
LuaEnv env = null;
private void Start()
{
env = new LuaEnv();
env.DoString("require 'CsharpCallLua'");
//得到lua中的表信息
IGameUser gameUser = env.Global.Get<IGameUser>("gameUser");
//输出显示
Debug.Log("[使用接口]gameLan.ID=" + gameUser.ID);
Debug.Log("[使用接口]gameLan.Name=" + gameUser.name);
Debug.Log("[使用接口]gameLan.Age=" + gameUser.age);
//输出调用方法
gameUser.Speak();
gameUser.Walking();
int tmpResult=gameUser.Calulation(100,200);
Debug.Log("经过lua中计算,结果= "+tmpResult);
}
private void OnDestroy()
{
//释放luaenv
env.Dispose();
}
//定义接口
[CSharpCallLua]
public interface IGameUser
{
string name { get; set; }
int age { get; set; }
string ID { get; set; }
void Speak();
void Walking();
int Calulation(int num1, int num2);
}
}
方式3: by value 方式:
这是更轻量级的by value 方式,映射到Dictionary<>,List<> (适合用在
简单表)不想定义class或者interface的话,可以考虑用这个,前提table下
key和value的类型都是一致的。如下:
//得到lua中的简单表信息
Dictionary<string, object> dicGameLan=env.Global.Get<Dictionary<string, object>>("gameLanguage");//映射一个简单表
//得到lua中的复杂表信
Dictionary<string, object> dicGameLan=env.Global.Get<Dictionary<string, object>>("gameUser");//映射一个复杂表
//得到一个更加简单lua表,使用List<> 来映射。
List<string> liProLan=env.Global.Get<List<string>>("programLanguage");
Example: 讲解使用委托的方式,调用Lua中具备多个返回数值的函数。
* 1: 定义out 关键字的委托。
* 2:定义ref 关键字的委托。
//自定义委托(使用out关键字)
[CSharpCallLua]
public delegate void delAddingMutilReturn(int num1, int num2, out int res1, out int res2, out int res3);
[CSharpCallLua]
public delegate void delAddingMutilRetRef(ref int num1, ref int num2, out int result);
public class CallLuaFunctionByDeleMultiReturn:MonoBehaviour
{
//lua环境(官方建议全局唯一)
LuaEnv env = null;
//委托声明
delAddingMutilReturn act1 = null;
delAddingMutilRetRef act2 = null;
private void Start()
{
env = new LuaEnv();
env.DoString("require 'CsharpCallLua'");
////得到lua中的具有多个返回数值的函数(通过委托out关键字来进行映射)
//act1=env.Global.Get<delAddingMutilReturn>("ProcMyFunc5");
////输出返回结果
//int intOutRes1 = 0;
//int intOutRes2 = 0;
//int intOutRes3 = 0;
//act1(100, 880,out intOutRes1, out intOutRes2, out intOutRes3);
//Debug.Log(string.Format("res1={0},res2={1},res3={2}", intOutRes1, intOutRes2, intOutRes3));
//得到lua中的具有多个返回数值的函数(通过委托ref关键字来进行映射)
act2=env.Global.Get<delAddingMutilRetRef>("ProcMyFunc5");
//输出返回结果
int intNum1 = 20;
int intNum2 = 30;
int intResult = 0;
act2(ref intNum1, ref intNum2, out intResult);
Debug.Log(string.Format("使用ref关键字,测试多输出 res1={0},res2={1},res3={2}", intNum1,intNum2, intResult));
}
方式4: by ref方式,映射到LuaTable类。
这种方式好处是不需要生成代码,但问题就是比较慢(即:效率低下),
比interface方式要慢一个数量级,比如没有类型检查。
性能分析:
因为效率较低,所以不推荐常用,适合用在一些较为复杂且使用频率很
低的情况下,一般不推荐使用。如下:
env = new LuaEnv();
env.DoString("require 'CsharpCallLua'");
//得到lua中的复杂表信息
XLua.LuaTable tabGameUser=env.Global.Get<XLua.LuaTable>("gameUser");
//输出显示
Debug.Log("name="+tabGameUser.Get<string>("name"));
Debug.Log("Age=" + tabGameUser.Get<int>("age"));
Debug.Log("ID=" + tabGameUser.Get<string>("ID"));
//输出表中函数
XLua.LuaFunction funSpeak=tabGameUser.Get<XLua.LuaFunction>("Speak");
funSpeak.Call();
XLua.LuaFunction funWalking = tabGameUser.Get<XLua.LuaFunction>("Walking");
funWalking.Call();
XLua.LuaFunction funCalulation = tabGameUser.Get<XLua.LuaFunction>("Calulation");
object[] objArray=funCalulation.Call(tabGameUser, 10, 20);
Debug.Log("输出结果="+ objArray[0]);//输出结果: 70
2. 访问 一个全局的
方式1: 映射到delegate [* 推荐方式]
优点: 这是建议的方式,性能好很多,而且类型安全。
缺点: (含有out与ref关键字delegate)要生成代码(如果没生成代码会抛
InvalidCastException异常)。
注意:
A: 含有 out 与 ref 关键字委托也需要添加特性标签[ [ CSharpCallLua] ]
B: 委托 引用后,退出 luaEnv 前,需要释放委托引用,否则 lua 报错 !
C: 对于 Unity 与 C# 中的复杂类型 API, 必须加入 Xlua 的配置文件,经过生
成代码后才能正确应用。
例如: Action< int,int,int > 、 Func< < int,int,int >
delegate要怎样声明呢?
对于function的每个参数就声明一个输入类型的参数。
多返回值要怎么处理?
从左往右映射到c#的输出参数,输出参数包括返回值,out参数,ref参
数。
参数、返回值类型支持哪些呢?
都支持,各种复杂类型,out,ref修饰的,甚至可以返回另外一个
delegate。
public class CallLuaFunctionByDele:MonoBehaviour
{
//lua环境(官方建议全局唯一)
LuaEnv env = null;
//自定义委托
public delegate void delAdding(int num1, int num2);
Action act = null;
delAdding act2 = null;
//注意: 以下两种委托定义,需要配置文件支持。
Action<int, int, int> act3 = null;
Func<int, int, int> act4 = null;
private void Start()
{
env = new LuaEnv();
env.DoString("require 'CsharpCallLua'");
//得到lua中的函数信息(通过委托来进行映射)
act=env.Global.Get<Action>("ProcMyFunc1");
//使用自定义委托调用具备两个输入参数的lua中的函数
act2=env.Global.Get<delAdding>("ProcMyFunc2");
//定义三个输入参数的委托。
act3 = env.Global.Get<Action<int, int, int>>("ProcMyFunc4");
//定义具备返回数值,两个输入数值的委托
act4 = env.Global.Get<Func<int, int, int>>("ProcMyFunc3");
//调用
act.Invoke();
act2(50,60);
act3(20, 30, 40);
int intResult=act4(60,40);
Debug.Log("Func 委托,输出结果="+intResult);
}
private void OnDestroy()
{
act = null;
act2 = null;
act3 = null;
act4 = null;
//释放luaenv
env.Dispose();
}
}
方式2: 映射到LuaFunction
优点: 无需生成代码
缺点: 性能不高,不推荐。
这种方式的优缺点刚好和第一种相反。使用也简单。LuaFunction上有个
变参的Call函数,可以传任意类型,任意个数的参数,返回值是object的数
组,对应于lua的多返回值。
private void Start()
{
env = new LuaEnv();
env.DoString("require 'CsharpCallLua'");
//得到lua中的函数信息(通过LuaFunction来进行映射)
LuaFunction luaFun=env.Global.Get<LuaFunction>("ProcMyFunc1");
LuaFunction luaFun2 = env.Global.Get<LuaFunction>("ProcMyFunc2");
LuaFunction luaFun3 = env.Global.Get<LuaFunction>("ProcMyFunc3");
//调用具有多返回数值。
LuaFunction luaFun4 = env.Global.Get<LuaFunction>("ProcMyFunc5");
//输出
luaFun.Call();
luaFun2.Call(10, 20);
object[] objArray = luaFun3.Call(30, 40);
Debug.Log("调用ProcMyFunc3 ,结果=" + objArray[0]);
object[] objArray2=luaFun4.Call(22,80);
Debug.Log(string.Format("测试多返回数值 res1={0},res2={1},res3={2}",objArray2[0], objArray2[1], objArray2[2]));
}
官方使用建议:
A: 访问lua全局数据,特别是table以及function,代价比较大,建议
尽量少做,比如在初始化时把要调用的lua function获取一次(映射到
delegate)后,保存下来,后续直接调用该delegate即可。table也类似。
B: 如果lua实现的部分都以delegate和interface的方式提供,使用方
可以完全和xLua解耦: 由一个专门的模块负责xlua的初始化以及delegate、
interface的映射,然后把这些delegate和interface设置到要用到它们的地
方。
Lua调用C#
1. lua 访问 C# 静态属性与 方法
基本方法:
使用CS开头,实例化类。
在C#这样new一个对象:
var newGameObj = new UnityEngine.GameObject();
对应到Lua是这样:
local newGameObj = CS.UnityEngine.GameObject()
基本规则:
A: lua里头没有new关键字。
B: 所有C#相关的都放到CS下,包括构造函数,静态成员属性、方法。
如果有多个构造函数呢?
xlua支持重载,比如你要调用GameObject的带一个string参数的构造函
数,这么写:
local newGameObj2 = CS.UnityEngine.GameObject('helloworld')
2. lua 访问 C# 常用方式
1. 方式1:访问成员属性与方法
读成员属性 testobj.DMF
写成员属性 testobj.DMF = 1024
调用成员方法 testobj:DMFunc()
注意:
lua中使用冒号,表示成员方法的调用。它自动完成把当前对象作为一
个参数,传入方法。
lua中使用点,则表示静态属性与方法调用。它需要手工往方法中传递
当前对象。
2. 方式2:访问父类属性与方法
xlua支持(通过派生类)访问基类的静态属性,静态方法,(通过派生
类实例)访问基类的成员属性,成员方法。
3. 方式3: 访问重载方法
Xlua 支持方法的重载,但为“有限重载”。直接通过不同的参数类型进
行重载函数的访问,例如:
testobj:TestFunc(100)
testobj:TestFunc('hello')
将分别访问整数参数的TestFunc和字符串参数的TestFunc。
注意:
xlua只一定程度上支持重载函数的调用,因为lua的类型远远不如C#丰富,
存在一对多的情况,
比如C#的int,float,double都对应于lua的number,上面的例子中
TestFunc如果有这些重载参数,第一行将无法区分开来,只能调用到其中一
个(生成代码中排前面的那个)
3. lua 调用 C# 中带参方法
1. 方式1: C# 中可变参数方法
对于C#的如下方法:
void VariableParamsFunc(int a, params string[] strs)
可以在lua里头这样调用:
testobj:VariableParamsFunc(5, 'hello', 'john')
2. 方式2: C#结构体参数
lua 使用一个表,来映射C#的结构体。
3. 方式3:C#接口参数
注意: 接口需要加入标记: [CSharpCallLua]
lua 使用一个表,来映射C#的接口。
4. 方式4: C#委托参数
委托需要加入标记: [CSharpCallLua]
lua 使用一个函数,来映射C#的委托。
4. lua 接收 C# 方法返回的多个结果数值
1. 基本规则: 参数的输入输出属性(out,ref)
A: C#的普通参数算一个输入形参,ref修饰的算一个输入形参,out不算,
然后从左往右对应lua 调用的实参列表。
B: Lua调用返回值处理规则:
C#函数的返回值(如果有的话)算一个返回值,out算一个返回值,
ref算一个返回值,然后从左往右对应lua的多返回值。
5. lua 如何调用 C# 泛型方法
基本规则:
lua 不直接支持C#的泛型方法,但可以通过扩展方法功能进行封装后调
用。
使用 Extension methods (扩展方法)技术就是C#中在不改变原始类的基
础上,使用一种机制可以无限扩展这个类功能的机制。
Eg:
原始类为: A 扩展类为: Ext_A
注意: Ext_A 必须是一个静态类,且扩展方法也必须是静态的。方法的
参数中必须要有被扩展类作为其中一个参数,此参数前面必须有this 关键字
修饰。
6.Lua 调用其他 C# 知识点
1. 参数带默认值的方法
与C#调用有默认值参数的函数一样,如果所给的实参少于形参,则会
用默认值补上。
枚举类型
枚举值就像枚举类型下的静态属性一样。
testobj:EnumTestFunc(CS.Tutorial.TestEnum.E1)
上面的EnumTestFunc函数参数是Tutorial.TestEnum类型的
2. 委托与事件
delegate 属性可以用一个luaFunction 来赋值(前面已经讲过)
比如testobj里头有个事件定义是这样:public event Action
TestEvent;
增加事件回调
testobj:TestEvent('+', lua_event_callback)
移除事件回调
testobj:TestEvent('-', lua_event_callback)
委托与事件(续)
delegate 使用 (调用,+,-)
注意:
A: 这里lua中可以使用“+/-”操作符,来增加与减少一个委托的调用。
B: delegate 属性可以用一个 luafunction 来赋值。
委托与事件(续)
event使用
Eg:
public event Action TestEvent;
增加事件回调:
testObj: TestEvent('+',lua_event_callback)
移除事件回调:
testObj: TestEvent('-',lua_event_callback)
7. Lua 调用 C# 经验总结
1. 一: lua 调用C#,需要在Xlua中生成“适配代码”,则在这个类打入一个
[luaCallCSharp] 的标签。
二: 如果lua调用C#的系统API ,则无法拿到源代码,无法打入标签。则
使用“静态列表”方式解决。
Eg:
public static List<Type> mymodule_LuaCallCS_List=new
List<Type>()
{
typeof(GameObject),
typeof(Dictionary<string,int>),
};
然后把以上代码放入一个静态类中即可。
三: 实际开发过程中,lua 调用C# 用的比较多。
xlua 的优点体现的没有必要每次改的时候,都要生成代码。主要原理是
依赖于编译器环境下,利用反射来动态生成代码接口。
四: 在标有“[XLua.LuaCallCSharp]”的C#类中,添加新的方法后,如
果是生成了代码类,则必须重新生成或者删除,否则Xlua 用以前生成的,
进行注册查询,会出现lua 异常:“试图访问一个nil 的方法”
LuaCallCsharp.lua文件
--详细演示 lua Call C# 各种特性
--
print("测试lua文件是否正确加载")
--[[ 一: 学习 lua 调用 Unity 系统 API ]]--
--1: lua 中实例化一个 Unity 的对象
local newGo=CS.UnityEngine.GameObject()
newGo.name="New GameObject"
--2: 查找游戏物体
-- 学习lua 访问Unity API 中静态属性与方法
local TxtGo=CS.UnityEngine.GameObject.Find("Txt_Logo");
--TxtGo.name="Modify Name"
--3: 得到游戏对象上的组件,学习使用冒号与句号(":",“.”)
local txtLogo=TxtGo:GetComponent("UnityEngine.UI.Text")
txtLogo.text="公告系统"
--[[ 二: 学习 lua 调用自定义C#脚本 ]]--
local IsInvoked=CS.XluaPro.IsInvokedClass
local classObj=IsInvoked() --自动调用父类与子类的构造函数
--调用普通方法
--classObj:Mehtod1() --ok
--classObj.Mehtod1() --语法报错!
--classObj.Mehtod1(classObj) --语法OK
--调用父类的字段与方法
--classObj:ShowFatherInfo(); --调用父类的方法
--print(classObj.FatherClassName) --调用父类的公共字段
--print(classObj.ChildClassName) --调用子类的公共字段
--测试调用C#方法重载
--classObj:Method2(10,20)
--classObj:Method2("abc","def")
--测试C#中带有params 关键字的方法
--local intResult=classObj:Method3(20,70,"Hello ","World","EveryOne")
--print("调用parmas关键字的方法,返回数值= "..intResult)
--测试lua调用C#中带有结构体参数的方法
--定义一个表
--myStructTable={x="C#语言",y="lua语言"}
--classObj:Method4(myStructTable)
--测试lua调用C#中带有接口参数的方法
--定义一个表
myInterfaceTable=
{
x=1000,
y=300,
Speak=function()
print("lua中 Speak 方法被调用!")
end
}
--classObj:Method5(myInterfaceTable)
--定义lua调用C#中带有委托参数的方法
--定义函数
myDelegate=function(num)
print("lua 中对应委托方法。参数num="..num)
end
--classObj:Method6(myDelegate)
--接收C#多返回数值
local num1=10
local num2=20
local res1,res2,res3=classObj:Method7(num1,num2)
--print("res1="..res1) --输出结果: 110
--print("res2="..res2) --输出结果: 3000
--print("res3="..res3) --输出结果: 999
--lua中可以直接调用具有泛型为参数的方法
--myTable8={"lua语言","C#语言","C++语言"}
--classObj:Method8(myTable8);
--让C#方法运行起来。
--classObj:Method_InvokeGenger()
--lua中直接调用C#中定义的泛型方法
--local maxNum=CS.XluaPro.MyGengerric:GetMax<int>(20,30) --报语法错误
--print("maxNum="..maxNum)
--lua调用C#中一个测试方法
--测试C#中调用C#中的“扩展方法”
--classObj:Test8_InvokeExtensionMethod();
--演示: 在lua中通过调用"扩展方法",来间接完成对C#“泛型方法”功能的实现。
local maxNum=CS.XluaPro.MyGengerric():ExtGetMax(888,66)
print("[在lua中扩展方法调用] maxNum="..maxNum)