2008-09-20 11 views
56

Mamy dwie wersje zarządzanego zestawu C++, jedną dla x86 i jedną dla x64. Ten zestaw jest wywoływany przez aplikację .net zgodną z AnyCPU. Wdrażamy nasz kod poprzez instalację kopii pliku i chcielibyśmy to kontynuować.Korzystanie z zestawów Side-by-Side do ładowania wersji x64 lub x32 biblioteki DLL

Czy jest możliwe użycie manifestu zespołu Side-by-Side do ładowania zestawu x86 lub x64 odpowiednio, gdy aplikacja wybiera dynamicznie architekturę procesora? Czy istnieje inny sposób, aby to zrobić w przypadku wdrożenia kopii pliku (np. Nie używając GAC)?

Odpowiedz

60

Stworzyłem proste rozwiązanie, które jest w stanie załadować specyficzny dla platformy zestaw z pliku wykonywalnego skompilowanego jako AnyCPU. Technika stosowana można podsumować w następujący sposób:

  1. Upewnij domyślnego mechanizmu ładowania .NET montaż („Fusion” silnika) nie można znaleźć zarówno w wersji x86 lub x64 zespołu platformy specyficzne
  2. przed głównym Próby aplikacji ładują specyficzny dla platformy montaż, instalują niestandardowy resolver zespołu w bieżącej aplikacji AppDomain
  3. Teraz, gdy główna aplikacja potrzebuje zespołu specyficznego dla platformy, silnik Fusion zrezygnuje (z powodu kroku 1) i wywoła nasz niestandardowy przelicznik (z powodu kroku 2); w niestandardowym przeliczniku określamy aktualną platformę i korzystamy z wyszukiwania opartego na katalogu, aby załadować odpowiednią bibliotekę DLL.

Aby zademonstrować tę technikę, dołączam krótki samouczek oparty na wierszu poleceń. Przetestowałem powstałe pliki binarne w systemie Windows XP x86, a następnie Vista SP1 x64 (przez skopiowanie plików binarnych, podobnie jak w przypadku wdrożenia).

Uwaga 1: "csc.exe" jest kompilatorem C-sharp. Ten poradnik zakłada, że ​​jest na swojej drodze (moje testy były przy użyciu "C: \ WINDOWS \ Microsoft.NET \ Framework \ v3.5 \ csc.exe")

UWAGA 2: Polecam utworzyć folder tymczasowy dla testów i uruchom linię poleceń (lub powershell), których aktualny katalog roboczy jest ustawiony na tę lokalizację, np

(cmd.exe) 
C: 
mkdir \TEMP\CrossPlatformTest 
cd \TEMP\CrossPlatformTest 

Etap 1: Zespół od platformy jest reprezentowany przez proste C# biblioteki klas:

// 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(); 
     } 
    } 
} 

Etap 2: W kompilacji zespołów danej platformy za pomocą komend linii poleceń :

(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 

Etap 3: Główny program jest podzielony na dwie części. „Inicjującego” zawiera główny punkt wejścia do pliku wykonywalnego i rejestruje resolverowi zwyczaj składania w bieżącym 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; 
     } 
    } 
} 

„Program” jest „prawdziwy” Wdrażanie aplikacji (zauważ, że App.Run została wywołana u koniec Bootstrapper.Główny):

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

Krok 4: Kompilacja główny wniosek z linii komend:

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

Krok 5: Mamy teraz zakończona. Struktura katalogu stworzyliśmy powinna być następująca:

(C:\TEMP\CrossPlatformTest, root dir) 
    platform (dir) 
     amd64 (dir) 
      library.dll 
     x86 (dir) 
      library.dll 
    program.exe 
    *.cs (source files) 

Jeśli teraz uruchomić program.exe na platformie 32-bitowej, platforma \ x86 \ library.dll zostanie załadowany; jeśli uruchomisz program.exe na 64-bitowej platformie, zostanie załadowana platforma \ amd64 \ library.dll. Zauważ, że dodałem Console.ReadLine() na końcu metody Worker.Run, dzięki czemu możesz użyć menedżera zadań/eksploratora procesów do zbadania załadowanych bibliotek DLL lub możesz użyć Visual Studio/Windows Debugger, aby dołączyć do procesu, aby zobaczyć stos wywołań itp.

