XLua Lua调用C

阅前须知

由于该内容为本人的个人笔记,再加上本人喜欢将重要的说明放在注释中,所以只有一些特别重要的内容才会写在代码块外部,整体的内容都是以代码为主

对于一些简单的代码并没有全部给出(太占篇幅了)

对于Xlua来说Lua如何调用C#代码是要比上一篇C # 如何调用Lua更重要的,一般我们会在C#使用一个require将逻辑转移到lua

using UnityEngine;

/*
1.lua没有办法直接访问C#
2.需要先使用C#脚本调用lua
*/
public class Main : MonoBehaviour
{
    void Start()
    {
        LuaMgr.GetInstance().Init();
        //等效于LuaDoString("require('Main')"),但注意要用自定义Loader加载你的lua脚本(如果在Resource文件夹下记得加上.txt后缀)
        LuaMgr.GetInstance().DoLuaFile("Main");
    }
}

Lua调用C#类

image

主要方式为

CS.命名空间.类名
对于Unity类即为:CS.UnityEngine.GameObject
  • 对于成员方法需使用调用,对于静态方法要使用.调用
local go4=GameObject.Find("ColdPlay")
go4.transform:Translate(Vector3.right)

代码如下:

  • XLua默认情况(不使用反射)下只支持调用有约束、有参数的泛型函数,对于AddComponent我们可以:
go5:AddComponent(typeof(CS.LuaCallCsharp))

因为Unity提供了如下API·

image

lua代码

print("*********XLua调用C#类***********")
--[[
CS.命名空间.类名
对于Unity类即为:CS.UnityEngine.GameObject
]]
local go1=CS.UnityEngine.GameObject()
local go2=CS.UnityEngine.GameObject("ColdPlay")

--节约性能,定义全局变量存储(本质上就是Table)(别名)
GameObject=CS.UnityEngine.GameObject
Debug=CS.UnityEngine.Debug
Vector3=CS.UnityEngine.Vector3
-- 创建一个GameObjecet
local go3=GameObject("ColdPlay2")

--静态对象、方法,使用.调用
local go4=GameObject.Find("ColdPlay")
Debug.Log( go4.transform.position);
print(go4.transform.position)

--使用对象中的成员方法 必须使用:调用 传入self
go4.transform:Translate(Vector3.right)
Debug.Log(go4.transform.position)

--使用自定义无命名空间的类(不继承Mono)
local test=CS.Test()
test:Speak("speak")
--使用自定义有命名空间的类(不继承Mono)
local test2=CS.ColdPlay.Test2()
test2:Speak2("speak2")

--自定义类(Mono)
local go5=GameObject("AddComponent")
--使用AddComponent加入
--Xlua不支持无参泛型,需利用Xlua提供的typeof添加Mono脚本
go5:AddComponent(typeof(CS.LuaCallCsharp))

Lua调用C# 枚举

image

  • 使用枚举不需要实例化
  • 枚举转换使用__CastFrom

lua代码

print("*********XLua调用C#枚举***********")

--别名
GameObject=CS.UnityEngine.GameObject
Debug=CS.UnityEngine.Debug
Vector3=CS.UnityEngine.Vector3
PrimitiveType=CS.UnityEngine.PrimitiveType

--使用Unity自带枚举
local go=GameObject.CreatePrimitive(PrimitiveType.Cube)

--自定义枚举
--不存在实例化
E_MyEnum=CS.E_MyEnum

local c=E_MyEnum.Idle
print(c)

--枚举转换
local a =E_MyEnum.__CastFrom(1)
print(a)
local b =E_MyEnum.__CastFrom("Atk")
print(b)

Lua调用C# 集合

image

  • 使用数组、List等不要使用#要使用Length、Count
  • 创建数组
local array=CS.System.Array.CreateInstance(typeof(CS.System.Int32),10)
  • 创建List
