C#?NLua?Winform實現(xiàn)熱更新的項目實踐
一、概述
NLua 是一個用于 .NET 平臺的 Lua 腳本綁定庫。它允許在 C# 代碼中嵌入 Lua 腳本,并允許兩者之間進行交互。NLua 的主要特點包括:
- 輕量級:NLua 是一個輕量級的庫,易于集成到現(xiàn)有的 .NET 項目中。
- 動態(tài)類型:Lua 是動態(tài)類型的語言,這意味著變量的類型可以在運行時改變。
- 靈活的綁定:NLua 提供了靈活的綁定機制,使得 C# 和 Lua 之間的數(shù)據(jù)交互變得簡單。
- 豐富的 API:NLua 提供了豐富的 API,以便在 Lua 腳本中調(diào)用 .NET 的類和方法。
- 調(diào)試支持:NLua 支持調(diào)試功能,方便開發(fā)者在 Lua 腳本中設(shè)置斷點、單步執(zhí)行等操作。
- 社區(qū)支持:NLua 有一個活躍的社區(qū),為開發(fā)者提供了一個交流和尋求幫助的平臺。
使用 NLua,你可以在 .NET 應(yīng)用中輕松地嵌入 Lua 腳本,從而實現(xiàn)動態(tài)邏輯、配置管理、插件系統(tǒng)等功能。通過 NLua,你可以利用 Lua 的靈活性和易用性,同時保持 .NET 的強大功能和性能。
NLua 支持 UWP、Windows、Linux、Mac、iOS、Android 平臺。
NLua 源碼和示例 Github 地址
GitHub - NLua/NLua: Bridge between Lua and the .NET.
二、創(chuàng)建項目
創(chuàng)建一個 .NET Framework Winform 項目,這里我用的版本是 4.8.1,取名叫 NLuaDemo,在 NuGet 中安裝 NLua 包。

winform 界面設(shè)計如下:

為了先看看 NLua 到底有沒有效果,先做一個小案例,讓你先熟悉一下 NLua 框架,在后面的案例中,會有完整的熱更新方式展示。
需求:用 Lua 腳本來改變 Winform 界面中的抽獎人數(shù),數(shù)量隨意。
本章節(jié)非重點,可以直接看第三章節(jié)
1.添加一個類 PublicData ,這個類用來保存公共數(shù)據(jù)(在第三節(jié)的案例中,PublicData 這個類將不再使用)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
public class PublicData
{
public static NLuaDemo.Form1 Form1s { get; set; }
}我將 From1 保存在這里,主要是為了方便后面 Lua 調(diào)用,在實際工作中,最好還是不要這么寫。
2.將 Form1.Designer.cs 中的控件設(shè)置為 public,以便讓 Lua 能直接調(diào)用 winform 控件。

3.將 Program 類改為如下,在第三節(jié)的案例中,這里還是會恢復(fù)默認(rèn)的代碼,這里只是演示,這并不是一個好的寫法
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace NLuaDemo
{
internal static class Program
{
/// <summary>
/// 應(yīng)用程序的主入口點。
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
PublicData.Form1s = new Form1();
Application.Run(PublicData.Form1s);
}
}
}
4.Form1 代碼如下(第三節(jié)案例中,代碼會不一樣)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using NLua;
namespace NLuaDemo
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private Lua Luas = new Lua();
//lua 腳本的地址
private string LuaPath;
private void Form1_Load(object sender, EventArgs e)
{
Luas.State.Encoding = Encoding.UTF8;
LuaPath = $"{Application.StartupPath}\\main.lua";
LoadLua();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
Luas.Close();
}
//熱重載
private void Button_HotUpdate_Click(object sender, EventArgs e)
{
Luas.DoString("update()");
}
private void LoadLua()
{
if (File.Exists(LuaPath))
{
Luas.LoadCLRPackage();
Luas.DoFile(LuaPath);
Luas["form1s"] = PublicData.Form1s;
}
}
}
}
5.在項目的 Debug 目錄中,新建一個 main.lua 文件,加入下面代碼
form1s = {};
function update()
form1s.Label_RaffleNumber.Text = "抽獎人數(shù):100" ;
end完成了上面的工作,現(xiàn)在可以開始測試了

