C#で作る最小DLLプラグイン構成

コンピュータ

アプリの機能を拡張する方法として、プラグインという方法があります。

Windowsの場合DLLファイルでプラグインを実現することが出来るので、

試してみたいと思います。

プロジェクトの作成手順

作業ディレクトリの作成

複数のプロジェクトをまとめたソリューションを保存するディレクトリを作成します。

mkdir PluginSample
cd PluginSample

ソリューションの作成

コマンド

dotnet new sln -n PluginSample

生成されるファイル

PluginSample.sln

以降このソリューションファイルにプロジェクトなどを追加することになります。

PluginContractsプロジェクト作成

このプロジェクトは、プラグインのインターフェイスになります。
コマンド

dotnet new classlib -n PluginContracts

結果(参考)

 ls ./PluginContracts

    Directory: C:\Users\karet\src\csharp\PluginSample\PluginContracts

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----          2026/01/01    12:47                obj
-a---          2026/01/01    12:47             62 Class1.cs
-a---          2026/01/01    12:47            218 PluginContracts.csproj

4.Hostプロジェクト作成

このプロジェクトはプラグインを使う側で、

今回は通常のコンソールアプリで作成します。

コマンド

dotnet new console -n Host.Console

結果(参考)

ls ./Host.Console

    Directory: C:\Users\karet\src\csharp\PluginSample\Host.Console

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----          2026/01/01    12:50                obj
-a---          2026/01/01    12:50            252 Host.Console.csproj
-a---          2026/01/01    12:50            105 Program.cs

SamplePluginプロジェクトの作成

このプロジェクトは呼び出される側のプラグイン本体で、

classlibとして作成します。

dotnet new classlib -n SamplePlugin

結果(参考)

 ls ./SamplePlugin

    Directory: C:\Users\karet\src\csharp\PluginSample\SamplePlugin

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----          2026/01/01    12:54                obj
-a---          2026/01/01    12:54             59 Class1.cs
-a---          2026/01/01    12:54            218 SamplePlugin.csproj

ソリューションにプロジェクトを追加

dotnet sln add PluginContracts/PluginContracts.csproj
dotnet sln add Host.Console/Host.Console.csproj
dotnet sln add SamplePlugin/SamplePlugin.csproj

出来上がった、PluginSample.slnの内容は次のようになりました。


Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluginContracts", "PluginContracts\PluginContracts.csproj", "{960D87F1-98CA-42BD-95C1-CD46E37CFD84}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Host.Console", "Host.Console\Host.Console.csproj", "{14F4246A-E581-43AA-8757-2258FEF86BC8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SamplePlugin", "SamplePlugin\SamplePlugin.csproj", "{07A39DB2-8662-4354-B40A-02F4662291F0}"
EndProject
Global
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
		Debug|Any CPU = Debug|Any CPU
		Release|Any CPU = Release|Any CPU
	EndGlobalSection
	GlobalSection(SolutionProperties) = preSolution
		HideSolutionNode = FALSE
	EndGlobalSection
	GlobalSection(ProjectConfigurationPlatforms) = postSolution
		{960D87F1-98CA-42BD-95C1-CD46E37CFD84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{960D87F1-98CA-42BD-95C1-CD46E37CFD84}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{960D87F1-98CA-42BD-95C1-CD46E37CFD84}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{960D87F1-98CA-42BD-95C1-CD46E37CFD84}.Release|Any CPU.Build.0 = Release|Any CPU
		{14F4246A-E581-43AA-8757-2258FEF86BC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{14F4246A-E581-43AA-8757-2258FEF86BC8}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{14F4246A-E581-43AA-8757-2258FEF86BC8}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{14F4246A-E581-43AA-8757-2258FEF86BC8}.Release|Any CPU.Build.0 = Release|Any CPU
		{07A39DB2-8662-4354-B40A-02F4662291F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{07A39DB2-8662-4354-B40A-02F4662291F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{07A39DB2-8662-4354-B40A-02F4662291F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{07A39DB2-8662-4354-B40A-02F4662291F0}.Release|Any CPU.Build.0 = Release|Any CPU
	EndGlobalSection
EndGlobal

複数のプロジェクトが、一つのソリューションとして扱えるようになりました。
次はプロジェクト同士の関係性を設定の工程になります。

HostプロジェクトからPluginContractsを参照する設定

dotnet add Host.Console reference PluginContracts

SamplePluginプロジェクトからPluginContractsを参照する設定

dotnet add SamplePlugin reference PluginContracts

ビルド確認

正しく、ソリューションやプロジェクトが作成されたことを確認するため、
ビルドコマンドを実行してみます。

dotnet build

ソリューションに含まれる3つのプロジェクトがビルドされます。

プロジェクトごとに個別にビルドする場合は、以下のコマンドを実行します。

dotnet build Host.Console

これで、ソリューションとプロジェクトが出来上がりましたので、

コーディングすることが出来るようになりました。

構成図(暫定)

PluginSample(ディレクトリ)
│  PluginSample.sln
│  
├─Host.Console
│      Host.Console.csproj
│      Program.cs
│      
│                      
├─PluginContracts
│      Class1.cs
│      PluginContracts.csproj
│      
│                      
└─SamplePlugin
        Class1.cs
        SamplePlugin.csproj

ソースコード

PluginContracts\PluginContracts.csproj

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

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

PluginContracts\IPlugin.cs

namespace PluginContracts;

/// <summary>
/// プラグインのインターフェイス
/// </summary>
public interface IPlugin
{
    /// <summary>
    /// プラグイン名
    /// </summary>
    string Name { get; }

    /// <summary>
    /// ファイルを引数に処理を行う
    /// </summary>
    void Execute(string[] files);
}

SamplePlugin\SamplePlugin.csproj

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

  <ItemGroup>
    <ProjectReference Include="..\PluginContracts\PluginContracts.csproj" />
  </ItemGroup>

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>

    <!-- PluginContracts.dll を Host 側に 1 つだけ置くため -->
    <CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
  </PropertyGroup>

</Project>

SamplePlugin\HelloPlugin.cs

using PluginContracts;

namespace SamplePlugin;

/// <summary>
/// サンプルプラグイン
/// </summary>
public class HelloPlugin : IPlugin
{
    public string Name => "Hello Plugin";

    public void Execute(string[] files)
    {
        Console.WriteLine($"[{Name}]");

        if (files.Length == 0)
        {
            Console.WriteLine("No files.");
            return;
        }

        foreach (var file in files)
        {
            Console.WriteLine($"- {file}");
        }
    }
}

Host.Console\Host.Console.csproj

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

  <ItemGroup>
    <ProjectReference Include="..\PluginContracts\PluginContracts.csproj" />
  </ItemGroup>

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

Host.Console\Program.cs

using PluginContracts;
using System.Reflection;

class Program
{
    static void Main(string[] args)
    {
        var baseDir = AppContext.BaseDirectory;
        var pluginDir = Path.Combine(baseDir, "Plugins");

        Directory.CreateDirectory(pluginDir);

        var plugins = LoadPlugins(pluginDir);

        Console.WriteLine($"Plugins loaded: {plugins.Count}");

        foreach (var plugin in plugins)
        {
            Console.WriteLine($"Execute: {plugin.Name}");
            ExecuteSafe(plugin, args);
        }
    }

    static List<IPlugin> LoadPlugins(string folder)
    {
        var result = new List<IPlugin>();

        foreach (var dll in Directory.GetFiles(folder, "*.dll"))
        {
            try
            {
                var asm = Assembly.LoadFrom(dll);

                foreach (var type in asm.GetTypes())
                {
                    if (!typeof(IPlugin).IsAssignableFrom(type)) continue;
                    if (type.IsAbstract) continue;

                    var instance = (IPlugin?)Activator.CreateInstance(type);
                    if (instance != null)
                        result.Add(instance);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Failed to load: {dll}");
                Console.WriteLine(ex.Message);
            }
        }

        return result;
    }

    static void ExecuteSafe(IPlugin plugin, string[] files)
    {
        try
        {
            plugin.Execute(files);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Plugin crashed: {plugin.Name}");
            Console.WriteLine(ex);
        }
    }
}

※Class1.csは不要なので削除

ソリューションのビルド

dotnet build -c Release

実行してみます。

Host.Console\bin\Release\net8.0\Host.Console.exe
Plugins loaded: 0

プラグインがロードされた数が0というメッセージが表示されました。

SamplePlugin.dllをPluginsディレクトリへコピーします。

cp SamplePlugin\bin\Release\net8.0\SamplePlugin.dll Host.Console\bin\Release\net8.0\Plugins

実行してみます。

Host.Console\bin\Release\net8.0\Host.Console.exe
Plugins loaded: 1
Execute: Hello Plugin
[Hello Plugin]
No files.

今度は1件プラグインがロードされ、

Hello Pluginというメッセージが表示されてました。

コマンドライン引数をセットしてます。

Host.Console\bin\Release\net8.0\Host.Console.exe a.txt b.txt
Plugins loaded: 1
Execute: Hello Plugin
[Hello Plugin]
- a.txt
- b.txt

「Hello Plugin」が表示された後、

コマンドライン引数にセットした文字列が表示されました。

どうやら、SamplePlugin.dllが正しくロードされ実行された模様です。

感想

DLLファイルを使った、プラグインの作り方を確認しました。

コメント

  1. sheephuman より:

    こちらは独自性があるネタみたいですね。
    こんなの作ったことないからなかなか面白い試みだと思います。

    • 八 四 より:

      コメントありがとうございます。
      本記事は、YMM4プラグイン作成の学習の一環として
      DLLプラグインの最小構成を整理したものになります。