--lua创建List对象
--老版本:
-- List`1代表CLR中的List<T>  其中`1代表有一个泛型参数 后续[]中就是实际的类型
-- 创建字典:local dict1 = CS.System.Collections.Generic["Dictionary`2[System.String,System.Int32]"]()
local list1=CS.System.Collections.Generic["List`1[System.String]"]()
print(list1)
list1:Add("123")
print(list1[0])
--新版本(lua>2.1.12):
local List_String=CS.System.Collections.Generic.List(CS.System.String)
local list2=List_String()
print(list2)
list2:Add("321")
print(list2[0])
  • 遍历Dictionary需要使用pairs(遍历不确保顺序)
-- 遍历字典需要使用pairs
-- ipairs只遍历数组部分(更高效) 也就是table的数组部分
-- pairs 会遍历所有的部分 数组+哈希部分
-- 在xlua中List会被映射数组部分
for key, value in pairs(dic) do
    print(key,value)
end

lua代码:

print("*********XLua调用C# Array List Dic***********")

local obj= CS.Learn3()

--Array
--长度 不能使用#
--C#中的数组  索引从0开始
print(obj.array.Length)
print(obj.array[0])

for i=0,obj.array.Length-1 do
    print("i:" .. obj.array[i])
end

--lua创建C#数组
local array=CS.System.Array.CreateInstance(typeof(CS.System.Int32),10)
print(array)
print("array.Length:" .. array.Length)


--List

obj.list:Add(1)
obj.list:Add(2)
obj.list:Add(3)
print(obj.list)
print(obj.list.Count)
for i = 0, obj.list.Count-1, 1 do
    print(obj.list[i])
end

--lua创建List对象
--老版本:
-- List`1代表CLR中的List<T>  其中`1代表有一个泛型参数 后续[]中就是实际的类型
-- 创建字典:local dict1 = CS.System.Collections.Generic["Dictionary`2[System.String,System.Int32]"]()
local list1=CS.System.Collections.Generic["List`1[System.String]"]()
print(list1)
list1:Add("123")
print(list1[0])
--新版本(lua>2.1.12):
local List_String=CS.System.Collections.Generic.List(CS.System.String)
local list2=List_String()
print(list2)
list2:Add("321")
print(list2[0])

--Dictionary

obj.dic:Add(1,"123")
print(obj.dic[1])
for key, value in pairs(obj.dic) do
    print(key,value)
end
--lua创建Dic
local Dic_String_Vector3=CS.System.Collections.Generic.Dictionary(CS.System.String,CS.UnityEngine.Vector3)
local dic=Dic_String_Vector3()

dic:Add("2",CS.UnityEngine.Vector3.right)
print(dic)



-- lua中创建的字典 无法使用[]访问
-- 使用 dic:get_Item("123")
print(dic["2"])
--两个返回值 bool和 out value(lua直接多返回值即可)
print(dic:TryGetValue("2"))

print(dic:get_Item("2"))
dic:set_Item("2",CS.UnityEngine.Vector3.left)
print(dic:get_Item("2"))
--由于是Vector3是struct,所以有默认值
dic:set_Item("2",nil)


print(dic:get_Item("2"))
-- 遍历字典需要使用pairs
-- ipairs只遍历数组部分(更高效) 也就是table的数组部分
-- pairs 会遍历所有的部分 数组+哈希部分
-- 在xlua中List也会被映射数组部分
for key, value in pairs(dic) do
    print(key,value)
end

Lua调用C#静态扩展方法

image

  • 给静态扩展类添加LuaCallCsharp特性

静态扩展方法

print("*********XLua调用C# 静态拓展方法***********")
Learn4=CS.Learn4

Learn4.Eat()

local obj=Learn4()
obj:Speak("speak")
--静态拓展方法和成员方法调用方式相同
obj:Move()

Lua调用C#使用ref和out的方法

image

  • 对于ref和out可以直接使用lua的多返回值的方式接收即可
  • ref必须初始化、out无需初始化

