原生实现C#和Lua相互调用-Unity3D可用

简介

本文简单介绍如何在C#中执行Lua脚本,将数据传递给Lua使用,在Lua中调用C#导出的方法。在 Unity 中开发测试并在模拟器上运行打包的包。 Lua 版本使用 Lua5.1.5.

一、编译Lua动态链接库

1.编译下使用的DLL文件

用于创建一个空的动态链接库,删除里面默认创建的几个文件(如果要自定义扩展,可以保留),然后复制Lua的源码添加到工程项目中编译宏需要配置和 NGS。然后就可以编译x86和x64的DLL动态库了,整体步骤简单易操作。

2. 用于编译的 SO 文件

so动态库需要通过NDK编译,所以需要手写两个mk文件,.mk和.mk。以下是我使用的两个文件的内容,可以创建并放在上面的VS项目中。路径为lua源码src的上层目录。

# Application.mk
APP_PLATFORM = android-23
APP_ABI := armeabi-v7a arm64-v8a
APP_STL := stlport_shared

# Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
MY_FILES_PATH  :=  $(LOCAL_PATH)/src
MY_FILES_SUFFIX := %.c
MY_UN_INCLUDE := %lua.c %luac.c
# 递归遍历目录下的所有的文件
rwildcard=$(wildcard $1$2) $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2))
# 获取相应的源文件
MY_ALL_FILES := $(foreach src_path,$(MY_FILES_PATH), $(call rwildcard,$(src_path),*.*) ) 
MY_SRC_LIST  := $(filter $(MY_FILES_SUFFIX),$(MY_ALL_FILES)) 
MY_SRC_LIST  := $(filter-out $(MY_UN_INCLUDE),$(MY_SRC_LIST)) 
MY_SRC_LIST  := $(MY_SRC_LIST:$(LOCAL_PATH)/%=%)
LOCAL_SRC_FILES = $(MY_SRC_LIST)
#打印编译信息
$(warning 'src_list='$(LOCAL_SRC_FILES))
LOCAL_MODULE    := CSharpLua
LOCAL_LDLIBS += -ldl
LOCAL_CFLAGS := $(L_CFLGAS)
include $(BUILD_SHARED_LIBRARY)

放置上面的mk文件后,打开CMD命令行,执行ndk编译。由于不在jni项目目录下,所以执行命令会有所不同。您可以使用以下命令来执行生成。 ndk执行完成后,会生成需要的so库。

ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk NDK_APPLICATION_MK=./Application.mk

二、编写C#的API

1. Unity 中动态链接库的存放位置。

在Unity工程目录下创建一个目录,用于存放不同平台的DLL库。所需的DLL存储目录是//x86和//;需要的SO文件存放在目录 //[libs/arm64-v8a] 括号内的目录其实就是上面NDK编译生成的路径。

2. 编写 C# API[.cs]

动态库中的大部分接口都可以直接使用如下方式,用来表示 * ,传入参数 char* 可以使用 byte[] 或者,但是会有一点区别。

[DllImport("CSharpLua", EntryPoint = "luaL_newstate")]
public static extern IntPtr luaL_newstate();
[DllImport("CSharpLua", EntryPoint = "luaL_openlibs")]
public static extern void luaL_openlibs(IntPtr L);
[DllImport("CSharpLua", EntryPoint = "luaL_loadbuffer")]
public static extern int luaL_loadbuffer(IntPtr L, byte[] buff, uint size, string name);
[DllImport("CSharpLua", EntryPoint = "lua_call")]
public static extern void lua_call(IntPtr L, int nargs, int nresults);
[DllImport("CSharpLua", EntryPoint = "lua_pcall")]
public static extern int lua_pcall(IntPtr L, int nargs, int nresults, int errfunc);

3.注意几点

1.返回char*的时候不能直接用,否则调用会导致crash,所以需要按照如下代码进行转换才能使用。

[DllImport("CSharpLua", EntryPoint = "lua_tolstring")]
private static extern IntPtr _lua_tolstring(IntPtr L, int idx, ref uint size);
public static string lua_tolstring(IntPtr L, int idx, ref uint size)
{
    IntPtr buffer = _lua_tolstring(L, idx, ref size);
    return Marshal.PtrToStringAnsi(buffer);
}