可以看到,Lua 腳本確實改變了 winform 界面的數(shù)據(jù)。
這里為什么不把邏輯直接寫在 main.lua 中呢?
form1s = {};
form1s.Label_RaffleNumber.Text = "抽獎人數(shù):100" ;
function update()
form1s.Label_RaffleNumber.Text = "抽獎人數(shù):100" ;
end因為運行后就會報錯:

由于 Lua 腳本在運行后,會執(zhí)行所有的代碼,但是我們定義的 Lua 全局變量此時還沒有賦值,直接運行當(dāng)然報錯了,于是我將熱更新內(nèi)容寫入到 lua 腳本中的 update 方法中,等 C# 這邊初始化完成后,調(diào)用 update 方法就不會有問題了。
另外還有一種方法,使用 LoadFile 方法,我試了一下,沒有效果,可能是我用法不對
LuaFunction luaFunction = Luas.LoadFile("C:\\test.lua");有興趣的朋友可以去試試,有好的建議歡迎留言評論。
三、實現(xiàn) Lua 熱更新
在上面的案例中,我們是把 Form1 賦值給了 lua 腳本中的 form1s 這個變量(也可以叫表單),其實還可以這么寫:
private void LoadLua()
{
if (File.Exists(LuaPath))
{
Luas.LoadCLRPackage();
Luas.DoFile(LuaPath);
Luas["this"] = this;
}
}但是這種用法在 Lua 腳本用會有錯誤,在 Visual Studio Code 中搭建好 Lua 開發(fā)環(huán)境,就會看到提示:未定義的全局變量 “this”

