经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 软件/图像 » unity » 查看文章
原生实现C#与Lua相互调用方法(Unity3D可用)
来源:jb51  时间:2022/4/18 10:11:10  对本文有异议

引言

本篇简单介绍如何在C#中执行Lua脚本,传递数据到Lua中使用,以及Lua中调用C#导出的方法等。在Unity中开发测试,并打IL2CPP的Android包在模拟器上运行通过。Lua版本使用的是Lua5.1.5。

一、编译Lua动态链接库

1. 编译Windows下使用的DLL文件

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

2. 编译Android下使用的SO文件

通过NDK编译Android需要的so动态库,因此需要手写Application.mk和Android.mk两个mk文件,下面是我使用的两个文件的内容,创建放在上面VS的工程里面即可,路径是在lua源码src的上一层目录。

  1. # Application.mk
  2. APP_PLATFORM = android-23
  3. APP_ABI := armeabi-v7a arm64-v8a
  4. APP_STL := stlport_shared
  1. # Android.mk
  2. LOCAL_PATH := $(call my-dir)
  3. include $(CLEAR_VARS)
  4. MY_FILES_PATH := $(LOCAL_PATH)/src
  5. MY_FILES_SUFFIX := %.c
  6. MY_UN_INCLUDE := %lua.c %luac.c
  7. # 递归遍历目录下的所有的文件
  8. rwildcard=$(wildcard $1$2) $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2))
  9. # 获取相应的源文件
  10. MY_ALL_FILES := $(foreach src_path,$(MY_FILES_PATH), $(call rwildcard,$(src_path),*.*) )
  11. MY_SRC_LIST := $(filter $(MY_FILES_SUFFIX),$(MY_ALL_FILES))
  12. MY_SRC_LIST := $(filter-out $(MY_UN_INCLUDE),$(MY_SRC_LIST))
  13. MY_SRC_LIST := $(MY_SRC_LIST:$(LOCAL_PATH)/%=%)
  14. LOCAL_SRC_FILES = $(MY_SRC_LIST)
  15. #打印编译信息
  16. $(warning 'src_list='$(LOCAL_SRC_FILES))
  17. LOCAL_MODULE := CSharpLua
  18. LOCAL_LDLIBS += -ldl
  19. LOCAL_CFLAGS := $(L_CFLGAS)
  20. include $(BUILD_SHARED_LIBRARY)

将上面的mk文件放置完成后,打开CMD命令行,执行ndk编译。由于并不是在Android的jni项目目录,因此执行命令会有所不同,可以使用下面的命令执行生成,等待ndk执行完成后就生成了需要的so库。

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

二、编写C#使用的API

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

在Unity项目Assets目录里面创建Plugins目录,用于存放不同平台的DLL库。Windows需要的DLL存放的目录为Assets/Plugins/x86和Assets/Plugins/x86_64;Android需要的SO文件存放的目录为Assets/Android/[libs/arm64-v8a]括号里面的目录其实就是上面NDK编译后生成的路径。

2. 编写C#的API[LuaDll.cs]

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

  1. [DllImport("CSharpLua", EntryPoint = "luaL_newstate")]
  2. public static extern IntPtr luaL_newstate();
  3. [DllImport("CSharpLua", EntryPoint = "luaL_openlibs")]
  4. public static extern void luaL_openlibs(IntPtr L);
  5. [DllImport("CSharpLua", EntryPoint = "luaL_loadbuffer")]
  6. public static extern int luaL_loadbuffer(IntPtr L, byte[] buff, uint size, string name);
  7. [DllImport("CSharpLua", EntryPoint = "lua_call")]
  8. public static extern void lua_call(IntPtr L, int nargs, int nresults);
  9. [DllImport("CSharpLua", EntryPoint = "lua_pcall")]
  10. public static extern int lua_pcall(IntPtr L, int nargs, int nresults, int errfunc); 

3.需要注意的几个地方