2. C#函数传递给Lua使用时,需要使用委托类型。

public delegate int LuaFunction(IntPtr L);
[DllImport("CSharpLua", EntryPoint = "lua_pushcclosure")]
public static extern void lua_pushcclosure(IntPtr L, LuaFunction func, int idx);
public static void lua_pushcfunction(IntPtr L, LuaFunction func)
{
   lua_pushcclosure(L, func, 0);
}

3. lua源码中定义的宏代码不能使用,会提示找不到。它需要在 C# 中手动实现。例如下面显示的 2 个宏。

#define lua_setglobal(L,s)  lua_setfield(L, LUA_GLOBALSINDEX, (s))
#define lua_getglobal(L,s)  lua_getfield(L, LUA_GLOBALSINDEX, (s))

[DllImport("CSharpLua", EntryPoint = "lua_getfield")]
public static extern void lua_getfield(IntPtr L, int idx, string s);
public static void lua_getglobal(IntPtr L, string s)
{
   lua_getfield(L, LUA_GLOBALSINDEX, s);
}
[DllImport("CSharpLua", EntryPoint = "lua_setfield")]
public static extern void lua_setfield(IntPtr L, int idx, string s);
public static void lua_setglobal(IntPtr L, string s)
{
   lua_setfield(L, LUA_GLOBALSINDEX, s);
}

4.如果要将C#类实例对象传递给lua,需要在C#中转换后,再转换回C#的实例对象。

[DllImport("CSharpLua", EntryPoint = "lua_pushlightuserdata")]
public static extern void _lua_pushlightuserdata(IntPtr L, IntPtr p);
public static void lua_pushlightuserdata(IntPtr L, T p)
{
    IntPtr obj = Marshal.GetIUnknownForObject(p);
    _lua_pushlightuserdata(L, obj);
}
[DllImport("CSharpLua", EntryPoint = "lua_touserdata")]
public static extern IntPtr _lua_touserdata(IntPtr L, int idx);
public static T lua_touserdata(IntPtr L, int idx)
{
   IntPtr p = _lua_touserdata(L, idx);
   return (T)Marshal.GetObjectForIUnknown(p);
}

三、C#与Lua相互调用示例

1.在 C# 中创建 Lua 环境

p>

IntPtr L = LuaDll.luaL_newstate();
LuaDll.luaL_openlibs(L);

2. 加载 Lua 代码并执行,调用 Lua 函数并将参数传递给 Lua。

var data = Resources.Load(lua_file);
int rc = LuaDll.luaL_loadbuffer(L, data.bytes, (uint)data.bytes.Length, lua_file);
rc = LuaDll.lua_pcall(L, 0, 0, 0)
LuaDll.lua_getglobal(L, "main");
// 传递参数
LuaDll.lua_pushinteger(L, 3333);
LuaDll.lua_pushnumber(L, 3.3);
// 执行main方法
int i = LuaDll.lua_pcall(L, 2, 0, 0);

3. 为 Lua 提供 C# 函数,需要使用静态方法来引用上面的定义。

LuaDll.lua_pushcfunction(L, LuaPrint);
LuaDll.lua_setglobal(L, "print");
[MonoPInvokeCallback]   // 这个主要是在Android上需要。
static int LuaPrint(IntPtr L)
{
  Debug.Log(".....");
  return 0;
}

4. Lua 代码调用 C# 方法并提供回调,由 C# 函数调用。

static int FindAndBind(IntPtr L)
{
   GameObject go = LuaDll.lua_touserdata(L, 1);
   string path = LuaDll.lua_tostring(L, 2);
   // 这里将lua的函数放到LUA_REGISTRYINDEX上
   int idx = LuaDll.luaL_refEx(L);
   Transform t = go.transform.Find(path);
   Button btn = t.GetComponent

四、总结

总体来说,交互调用还是比较简单方便的,对比使用C/C++和Lua交互差不多。我简单的使用Lua源码编译动态库,可以方便的替换各种版本的lua使用。对于Lua使用C#的导出方式也比较简单,但是在Unity中使用Lua的时候,是不可能手动编写每个类的导出代码给Lua使用的。本节可以看一下tolua和xlua的实现,需要考虑的东西很多。

© 版权声明
THE END
喜欢就支持一下吧
点赞9赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容