.NET4.x から .NET5 以降(.NET6, .NET7, ...) の.dllを呼び出す

秀丸エディタ v8.97 以降かどうかで判断が変化します。

秀丸エディタ v8.97 では.NET5の.dllを、COM経由で普通に読むことができます。
そちらも参照してください。

参照先を見れば、それをhm.NET上で行うことで、.NET Framwork4.xから.NET5のCOMライブラリも普通に読めることがわかります。

秀丸エディタ v8.97 以降 なのであれば、

などを利用して記述すれば、
秀丸マクロ上で、createobject関数 や メソッドを呼び出すことと同じですので、
.NET4.xから.NET5.xの呼び出しや値のやり取りが実現できることがわかるかと思います。

以下は、秀丸からは.NET5以降のCOMを扱えないバージョンとなる、
秀丸エディタ v8.96以下のバージョンを対象とした説明となります。

概要

「.NET4.x」のアセンブリから直接、「.NET5のアセンブリ」を「参照」することは出来ません。
「.NET5」はアセンブリとしては「.NET Core3.1」の後継であり、「.NET Framework 4.x」とは原則、互換性がありません。
(とはいえ、.NET Framework4.xを対象に制作したライブラリは、.NET5からも結構な高確率で参照して利用することが出来ます。)

「.NET4.x」のルーズな互換性と、「.NET5」以降の厳しい非互換性が生み出す問題 .NET4.xでは、ルーズに互換性があり、例えば、.NET4.5から.NET4.8の.dllをロードすることが出来ました。
Visual Studio上で直接参照として加えることはできずとも、よほどのことが無い限り、Assembly.Loadは出来たため、
古いNET4.5のdllから新しいNET4.8のdllを同一のAppDomainへと読み込むことが出来たのです。
hm.NET自体は.NET4.5で制作されていますが、そこから呼び出されるあなたが自作する.dllについては「.NET4.5のdll」でも「.NET4.8のdll」でもほとんどのシーンにおいて問題なく実行できるのは、この相互に高い互換性があればこそです。

しかし、.NET5以降はこれは当てはまりません。

「新しい.NET6」から、古い「.NET5や.NET Core3.1(そして、多くの.NET Framework ライブラリも)」を読み込むことはできても、
その逆「.NET5」から「.NET6」の.dllを読み込んだり、「.NET Core3.1」から「.NET5」を読み込むことは一切できなくなりました。

これにより、C#のオブジェクト指向のあり方を考慮すれば、今後は、基本的にはかなり強く「同一バージョンの.NETを使うことを意識する必要が出た」と言えます。
2021年以降は、.NET5→.NET6→.NET7と毎年のようにバージョンアップしていくわけですが、
「呼び出し元」と「呼び出し先」が互いのクラスやメソッドをロードしているならば、「どちらも.NETのバージョンを上げたくとも上げくい状況」となったと言えます。

2021年現在の段階では、nugetライブラリ上に公開されている「.NET系ライブラリ」は、ほとんどのライブラリが.NET 4.8で使えます。

しかし、長い目でみれば、今後制作される新しいライブラリ、あるいは既存ライブラリの新バージョンは、
.NET5以降のものへと徐々に移ってゆき、.NET4.xはサポート対象から外れてくることが予想されます。

それはちょうど、アクティブな活動をしているライブラリの多くが、最新版では、すでに.NET 3.5を動作対象から外していることからも明らかです。

このような将来像を見据え、「.NET4.x」から「.NET5」のライブラリを呼び出す使い方を知っておくと良いでしょう。

ここで解説する方法は、COM経由で「.NET Core 3.1以降で作ったdll」「.NET5 以降で作ったdll」を「.NET4.x」から呼び出す方法です。
COM経由で制作した場合の、パッキングの仕方(インストーラーを使って簡単にレジストリへの登録や解除も含めてパックしてしまう)方法も解説します。

今回の解説の基礎となる情報は「COM への .NET Core コンポーネントの公開」のものとなります。
そちらも合わせて参照することをオススメします。

ダウンロード

以降の解説内容のソリューションやプロジェクト、そしてC#ソース群となります。
ダウンロードした後、プロパティで「許可する(or ブロック解除)」にチェックを入れて、適用してください。

更新日 2021/02/22
MyTestCoreServer.zip

動作環境

  • Visual Studio 2019 v16.8 以上

    .NET 5 は Visual Studio 2019 v16.8以降利用可能です

  • Microsoft Visual Studio Installer Projects

    zipファイルには.slnのソリューションファイルと3つのプロジェクトファイルが含まれています。
    1つは「.NET5のCOM サーバー用」のプロジェクト。
    1つは「.NET4.xのクライアント用」のプロジェクト。
    残りの1つは、「Microsoft Visual Studio Installer Projects」です。
    この「Microsoft Visual Studio Installer Projects」用のプロジェクトはデフォルトではVisual Studioにインストールされません。
    Visual Studio の「拡張機能」から入れることが出来ます。

    PICTURE