1. 返回char*时,不可直接使用string替换,否则调用会导致崩溃,因此需要像下面代码展示的那样进行一下转换才可以使用。

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

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

  1. public delegate int LuaFunction(IntPtr L);
  2. [DllImport("CSharpLua", EntryPoint = "lua_pushcclosure")]
  3. public static extern void lua_pushcclosure(IntPtr L, LuaFunction func, int idx);
  4. public static void lua_pushcfunction(IntPtr L, LuaFunction func)
  5. {
  6. lua_pushcclosure(L, func, 0);
  7. }

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

  1. #define lua_setglobal(L,s) lua_setfield(L, LUA_GLOBALSINDEX, (s))
  2. #define lua_getglobal(L,s) lua_getfield(L, LUA_GLOBALSINDEX, (s))
  1. [DllImport("CSharpLua", EntryPoint = "lua_getfield")]
  2. public static extern void lua_getfield(IntPtr L, int idx, string s);
  3. public static void lua_getglobal(IntPtr L, string s)
  4. {
  5. lua_getfield(L, LUA_GLOBALSINDEX, s);
  6. }
  7. [DllImport("CSharpLua", EntryPoint = "lua_setfield")]
  8. public static extern void lua_setfield(IntPtr L, int idx, string s);
  9. public static void lua_setglobal(IntPtr L, string s)
  10. {
  11. lua_setfield(L, LUA_GLOBALSINDEX, s);
  12. }

4. 如需要将C#的类实例对象即userdata传递给lua,需要在C#中转换成IntPtr后传递,Lua返回的则需要通过IntPtr转换回C#的实例对象。

  1. [DllImport("CSharpLua", EntryPoint = "lua_pushlightuserdata")]
  2. public static extern void _lua_pushlightuserdata(IntPtr L, IntPtr p);
  3. public static void lua_pushlightuserdata<T>(IntPtr L, T p)
  4. {
  5. IntPtr obj = Marshal.GetIUnknownForObject(p);
  6. _lua_pushlightuserdata(L, obj);
  7. }
  8. [DllImport("CSharpLua", EntryPoint = "lua_touserdata")]
  9. public static extern IntPtr _lua_touserdata(IntPtr L, int idx);
  10. public static T lua_touserdata<T>(IntPtr L, int idx)
  11. {
  12. IntPtr p = _lua_touserdata(L, idx);
  13. return (T)Marshal.GetObjectForIUnknown(p);
  14. }

三、C#与Lua的相互调用举例

1. C#中创建Lua环境

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

2. 加载Lua代码并执行,调用Lua的函数及向Lua传递参数。

  1. var data = Resources.Load<TextAsset>(lua_file);
  2. int rc = LuaDll.luaL_loadbuffer(L, data.bytes, (uint)data.bytes.Length, lua_file);
  3. rc = LuaDll.lua_pcall(L, 0, 0, 0)
  4. LuaDll.lua_getglobal(L, "main");
  5. // 传递参数
  6. LuaDll.lua_pushinteger(L, 3333);
  7. LuaDll.lua_pushnumber(L, 3.3);
  8. // 执行main方法
  9. int i = LuaDll.lua_pcall(L, 2, 0, 0);

3. 将C#函数提供给Lua使用,需要使用静态方法参考上面LuaFunction的定义。

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

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

  1. static int FindAndBind(IntPtr L)
  2. {
  3. GameObject go = LuaDll.lua_touserdata<GameObject>(L, 1);
  4. string path = LuaDll.lua_tostring(L, 2);
  5. // 这里将lua的函数放到LUA_REGISTRYINDEX上
  6. int idx = LuaDll.luaL_refEx(L);
  7. Transform t = go.transform.Find(path);
  8. Button btn = t.GetComponent<Button>();
  9. btn.onClick.AddListener(delegate() {
  10. // 从LUA_REGISTRYINDEX栈获取lua的函数进行执行。
  11. LuaDll.lua_rawgeti(L, LuaDll.LUA_REGISTRYINDEX, idx);
  12. LuaDll.lua_pcall(L, 0, 0, 0);
  13. });
  14. return 0;
  15. }

四、总结

总体来说交互调用还是比较的简单方便,跟使用C/C++与Lua交互差不多。我仅仅简单使用Lua源码进行编译动态库使用,可以方便的替换各个版本的lua进行使用。C#导出方法给Lua使用也相对简单,但是Unity中使用Lua的时候,不可能每个类例如GameObject、Transform等都手动写导出的代码给Lua使用。这块就可以去看tolua、xlua的实现,需要考虑很多东西。

到此这篇关于原生实现C#与Lua相互调用方法的文章就介绍到这了,更多相关C#与Lua相互调用内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号