【C#】DLLを動的に読み込む方法

C#

はじめに

こうちゃ
こんにちは、こうちゃです。

今回は、C#におけるDLLの動的読み込み方法について説明していきます。

  • C#でDLLを動的に読み込む方法
  • 読み込んだDLLに実装されているクラスのインスタンスを作成する方法
  •  C#においてDLLを動的に読み込ませたいと思っている方
こうちゃ
それでは、やっていきましょう!

フォルダ構成

今回のフォルダ構成は下図のようになります。

ソリューションディレクトリ
│  
├─プロジェクト (Car)
│      Car.cs
│      Car.csproj
│
│          
├─プロジェクト (Human)
│      Human.cs
│      Human.csproj
│
│          
└─プロジェクト (Main)
    │  Main.csproj
    │  Main.cs
    │  
    └─bin
       └─x64              
           └─Release
               └─net8.0
                    │  Main.dll
                    │  Main.exe
                   │  
                   └─DLLs
                            Car.dll
                            Human.dll

各プロジェクトの役割は以下です。
プロジェクト (Main):DLLを動的読み込みし、インスタンスを作成するプロジェクト
プロジェクト (Car):Carというクラスが実装されているプロジェクト
プロジェクト (Human): Humanというクラスが実装されているプロジェクト

フォルダ構成図のように、「プロジェクト (Car)」と「プロジェクト (Human)」をビルドし作成された「Car.dll」「Human.dll」を、「Main.exeが生成されるディレクトリ/DLLs」に配置します。
そしてプロジェクト (Main)では、「Main.exeが生成されるディレクトリ/DLLs」に存在しているDLLをすべて読み込みインスタンス化する という処理を行います。

ソースコード

実際にソースコードを見てみましょう。

まずは「Car.cs」です。
今回は、クラスの中身については記述せず、ただクラスの枠組みを作るのみにとどめます。

using System;

namespace Car
{
    public class Car
    {
    }
}

続いて「Human.cs」です。
こちらも「Car.cs」同様、今回はクラスの枠組みを作るのみにとどめます。

using System;

namespace Human
{
    public class Human
    {
    }
}

最後に「Main.cs」です。
まずは、「LoadDll_CreateInstance」メソッドを見ていきましょう。

/// <summary>
/// DLL読み込み & インスタンス作成
/// </summary>
/// <param name="dllDirPath">読み込みたいDLLが配置されているフォルダへのパス</param>
/// <param name="objList">作成したインスタンスリスト</param>
static void LoadDll_CreateInstance(string dllDirPath, List<object> objList)
{
    if (Directory.Exists(dllDirPath))
    {
        // dllDirPath内に存在する".dll"ファイルをピックアップしリスト化する
        List<string> dlls = Directory.GetFiles(dllDirPath, "*.dll").ToList();

        foreach (string dllName in dlls)
        {
            // DLLを読み込む
            Assembly assembly = Assembly.LoadFrom(dllName);

            // DLLに実装されているすべての型を取得する
            Type[] types = assembly.GetTypes();
            foreach (Type type in types)
            {
                // 非クラス型、非パブリック型、抽象クラスはスキップ
                if (!type.IsClass || !type.IsPublic || type.IsAbstract)
                {
                    continue;
                }

                // インスタンスを作成する
                object? obj = Activator.CreateInstance(type);
                if (obj != null)
                {
                    // 作成したインスタンスが null でなければ、引数のインスタンスリストに格納する
                    objList.Add(obj);
                }
            }
        }
    }
}

1行ずつ見ていきましょう。

List<string> dlls = Directory.GetFiles(dllDirPath, "*.dll").ToList();

まず最初に、「dllDirPath」内に存在している拡張子が「.dll」のファイルをすべて取得します。
「dlls」には、各DLLファイルの絶対パスが格納されます。

Assembly assembly = Assembly.LoadFrom(dllName);

こちらの処理が本記事のメインとなりますが、指定されたパス(dllName)のDLLを実際に読み込む箇所になります。
Assemblyクラスにはいろいろな情報が入っていますので、気になる方は調べてみて下さい。

Type[] types = assembly.GetTypes();

こちらでは、読み込んだDLLの内部に実装されているクラス情報等を取得することができます。
例えばクラスが4つ実装されている場合、4つすべてのクラス情報が得られます。
逆に、特定のクラス名を指定してクラス情報を取得できる「GetType」というメソッドもありますので、気になる方は「Assembly.GetType」などで検索すればヒットすると思います。

if (!type.IsClass || !type.IsPublic || type.IsAbstract)
{
    continue;
}