COMサーバー側 (.NET5 側)

まずは「COMサーバー側」のプロジェクトファイルから見ていきましょう。

MyTestComServer.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
    <EnableComHosting>true</EnableComHosting>
    <EnableRegFreeCom>true</EnableRegFreeCom>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
    <PlatformTarget>x86</PlatformTarget>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
    <PlatformTarget>x86</PlatformTarget>
    <DebugType>none</DebugType>
    <DebugSymbols>false</DebugSymbols>
  </PropertyGroup>

</Project>
    
  • 「EnableComHosting」や「EnableRegFreeCom」

    「EnableComHosting」や「EnableRegFreeCom」はCOM参照可能とするためにとりあえず「MyTestComServer.csprojを直接手で編集して」付けておくようにしましょう。
    それぞれの詳細な役目は「COM への .NET Core コンポーネントの公開」を参照してください。
    そちらも合わせて参照してください。
    (秀丸用の.dllに限って言えば、「EnableRegFreeCom」の方は特に役には立ちません)

  • x86

    x86にしている理由は、このプロジェクトを「ANY CPU」でコンパイルして出力される「MyTestComServer.comhost.dll」は、
    ANYCPUを選択しているにもかかわらず、x64を選択していることと同じ64bit専用のものが出力されるためです。

    PICTURE

    .NET Core 3.1 や .NET 5 のCOMは ANY CPUでコンパイルするとx64になる

    COM への .NET Core コンポーネントの公開」に記載されているように、 「EnableComHosting」を付けた場合、 .NET Core、.NET 5、およびそれ以降のバージョンでは、既定で "Any CPU" アセンブリに 64 ビットの *.comhost.dll が付属しています。

    とあります、ANY CPU のままでは x64 の comhost の.dllとなってしまいますので、x86を選ぶ必要があります。
    ただし、あなたがお使いの秀丸エディタが64bit versionであり、hm.NET.dll も64bit版なのであれば、今回のサンプルもx86ではなく、x64(もしくはANY CPU)にする必要があります。

  • COMサーバーとクライアントのコントラクト(インターフェイス)

    GUIDについては、このCOMサーバーのdllと一対となりますので、あたなが制作する場合は新しくGUIDを発行してください。
    他は「こんな風にアトリビュートをくっつけるんだな」と思ったほうがいいでしょう。

    MyTestComInterface.cs
    using System;
    using System.Runtime.InteropServices;
    
    internal sealed class ContractGuids
    {
        public const string ServerClass = "35453D63-CC4C-467F-BD2B-457C417B15A5";
        public const string ServerInterface = "473100CB-2110-4264-BCEB-8394C487D891";
    }
    
    [ComVisible(true)]
    [Guid(ContractGuids.ServerInterface)]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IMyTestCOMServer
    {
        /// <summary>
        /// 文字列を足す
        /// </summary>
        string AddString(string str1, string str2, string[] list);
    }
        
  • COMサーバーの実装クラス

    原則、オブジェクトとして作るものではなく、単純に関数を呼び出すタイプものにするのが良いでしょう。
    引数の類は、COM経由でマーシャルできる範囲のものである必要性があるため、.NET4.xと.NET5の間でオブジェクトをポンと気軽に渡せるわけではありません。。
    引数として渡す、引数として返すといった対象は、bool, int, IntPtr, nint などのプリミティブ型と、
    string、そしてstring[](stringの配列)あたりに絞るのが無難です。
    こちらの「共通型をマーシャリングする場合の既定の規則・COM シナリオでの既定のマーシャリング」でも軽く触れられています。

    MyTestComServer.cs
    using System;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    
    namespace MyTestCOMServer
    {
        [ComVisible(true)]
        [Guid(ContractGuids.ServerClass)]
        public class MyTestCOMServer : IMyTestCOMServer
        {
    
            public string AddString(string str1, string str2, string[] list)
            {
                return str1 + str2 + list.Length.ToString(); ;
            }
        }
    }
    

COMサーバーをレジストする

.NET Framework では「RegAsm」が利用されていたが、.NET5では再び、ネイティブCOM同様、「Regsvr32.exe」 へと戻っている。
「管理者」権限でプロンプトを開き、対象の「MyTestComServer.comhost.dll」を登録する。

Cmdコンソール もしくはPowerShell の管理者権限
regsvr32 .\MyTestComServer.comhost.dll
    

登録を解除するには、

  regsvr32 /u .\MyTestComServer.comhost.dll
      