這個不用管它,lua 腳本只要在 C# 項目中運行不報錯就行了。
這里的寫法改變后,上一個 demo 中代碼也要改改了。
1.Program 類恢復(fù)默認(rèn)的寫法:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace NLuaDemo
{
internal static class Program
{
/// <summary>
/// 應(yīng)用程序的主入口點。
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//PublicData.Form1s = new Form1();
//Application.Run(PublicData.Form1s);
Application.Run(new Form1());
}
}
}2.PublicData 類可以刪掉,或者注釋所有的代碼,現(xiàn)在用不上了。
另一個,Lua 調(diào)用 C# 方法,要用英文的冒號去調(diào)用方法,而不是像 C# 一樣用點去調(diào)用,這個在 Unity3d 中 XLua 等熱更新框架是同樣的用法,具體用法可以參考下面 main.lua 代碼。
--import("System")
--import('System.Windows.Forms')
--import("System.Threading");
--form1s = {};
--獎品
prize = {"手機", "電腦", "耳機", "鼠標(biāo)", "鍵盤", "充電寶"}
--抽獎人
participant = {"張三", "李四", "老王", "狗蛋", "鐵剩"}
--熱重載
function update()
this:Print("抽獎人 長度是:" .. #participant)
this.Label_RaffleNumber.Text = "抽獎人數(shù):" .. #participant
this.Label_PrizesNum.Text = "獎品數(shù):" .. #prize
--獎品下拉框
this.ComboBox_Prize.Items:Clear()
for i,item in ipairs(prize) do
this.ComboBox_Prize.Items:Add(item)
end
this.ComboBox_Prize.SelectedIndex = 0
--抽獎人下拉框
this.ComboBox_LotteryGiver.Items:Clear();
for i,item in ipairs(participant) do
this.ComboBox_LotteryGiver.Items:Add(item);
end
this.ComboBox_LotteryGiver.SelectedIndex = 0
end
--抽獎按鈕
function raffle()
--隨機獎品
local prizeIndex = math.random(1, #prize)
local prize = prize[prizeIndex]
--隨機抽獎人
local participantIndex = math.random(1, #participant)
local participant = participant[participantIndex]
local content = "抽獎人:"..participant..",獲得獎品:"..prize
this:Print(content)
end注意上面 lua 腳本中的 Label_RaffleNumber,Label_PrizesNum,ComboBox_Prize 等關(guān)鍵字,這都是 winform 的控件,如果你用的不是我的源碼,winform 控件名和 lua 腳本中的也不一致,那么運行就會報錯:
錯誤:

當(dāng)前 demo 所有的控件名

winform 這個控制臺不知道怎么回事,lua 中打印用的 print 方法打印出來全是亂碼,但是 winform 控件使用 lua 腳本中的中文還是正常的。

后面我只能在 Form1 中添加一個 Print 方法,這樣打印才是正常的。
Form1代碼:
using NLua;
using System;
using System.IO;
using System.Text;
using System.Windows.Forms;
namespace NLuaDemo
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private Lua Luas = new Lua();
//lua 腳本的地址
private string LuaPath;
private void Form1_Load(object sender, EventArgs e)
{
Luas.State.Encoding = Encoding.UTF8;
LuaPath = $"{Application.StartupPath}\\main.lua";
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
Luas.Close();
}
//熱重載 點擊事件
private void Button_HotUpdate_Click(object sender, EventArgs e)
{
LoadLua();
Luas.DoString("update()");
}
//抽獎 點擊事件
private void Button_Raffle_Click(object sender, EventArgs e)
{
Luas.DoString("raffle()");
}
//加載 lua 腳本
private void LoadLua()
{
if (File.Exists(LuaPath))
{
Luas.LoadCLRPackage();
Luas.DoFile(LuaPath);
Luas["this"] = this;
}
}
//lua 打印用的
public void Print(string text)
{
Console.WriteLine(text);
}
}
}
運行后,點擊 “熱重載” 按鈕,就會看到獎品和抽獎人都有那些內(nèi)容,這些都是由 lua 進行賦值的。

點擊 “抽獎” 按鈕,就可以看到當(dāng)前的中獎人和獎品

既然是熱更新,那我在運行過程中改變代碼可以么?當(dāng)然可以!
我們把 lua 腳本中抽獎人和獎品表單刪除一部分,改完后記得保存,如下:
--獎品
prize = {"手機", "電腦", "耳機", "鼠標(biāo)"}
--抽獎人
participant = {"張三", "李四", "老王"}再次點擊 “熱重載” 按鈕,這時界面就發(fā)生了一些變化

抽獎人和獎品都少了

NLua 其實還有很多其他的用法,這里就沒一一展示了。
到此這篇關(guān)于C# NLua Winform實現(xiàn)熱更新的項目實踐的文章就介紹到這了,更多相關(guān)C# NLua Winform 熱更新內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
完成OSS.Http底層HttpClient重構(gòu)封裝 支持標(biāo)準(zhǔn)庫
OSS.Http項目對于.Net Standard標(biāo)準(zhǔn)庫的支持已經(jīng)遷移完畢,OSS開源系列兩個最底層的類庫已經(jīng)具備跨運行時支持的能力。本篇文章主要包含 1. HttpClient的介紹,2. 重構(gòu)的思路, 3. 容易遇到的問題。具有很好的參考價值,下面跟著小編一起來看下吧2017-02-02
淺談C#在網(wǎng)絡(luò)波動時防重復(fù)提交的方法
這篇文章主要介紹了淺談C#在網(wǎng)絡(luò)波動時防重復(fù)提交的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
C#利用Refit實現(xiàn)JWT自動續(xù)期詳解
Refit?是一個受到Square的Retrofit庫(Java)啟發(fā)的自動類型安全REST庫,這篇文章主要為大家介紹了C#如何利用Refit實現(xiàn)JWT自動續(xù)期,感興趣的可以了解下2023-08-08

