Side-by-Side アセンブリを使用して x64 または x32 バージョンの DLL をロードする
質問
マネージド C++ アセンブリには 2 つのバージョンがあり、1 つは x86 用、もう 1 つは x64 用です。このアセンブリは、AnyCPU 用にコンパイルされた .net アプリケーションによって呼び出されます。私たちはファイル コピー インストールを介してコードをデプロイしており、今後もそうしたいと考えています。
アプリケーションがプロセッサ アーキテクチャを動的に選択しているときに、Side-by-Side アセンブリ マニフェストを使用して x86 または x64 アセンブリをそれぞれ読み込むことはできますか?それとも、ファイルコピーデプロイメントでこれを行う別の方法はありますか(例:GAC を使用していない場合)
解決
AnyCPU としてコンパイルされた実行可能ファイルからプラットフォーム固有のアセンブリをロードできるシンプルなソリューションを作成しました。使用されるテクニックは次のように要約できます。
- デフォルトの .NET アセンブリ読み込みメカニズム (「Fusion」エンジン) がプラットフォーム固有のアセンブリの x86 バージョンまたは x64 バージョンを見つけられないことを確認します。
- メイン アプリケーションがプラットフォーム固有のアセンブリを読み込む前に、現在の AppDomain にカスタム アセンブリ リゾルバーをインストールします。
- メイン アプリケーションがプラットフォーム固有のアセンブリを必要とする場合、Fusion エンジンは (ステップ 1 のため) あきらめて、(ステップ 2 のため) カスタム リゾルバーを呼び出します。カスタム リゾルバーでは、現在のプラットフォームを特定し、ディレクトリベースの検索を使用して適切な DLL をロードします。
このテクニックをデモンストレーションするために、コマンドラインベースの短いチュートリアルを添付します。結果のバイナリを Windows XP x86 でテストし、次に Vista SP1 x64 でテストしました (展開と同じようにバイナリをコピーすることによって)。
注1:「csc.exe」はC-sharpコンパイラです。このチュートリアルでは、それがパス内にあることを前提としています (私のテストでは「C:\WINDOWS\Microsoft.NET\Framework\v3.5\csc.exe」を使用していました)。
注2:テスト用の一時フォルダーを作成し、現在の作業ディレクトリがこの場所に設定されているコマンド ライン (または PowerShell) を実行することをお勧めします。
(cmd.exe)
C:
mkdir \TEMP\CrossPlatformTest
cd \TEMP\CrossPlatformTest
ステップ1:プラットフォーム固有のアセンブリは、単純な C# クラス ライブラリで表されます。
// file 'library.cs' in C:\TEMP\CrossPlatformTest
namespace Cross.Platform.Library
{
public static class Worker
{
public static void Run()
{
System.Console.WriteLine("Worker is running");
System.Console.WriteLine("(Enter to continue)");
System.Console.ReadLine();
}
}
}
ステップ2:単純なコマンドライン コマンドを使用して、プラットフォーム固有のアセンブリをコンパイルします。
(cmd.exe from Note 2)
mkdir platform\x86
csc /out:platform\x86\library.dll /target:library /platform:x86 library.cs
mkdir platform\amd64
csc /out:platform\amd64\library.dll /target:library /platform:x64 library.cs
ステップ3:メインプログラムは 2 つの部分に分かれています。「ブートストラップ」には実行可能ファイルのメイン エントリ ポイントが含まれており、現在のアプリドメインにカスタム アセンブリ リゾルバーを登録します。
// file 'bootstrapper.cs' in C:\TEMP\CrossPlatformTest
namespace Cross.Platform.Program
{
public static class Bootstrapper
{
public static void Main()
{
System.AppDomain.CurrentDomain.AssemblyResolve += CustomResolve;
App.Run();
}
private static System.Reflection.Assembly CustomResolve(
object sender,
System.ResolveEventArgs args)
{
if (args.Name.StartsWith("library"))
{
string fileName = System.IO.Path.GetFullPath(
"platform\\"
+ System.Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")
+ "\\library.dll");
System.Console.WriteLine(fileName);
if (System.IO.File.Exists(fileName))
{
return System.Reflection.Assembly.LoadFile(fileName);
}
}
return null;
}
}
}
「プログラム」はアプリケーションの「実際の」実装です (App.Run は Bootstrapper.Main の最後に呼び出されていることに注意してください)。
// file 'program.cs' in C:\TEMP\CrossPlatformTest
namespace Cross.Platform.Program
{
public static class App
{
public static void Run()
{
Cross.Platform.Library.Worker.Run();
}
}
}
ステップ4:コマンドラインでメインアプリケーションをコンパイルします。
(cmd.exe from Note 2)
csc /reference:platform\x86\library.dll /out:program.exe program.cs bootstrapper.cs
ステップ5:これで終わりです。作成したディレクトリの構造は次のようになります。
(C:\TEMP\CrossPlatformTest, root dir)
platform (dir)
amd64 (dir)
library.dll
x86 (dir)
library.dll
program.exe
*.cs (source files)
ここで、program.exe を 32 ビット プラットフォームで実行すると、platform\x86\library.dll がロードされます。64 ビット プラットフォームで Program.exe を実行すると、platform\amd64\library.dll がロードされます。Worker.Run メソッドの最後に Console.ReadLine() を追加したので、タスク マネージャー/プロセス エクスプローラーを使用してロードされた DLL を調査したり、Visual Studio/Windows デバッガーを使用してプロセスにアタッチして、コールスタックなど
Program.exe が実行されると、カスタム アセンブリ リゾルバーが現在のアプリドメインに接続されます。.NET は Program クラスの読み込みを開始するとすぐに、「ライブラリ」アセンブリへの依存関係を認識し、そのアセンブリの読み込みを試みます。ただし、そのようなアセンブリは見つかりません (platform/* サブディレクトリに隠したため)。幸いなことに、カスタム リゾルバーは私たちのトリックを知っており、現在のプラットフォームに基づいて、適切な platform/* サブディレクトリからアセンブリをロードしようとします。
他のヒント
私のバージョンは @Milan に似ていますが、いくつかの重要な変更があります。
- 見つからなかったすべての DLL で動作します
- オンとオフを切り替えることができます
AppDomain.CurrentDomain.SetupInformation.ApplicationBase
の代わりに使用されますPath.GetFullPath()
現在のディレクトリが異なる可能性があるためです。ホスティング シナリオでは、Excel がプラグインを読み込む可能性がありますが、現在のディレクトリは DLL に設定されません。Environment.Is64BitProcess
の代わりに使用されますPROCESSOR_ARCHITECTURE
, OS が何かではなく、このプロセスがどのように開始されたかに依存する必要があるため、x64 OS 上の x86 プロセスである可能性があります。.NET 4 より前では、次を使用します。IntPtr.Size == 8
その代わり。
このコードは、他のすべての前にロードされるメイン クラスの静的コンストラクター内で呼び出します。
public static class MultiplatformDllLoader
{
private static bool _isEnabled;
public static bool Enable
{
get { return _isEnabled; }
set
{
lock (typeof (MultiplatformDllLoader))
{
if (_isEnabled != value)
{
if (value)
AppDomain.CurrentDomain.AssemblyResolve += Resolver;
else
AppDomain.CurrentDomain.AssemblyResolve -= Resolver;
_isEnabled = value;
}
}
}
}
/// Will attempt to load missing assembly from either x86 or x64 subdir
private static Assembly Resolver(object sender, ResolveEventArgs args)
{
string assemblyName = args.Name.Split(new[] {','}, 2)[0] + ".dll";
string archSpecificPath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
Environment.Is64BitProcess ? "x64" : "x86",
assemblyName);
return File.Exists(archSpecificPath)
? Assembly.LoadFile(archSpecificPath)
: null;
}
}
SetDllDirectory を見てください。私はこれを、x64 と x86 の両方の IBM spss アセンブリの動的ロードに使用しました。また、私の場合は spss dll の場合でしたが、アセンブリによって読み込まれた非アセンブリサポート dll のパスも解決しました。
http://msdn.microsoft.com/en-us/library/ms686203%28VS.85%29.aspx
使用できます コルフラッグ AnyCPU exe を x86 または x64 実行可能ファイルとして強制的にロードするユーティリティですが、ターゲットに基づいてコピーする exe を選択しない限り、ファイル コピーの展開要件を完全に満たすことはできません。
このソリューションは、非マネージド アセンブリでも機能します。Milan Gardian の素晴らしい例に似た簡単な例を作成しました。私が作成した例では、マネージド C++ DLL を、Any CPU プラットフォーム用にコンパイルされた C# DLL に動的に読み込みます。このソリューションでは、InjectModuleInitializer nuget パッケージを使用して、アセンブリの依存関係が読み込まれる前に AssemblyResolve イベントをサブスクライブします。