通常は実装することはありませんが、もしもこの「レジスト登録」や「レジスト解除」時に、何か特別なことをC#側から実行したいのであれば、以下のようなメソッドを定義すれば、
呼び出されます。

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace MyTestCOMServer
{
    [ComVisible(true)]
    [Guid("A7500A08-B63F-49DB-9EC2-1CE2CDBEC319")]
    public class MyTestCOMServer
    {

        public string AddString(string str1, string str2, string[] list)
        {
            return str1 + str2 + list.Length.ToString(); ;
        }

        [ComRegisterFunction]
        private static void RegisterFunction(Type type)
        {
            System.Diagnostics.Trace.WriteLine("★RegisterFunction" + type.ToString());
        }
        [ComUnregisterFunction]
        private static void UnregisterFunction(Type type)
        {
            System.Diagnostics.Trace.WriteLine("★UnRegisterFunction" + type.ToString());
        }
    }
}
  

COMクライアント側 (.NET4.x 側)

「COMクライアント側」のプロジェクトファイルは普通に作ればよく、変わったオプションは必要ありません。

  • Interface(コントラクト)をリンクとして追加し、間違い防止

    サーバー側(MyTestComServer)のソースとなる「MyTestComInterface.cs」が更新された時、
    クライアント側のソース(MyTestComClient)の、「MyTestComInterface.cs」も同一になる必要がありますので、
    ファイルを2つ持つのではなく、リンクするようにしましょう。

  • プロジェクトファイルで「右クリック」→「既存ファイルの追加」で、追加する際に下の方に「追加」と「リンクとして追加」が選べますので、
    リンクとして追加してください。

    PICTURE

  • COMクライアントのソース

    COMクライアントのソース例では、「MyTestComActivation」の名前空間で、.NET 4.x側から使える形にしています。
    これも深くは考えず、クラスとインターフェイスを空にして、それぞれこのような形でアトリビュートを付けると使えるようになるのだな、
    程度で十分です。

    MyTestComClient.cs
    using System;
    using System.Runtime.InteropServices;
    
    
    namespace MyTestCOMClient
    {
        public class Program
        {
            public static string abc(string str)
            {
                try
                {
                    var server = new MyTestComActivation.IMyTestComInterface();
                    string[] list = { "aaa", "bbb", "ccc" };
                    var add = server.AddString(str, "かきくけこ", list);
                    return add;
                } catch(Exception e)
                {
                    System.Diagnostics.Trace.WriteLine(e.Message);
                }
                return "";
            }
        }
    }
    
    //シナリオを単純化するためにここで定義されています。
    namespace MyTestComActivation
    {
        // CoClass のマネージドの定義
        [ComImport]
        [CoClass(typeof(MyTestComServer))]
        [Guid(ContractGuids.ServerInterface)] // TlbImp規則により、これを親インターフェイスのGUIDに設定します
        internal interface IMyTestComInterface : IMyTestCOMServer
        {
        }
    
        // CoClassのマネージドアクティベーション
        [ComImport]
        [Guid(ContractGuids.ServerClass)]
        internal class MyTestComServer
        {
        }
    }
    

hm.NETを使い、秀丸マクロから呼び出し

最後に一応秀丸マクロから呼び出せることを確認してみましょう。

MyTestComInterface.mac
#DLL = loaddll( hidemarudir + @"\hm.NET.dll");