Po uruchomieniu programu program.exe nasz moduł rozwiązywania problemów z połączeniem niestandardowym jest dołączany do bieżącego appdomain. Gdy tylko .NET rozpoczyna ładowanie klasy Program, widzi zależność od złożenia "biblioteki", więc próbuje załadować ją. Jednak nie znaleziono takiego zestawu (ponieważ ukryliśmy go w podkatalogach platform/*). Na szczęście nasz niestandardowy resolver zna naszą sztuczkę i bazuje na bieżącej platformie, którą próbuje załadować złożenie z odpowiedniego podkatalogu platform/*.

+1

Używamy podobne podejście, ale zdarzenie jest dołączony w statycznym konstruktorze - ten sposób w niektórych przypadkach dzieje się przed przywiązanie.NET próbuje załadować inny zestaw. – Yurik

+3

Proszę zaktualizować, aby użyć Environment.Is64BitProcess - ponieważ może się różnić od procesora na komputerze. W przeciwnym razie - bardzo dobrze odpowiedziano - używamy czegoś podobnego. – Yurik

+1

"PROCESSOR_ARCHITECTURE" jest poprawne - w rzeczywistości odzwierciedla proces, a nie maszynę. – Fowl

2

Możesz użyć narzędzia corflags, aby wymusić plik EXE AnyCPU, który zostanie załadowany jako plik wykonywalny x86 lub x64, ale to nie spełni całkowicie wymogu wdrożenia kopii pliku, chyba że wybierzesz wersję do skopiowania na podstawie celu.

21

Moja wersja, podobnie jak @Milan, ale z kilku istotnych zmian:

  • działa dla wszystkich bibliotek DLL, które nie zostały znalezione
  • można włączać i wyłączać
  • AppDomain.CurrentDomain.SetupInformation.ApplicationBase jest używany zamiast Path.GetFullPath(), ponieważ katalog bieżący może być inny, np w scenariuszach hostowania, Excel może załadować twoją wtyczkę, ale bieżący katalog nie zostanie ustawiony na twoją bibliotekę DLL.

  • jest używany zamiast PROCESSOR_ARCHITECTURE, ponieważ nie powinniśmy polegać na tym, czym jest OS, raczej jak ten proces został uruchomiony - mógł to być proces x86 na systemie operacyjnym x64. Przed .NET 4, zamiast tego użyj IntPtr.Size == 8.

Wywołanie tego kodu w statycznym konstruktorze klasy głównej ładowanej przed wszystkim innym.

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; 
    } 
} 
+0

"PROCESSOR_ARCHITECTURE" faktycznie odzwierciedla proces, a nie maszynę; spróbuj 'Start-> Uruchom' '% SYSTEMROOT% \ SysWOW64 \ cmd/V: ON/q/c" echo Ten proces:! PROCESSOR_ARCHITECTURE! && pause "' – Fowl

+0

_ "mógł to być proces x86 na systemie operacyjnym x64 "_ - to nie ma sensu. Na pewno jedyną rzeczą, która decyduje o tym, jest to, jak ty, jako programista, wybierasz skompilowanie własnej aplikacji? Jeśli nie skompilujesz się jako "jakikolwiek procesor", to jaki jest pożytek z tego? – Nyerguds

+4

To jest biblioteka. Może być używany przez różne aplikacje. Jeśli ktoś, kto tworzy rzeczywisty exe, decyduje się nie używać "jakiegokolwiek procesora", powinniśmy z wdziękiem sobie z tym poradzić. Stąd, z reguły, zawsze używaj bitness procesu, a nie hosta. Założenia są złe :) – Yurik

3

Zobacz SetDllDirectory. Użyłem go do dynamicznego ładowania zestawu IBM spss dla x64 i x86. Rozwiązał również ścieżki dla niezespolonych bibliotek dll obsługiwanych przez złożenia w moim przypadku było w przypadku bibliotek SPSS.

http://msdn.microsoft.com/en-us/library/ms686203%28VS.85%29.aspx

+0

Różnica w stosunku do rozwiązań wykorzystujących rozwiązanie Resolver polega na tym, że w moim przypadku SPSS sam proces pakował i ładował pliki dll 3 C, sam zawierający aktualny kod SPSS. Pliki dll C, w których zostały skompilowane dla x86 lub x64 (tak więc zarówno zestaw x86, jak i x64 miał własny zestaw dll 3 C). Tutaj Resolver nie będzie działał (wybierze tylko prawidłowy zestaw do owijania, ale nie będzie działać dla biblioteki dll związanej z montażem). –

1

To rozwiązanie może pracować dla niezarejestrowanych zarządzanych zespołów, jak również. Stworzyłem prosty przykład podobny do świetnego przykładu Milana Gardiana. Utworzony przeze mnie przykład dynamicznie ładuje bibliotekę dll Managed C++ do biblioteki dll C# skompilowanej dla platformy Any CPU. Rozwiązanie wykorzystuje pakiet nuget InjectModuleInitializer do subskrybowania zdarzenia AssemblyResolve, zanim zostaną załadowane zależności zespołu.

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

Powiązane problemy