58


60

マネージドCアセンブリには、x86用とx64用の2つのバージョンがあります。 このアセンブリは、AnyCPUに準拠した.netアプリケーションによって呼び出されます。 私たちはファイルコピーインストールを通して私たちのコードをデプロイしています、そしてそうし続けたいです。

アプリケーションがそのプロセッサアーキテクチャを動的に選択しているときに、サイドバイサイドアセンブリマニフェストを使用してそれぞれx86またはx64アセンブリをロードすることは可能ですか。 または、ファイルコピーの展開でこれを実現するための別の方法があります(例: GACを使用していませんか?

5 Answer


63


AnyCPUとしてコンパイルされた実行可能ファイルからプラットフォーム固有のアセンブリをロードできる簡単なソリューションを作成しました。 使用される手法は次のようにまとめることができます。

  1. デフォルトの.NETアセンブリロードメカニズム( "Fusion"エンジン)がx86またはx64バージョンのプラットフォーム固有のアセンブリを見つけられないことを確認します

  2. メインアプリケーションがプラットフォーム固有のアセンブリをロードする前に、現在のAppDomainにカスタムアセンブリリゾルバをインストールします。

  3. メインアプリケーションがプラットフォーム固有のアセンブリを必要とするとき、Fusionエンジンは(ステップ1のために)あきらめて(ステップ2のために)カスタムリゾルバを呼び出します。カスタムリゾルバでは、現在のプラットフォームを決定し、ディレクトリベースのルックアップを使用して適切なDLLをロードします。

このテクニックを実証するために、短いコマンドラインベースのチュートリアルを添付します。 結果として得られたバイナリをWindows XP x86、次にVista SP1 x64でテストしました(展開を実行するのと同じように、バイナリをコピーして)。

*注意1 *: "csc.exe"はCシャープコンパイラです。 このチュートリアルはあなたのパスにあると仮定します(私のテストは "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
名前空間Cross.Platform.Library {public static class Worker {public static void Run(){System.Console.WriteLine( "Worker is running"); System.Console.WriteLine( "(続行するにはEnter)"); System.Console.ReadLine(); }}}

*ステップ2 *:簡単なコマンドラインコマンドを使ってプラットフォーム固有のアセンブリをコンパイルします。

(注2のcmd.exe)mkdir platform \ x86 csc /out:platform\x86\library.dll /ターゲット:library / platform:x86 library.cs mkdir platform \ amd64 csc /out:platform\amd64\library.dll /ターゲット:ライブラリ/プラットフォーム:x64 library.cs

*ステップ3 *:メインプログラムは2つの部分に分けられます。 「Bootstrapper」は実行可能ファイルのメインエントリポイントを含み、現在のアプリケーションドメインにカスタムアセンブリリゾルバを登録します。

// file 'bootstrapper.cs' in C:\TEMP\CrossPlatformTest
名前空間Cross.Platform.Program {public static class Bootstrapper {public static void Main(){System.AppDomain.CurrentDomain.AssemblyResolve = CustomResolve; App.Run(); }

プライベート静的System.Reflection.Assembly CustomResolve(オブジェクト送信側、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); nullを返します。 }}}

「プログラム」は、アプリケーションの「実際の」実装です(App.RunはBootstrapper.Mainの最後に呼び出されたことに注意してください)。

// file 'program.cs' in C:\TEMP\CrossPlatformTest
名前空間Cross.Platform.Program {public static class App {public static void Run(){Cross.Platform.Library.Worker.Run(); }}}

*ステップ4 *:コマンドラインでメインアプリケーションをコンパイルします。

(注2のcmd.exe)csc /reference:platform\x86\library.dll /out:program.exe program.cs bootstrapper.cs

*ステップ5 *:これで終わりです。 作成したディレクトリの構造は次のとおりです。

(C:\ TEMP \ CrossPlatformTest、ルートディレクトリ)プラットフォーム(ディレクトリ)amd64(ディレクトリ)library.dll x86(ディレクトリ)library.dll program.exe * .cs(ソースファイル)

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がProgramクラスのロードを開始するとすぐに、それは 'library’アセンブリへの依存関係を見るので、それをロードしようとします。 ただし、そのようなアセンブリは見つかりません(これをplatform / *サブディレクトリに隠しているため)。 幸いなことに、私たちのカスタムリゾルバは私たちのトリックを知っていて、現在のプラットフォームに基づいて適切なplatform / *サブディレクトリからアセンブリをロードしようとします。


23


私のバージョンは@Milanに似ていますが、いくつかの重要な変更があります。

  • 見つからなかったすべてのDLLに対して機能

  • オンとオフを切り替えることができます

  • `AppDomain.CurrentDomain.SetupInformation.ApplicationBase`が使用されます 現在のディレクトリが異なる場合があるため、 `Path.GetFullPath()`の代わりに ホスティングのシナリオでは、Excelがプラグインをロードする可能性がありますが、現在のディレクトリはDLLに設定されません。

  • 代わりに `Environment.Is64BitProcess`が使用されます 「PROCESSOR_ARCHITECTURE」。OSに依存するべきではなく、このプロセスがどのように開始されたかに依存するべきではありません。x64OS上の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;
    }
}


3


SetDllDirectoryを見てください。 私はそれをx64とx86の両方のためのIBM spssアセンブリの動的ロードの周りに使いました。 それはまた私の場合のアセンブリによってロードされた非アセンブリサポートdllがspss dllの場合であるというパスを解決しました。


2


http://msdn.microsoft.com/ja-jp/library/ms164699(VS.80).aspx[corflags]ユーティリティを使用すると、AnyCPU exeをx86またはx64の実行可能ファイルとして強制的に読み込むことができますが、これは不可能です。ターゲットに基づいてコピーするexeファイルを選択しない限り、ファイルコピーの展開要件を完全に満たします。


1


このソリューションは、管理されていないアセンブリに対しても機能します。 私はMilan Gardianのすばらしい例に似た簡単な例を作成しました。 私が作成した例では、Any CPUプラットフォーム用にコンパイルされたC#dllにManaged C dllを動的にロードします。 このソリューションは、InjectModuleInitializer nugetパッケージを使用して、アセンブリの依存関係が読み込まれる前にAssemblyResolveイベントをサブスクライブします。