$_ = dllfuncstrw(#DLL, "CallMethod", currentmacrodirectory + @"\MyTestComClient.dll", "MyTestCOMClient.Program", "abc", "あいうえお");

message($_);
  

ここまで登場した.NET5 COM関連ファイル群の詳細

一番詳細な資料は以下のものだと思われます。

https://github.com/dotnet/runtime/blob/master/docs/design/features/COM-activation.md

レジストやアンレジストをインストーラーで行うには

上述していた「Regsvr32」によるレジスト登録や解除は、個人で利用する分には「バッチファイル」か何かで行えば良いでしょう。
幸いにもdll名には「****.comhost.dll」といった名前や、見慣れない.jsonファイルなどが付随しているため、
少し慣れればそれが「ただの.dllではなく、COM用の.NET 5(or .NET Core)用の.dllだとすぐにわかります。

一方で配布する際にはどのようにすればよいのでしょう。
1つはReadmeやバッチファイルを作り、登録や解除を使用者本人にやってもらう方法です。

もうひとつがインストーラー(アンインストーラー兼ねる)を制作し、
インストール時にレジストリへの登録、アンインストール時にレジストリからの解除を行うというものです。

  • Microsoft Visual Studio Installer Projects

    Microsoft Visual Studio Installer Projects はインストーラー作成において最も一般的に利用されています。

    PICTURE

  • MyTestCOMServerSetup

    セットアッププロジェクトを見てみましょう。
    セットアッププロジェクトの作り方などを知らない人は調べてください。

    PICTURE

  • 必要なdllをセットアップ用のフォルダーへと追加してく

    Visual Studioのメニューより「表示」→「Editor」でフォルダを表示し、普通に追加していきましょう。
    秀丸マクロ用のプログラムを作る目的だと、「***.X.manifest」は「Regfree COM」用のファイルで今回は含めていません。

    PICTURE

  • インストールするのに自動的にレジストリへの登録(=regsvr32)をやってもらうには

    「****.comhost.dll」を選択して、Visual Studio のメニューより「表示」→「プロパティウィンドウ」で「vsdrfCOM」にします。

    PICTURE

        ↓

    PICTURE

    ビルドすると、***.msiファイルが完成します。
    このインストーラーを使うと、「インストールしたフォルダのMyTestCOMServer.comhost.dll」に対して、COM登録されます。
    アンインストールすると、登録は解除されます。

  • インストーラーの細かな設定項目

    こちらのサイトの解説などをヒントにすればよいでしょう。

EnableRegFreeCom

秀丸マクロから呼び出す分には、全く役立ちませんが、
一般的な.NET5系のCOM利用をレジスト登録せずに利用することができる方法に関係しており、
知識としては役立つ部分も多いため、触れておきます。

  • クライアントを .exe にする

    まず、MyTestComServerに「static void Mainメソッド」を作り、コンソールexeの形にします。
    プロジェクト→プロパティ→アプリケーション→出力の種類→コンソールアプリケーションを選びましょう。

    MyTestCOMClient.cs
    using System;
    using System.Runtime.InteropServices;
    
    
    namespace MyTestCOMClient
    {
        [Guid("403135F9-5D31-4849-9907-411936943FCD")]
        public class Program
        {
            public static void Main(string[] args)
            {
                string str = "aaaaa";
                try
                {
                    var server = new MyTestComActivation.IMyTestComInterface();
                    string[] list = { "aaa", "bbb", "ccc" };
                    var add = server.AddString(str, "かきくけこ", list);
                } catch(Exception e)
                {
                    Console.WriteLine(e);
                    System.Diagnostics.Trace.WriteLine(e.Message);
                }
            }
        }
    
    
        //次のクラスは通常PIAで定義されていますが、この例では
        //シナリオを単純化するためにここで定義されています。
        namespace MyTestComActivation
        {
            // CoClass のマネージドの定義
            [ComImport]
            [CoClass(typeof(MyTestComServer))]
            [Guid(ContractGuids.ServerInterface)] // TlbImp規則により、これを親インターフェイスのGUIDに設定します
            internal interface IMyTestComInterface : IMyTestCOMServer
            {
            }
    
            // CoClassのマネージドアクティベーション
            [ComImport]
            [Guid(ContractGuids.ServerClass)]
            internal class MyTestComServer
            {
            }
        }
    }
    
  • .exe用の.manifestを作成、RegFree COM の X.manifestがあれば自動的認識するようにする

    次に、「MyTestCOMClient.manifest」というファイルを作り、「MyTestCOMClient.cs」ファイルと同じディレクトリに配置します。
    MyTestCOMClient.manifest の中身を下記のようにします。

    MyTestCOMClient.manifest
      <?xml version="1.0" encoding="utf-8"?>
      <!-- https://docs.microsoft.com/windows/desktop/sbscs/assembly-manifests -->
      <assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
        <assemblyIdentity
          type="win32" 
          name="MyTestComClient"
          version="1.0.0.0" />
      
        <dependency>
          <dependentAssembly>
            <!-- RegFree COM matching the registration of the generated managed COM server -->
            <assemblyIdentity
                type="win32"
                name="MyTestComServer.X"
                version="1.0.0.0"/>
          </dependentAssembly>
        </dependency>
      
      </assembly>
    
  • .exeに.manifestをコンパイル時に埋めるよう.csprojで指定する

    MyTestCOMClient.csprojの「 <PropertyGroup>」下にぶら下げる形で、以下のようにマニフェストファイルを指定することで、コンパイル時にexeに埋め込まれるようにします。

    MyTestComServer.csproj
    <?xml version="1.0" encoding="utf-8"?>
    <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
      <PropertyGroup>
        <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
        <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
        <ProjectGuid>{965C4621-C56A-424C-A52F-A317C039C8A2}</ProjectGuid>
        <ApplicationManifest>MyTestComClient.manifest</ApplicationManifest>
        

    Visual Studioで再読み込みすれば、GUI上でもマニフェストの反映が確認出来るはずです。
    PICTURE

このMyTestCOMClient.exe をコンパイルし、
「MyTestCOMClient.exe」と「MyTestCOMServer.comhost.dll」、「MyTestCOMServer.X.manifest」、「他のdllや.json」が同じディレクトリにあれば、
Regsvr32をせずとも、MyTestCOMServer.comhost.dllを自動的にCOMサーバーとして認識してくれます。