最終更新日 2024-09-25

C#で作成した「中間アセンブリdll」の配置フォルダの追加

概要

秀丸用の自作.dllを作成した場合、通常は「秀丸本体のhidemaru.exe」と「自分で作成した.dll」とは、
「異なるディレクトリ」へと配置するのが普通です。
通常はおそらく、「起動のきっかけとなるマクロファイル(.mac)」と同じフォルダに自作のdllを配置したいと思うことでしょう。
たとえば、次のような感じです。

ためしに以下のようなプログラムが実行可能かどうかみてみましょう。

別のdllの参照

ここではちょうど良いありがちな実験として、「nuget」のdllを利用することを想定したミニマムな実験をしてみしょう。
nuget 経由で、HtmlAgilityPack(=Htmlをパースするライブラリ)を入れてみて、自作のdllで利用してみます。

ClassLibrary36.cs
using System;
using System.Runtime.InteropServices;
using HtmlAgilityPack;

namespace ClassLibrary36 {

    public class Class1
    {
        [DllExport]
        [return: MarshalAs(UnmanagedType.LPWStr)]
        static String MyFuncB([MarshalAs(UnmanagedType.LPWStr)] String a, [MarshalAs(UnmanagedType.LPWStr)] String b)
        {
            try
            {
                // HttmlAgiliyPack を実際に利用する記述
                var doc = new HtmlDocument();
                doc.LoadHtml("<html></html>");
                System.Diagnostics.Trace.WriteLine(doc.ToString());
            } catch(Exception e)
            {
                System.Diagnostics.Trace.WriteLine(e);

            }
            return a +b;
        }
    }
}
              
ClassLibrary36.cs
#DLL = loaddll( currentmacrodirectory + @"\ClassLibrary36.dll");

$add_str = dllfuncstrw( #DLL, "MyFuncB", "あいうえお", "素麺");
message($add_str);

実行できずエラーとなる

適当なディレクトリ(秀丸本体があるディレクトリ以外のディレクトリ)でマクロから呼び出して実行してみましょう。

エラーとなり実行できません。

なぜなら、中間アセンブリは、デフォルトの状態では呼び出し元(Hidemaru.exe)と同じディレクトリに配置する必要があるからです。

しかし、Hidemaru.exeと同じディレクトリにどんどん配置していってはファイル群の役割の区別がつかなくなってゆき、
メンテナンスできなくなります

そこで、以下のようにプログラムを追加することで、このような配置でも実行できるようにすることができます。
(hm.NET は、これと類似のことを事前にやっています)

アセンブリの解決

自作のdllに、以下のような「AssemblyResolve」とも呼べるプログラムを、追加することで、「アセンブリが読み込めなかったら、ここにあるこのdllで代用してください」と パスを指定してアセンブリを読み込ませることができます。

DllAssemblyResolver.cs
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;

internal class DllAssemblyResolver
{
    public DllAssemblyResolver()
    {
        AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
    }

    ~DllAssemblyResolver()
    {
        AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve;
    }

    // アセンブリdllの読み込みに失敗した時、このメソッドが実行される。
    // ようするに「dllがみつからなかったので、この場所のこのファイルを探してください」といった形で返すメソッドである。
    private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        try
        {
            var requestingAssembly = args.RequestingAssembly;
            var requestedAssembly = new AssemblyName(args.Name);
            System.Diagnostics.Trace.WriteLine($"CurrentDomain_AssemblyResolve:{args.Name}"); // デバッグモニター表示用

            // このdll自体を置いているフォルダに読み込み対象のアセンブリがあるかもしれない。
            String self_full_path = Assembly.GetExecutingAssembly().Location;
            String self_dir = Path.GetDirectoryName(self_full_path);

            // このフルパスを整形することで、違うフォルダ、あるいはサブフォルダに配置してあるdllをアセンブリとして読み込ませることが出来る。
            var targetfullpath = self_dir + $@"\{requestedAssembly.Name}.dll";

            if (File.Exists(targetfullpath))
            {
                return Assembly.LoadFile(targetfullpath);
            }

            // そのようなフルパスが指定されている場合(フルパスを指定した書き方)
            targetfullpath = requestedAssembly.Name;
            if (File.Exists(targetfullpath))
            {
                return Assembly.LoadFile(targetfullpath);
            }
        }
        catch (Exception ex)
        {
            return null;
        }
        return null;
    }
}

秀丸マクロから最初に呼ぶ予定の「メソッドを含むクラス」の「静的コンストラクタ」に以下のようなものを記述することです。
ポイントは秀丸マクロから最初に呼ぶ予定の「メソッドを含むクラス」の「静的コンストラクタ」に書くことです。

ClassLibrary36.cs
using System;
using System.Runtime.InteropServices;
using HtmlAgilityPack;

namespace ClassLibrary36 {

    public class Class1
    {
        static DllAssemblyResolver dasmr;
        // 秀丸から最初に呼ぶメソッドが含まれているクラスの「静的コンストラクタ(static付きのコンストラクタ)」
        // でアセンブリリゾルバーオブジェクトを生成する。
        static Class1() {
            dasmr = new DllAssemblyResolver();
            System.Diagnostics.Trace.WriteLine("MyAssemblyResolver:");
        }

        [DllExport]
        [return: MarshalAs(UnmanagedType.LPWStr)]
        static String MyFuncB([MarshalAs(UnmanagedType.LPWStr)] String a, [MarshalAs(UnmanagedType.LPWStr)] String b)
        {
            try
            {
                // HttmlAgiliyPack を実際に利用する記述
                var doc = new HtmlDocument();
                doc.LoadHtml("<html></html>");
                System.Diagnostics.Trace.WriteLine(doc.ToString());
            } catch(Exception e)
            {
                System.Diagnostics.Trace.WriteLine(e);

            }
            return a +b;
        }
    }
}