我們有兩個版本的託管C++程序集,一個用於x86,一個用於x64。該程序集由AnyCPU編譯的.net應用程序調用。我們正在通過文件複製安裝部署我們的代碼,並希望繼續這樣做。使用並行程序集加載DLL的x64或x32版本
當應用程序動態選擇它的處理器架構時,是否可以使用並行程序集清單分別加載x86或x64程序集?或者還有另一種方法可以在文件複製部署中完成此操作(例如,不使用GAC)?
我們有兩個版本的託管C++程序集,一個用於x86,一個用於x64。該程序集由AnyCPU編譯的.net應用程序調用。我們正在通過文件複製安裝部署我們的代碼,並希望繼續這樣做。使用並行程序集加載DLL的x64或x32版本
當應用程序動態選擇它的處理器架構時,是否可以使用並行程序集清單分別加載x86或x64程序集?或者還有另一種方法可以在文件複製部署中完成此操作(例如,不使用GAC)?
我創建了一個簡單的解決方案,它能夠從編譯爲AnyCPU的可執行文件加載平臺特定的程序集。使用可歸納爲技術如下:
爲了演示這種技術,我附上了一個簡短的基於命令行的教程。我測試了在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/*子目錄中)。幸運的是,我們的自定義解析器知道我們的詭計,並基於當前平臺嘗試從適當的平臺/ *子目錄加載程序集。
您可以使用corflags實用程序來強制AnyCPU exe作爲x86或x64可執行文件加載,但除非您根據目標選擇要複製的哪個exe文件,否則不會完全滿足文件複製部署要求。
我的版本,類似於@Milan,但有幾個重要的變化:
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;
}
}
看看SetDllDirectory。我用它來動態加載用於x64和x86的IBM spss程序集。它也解決了在我的情況下,非程序集支持dll由程序集加載的路徑與spss dll的情況。
http://msdn.microsoft.com/en-us/library/ms686203%28VS.85%29.aspx
該解決方案還可以將非託管程序正常工作。我創建了一個類似於米蘭加迪安偉大榜樣的簡單例子。我創建的示例動態地將Managed C++ dll加載到爲Any CPU平臺編譯的C#dll中。該解決方案使用InjectModuleInitializer nuget包在加載程序集的依賴關係之前訂閱AssemblyResolve事件。
我們使用類似的方法,但該事件附加在靜態構造函數 - 這種方式在某些情況下,附件之前發生。NET嘗試加載另一個程序集。 – Yurik 2012-03-21 04:00:26
請更新以使用Environment.Is64BitProcess - 因爲它可能與機器上的CPU不同。否則 - 很好地回答 - 我們正在使用類似的東西。 – Yurik 2012-03-30 22:57:00
PROCESSOR_ARCHITECTURE是正確的 - 它實際上反映了進程而不是機器。 – Fowl 2013-06-27 06:58:44