【入門編】VB.NETで作る独自の簡易プラグインアーキテクチャ:Assembly.LoadFromを用いた外部DLL動的読み込みとインターフェイス分離による拡張性確保 – Visual Basic (VB / VB.NET)解析バイブル

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

「書き換える」から「拡張する」へ。VB.NETで極める、プラグイン・アーキテクチャの極意

こんにちは。今日は、あなたが「マクロを書く人」から「ソフトウェアを設計するエンジニア」へと進化するための、非常に重要なステップについてお話ししましょう。

VBA(Excelマクロなど)を使いこなしている方は、機能が増えるたびにコードの末尾に `If…ElseIf` を追加したり、巨大な `Select Case` 文を書き足したりしていませんか?
「新しい帳票形式を追加して」と言われたとき、本体のプログラムをいちいち開き直して、ビルドし直して……。これは、プロフェッショナルな開発現場では「避けなければならない設計」とされています。

なぜなら、「本体を触らずに、外側から機能を追加できる」仕組みこそが、変化に強い、真に価値のあるソフトウェアだからです。

今日は、VB.NETの強力な機能である `Assembly.LoadFrom` を使い、「プラグイン・アーキテクチャ」を構築する方法を伝授します。ここをクリアすれば、あなたの作るプログラムは、まるでレゴブロックのように組み替え可能な、魔法のような道具に変わりますよ。

1. 設計の核心: 「契約(インターフェース)」という魔法

プラグイン・アーキテクチャを理解する上で、最も大切な概念は「インターフェース(Interface)」です。

想像してみてください。あなたはコンセントの設計者です。
コンセント(本体)は、そこに「ドライヤー」が刺さるのか「テレビ」が刺さるのかを知る必要はありません。ただ、「決められた形状のプラグであれば、電気を流す」というルールさえ守っていればいいのです。

この「決められた形状」こそが、プログラミングにおけるインターフェースです。

アーキテクチャの3つの登場人物

今回の設計では、プロジェクトを以下の3つに分離します。

1. 共通インターフェース・ライブラリ (Shared.dll)

  • 「プラグインとはこういうものですよ」という「契約(ルール)」だけを定義した、非常に軽いDLLです。

2. 本体プログラム (MainApp.exe)

  • ユーザーが操作する画面。`Shared.dll` を参照し、「契約」に従ったプラグインを動的に読み込みます。プラグインの中身は知りません。

3. プラグイン (PluginA.dll, PluginB.dll…)

  • 実際の業務ロジック(計算や帳票出力)が書かれた、後から追加できる部品です。

2. 実装ステップ: 実際にコードを書いてみよう

さあ、実際に手を動かしていきましょう。Visual Studioを開いて、3つのプロジェクトを作成するイメージで進めます。

Step 1: 「契約」を作る (Shared プロジェクト)

まずは、クラスライブラリ形式のプロジェクトを作成します。これが「コンセントの形状」になります。

' プロジェクト名: Shared
' このDLLは、本体とプラグインの両方が参照します。

Public Interface IPlugin
    ''' <summary>
    ''' プラグインの名前を返します
    ''' </summary>
    ReadOnly Property PluginName As String

    ''' <summary>
    ''' 実行されるメインのロジック
    ''' </summary>
    ''' <param name="input">入力データ</param>
    ''' <returns>処理結果</returns>
    Function Execute(input As String) As String
End Interface

ここがポイント!
`IPlugin` は実体を持たない「型」です。これがあるおかげで、本体プログラムは「中身が何であれ、`Execute` というメソッドを持っているなら呼び出せる」と確信できるのです。

Step 2: 「部品」を作る (Plugin プロジェクト)

次に、別のクラスライブラリプロジェクトを作成します。ここでは、先ほどの `Shared.dll` を参照設定に追加してください。

' プロジェクト名: MyCalculatorPlugin
' このDLLは、後からフォルダに放り込むだけで機能追加できる「部品」です。

Imports Shared ' 先ほど作ったインターフェースを参照

Public Class CalculatorPlugin
    Implements IPlugin

    ' インターフェースの規定に従って実装します
    Public ReadOnly Property PluginName As String Implements IPlugin.PluginName
        Get
            Return "高度な計算プラグイン"
        End Get
    End Property

    Public Function Execute(input As String) As String Implements IPlugin.Execute
        ' ここに実際の業務ロジックを書きます
        Try
            Dim result As Double = Convert.ToDouble(input)
            Return $"【計算結果】: {result * 1.1} (消費税10%込み)"
        Catch ex As Exception
            Return $"エラーが発生しました: {ex.Message}"
        End Try
    End Function
End Class

Step 3: 「本体」を作る (MainApp プロジェクト)

最後に、Windows Forms アプリケーションを作成します。ここでも `Shared.dll` は参照しますが、`MyCalculatorPlugin.dll` は絶対に参照しないでください。(参照してしまうと、プラグインを「後から追加する」意味がなくなってしまいます)