上記の「GetTypes」メソッドで得られる情報はクラスに限らず、DLLに定義されている型をすべて取得してしまいます。
そのため、DLLに実装されているクラスのインスタンスを作成するには、クラスでないものは除外する必要があります。
それが「!type.IsClass」になります。
次に、アクセス修飾子がPublicでないクラスを「!type.IsPublic」で除外しています。(本記事のテーマとしては、これはなくても問題ありません)
最後に、抽象クラス(インスタンス化ができないクラス)を「type.IsAbstract」で除外しています。

object? obj = Activator.CreateInstance(type);

最後に、この処理により「type」で指定されているクラスをインスタンス化しています。
このクラスがどのようなクラスかは、コンパイル時に判定ができないため、object型のインスタンスとなります。
以上が「LoadDll_CreateInstance」メソッドの中身でした。

次に、この「LoadDll_CreateInstance」メソッドを呼び出す部分のコードを記載していきます。

static void Main(string[] args)
{
    // 読み込みたいDLLが配置されているフォルダへのパス
    string dllDirPath = $"{AppDomain.CurrentDomain.BaseDirectory}DLLs";
    List<object> objList = new List<object>();
    LoadDll_CreateInstance(dllDirPath, objList);

    foreach (object obj in objList)
    {
        Console.WriteLine(obj.GetType().ToString());
    }
}

実際にプログラムが起動する「Main」メソッドになります。
AppDomain.CurrentDomain.BaseDirectory」では、Main.exeが起動しているディレクトリへの絶対パスが得られます。
フォルダ構成でも記載したように、「Main.exeが生成されるディレクトリ/DLLs」にDLLを配置しているため、上記のようになります。
この「dllDirPath」とobject型のインスタンスを格納する「objList」を「LoadDll_CreateInstance」メソッドの引数として渡すと、インスタンスが「objList」に格納されて戻ってきます。
最後に、コンソールにインスタンスの型(ここだとクラス名になります)を表示するようになっています。

「Main.cs」全体のコードは以下のようになります。

using System.Reflection;

namespace Main
{
    class Program
    {
        static void Main(string[] args)
        {
            // 読み込みたいDLLが配置されているフォルダへのパス
            string dllDirPath = $"{AppDomain.CurrentDomain.BaseDirectory}DLLs";
            List<object> objList = new List<object>();
            LoadDll_CreateInstance(dllDirPath, objList);

            foreach (object obj in objList)
            {
                Console.WriteLine(obj.GetType().ToString());
            }
        }

        /// <summary>
        /// DLL読み込み & インスタンス作成
        /// </summary>
        /// <param name="dllDirPath">読み込みたいDLLが配置されているフォルダへのパス</param>
        /// <param name="objList">作成したインスタンスリスト</param>
        static void LoadDll_CreateInstance(string dllDirPath, List<object> objList)
        {
            if (Directory.Exists(dllDirPath))
            {
                // dllDirPath内に存在する".dll"ファイルをピックアップしリスト化する
                List<string> dlls = Directory.GetFiles(dllDirPath, "*.dll").ToList();

                foreach (string dllName in dlls)
                {
                    // DLLを読み込む
                    Assembly assembly = Assembly.LoadFrom(dllName);

                    // DLLに実装されているすべての型を取得する
                    Type[] types = assembly.GetTypes();
                    foreach (Type type in types)
                    {
                        // 非クラス型、非パブリック型、抽象クラスはスキップ
                        if (!type.IsClass || !type.IsPublic || type.IsAbstract)
                        {
                            continue;
                        }

                        // インスタンスを作成する
                        object? obj = Activator.CreateInstance(type);
                        if (obj != null)
                        {
                            // 作成したインスタンスが null でなければ、引数のインスタンスリストに格納する
                            objList.Add(obj);
                        }
                    }
                }
            }
        }
    }
}

実施結果

それでは、上記のコードを実行してみましょう。
実行すると、コンソールに以下が表示されます。

Car.Car
Human.Human

「Car」という名前空間に実装されている「Car」クラス
「Human」という名前空間に実装されている「Human」クラス
のインスタンスが作成されたことがわかります。

さいごに

今回は、C#におけるDLLの動的読み込みと、インスタンス化の方法について説明しました。
ただ今回は、インスタンス化ができただけで、実際にクラスに実装されているメソッドを呼び出すなどはできていません。
作成されたインスタンスがどんなクラスなのかコンパイル時には判定できないためです。
クラスに実装されているメソッドを呼び出すにはもうひと手間必要になるので、それは以下の記事で紹介したいと思います。

関連記事

はじめに こうちゃ こんにちは、こうちゃです。 今回は、C#において動的に読み込んだDLLに実装されているクラスの使用方法 について説明していきます。 今回の内容は下記記事の続きですので、まだ読んでいない方は下記記事[…]

今回はここまで♪

こうちゃ
それではみなさま、お疲れ様でした!
楽しいプログラミングライフを!
最新情報をチェックしよう!