2008-09-20 90 views
56

我們有兩個版本的託管C++程序集,一個用於x86,一個用於x64。該程序集由AnyCPU編譯的.net應用程序調用。我們正在通過文件複製安裝部署我們的代碼,並希望繼續這樣做。使用並行程序集加載DLL的x64或x32版本

當應用程序動態選擇它的處理器架構時,是否可以使用並行程序集清單分別加載x86或x64程序集?或者還有另一種方法可以在文件複製部署中完成此操作(例如,不使用GAC)?

回答

60

我創建了一個簡單的解決方案,它能夠從編譯爲AnyCPU的可執行文件加載平臺特定的程序集。使用可歸納爲技術如下:

  1. 確保默認.NET程序集加載機制(「融合」引擎)找不到x86或特定於平臺的組裝
  2. 的64位版本的主要前應用程序嘗試加載特定於平臺的程序集,在當前AppDomain中安裝自定義程序集解析器現在,當主應用程序需要特定於平臺的程序集時,Fusion引擎將放棄(因爲步驟1)並調用我們的自定義解析器(由於步驟2);在自定義解析器中,我們確定當前平臺並使用基於目錄的查找來加載適當的DLL。

爲了演示這種技術,我附上了一個簡短的基於命令行的教程。我測試了在Windows XP x86和Vista SP1 x64上產生的二進制文件(通過複製二進制文件,就像部署一樣)。

注1:「csc.exe」是C-sharp編譯器。本教程假定它是在你的路徑(我的測試中,使用「C:\ WINDOWS \ Microsoft.NET \框架\ 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:主程序分爲兩部分。 「引導程序」包含主入口點的可執行文件和它註冊在當前的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; 
     } 
    } 
} 

「程序」是「真正的」執行的應用程序(注意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開始加載Program類,它就會看到對'library'程序集的依賴,所以它會嘗試加載它。但是,沒有找到這樣的程序集(因爲我們已經將它隱藏在platform/*子目錄中)。幸運的是,我們的自定義解析器知道我們的詭計,並基於當前平臺嘗試從適當的平臺/ *子目錄加載程序集。

+1

我們使用類似的方法,但該事件附加在靜態構造函數 - 這種方式在某些情況下,附件之前發生。NET嘗試加載另一個程序集。 – Yurik 2012-03-21 04:00:26

+3

請更新以使用Environment.Is64BitProcess - 因爲它可能與機器上的CPU不同。否則 - 很好地回答 - 我們正在使用類似的東西。 – Yurik 2012-03-30 22:57:00

+1

PROCESSOR_ARCHITECTURE是正確的 - 它實際上反映了進程而不是機器。 – Fowl 2013-06-27 06:58:44

2

您可以使用corflags實用程序來強制AnyCPU exe作爲x86或x64可執行文件加載,但除非您根據目標選擇要複製的哪個exe文件,否則不會完全滿足文件複製部署要求。

21

我的版本,類似於@Milan,但有幾個重要的變化:

  • 作品對於未找到所有dll
  • 在開與關
  • ​​3210被關是用來代替Path.GetFullPath(),因爲當前目錄可能不同,例如在託管方案中,Excel可能會加載您的插件,但當前目錄不會設置爲您的DLL。

  • Environment.Is64BitProcess被用來代替PROCESSOR_ARCHITECTURE,因爲我們不應該依賴於操作系統是什麼,而是如何啓動這個過程 - 它可能是x64操作系統上的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; 
    } 
} 
1

該解決方案還可以將非託管程序正常工作。我創建了一個類似於米蘭加迪安偉大榜樣的簡單例子。我創建的示例動態地將Managed C++ dll加載到爲Any CPU平臺編譯的C#dll中。該解決方案使用InjectModuleInitializer nuget包在加載程序集的依賴關係之前訂閱AssemblyResolve事件。

https://github.com/kevin-marshall/Managed.AnyCPU.git

相關問題