Mono 運行 C# 程式 – 【Linux 學習筆記】

最近因為工作的需要,要把一支 C# 程式運行在 Linux (Ubuntu) 平臺,所以接觸了 Mono,同時因為相容性問題必須修改dll,而使用 dnSpy軟體。

Linux Mono

Mono 官網
Mono 官網

Mono 是 Microsoft 支持的 C# 及 CLR 開源專案,它基於 ECMA 標準實現 Microsoft’s .NET Framework 的跨平台運行。需要安裝這個開源環境,才能在 Linux 上運行 C# 程式。

安裝步驟:

$ sudo apt install gnupg ca-certificates 
$ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF 
$ echo "deb https://download.mono-project.com/repo/ubuntu stable-focal main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list 
$ sudo apt update

官方安裝指引

安裝完成後,執行 C# 執行檔的指令:

$ sudo mono MyExe.exe

警告:libpng warning

因為這支 C# 程式有 UI 介面,使用了大量圖片,在 Linux 運行時,曾遇到下面警告:

"libpng warning: iCCP: known incorrect sRGB profile"

這是因為圖片嵌入的屬性,無法通過 libpng-1.6 以上版本的 ICC profiles 檢查,這個警告可以忽略。如果要消除,則要從原圖像中去掉 ICCP chunk,另存成新圖片。參考 Stackoverflow 上的解法:

$ pngcrush -ow -rem allb -reduce xxx.png

參考來源:

https://stackoverflow.com/questions/22745076/libpng-warning-iccp-known-incorrect-srgb-profile

System.EntryPointNotFoundException

後續執行時,又遇到以下例外訊息:

"System.EntryPointNotFoundException: GetPrivateProfileString assembly"

這是因為這支程式使用 Windows kernel32.dll 的函式庫,處理 INI 文件,從而取得一些程式運行的相關設定參數。然而 Linux 不支援 Windows kernel32.dll 。這時只好改用其他檔案存取方式,或是把參數 hard coding 在程式內。

DLL 編輯:dnSpy

運行過程中,遇到一些異常,是發生在 C# 程式的 DLL 檔案內,這時可以使用免安裝的 dnSpy 軟體,連結程式執行檔 exe 與 dll,對 dll 內容進行編輯與更新。簡易步驟:

  1. 開啟 dnSpy,將 MyExe.exe 拖曳到左側 Assembly Explorer
  2. 在 Assembly Explorer 展開 MyExe.exe,找到 Main(),dnSpy 會自動連結相關的 dll
  3. 找到你要修改的 dll 內容位置,右鍵選擇 Edit Method,就可修改程式碼並重新編譯
  4. 在上方工具列 File 清單內,選取 Save Module 儲存修改後的 DLL

下面這篇網誌有清楚的教學:

https://blog.darkthread.net/blog/dnspy/

dnSpy 官方 Git:

https://github.com/dnSpy/dnSpy

Mono 對 Series port 的支持

這支程式執行時,會從 USB COM port 讀取外接 Sensor 的資料,前面在 dll 內遇到的問題,就是 Serial port 在 Windows 跟 Linux 的命名不同,例如前者是 COM1,後者是 /dev/ttyUSB1 ,因此需要做對應的修改。

此外,Mono 並不支援事件型 (Event notification or Interrupt) 的 serial port 資料接收:

Mono官方原文:

Mono對event notification的官方說明 -
官方說明

只能使用 Thread + Read 組成 polling 的方式,讀取 Serial data:

new Thread(new ThreadStart(this.ThreadProc))
{
  IsBackground = true
}.Start();

public void ThreadProc()
{
  ...
  for(;;)
  {
    MyserialPort.Read()
  }
  Thread.Sleep(100);
  ...
}

SerialPort.Read 官方教學:

https://learn.microsoft.com/zh-tw/dotnet/api/system.io.ports.serialport.read?view=dotnet-plat-ext-6.0

https://mapostech.com/socket/

下一篇: