網站首頁 編程語言 正文
引言
本篇簡單介紹如何在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的上一層目錄。
# 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編譯。由于并不是在Android的jni項目目錄,因此執行命令會有所不同,可以使用下面的命令執行生成,等待ndk執行完成后就生成了需要的so庫。
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,但是會有一點點區別。
[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替換,否則調用會導致崩潰,因此需要像下面代碼展示的那樣進行一下轉換才可以使用。
[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使用時,需要使用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源碼中定義的宏代碼是無法使用的,會提示找不到,需要在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#的類實例對象即userdata傳遞給lua,需要在C#中轉換成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使用,需要使用靜態方法參考上面LuaFunction的定義。
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<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/C++與Lua交互差不多。我僅僅簡單使用Lua源碼進行編譯動態庫使用,可以方便的替換各個版本的lua進行使用。C#導出方法給Lua使用也相對簡單,但是Unity中使用Lua的時候,不可能每個類例如GameObject、Transform等都手動寫導出的代碼給Lua使用。這塊就可以去看tolua、xlua的實現,需要考慮很多東西。
原文鏈接:https://www.cnblogs.com/jeffxun/p/16155443.html
相關推薦
- 2022-08-16 C#工程建立后修改工程文件名與命名空間操作_C#教程
- 2023-01-31 React受控組件與非受控組件深入講解_React
- 2023-06-05 Go實現共享庫的方法_Golang
- 2022-07-18 Nio中Buffer的Scattering和Gathering
- 2022-07-06 C#數據適配器DataAdapter_C#教程
- 2022-06-19 Rainbond云原生部署SpringCloud應用架構實踐_云其它
- 2022-03-16 Linux系統中日志詳細介紹_Linux
- 2022-05-16 C語言中有哪些字符處理函數你知道嗎_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支