CCW 封裝 .NET 元件範例
6 |
都 2021 年了,還需要把 .NET 元件包成 COM+ 給老 ASP/VBScript/IE ActiveX/VB6/Delphi 用的場合如鳳毛麟角,但我有不少文章本來就是寫給有緣人看的 (收到有緣人留言還會一陣激動),這篇文件適合從事古蹟維修的同學,沒聽過 COM+ 的朋友請跳過,把時間花在更有意義的地方。
將 .NET 元件包成 COM+ 元件的技術,學名叫做 COM Callable Wrapper,簡稱 CCW。網路有不少教學,但通常都很簡單,多是傳回現在時間、數字相加之類為賦新辭強說愁之類的應用,但實務上若有用到第三方程式會有一些眉角比較少人討論,這篇將用一個實例示範。
我選擇 XML/JSON 互相轉換當題材,這要在 VBScript 實現有點難度,但偉大的 Json.NET 有現成方法,我們只需寫個元件參照 Json.NET 當白手套,接收 JSON 呼叫 JsonConvert.DeserializeXNode(json)、接收 XML 呼叫 JsonConvert.SerializeXNode(XDocument.Parse(xml).Root) 就能輕鬆搞定,只需處理一下製作 CCW 所需的細節。
範例 CCW 專案的結構像這樣:
我習慣將 gacutil.exe、gacutil.exe.config、gacutlrc.dll 包進專案(參考),並提供 Reg.bat 與 Unreg.bat 以快速註冊及反註冊,另外需要一支 test.vbs 做驗證測試。如此可串接成:改程式 -> 編譯 -> Reg.bat 註冊 -> C:\Windows\SysWow64\cscript test.vbs 測試 -> 發現 Bug -> Unreg.bat 取消註冊 -> 改程式 -> 編譯 -> Reg.bat 註冊... 的開發流程,測試與開發會比較流暢有效率。
由於 CCW 元件一般會註冊到 GAC,需要設定數位簽章:(專案中的 DummyKey.snk 是加密金鑰,每個專案隨便產生一把能簽署即可)
週邊檔案講完了,再來看核心類別 JsonXmlConverter 怎麼寫。先定義一個 IJsonXmlConverter 介面,再寫 JsonXmlConverter 類別實作它。JsonXmlConverter、IJsonXmlConverter 要加上唯一的 [Guid()],並附加 [ComVisible(true)]、[ClassInterface(ClassInterfaceType.None)]、ComDefaultInterface(typeof(IJsonXmlConverter))]、 [ProgId("JsonToolkitCom.JsonXmlConverter")]... 等必要 Attribute。
完整程式範例如下:
using Newtonsoft.Json;
using System;
using System.Runtime.InteropServices;
using System.Xml.Linq;
namespace JsonToolkitCom
{
[ComVisible(true)]
[Guid("7FD0B31A-8DAD-4F87-B325-16789EBB985E")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IJsonXmlConverter
{
string XmlToJson(string xml);
string JsonToXml(string json);
}
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
[Guid("1012B3FF-333B-43CA-A931-A9EAB4A4643E")]
[ProgId("JsonToolkitCom.JsonXmlConverter")]
[ComDefaultInterface(typeof(IJsonXmlConverter))]
public class JsonXmlConverter : IJsonXmlConverter
{
public string XmlToJson(string xml)
{
return JsonConvert.SerializeXNode(XDocument.Parse(xml).Root);
}
public string JsonToXml(string json)
{
return JsonConvert.DeserializeXNode(json).ToString();
}
}
}
一般簡單的傳回現在時間、數字相加範例這樣就可以跑了,但我們因為用到第三方程式庫,直接執行時會出現找不到 Netwonsoft.Json.dll 錯誤:
最直覺的解法是將 Newtonsoft.Json.dll 也註冊到 GAC,但註冊 GAC 可能會影響系統上其他應用程式的組件解析 (延伸閱讀:ASP.NET /bin 組件載入跟你想的不一樣),故我想到另一種做法是用 ILMerge 將參照組件併進來,但實測發現 MSBuild.ILMerge 跟 CCW 專案不相容,之前 WPF 也遇過類似問題,這裡也比照辦理。在 csproj 加入 <Target Name="AfterResolveReferences"> 併入第三方組件,JsonXmlConverter 則加入靜態建置式掛載 AppDomain.CurrentDomain.AssemblyResolve 以支援從內嵌資源取得組件:
using Newtonsoft.Json;
using System;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Xml.Linq;
namespace JsonToolkitCom
{
[ComVisible(true)]
[Guid("7FD0B31A-8DAD-4F87-B325-16789EBB985E")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IJsonXmlConverter
{
string XmlToJson(string xml);
string JsonToXml(string json);
}
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
[Guid("1012B3FF-333B-43CA-A931-A9EAB4A4643E")]
[ProgId("JsonToolkitCom.JsonXmlConverter")]
[ComDefaultInterface(typeof(IJsonXmlConverter))]
public class JsonXmlConverter : IJsonXmlConverter
{
public string XmlToJson(string xml)
{
return JsonConvert.SerializeXNode(XDocument.Parse(xml).Root);
}
public string JsonToXml(string json)
{
return JsonConvert.DeserializeXNode(json).ToString();
}
static JsonXmlConverter()
{
AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
}
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);
}
}
}
}
修改後,重跑 test.vbs:
Set conv = CreateObject("JsonToolkitCom.JsonXmlConverter")
WScript.Echo conv.XmlToJson("<User><Id>A001</Id><Name>Jeffrey</Name></User>")
WScript.Echo conv.JsonToXml("{ ""User"": { ""Id"": ""D001"", ""Name"": ""darkthread"" } }")
測試成功!
範例專案已放上 Github 給未來的自己參考(希望是用不到啦),一併分享有需要的朋友(也希望沒人需要啦)。
Example of create a CCW project with 3rd party assembly dependency.
Comments
# by Rain
有緣人在此 ^_^ , 謝謝分享
# by thomas
有緣人感謝分享
# by Ray
感謝分享!知識增加!!
# by Huang
也是以下這樣,直接專案加引用,會直接顯示bin或GAC(可能在web.config出現該元件) 在 csproj 加入 <Target Name="AfterResolveReferences"> 併入第三方組件
# by 洪有德
我在想這個做法的用途。我記得曾經有骨董程式需要用。因為當時沒經驗就放棄了。現在,記起來,下次預到類似的問題可以試試。 謝謝黑大。
# by Clark
這篇文件適合從事古蹟維修的同學,沒聽過 COM+ 的朋友請跳過,把時間花在更有意義的地方 =>推這段說明文字放第一段落,棒棒