C #代码

    public int RefFunc(int a, ref int b, ref int c, int d)
    {
        b = a + b;
        c = c + d;
        return 100;
    }
    public int OutFunc(int a, out int b, out int c, int d)
    {
        b = a;
        c = d;
        return 200;
    }
    public int RefOutFunc(int a, out int b, ref int c, int d)
    {
        b = a * 100;
        c = a * d;
        return 300;
    }

lua代码

print("*********XLua调用C# ref out***********")

Learn5 = CS.Learn5

local obj = Learn5()

-- ref会以多返回值返回
-- a为函数返回值,b,c为ref
-- ref参数需要初始化
local a, b, c = obj:RefFunc(1, 0, 0, 1)
-- local a, b, c = obj:RefFunc(1, 1) 后面的参数会默认用0补
print('call ref' .. ':' .. a .. ':' .. b .. ':' .. c)

-- out参数可以不初始化
-- 传入的为 a和d
local d, e, f = obj:OutFunc(1, 1)
print('call out' .. ':' .. d .. ':' .. e .. ':' .. f)

-- 传入的为 a,c,d
local g, h, i = obj:RefOutFunc(20, 1, 200)
print('call ref out' .. ':' .. g .. ':' .. h .. ':' .. i)

Lua调用C#重载方法

image

  • C#和lua语言精度不一致(float)
  • 使用如下代码的时候
print(obj:Calc(1))
print(obj:Calc(1.2))

我们会发现输出为(如果我们先调用Calc(1.2)在调用Calc(1),浮点数部分的输出仍然为0)

image

解决办法

--解决 借助反射,效率低(尽量不用)
--得到指定函数信息
local m1=typeof(CS.Learn6):GetMethod("Calc",{typeof(CS.System.Int32)})
local m2=typeof(CS.Learn6):GetMethod("Calc",{typeof(CS.System.Single)})

--使用xlua提供的方法转换为方法,一般一个方法转化一次

local f1=xlua.tofunction(m1)
local f2=xlua.tofunction(m2)
-- LUA: 10.199999809265 语言float精度不同
print(f1(obj,10))
print(f2(obj,10.2))

C #代码

    public int Calc()
    {
        return 100;
    }
    public int Calc(int a, int b)
    {
        return a + b;
    }
    public int Calc(int a)
    {
        return a;
    }
    public float Calc(float a)
    {
        return a;
    }

lua代码

print("*********XLua调用C# overload func***********")

Learn6=CS.Learn6
local obj =Learn6()

print(obj:Calc())
print(obj:Calc(1,1))

-- 虽然支持调用C#重载函数
-- lua只有number
-- 对于C#多精度的重载函数支持不好
print(obj:Calc(1))
print(obj:Calc(1.2))

--解决 借助反射,效率低(尽量不用)
--得到指定函数信息
local m1=typeof(CS.Learn6):GetMethod("Calc",{typeof(CS.System.Int32)})
local m2=typeof(CS.Learn6):GetMethod("Calc",{typeof(CS.System.Single)})

--使用xlua提供的方法转换为方法,一般一个方法转化一次

local f1=xlua.tofunction(m1)
local f2=xlua.tofunction(m2)
-- LUA: 10.199999809265 语言float精度不同
print(f1(obj,10))
print(f2(obj,10.2))

Lua调用C# 委托和事件

