マネージC++アセンブリには、x86用とx64用の2つのバージョンがあります。このアセンブリは、AnyCPUに準拠した.netアプリケーションによって呼び出されます。ファイルコピーインストールを介してコードを展開していますが、引き続き実行したいと考えています。
アプリケーションがプロセッサアーキテクチャを動的に選択しているときに、Side-by-Side Assemblyマニフェストを使用して、それぞれx86またはx64アセンブリを読み込むことは可能ですか?または、ファイルコピーの展開でこれを行う別の方法がありますか(GACを使用しないなど)?
AnyCPUとしてコンパイルされた実行可能ファイルからプラットフォーム固有のアセンブリをロードできるシンプルなソリューションを作成しました。使用される手法は、次のように要約できます。
この手法を示すために、コマンドラインベースの短いチュートリアルを添付します。 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
ステップ:メインプログラムは2つの部分に分割されます。 「Bootstrapper」には実行可能ファイルのメインエントリポイントが含まれており、現在のappdomainにカスタムアセンブリリゾルバーを登録します。
// 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;
}
}
}
「プログラム」は、アプリケーションの「実際の」実装です(Bootstrapper.Mainの最後にApp.Runが呼び出されたことに注意してください)。
// 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)
32ビットプラットフォームでprogram.exeを実行すると、platform\x86\library.dllがロードされます。 64ビットプラットフォームでprogram.exeを実行すると、platform\AMD64\library.dllがロードされます。 Worker.Runメソッドの最後にConsole.ReadLine()を追加したので、タスクマネージャー/プロセスエクスプローラーを使用して読み込まれたDLLを調査したり、Visual Studio/Windowsデバッガーを使用してプロセスにアタッチして、呼び出しスタックなど.
Program.exeを実行すると、カスタムアセンブリリゾルバーが現在のappdomainにアタッチされます。 .NETがプログラムクラスのロードを開始するとすぐに、「ライブラリ」アセンブリへの依存関係が検出されるため、ロードが試行されます。ただし、そのようなアセンブリは見つかりません(platform/*サブディレクトリに非表示にしているため)。幸いなことに、カスタムリゾルバは私たちのトリックを知っており、現在のプラットフォームに基づいて、適切なplatform/*サブディレクトリからアセンブリをロードしようとします。
私のバージョンは@Milanに似ていますが、いくつかの重要な変更があります。
Path.GetFullPath()
の代わりにAppDomain.CurrentDomain.SetupInformation.ApplicationBase
が使用されます。現在のディレクトリが異なる可能性があるためです。ホスティングシナリオでは、Excelがプラグインをロードする可能性がありますが、現在のディレクトリはDLLに設定されません。
Environment.Is64BitProcess
の代わりにPROCESSOR_ARCHITECTURE
が使用されます。これは、OSが何であるかではなく、このプロセスがどのように開始されたかに依存するべきではないからです。 .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アセンブリの動的なロードでそれを使用しました。また、私の場合、アセンブリによってロードされた非アセンブリサポートdllのパスも解決されました。これはspss dllの場合です。
http://msdn.Microsoft.com/en-us/library/ms686203%28VS.85%29.aspx
corflags ユーティリティを使用してAnyCPU exeを強制的にx86またはx64実行可能ファイルとしてロードできますが、ターゲットに基づいてどのexeをコピーするかを選択しない限り、ファイルコピーの展開要件を完全に満たしません。
このソリューションは、非マネージアセンブリでも機能します。 Milan Gardianの素晴らしい例に似た簡単な例を作成しました。私が作成した例では、マネージC++ dllをAny CPUプラットフォーム用にコンパイルされたC#dllに動的にロードします。このソリューションでは、InjectModuleInitializer nugetパッケージを使用して、アセンブリの依存関係が読み込まれる前にAssemblyResolveイベントをサブスクライブします。