简介
本文简单介绍如何在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的实现,需要考虑的东西很多。
暂无评论内容