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

上篇文章简单说到了 lua_pcall 这个方法,不过没有考虑到参数和返回值的情况,本节重点讲这个函数,还会讲如何把C#端的方法放在lua的栈上以供lua调用


先上代码:

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


函数、参数、返回值的入栈顺序

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

  • 先将方法入栈
  • 再将方法需要的参数按顺序入栈
  • 调用 lua_pcall
  • 此时lua会把返回值入栈

上面的东西很简单,接下来讲更重要的东西,如何把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);
}

[MonoPInvokeCallbackAttribute(typeof(MyCSFunction))]
private static void HelloWorld(IntPtr L)
{
    
    
    Debug.Log("helloworld");
}

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void MyCSFunction(IntPtr L);

Lua调用C#方法

  • 1.定义委托
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void MyCSFunction(IntPtr L);

UnmanagedFunctionPointer就是字面意思,表面这个委托不受C#管理。在C++调用C#端函数也会用到这个特性。

CallConvention(调用约定):决定函数参数传送时入栈和出栈的顺序,由调用者还是被调用者把参数弹出栈,以及编译器用来识别函数名字的修饰约定。Cdecl表示由调用方把参数出栈,除了Cdecl外还有好几种。感兴趣的看这篇文章:调用约定(Calling convention)的介绍。

  • 2.定义委托对应的函数
[MonoPInvokeCallbackAttribute(typeof(MyCSFunction))]
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了。


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));
}

[MonoPInvokeCallbackAttribute(typeof(MyCSFunction))]
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");
   }
}

需要提一点的是,错误处理函数需要在要调用的方法入栈之前入栈
我们故意传入一个字符串,这样字符串和数值进行加法和减法运算就会出错。
结果如下:
在这里插入图片描述


大功告成,这就是本节全部内容。
github工程
对应的是Examples/03_Function

系列文章:
【Lua与C#交互①】Lua中的栈
【Lua与C#交互②】加载Lua文件
【Lua与C#交互③】方法调用和错误处理函数

qq群:891809847

猜你喜欢

转载自blog.csdn.net/j756915370/article/details/105906839