6

CCW 封裝 .NET 元件範例

 3 years ago
source link: https://blog.darkthread.net/blog/ccw-example/
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.

CCW 封裝 .NET 元件範例

2021-05-03 08:31 PM 4 922

都 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 json);
        string JsonToXml(string xml);
    }

    [ClassInterface(ClassInterfaceType.None)]
    [ComVisible(true)]
    [Guid("1012B3FF-333B-43CA-A931-A9EAB4A4643E")]
    [ProgId("JsonToolkitCom.JsonXmlConverter")]
    [ComDefaultInterface(typeof(IJsonXmlConverter))]
    public class JsonXmlConverter : IJsonXmlConverter
    {
        public string JsonToXml(string xml)
        {
            return JsonConvert.SerializeXNode(XDocument.Parse(xml).Root);
        }

        public string XmlToJson(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 json);
        string JsonToXml(string xml);
    }

    [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 給未來的自己參考(希望是用不到啦),一併分享有需要的朋友(也希望沒人需要啦)。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK