acecase 发表于 2023-2-16 17:12

【Lua与C#交互③】方法调用和错误处理函数

上篇文章简单说到了 lua_pcall 这个方法,不过没有考虑到参数和返回值的情况,本节重点讲这个函数,还会讲如何把C#端的方法放在lua的栈上以供lua调用。
<hr/>先上代码:
var L = LuaDLL.luaL_newstate();
var path = Application.dataPath + "/Examples/03_Function/03.lua";

LuaDLL.luaL_loadfile(L, path);//加载lua文件
LuaDLL.lua_pcall(L, 0, 0, 0);//运行lua文件

LuaDLL.lua_getglobal(L, "addandsub");
LuaDLL.lua_pushnumber(L, 10);
LuaDLL.lua_pushnumber(L, 20);

if (LuaDLL.lua_pcall(L, 2, 2, 0) != 0)
{
    Debug.LogError(LuaDLL.lua_tostring(L, -1));
}
Debug.Log(LuaDLL.lua_tonumber(L, -1));
Debug.Log(LuaDLL.lua_tonumber(L, -2));
LuaDLL.lua_close(L);
Lua代码如下:
function addandsub(x,y)
    return x+y,x-y
end
结果是先打印-10,再打印30
<hr/>函数、参数、返回值的入栈顺序

上面的各个方法之前我们已经学过了,这里需要理解 函数、参数、返回值的入栈顺序。如图所示:


- 先将方法入栈 - 再将方法需要的参数按顺序入栈 - 调用 lua_pcall - 此时lua会把返回值入栈
<hr/>上面的东西很简单,接下来讲更重要的东西,如何把C#的方法放到Lua里面? 这个问题本质上是如何让C来调用C#。 先放代码:
private void LuaHelloWorld(IntPtr L)
{
    var functionIntptr = Marshal.GetFunctionPointerForDelegate(new MyCSFunction(HelloWorld));
    LuaDLL.lua_pushcclosure(L, functionIntptr, 0);
    LuaDLL.lua_pcall(L, 0, 0, 0);
}


private static void HelloWorld(IntPtr L)
{
    Debug.Log("helloworld");
}


public delegate void MyCSFunction(IntPtr L);
<hr/>Lua调用C#方法


[*]1.定义委托

public delegate void MyCSFunction(IntPtr L);
UnmanagedFunctionPointer就是字面意思,表面这个委托不受C#管理。在C++调用C#端函数也会用到这个特性。
CallConvention(调用约定):决定函数参数传送时入栈和出栈的顺序,由调用者还是被调用者把参数弹出栈,以及编译器用来识别函数名字的修饰约定。Cdecl表示由调用方把参数出栈,除了Cdecl外还有好几种。感兴趣的看这篇文章:调用约定(Calling convention)的介绍。

[*]2.定义委托对应的函数

private static void HelloWorld(IntPtr L)
{
    Debug.Log("helloworld");
}
MonoPInvokeCallbackAttribute 用来标记这个方法是由C或者C++来调用的

[*]3.将方法转为指针
var functionIntptr = Marshal.GetFunctionPointerForDelegate(new MyCSFunction(HelloWorld));
Marshal是System.Runtime.InteropServices命名空间下的一个类,作用是提供了一个方法集合,这些方法用于分配非托管内存、复制非托管内存块、将托管类型转换为非托管类型,此外还提供了在与非托管代码交互时使用的其他杂项方法。MSDN上有更详细的说明和案例,MSDN关于Marshal类

[*]4.将函数入栈
LuaDLL.lua_pushcclosure(L, functionIntptr, 0);
使用lua_pushcclosure将函数入栈,第三个参数的意思是入栈的函数是否需要关联除了luastate以外的参数,这里不需要其他参数,所以写0。tolua里的lua_pushcclosure这个函数和lua提供的接口有些不同。lua提供的是这样的,



我推测是lua要和C#交互,所以tolua对接口进行了修改。 现在我们做完了全部准备工作,调用lua_pcall就能在unity的控制台上看到helloworld了。
<hr/>lua_pcall传入错误处理函数

现在再来看加入错误处理函数的代码:
void Start()
{
   var L = LuaDLL.luaL_newstate();
   HandleError(L);
   LuaDLL.lua_close(L);
}
private void HandleError(IntPtr L)
{
   var functionIntptr = Marshal.GetFunctionPointerForDelegate(new MyCSFunction(ErrorHandle));
   LuaDLL.lua_pushcclosure(L, functionIntptr, 0);

   var errorFuncIndex = LuaDLL.lua_gettop(L);//获得栈顶位置
   var path = Application.dataPath + "/Examples/03_Function/03.lua";

   LuaDLL.luaL_loadfile(L, path);
   LuaDLL.lua_pcall(L, 0, 0, 0);

   LuaDLL.lua_getglobal(L, "addandsub");

   LuaDLL.lua_pushnumber(L, 10);
   LuaDLL.lua_pushstring(L, "error");

   LuaDLL.lua_pcall(L, 2, 2, errorFuncIndex);

   Debug.Log(LuaDLL.lua_tonumber(L, -1));
   Debug.Log(LuaDLL.lua_tonumber(L, -2));
}


private static void ErrorHandle(IntPtr L)
{
   if (LuaDLL.lua_isstring(L, -1) == 1)
   {
       Debug.LogError(LuaDLL.lua_tostring(L, -1));
   }
   else
   {
       Debug.Log("Not find error string");
   }
}
需要提一点的是,错误处理函数需要在要调用的方法入栈之前入栈。 我们故意传入一个字符串,这样字符串和数值进行加法和减法运算就会出错。 结果如下:



<hr/>大功告成,这就是本节全部内容。 github工程 对应的是Examples/03_Function
系列文章:
【Lua与C#交互①】Lua中的栈
【Lua与C#交互②】加载Lua文件
【Lua与C#交互③】方法调用和错误处理函数
qq群:891809847
页: [1]
查看完整版本: 【Lua与C#交互③】方法调用和错误处理函数