' プロジェクト名: MainApp
' ボタンを押すと、指定したフォルダ内のDLLを自動で読み込みます。

Imports System.Reflection ' 反射(Reflection)機能を使うために必要
Imports System.IO          ' ファイル操作のために必要
Imports Shared            ' 契約(インターフェース)を参照

Public Class Form1
    ' 読み込んだプラグインを保持するリスト
    Private _plugins As New List(Of IPlugin)

    ''' <summary>
    ''' フォームロード時に、Pluginsフォルダ内のDLLをスキャンして読み込む
    ''' </summary>
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        LoadPlugins()
    End Sub

    Private Sub LoadPlugins()
        ' 実行ファイルと同じ場所にある "Plugins" フォルダを探す
        Dim pluginFolder As String = Path.Combine(Application.StartupPath, "Plugins")

        ' フォルダがなければ作成
        If Not Directory.Exists(pluginFolder) Then
            Directory.CreateDirectory(pluginFolder)
            Return
        End If

        ' フォルダ内の .dll ファイルをすべて取得
        Dim dllFiles = Directory.GetFiles(pluginFolder, "*.dll")

        For Each dllPath In dllFiles
            Try
                ' 【魔法の行】DLLを動的にメモリへロードする
                Dim assembly As Assembly = Assembly.LoadFrom(dllPath)

                ' ロードしたDLLの中から、IPluginインターフェースを実装しているクラスを探す
                For Each t As Type In assembly.GetTypes()
                    If TypeOf t Is IPlugin Then
                        ' インターフェースを実装しているクラスのインスタンスを作成
                        Dim plugin As IPlugin = DirectCast(Activator.CreateInstance(t), IPlugin)
                        _plugins.Add(plugin)
                        Console.WriteLine($"読み込み成功: {plugin.PluginName}")
                    End If
                Next
            Catch ex As Exception
                ' DLLが壊れている、あるいはShared.dllのバージョンが違う場合などのエラー対策
                MessageBox.Show($"DLLの読み込みに失敗しました ({Path.GetFileName(dllPath)}): {ex.Message}")
            End Try
        Next
    End Sub

    ''' <summary>
    ''' 実行ボタンが押された時の処理
    ''' </summary>
    Private Sub btnRun_Click(sender As Object, e As EventArgs) Handles btnRun.Click
        If _plugins.Count = 0 Then
            MessageBox.Show("利用可能なプラグインがありません。")
            Return
        End If

        ' 読み込まれたすべてのプラグインを順番に実行してみる
        Dim inputData As String = txtInput.Text
        Dim resultText As String = ""

        For Each plugin In _plugins
            resultText &= $"{plugin.PluginName} の実行結果: {plugin.Execute(inputData)}{Environment.NewLine}"
        Next

        txtOutput.Text = resultText
    End Sub
End Class

3. 陥りやすい罠: アーキテクトの視点

この設計は非常に強力ですが、初心者が必ずと言っていいほど踏み抜く「地雷」がいくつかあります。

① 「参照の不一致」問題(DLL地獄)

`Shared.dll`(インターフェース)を更新したとき、本体とプラグインの両方をビルドし直さないと、絶対に動きません。
本体が「新しい契約」を期待しているのに、プラグインが「古い契約」で作られていると、`InvalidCastException`(型変換エラー)が発生します。
「インターフェースを変えたら、全員に知らせる」のが鉄則です。

② ファイルのロック問題

`Assembly.LoadFrom` で読み込んだDLLは、プログラムが終了するまでメモリに残り、ファイルがロックされます。 つまり、アプリを起動したまま、新しいDLLを上書きコピーすることはできません。
開発中は、アプリを一度落としてからDLLを入れ替える癖をつけましょう。

③ エラーの連鎖

プラグインの中で致命的なエラー(例外)が発生すると、そのままでは本体プログラムまで巻き添えを食らってクラッシュしてしまいます。
プラグイン側の `Execute` メソッド内には、必ず `Try…Catch` を入れ、「エラーが起きても本体を殺さない」という守りの設計を心がけてください。

まとめ: あなたはもう、設計者である

お疲れ様でした! これであなたは、単なる「コードの書き手」ではなく、「拡張可能なシステムを設計するアーキテクト」への第一歩を踏み出しました。

今回学んだ `Assembly.LoadFrom` と `Interface` の組み合わせは、業務システムの基盤として非常に高い実用性を持っています。

  • 「新しい帳票が必要になったら、新しいDLLをPluginsフォルダに入れるだけ」
  • 「本体のコードを一行も書き換える必要はない」

この圧倒的な柔軟性を、ぜひあなたのプロジェクトで体感してください。

ここをクリアできれば、Visual Basic (VB.NET) の基本、そして「オブジェクト指向設計」の本質はバッチリですよ。自信を持って、次のステップへ進んでくださいね!

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