原生实现C#与Lua相互调用方法(Unity3D可用)
本文详细介绍如何基于原生Lua 5.1.5源码,在Unity环境中通过手动编译动态链接库和编写C#封装层,实现C#与Lua脚本的相互调用。内容包含NDK编译安卓SO库的配置代码、C#使用DLLImport处理数据类型转换的注意事项,以及参数传递、函数回调的实际代码演示。
一、编译Lua动态链接库
第1步:编译Windows下使用的DLL文件
在Visual Studio(如VS2015)中创建一个空的动态链接库项目。将Lua 5.1.5的源码拷贝至项目中并添加引用。若需自定义扩展可保留默认文件,否则可删除。编译前需在项目属性中配置以下宏定义:
LUA_BUILD_AS_DLL_CRT_SECURE_NO_WARNINGS
完成配置后,即可分别编译x86和x64平台的DLL动态库。
第2步:编译Android下使用的SO文件
Android平台需要使用NDK进行编译。需要创建 Application.mk 和 Android.mk 两个配置文件,放置在Lua源码 src 目录的上一级目录中。
1. Application.mk 配置内容:
# Application.mk APP_PLATFORM = android-23 APP_ABI := armeabi-v7a arm64-v8a APP_STL := stlport_shared
2. Android.mk 配置内容:
# 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)
3. 执行编译命令:
打开CMD命令行,进入放置mk文件的目录,执行以下NDK编译命令:
ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk NDK_APPLICATION_MK=./Application.mk
二、编写C#使用的API
第1步:动态链接库在Unity中的存放位置
在Unity项目的Assets目录下创建 Plugins 文件夹用于存放不同平台的DLL/SO库。路径结构如下:
- Windows DLL:
Assets/Plugins/x86和Assets/Plugins/x86_64 - Android SO:
Assets/Android/libs/arm64-v8a(对应NDK编译生成的路径)
第2步:编写C# API接口 [LuaDll.cs]
大部分Lua接口的动态库调用可直接使用 DllImport。注意传入参数中 char* 类型的处理方式。以下为常用函数的C#声明示例:
[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* 的处理
不可直接使用 string 接收返回值,否则会导致崩溃。需要通过 IntPtr 中转并使用 Marshal.PtrToStringAnsi 转换:
[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使用(委托)
导出给Lua调用的C#函数必须使用 delegate 类型:
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源码中的宏(如 lua_setglobal)无法直接在C#中使用,需手动封装:
[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. UserData 对象传递
C#类实例对象传递给Lua时需转换为 IntPtr,Lua返回时再通过 IntPtr 转换回C#对象:
[DllImport("CSharpLua", EntryPoint = "lua_pushlightuserdata")]
public static extern void _lua_pushlightuserdata(IntPtr L, IntPtr p);
public static void lua_pushlightuserdata<T>(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<T>(IntPtr L, int idx)
{
IntPtr p = _lua_touserdata(L, idx);
return (T)Marshal.GetObjectForIUnknown(p);
}
三、C#与Lua的相互调用举例
第1步:C#中创建Lua环境
IntPtr L = LuaDll.luaL_newstate(); LuaDll.luaL_openlibs(L);
第2步:加载Lua代码并执行,向Lua传递参数
加载资源中的Lua文本并执行函数:
var data = Resources.Load<TextAsset>(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步:将C#函数提供给Lua使用
需使用静态方法,并在Android环境下建议加上 [MonoPInvokeCallback] 标记:
LuaDll.lua_pushcfunction(L, LuaPrint);
LuaDll.lua_setglobal(L, "print");
[MonoPInvokeCallback]
static int LuaPrint(IntPtr L)
{
Debug.Log(".....");
return 0;
}
第4步:Lua代码调用C#方法并提供回调
示例展示了如何将Lua注册的函数保存至 LUA_REGISTRYINDEX,并在C#事件触发时回调执行:
static int FindAndBind(IntPtr L)
{
GameObject go = LuaDll.lua_touserdata<GameObject>(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<Button>();
btn.onClick.AddListener(delegate() {
// 从LUA_REGISTRYINDEX栈获取lua的函数进行执行
LuaDll.lua_rawgeti(L, LuaDll.LUA_REGISTRYINDEX, idx);
LuaDll.lua_pcall(L, 0, 0, 0);
});
return 0;
}
四、总结
通过上述步骤,可以较为轻松地实现C#与Lua的原生交互。该方式灵活度高,可以直接替换不同版本的Lua源码进行编译。但对于大规模项目而言,由于Unity中的每个组件(如 GameObject、Transform 等)都需要手动编写导出的接口代码,工作量巨大且容易出错。对于更复杂的生产环境,建议参考tolua或xlua等成熟插件的实现思路,它们考虑了更多底层细节和易用性封装。
以上就是原生实现C#与Lua相互调用方法的详细内容,更多关于C#与Lua交互的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













