

AppDomain.AssemblyResolve 內嵌 DLL 成單一 EXE 檔注意事項
source link: https://blog.darkthread.net/blog/netfx-single-file-exe/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

開發小工具時,把相依的 DLL 包進單一 EXE 是很有用的技巧,如此使用者只需複製單一檔案到特定目錄或桌面便能執行,省去跑安裝程式或建立資料夾放入 EXE + DLL 的麻煩。
要達成這個理想,早期我是用 ILMerge 實現(參考:Visual Studio編譯小技巧:工具程式一檔搞定),但實務上遇到不少問題,例如:不支援 WPF、可能出現同名型別衝突、遇到某些 DLL 會失靈... 等等。後期我偏好另一種做法,編譯時自動內嵌參照 DLL + AppDomain.CurrentDomain.AssemblyResolve,遇到的問題少很多,但仍有些注意事項。
最容易犯的錯是 - 在 Program.cs Main() 執行之前就參考到外部 DLL。用以下這個引用 Newtonsoft.Json.dll 做 Json/XML 轉換的主控台程式當案例:
using Newtonsoft.Json;
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
namespace Json2Xml
{
class Program
{
static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
try
{
string json = string.Empty;
if (Console.IsInputRedirected)
{
using (var sr = new StreamReader(Console.OpenStandardInput()))
{
json = sr.ReadToEnd();
}
}
else if (args.Any())
{
json = File.ReadAllText(args[0]);
}
else
{
Console.WriteLine("Syntax: Json2Xml json-file-name or use pipeline");
return;
}
var xml = JsonConvert.DeserializeXNode(json).ToString();
Console.WriteLine(xml);
}
catch (Exception ex)
{
Console.WriteLine($"ERROR - {ex.Message}");
}
}
private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
{
Assembly executingAssembly = Assembly.GetExecutingAssembly();
AssemblyName assemblyName = new AssemblyName(args.Name);
string path = assemblyName.Name + ".dll";
if (assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture) == false)
{
path = String.Format(@"{0}\{1}", assemblyName.CultureInfo, path);
}
using (Stream stream = executingAssembly.GetManifestResourceStream(path))
{
if (stream == null)
return null;
byte[] assemblyRawBytes = new byte[stream.Length];
stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
return Assembly.Load(assemblyRawBytes);
}
}
}
}
雖已成功將 Newtonsoft.Json.dll 併入 Json2Xml.exe,也宣告了 AppDomain.CurrentDomain.AssemblyResolve,但執行時仍會出現找不到 Json.NET 的錯誤訊息:
但只需稍做手腳,將 JsonConvert.DeserializeXNode() 移入獨立方法就可避開問題:
static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
try
{
//... 略 ...
var xml = ConvJsonToXml(json);
Console.WriteLine(xml);
}
catch (Exception ex)
{
Console.WriteLine($"ERROR - {ex.Message}");
}
}
static string ConvJsonToXml(string json)
{
return JsonConvert.DeserializeXNode(json).ToString();
}
這樣就成功了! (這裡還順便練習了 Console Application 從 Pipeline 接資料的技巧)
另外還有一種狀況,如下例:
static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
try
{
//... 略 ...
Console.WriteLine(JsonHelper.Parse(json).ToString());
}
catch (Exception ex)
{
Console.WriteLine($"ERROR - {ex.Message}");
}
}
程式看起來沒有引用 Json.NET,但一樣會發生找不到 未處理的例外狀況: System.IO.FileNotFoundException: 無法載入檔案或組件 'Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' 或其相依性的其中之一。 系統找不到指定的檔案。
錯誤,原因是 JsonHelper.Parse() 的回傳型別是 Json.NET 的 JObject 型別:
using Newtonsoft.Json.Linq;
namespace Json2Xml
{
public class JsonHelper
{
public static JObject Parse(string json)
{
return JObject.Parse(json);
}
}
}
依我的理解:由於要等 Main() 執行完,程式才具備解析所內嵌第三方程式庫的能力。若 Main() 本身涉及第三方程式庫,將導致 DLL 解析動作提前到 Main() 執行前的即時編譯過程,因而出錯。依此原理,上述程式只需稍微調整一下即可避開問題:
static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
try
{
//... 略 ...
Console.WriteLine(ExractAsSomeFunc(json));
}
catch (Exception ex)
{
Console.WriteLine($"ERROR - {ex.Message}");
}
}
static string ExractAsSomeFunc(string json)
{
return JsonHelper.Parse(json).ToString();
}
過去沒搞清楚前,遇到都內嵌了還抱怨找不到 DLL 的狀況,我總是一頭霧水,歷經這番梳理,未來就知道怎麼做了。
Recommend
-
5
Go 語言 Select Multiple Channel 注意事項
-
15
網頁內嵌 JSON 注意事項 2021-01-07 08:29 PM 0 2,468 在輸出網頁時內嵌 JSON 轉成 JavaScript 物件是我愛用的手法,這點之前有
-
5
February 6, 2021 ...
-
6
使用新版 AzCopy v10 的注意事項與使用教學最近在使用 AzCopy 的時候,發現怎麼跟以前差這麼多,這才發現原來最近出現了大改版,命令列的參數都跟以往不一樣了。這個新版改變蠻大的,我覺得對一個用過舊版的人來說,改用新版的第一印象真的不太好,研究的過程中...
-
10
PowerShell ConvertTo-Json 複雜巢狀結構注意事項-黑暗執行緒 ConvertTo-Json/ConvertFrom-Json 是少數我用不順手的 PowerShell 指令,過去就踩過幾次雷(ConvertFrom-JSON 解析大型...
-
5
最近有專案需要在報表上顯示 Code 128 條碼,但負責這個功能的開發人員會遇到特定文字產生的條碼無法被掃描/辨識的問題。由於我之前沒有親手處理過 Code 128 條碼,所以藉此研究了一下 Code 128 條碼的產...
-
7
.NET 6 圖形處理跨平台注意事項 2021-12-06 09:09 PM 0 999 .NET Core/.NET 6 號稱跨平台,但實際推進到 Linux,有些眉角遇上才會知道。 繼...
-
17
ASP.NET Core 部署 Linux 搭配 Nginx 注意事項一則-黑暗執行緒 幫忙看了一個茶包:建立 ASP.NET Core 6.0 專案部署到 CentOS 平台,搭配 Nginx Reverse Proxy 對外服務,卻怎麼...
-
8
閱讀筆記: 「升級 Kubernetes 1.22 的注意事項」 Posted on 2022-04-11 Views: 38 「升級 Kubernetes 1.22 的注意事項」 標題: 「升級 Kubernetes 1.22 的注意事項」類別: kubernetes
-
10
在 Go 語言中,如何高效的處理字串相加,由於字串 (string) 是不可變的,所以將很多字串拼接起來...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK