【入門編】VB.NETによるリフレクションとAppDomainを活用したプラグイン機構の自社開発:コアアプリケーションを変更せずに外部DLLから新機能を動的ロードする設計 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク
スポンサーリンク

拡張性の極致へ:VB.NETで実現する「プラグイン機構」の設計術

こんにちは。現場で数々のシステム崩壊と、そこからの再生を繰り返してきたアーキテクトです。

皆さんは、こんな経験はありませんか?
「クライアントから『新しい計算ルールを追加してほしい』と言われた。でも、それだけでメインのプログラムを書き換えて、またビルドして、テストして……。これ、毎回やってられませんよ!」

もし、あなたのプログラムが「中身を一切変えずに、後から新しい機能(DLLファイル)をポンと置くだけで動く」ようになれば、開発の景色は一変します。

今日は、VB.NETの真の力を引き出す「リフレクション」と「AppDomain」を使った、プロフェッショナルなプラグイン機構の作り方を伝授しましょう。マクロの記録から一歩踏み出し、「設計」という魔法を使いこなす準備はできましたか?

1. なぜ「直接書き込む」のがいけないのか?

まず、設計の考え方から整理しましょう。

普通のプログラムは、以下のような構造です。
`メインプログラム` $\rightarrow$ `機能A` $\rightarrow$ `機能B`

これだと、機能Cを追加したくなった時に、必ず`メインプログラム`を修正して再ビルドしなければなりません。これを「密結合(みつけつごう)」と呼び、大規模システムでは「変更の恐怖」を生みます。

私たちが目指すのは、「インターフェース(契約)」による分離です。

  • メインプログラム(ホスト): 「私は『実行ボタン』を持った部品が欲しい。名前は `IPlugin` というルールに従っていること!」と宣言する。
  • プラグイン(外部DLL): 「私は `IPlugin` というルールに従っています。中身は『Excel出力機能』です!」と提示する。

この「ルール」こそが、プラグイン開発の要です。

2. 実践:3つのステップで構築するプラグイン基盤

プラグイン機構を作るには、3つのプロジェクト(役割)が必要です。

1. 共通インターフェース層 (Shared Library): 「ルール」を定義する。
2. プラグイン層 (Plugin DLL): 「具体的な機能」を実装する。
3. メインアプリ層 (Host App): 「ルール」に従って、外部から読み込む。

Step 1: 「ルール」を決める (Shared Library)

まずは、メインとプラグインが共通で参照する「契約書」を作ります。これはクラスライブラリ(DLL)として作成します。

' --- プロジェクト名: MySystem.Shared ---
Public Interface IPlugin
    ''' <summary>
    ''' プラグインの名前を返します
    ''' </summary>
    ReadOnly Property PluginName As String

    ''' <summary>
    ''' メイン機能を実行します
    ''' </summary>
    ''' <param name="data">処理対象のデータ</param>
    Sub Execute(data As String)
End Interface

【ここがポイント!】
この `IPlugin` を、メインアプリもプラグインも両方で参照します。これにより、「型」が一致することを保証できるのです。

Step 2: 「機能」を作る (Plugin DLL)

次に、実際に動く機能を作ります。これもクラスライブラリとして作成します。

' --- プロジェクト名: MyPlugin.HelloWord ---
Imports MySystem.Shared ' さっき作ったルールを参照

Public Class HelloPlugin
    Implements IPlugin

    Public ReadOnly Property PluginName As String Implements IPlugin.PluginName
        Get
            Return "挨拶プラグイン"
        End Get
    End Property

    Public Sub Execute(data As String) Implements IPlugin.Execute
        ' ここに実際の業務ロジックを書く
        MsgBox($"プラグインが起動しました! メッセージ: {data}")
    End Sub
End Class

【ここがポイント!】
`Implements IPlugin` と書くことで、「私はルールを守っています」と宣言しています。

Step 3: 「魔法」で読み込む (Host App)

いよいよメインアプリです。ここが一番の難所であり、面白いところです。
「リフレクション(Reflection)」という技術を使い、実行時にDLLの中身を覗き見ます。

' --- プロジェクト名: MyMainApp (WinForms) ---
Imports System.Reflection
Imports System.IO
Imports MySystem.Shared

Public Class MainForm
    Private Sub btnLoadPlugin_Click(sender As Object, e As EventArgs) Handles btnLoadPlugin.Click
        ' 1. プラグインが置いてあるフォルダを指定
        Dim pluginFolder As String = Path.Combine(Application.StartupPath, "Plugins")
        
        ' フォルダがなければ作成
        If Not Directory.Exists(pluginFolder) Then Directory.CreateDirectory(pluginFolder)

        ' 2. フォルダ内のDLLをスキャン
        Dim files = Directory.GetFiles(pluginFolder, "*.dll")

        For Each file In files
            Try
                ' --- 【極意】リフレクションによる動的ロード ---
                
                ' DLLをメモリにロード
                Dim assembly As Assembly = Assembly.LoadFrom(file)

                ' DLLの中にある「IPlugin」を実装しているクラスを探す
                For Each t As Type In assembly.GetTypes()
                    If GetType(IPlugin).IsAssignableFrom(t) AndAlso Not t.IsInterface Then
                        
                        ' クラスのインスタンスを作成(実体化)
                        Dim plugin As IPlugin = DirectCast(Activator.CreateInstance(t), IPlugin)

                        ' 実行!
                        MsgBox($"読み込み成功: {plugin.PluginName}")
                        plugin.Execute("メインアプリからの指令です")
                        
                    End If
                Next

            Catch ex As Exception
                MsgBox($"エラーが発生しました ({Path.GetFileName(file)}): {ex.Message}")
            End Try
        Next
    End Sub
End Class

3. 伝説のエンジニアが教える「AppDomain」の重要性

上記のコードには、実は「大きなリスク」が潜んでいます。

もし、読み込んだプラグインの中に「バグがあって強制終了(クラッシュ)するもの」があったらどうなるでしょう? メインアプリごと道連れになって落ちてしまいます。 これでは業務システムとして失格です。

そこで登場するのが `AppDomain`(アプリケーションドメイン) です。

`AppDomain` は、いわば「隔離された砂場(サンドボックス)」です。
プラグインをメインの `AppDomain` ではなく、「別個の AppDomain」 にロードすることで、もしプラグインが暴走しても、メインアプリへの影響を最小限に抑えることができます。

> 【高度な知見】
> 現代の .NET (Core/5+) では `AssemblyLoadContext` という仕組みに進化していますが、従来の WinForms (Framework) 環境では、この `AppDomain` による隔離が最強の防御策となります。
>
> さらに、`AppDomain` を使えば、「一度読み込んだプラグインを、アプリを落とさずにメモリから完全に消去する」 ことも可能です。これは大規模システムにおいて、メモリリークを防ぐための生命線となります。

4. 陥りやすい「3つの罠」

これを知らないと、あなたは夜中に呼び出されることになります。

1. 「型が一致しない」エラー (InvalidCastException)
プラグイン側とメイン側で参照している `IPlugin` の定義が、微妙に(バージョン違いなどで)異なると、`DirectCast` で失敗します。「共通インターフェースのDLLは、絶対に勝手に更新しない」のが鉄則です。

2. 「DLLの二重ロード」問題
同じDLLを複数の場所からロードしようとすると、ファイルがロックされてしまいます。ロードする際は、パスの管理を徹底しましょう。

3. 「依存関係の地獄」
プラグインが「別のDLL」に依存している場合、その依存DLLも一緒に `Plugins` フォルダに入れておかないと、リフレクションは失敗します。

最後に:ここをクリアすれば、あなたはもう「初級者」ではない

いかがでしたか?

「リフレクションで中身を覗き、インターフェースで契約を交わし、AppDomainで安全を守る。」

この設計パターンをマスターすれば、あなたの作るシステムは、「成長し続ける有機体」のような強靭なものになります。機能追加のたびにコードを書き換える日々とは、今日でお別れです。

さあ、勇気を持って、この「動的な設計」に挑戦してみてください。困ったら、いつでもこの設計図を思い出してくださいね。応援していますよ!

タイトルとURLをコピーしました