image

  • 第一次使用使用C#某个委托需要先obj.del=func​(此时del为nil)才能使用obj.del=obj.del+func
  • 对于event需要使用(需注意的是event只能在声明他的类去调用、清空,我可以在C#部分封装一个调用、清空的方法
obj:eventAction('+',func2)
obj:eventAction('-',func2)
obj:DoEvent()

Lua代码

print('lua call C# delegate')
local obj=CS.Learn7()

local func = function () 
    print('lua func')
end

-- delegate
--第一次添加委托引用的函数,不能使用 -- obj.del=obj.del+func (del为nil)
obj.del=func
obj.del=obj.del+func

-- 不建议 不方便减少引用
obj.del =obj.del + function ()
    print('temp')
end
obj.del()

obj.del=obj.del-func
obj.del=obj.del-func

obj.del()

obj.del=nil
obj.del=func
obj.del()
local funcf= function (f)
    print('float' .. f)
end
obj.del2=funcf
obj.del2(2)

-- event
print('lua call C# event')
local func2 = function ()
    print('lua func2')
end
--调用事件使用 :
--增加
obj:eventAction('+',func2)
obj:eventAction('+',func2)
obj:eventAction('+' ,function ()
    print('func')
end)
obj:DoEvent()
obj:eventAction('-',func2)
obj:DoEvent()

-- obj.eventAction=nil 不能使用(事件不能在外部置空)
print('清空事件')
obj:ClearEvent()
obj:DoEvent()

Lua使用Unity的UI

GameObject = CS.UnityEngine.GameObject

UI = CS.UnityEngine.UI

local slider = GameObject.Find('Slider')
print(slider.name)
local sliderScript = slider:GetComponent(typeof(UI.Slider))
print(sliderScript)
sliderScript.onValueChanged:AddListener(
    function(p)
        print(p)
    end

)

Lua使用Unity携程

image

  • 需使用xlua.util
util = require("xlua.util")
-- 不能直接将lua函数直接传入
-- 借助xlua.util
-- 注意要添加 CsharpCallLua typeof(System.Collections.IEnumerator)
b=mono:StartCoroutine(util.cs_generator(fun))

lua代码

print('lua call mono coroutine')

--使用xlua.util
util = require("xlua.util")

GameObject = CS.UnityEngine.GameObject
WaitForSeconds = CS.UnityEngine.WaitForSeconds

local obj = GameObject("Coroutine")
local mono = obj:AddComponent(typeof(CS.LuaCallCsharp))

fun = function()
    local a = 1
    while true do
        --不能使用C# yield return
        --每一秒打印一次a
        coroutine.yield(WaitForSeconds(1))
        if a>10 then
            -- 关闭携程
            mono :StopCoroutine(b)
        end
        print(a)
        a = a + 1
    end
end

-- 不能直接将lua函数直接传入
-- 借助xlua.util
-- 注意要添加 CsharpCallLua typeof(System.Collections.IEnumerator)
b=mono:StartCoroutine(util.cs_generator(fun))

Lua调用C#泛型方法

image

  • 默认情况只支支持有约束有参数的泛型方法
  • 内部类使用
local child = CS.Learn12.TestChild()
  • 使用反射调用无约束或无参数的泛型方法,需注意mono和il2cpp的区别

C#代码

public class Learn12
{
    public interface ITest
    {

    }
    public class TestFather
    {

    }
    public class TestChild : TestFather,ITest
    {

    }

    public void TestFun1<T>(T a, T b) where T : TestFather
    {
        Debug.Log("有参数有约束的泛型");
    }
    public void TestFun2<T>(T a)
    {
        Debug.Log("有参数无约束的泛型");
    }
    public void TestFun3<T>() where T : TestFather
    {
        Debug.Log("无参数有约束的泛型");
    }
    public void TestFun4<T>(T a) where T : ITest
    {
        Debug.Log("有参数有约束(接口)的泛型");
    }

}

lua代码

print('lua call generic func')

local obj = CS.Learn12()

-- 内部类
local child = CS.Learn12.TestChild()
local father = CS.Learn12.TestFather()

-- 支持有参数有约束泛型
-- 作为参数时候变相相当于传type
obj:TestFun1(child, father)
obj:TestFun1(father, child)

-- 不支持没有约束的泛型
-- obj:TestFun2(child)

-- 不支持没有参数的泛型
-- obj : TestFun3()
-- 不支持非class约束
-- obj:TestFun4(child)


--[[
如何使用不支持的泛型 (反射,少用,效率低),
且有使用限制
1.Mono可以使用
2.Il2cpp 支持T为引用类型
如果为值类型,必须是C#已经使用过同类型的泛型参数,lua才能使用
原因:Il2cpp 为AOT(引用类型 使用共享代码,所有引用类型用一份泛型实现,对于值类型只有目标泛型实例使用过,才会被生成) 
Mono为JIT(即时编译)
]]
--[[
1.得到通用函数
2.设置实际的类型
]]

--获取通用函数
local testFun2=xlua.get_generic_method(CS.Learn12,"TestFun2")
--设置类型
local testFun2_Real=testFun2(CS.System.Int32)
--调用
testFun2_Real(obj,12)


其他特殊情况

对于一些没有办法修改的(如DLL)库应该怎么添加LuaCallCsharp和CsharpCallLua特性

image

用一个静态类的静态列表即可

public static class Learn10
{
    //dll等不可修改类加特性
    [CSharpCallLua]
    public static List<Type> csharpCallLuaList = new List<Type>()
    {
        typeof(UnityAction<float>),
        typeof(System.Collections.IEnumerator),

    };
    //包括LuaCallC#
    [LuaCallCSharp]
    public static List<Type> luaCallCsharpList = new List<Type>()
    {
        typeof(GameObject)
    };

}

C#的null不等于lua的nil

image

仅对于xlua2.1.15版本(其他版本本人还未使用过)

对于以下情况

local obj = GameObject("测试添加组件")
local rig= obj:GetComponent(typeof(Rigidbody))

print(rig)
-- nil和null无法进行
if rig==nil then
    print('== add rigidbody')
    rig=obj:AddComponent(typeof(Rigidbody))
end

我们会发现nil不等于null

解决办法

  1. 静态扩展方法
ublic static bool IsNull(this UnityEngine.Object obj)
    {
        return obj == null;
    }
  1. 使用Equals
function IsNull(obj)
    --这里借助了短路的特性 既obj==nil就不会在判断obj:Equals(nil),变相放置了对于lua中的nil调用Equals
    if obj == nil or obj:Equals(nil) then
        return true;
    end
    return false
end

Lua使用C# 的二维数组

image

# Lua使用C#二维数组

- **GetLength(0):row GetLength(1):col 中的0,1,2就代表维度**

‍```C#
print('lua use two-dimensional array')

local obj=CS.Learn8()
-- GetLength(0):row GetLength(1):col 0,1,2就代表维度
print("row:" .. obj.array:GetLength(0) .. " col:" .. obj.array:GetLength(1))
-- 不能使用 obj.array[0][0] 或 obj.array{0,1}

print(obj.array:GetValue(0,0))


for i=0,obj.array:GetLength(0)-1 do
    for j=0,obj.array:GetLength(1)-1 do
        print(obj.array:GetValue(i,j))
    end
end
‍```

本文详细介绍了在XLua框架下,Lua脚本如何调用C#代码的方法和注意事项。主要内容包括:

  1. 基本调用方式:通过CS.命名空间.类名访问C#类和Unity内置类,区分成员方法(:)和静态方法(.)的调用语法。
  2. 特殊处理:

    • 泛型方法的调用限制及解决方案
    • 枚举的使用和转换(__CastFrom)
    • 集合(List/数组)的正确遍历方式(避免使用#)
  3. 实际示例:

    • 创建GameObject和组件
    • 调用自定义类方法
    • 枚举转换应用
    • 集合创建和操作
  4. 性能优化建议:

    • 使用全局变量存储常用类引用
    • 类型转换的最佳实践
      文章采用代码注释为主的讲解方式,强调XLua与C#交互时的关键点和常见问题解决方案。
最后修改:2025 年 06 月 29 日
如果觉得我的文章对你有用,请随意赞赏