【テクニカル・上級編】上級プロフェッショナル向け:VB.NETでの「AssemblyName」と「Assembly.LoadFrom」によるアドイン機構:プラグイン型アーキテクチャの構築 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

境界線を越える設計:VB.NETにおける「動的アセンブリロード」によるプラグインアーキテクチャの真髄

世の中の多くのVB.NET開発者は、ソリューションにプロジェクトを追加し、参照設定を行い、静的なバイナリとしてビルドすることに安住している。しかし、真に堅牢で拡張性の高いシステムを構築しようとするならば、アプリケーションの「境界」を再定義しなければならない。

メインアプリを再コンパイルすることなく機能を拡張する。この「プラグイン型アーキテクチャ」こそが、肥大化するレガシーコードを延命させ、ビジネスの要求に即応する唯一の解だ。今日は、`AssemblyName`と`Assembly.LoadFrom`を駆使し、メモリを制御下に置くための「極限の知見」を共有する。

1. 静的参照からの脱却:疎結合への執念

通常、参照設定を行うと、コンパイル時にバイナリ同士が物理的に固着する。これは大規模システムにおいて致命的な「依存の地獄」を招く。これを打破するには、「インターフェースのみを共有する共通ライブラリ(Contract DLL)」を介したロード戦略が必要だ。

メインアプリとプラグインは、以下のルールを厳守せよ。

  • Contract DLL: プラグインが実装すべき `IPlugin` インターフェースのみを定義する。
  • Main App: `Assembly.LoadFrom` を使用し、実行時に動的に型を解決する。

2. 実装:プラグインローダーの核

単にロードするだけでは素人だ。`Assembly.LoadFrom` は強力だが、使用後のメモリ解放やバージョン管理において注意が必要となる。

Imports System.Reflection
Imports System.IO

”’

”’ プラグイン管理クラス:アセンブリのライフサイクルを握る
”’

Public Class PluginManager

Public Function LoadPlugin(ByVal path As String) As IPlugin
‘ ファイル存在確認は必須。存在しない場合のフォールバックを想定せよ
If Not File.Exists(path) Then Throw New FileNotFoundException(“Plugin not found”, path)

‘ アセンブリをロード。Assembly.LoadFromは依存関係も解決するが、
‘ 読み込み先コンテキストには注意が必要
Dim assembly As Assembly = Assembly.LoadFrom(path)

‘ 型をスキャンし、IPluginインターフェースを持つクラスをインスタンス化
For Each type As Type In assembly.GetTypes()
If GetType(IPlugin).IsAssignableFrom(type) AndAlso Not type.IsInterface Then
Return DirectCast(Activator.CreateInstance(type), IPlugin)
End If
Next

Return Nothing
End Function
End Class

3. シニアエンジニアが意識すべき「メモリの深淵」

ここからが本題だ。`Assembly.LoadFrom` でロードされたアセンブリは、デフォルトでは `Load` コンテキストに属し、一度ロードされるとアプリ終了までメモリから解放されない

長期稼働するシステム(Windowsサービス等)で頻繁にプラグインを再ロードすると、確実にメモリリークを起こす。これを回避するための「極限の知見」がこれだ。

AppDomainの隔離という選択

.NET Framework環境下では、`AppDomain` を切り分けることが鉄則である。個別の `AppDomain` にプラグインをロードし、不要になったら `AppDomain.Unload(domain)` を呼び出す。これにより、アセンブリを物理的にメモリからパージできる。

‘ AppDomainを利用した隔離の概念コード
Dim setup As New AppDomainSetup()
setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory

‘ ドメインを分離してロード(これぞ職人の設計)
Dim domain As AppDomain = AppDomain.CreateDomain(“PluginDomain”, Nothing, setup)
‘ 別ドメインでインスタンス生成を行うためのプロキシクラスが必要
Dim plugin As IPlugin = domain.CreateInstanceAndUnwrap(assemblyName, typeName)

‘ 使い終わったら容赦なく破棄する
AppDomain.Unload(domain)

4. レガシー環境との共存:Win32 APIの活用

プラグインがレガシーなシステムと連携する場合、マネージドコードの枠組みだけでは解決できない事態に遭遇する。例えば、特定のWindowsウィンドウハンドルへのメッセージ送信や、アンマネージドな共有メモリへのアクセスなどだ。

その際、`Declare Function` ではなく、`DllImport` を使用せよ。

‘ 推奨されるP/Invokeの実装

Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
End Function

`SetLastError:=True` を指定することで、エラー発生時に `Marshal.GetLastWin32Error()` で詳細を追える。この「エラーを隠蔽しない」姿勢こそ、伝説的なシステムを支える土台となる。

最後に:コードは「生き物」である

プラグインアーキテクチャは、コードを「静止した遺物」から「呼吸するシステム」へと昇華させる。しかし、自由には責任が伴う。動的ロードを行う以上、型安全性の欠如や、依存ライブラリのバージョン競合(DLL Hell)は常に隣り合わせだ。

  • インターフェースを厳格に守れ。
  • アセンブリのバージョン管理(厳密名)を怠るな。
  • メモリの解放戦略を設計の最初期に組み込め。

これらを守れる者だけが、VB.NETという枯れた、しかし強力な言語で、次世代の拡張性を持つシステムを設計する権利を得る。さあ、IDEを開け。境界線を引く作業は、今この瞬間から始まる。

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