Pytanie w sprawie .net, c#, 64bit – Używanie zestawów Side-by-Side do ładowania wersji x64 lub x32 biblioteki DLL

58

Mamy dwie wersje zarządzanego zestawu C ++, jedną dla x86 i jedną dla x64. Ten zespół jest wywoływany przez aplikację .net zgodną z AnyCPU. Wdrażamy nasz kod za pomocą pliku instalacyjnego i chcielibyśmy to kontynuować.

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

Twoja odpowiedź

5   odpowiedzi
63

które umożliwia ładowanie zestawu specyficznego dla platformy z pliku wykonywalnego skompilowanego jako AnyCPU. Zastosowaną technikę można podsumować w następujący sposób:

Upewnij się, że domyślny mechanizm ładowania zestawu .NET (silnik „Fusion”) nie może znaleźć wersji specyficznej dla platformy x86 lub x64Zanim główna aplikacja spróbuje załadować zestaw specyficzny dla platformy, zainstaluj niestandardowy resolver złożenia w bieżącej domenie aplikacjiTeraz, gdy główna aplikacja potrzebuje specyficznego dla platformy zespołu, silnik Fusion zrezygnuje (z powodu kroku 1) i wywoła nasz niestandardowy resolver (z powodu kroku 2); w niestandardowym resolveru określamy aktualną platformę i używamy wyszukiwania opartego na katalogu do załadowania odpowiedniej biblioteki DLL.

Aby zademonstrować tę technikę, dołączam krótki samouczek oparty na wierszu poleceń. Przetestowałem uzyskane pliki binarne w systemie Windows XP x86, a następnie w systemie Vista SP1 x64 (kopiując pliki binarne, tak jak w przypadku wdrożenia).

Notatka 1: „csc.exe” jest kompilatorem o ostrym kształcie litery C. Ten samouczek zakłada, że ​​znajduje się on na twojej ścieżce (moje testy używały „C: WINDOWS Microsoft.NET Framework v3.5 csc.exe”)

Uwaga 2: Zalecam utworzenie tymczasowego folderu dla testów i uruchomienie wiersza poleceń (lub PowerShell), którego bieżący katalog roboczy jest ustawiony na tę lokalizację, np.

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

Krok 1: Zestaw specyficzny dla platformy jest reprezentowany przez prostą bibliotekę klasy 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();
        }
    }
}

Krok 2: Kompilujemy zestawy specyficzne dla platformy za pomocą prostych poleceń wiersza polecenia:

(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

Krok 3: Główny program jest podzielony na dwie części. „Bootstrapper” zawiera główny punkt wejścia dla pliku wykonywalnego i rejestruje niestandardowy resolver zespołu w bieżącej domenie aplikacji:

// 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” to „prawdziwa” implementacja aplikacji (zwróć uwagę, że App.Run został wywołany pod koniec Bootstrapper.Main):

// 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: Skompiluj główną aplikację na linii poleceń:

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

Krok 5: Jesteśmy teraz skończeni. Struktura utworzonego katalogu 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 uruchomisz program.exe na platformie 32-bitowej, załadowana zostanie platforma b86.dll. jeśli uruchomisz program.exe na platformie 64-bitowej, 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ć programu Visual Studio / Windows Debugger, aby dołączyć do procesu, aby zobaczyć stos wywołań itp.

Po uruchomieniu programu.exe nasz niestandardowy przelicznik składania jest dołączany do bieżącej domeny aplikacji. Gdy tylko .NET zacznie ładować klasę programu, widzi zależność od zespołu 'biblioteka', więc próbuje ją załadować. Nie znaleziono jednak takiego zespołu (ponieważ ukryliśmy go w podkatalogach platform / *). Na szczęście nasz niestandardowy przelicznik zna nasze sztuczki i na podstawie bieżącej platformy próbuje załadować zespół z odpowiedniego podkatalogu platformy / *.

Czy ktoś wie, czy to działa dla niezarządzanych zespołów? Nie mogę dostać tegoAssemblyResolve zdarzenie do uruchomienia dla niezarządzanych zespołów. 9ee1
PROCESSOR_ARCHITECTURE jest poprawne - w rzeczywistości odzwierciedla proces, a nie maszynę. Fowl
Zaktualizuj, aby użyć Environment.Is64BitProcess - ponieważ może się różnić od procesora w komputerze. W przeciwnym razie - bardzo dobrze odpowiemy - używamy czegoś podobnego. Yurik
Używamy podobnego podejścia, ale zdarzenie jest dołączane w konstruktorze statycznym - w ten sposób w niektórych przypadkach załącznik występuje, zanim .NET spróbuje załadować inny zespół. Yurik
23

Działa dla WSZYSTKICH bibliotek DLL, które nie zostały znalezioneMożna włączyć i wyłączyć

AppDomain.CurrentDomain.SetupInformation.ApplicationBase jest używany zamiastPath.GetFullPath() ponieważ bieżący katalog może być inny, np. w scenariuszach hostingu program Excel może załadować wtyczkę, ale bieżący katalog nie zostanie ustawiony na bibliotekę DLL.

Environment.Is64BitProcess jest używany zamiastPROCESSOR_ARCHITECTURE, ponieważ nie powinniśmy polegać na systemie operacyjnym, a raczej na sposobie uruchomienia tego procesu - mógł to być proces x86 na systemie operacyjnym x64. Przed .NET 4 użyjIntPtr.Size == 8 zamiast.

Wywołaj ten kod w konstruktorze statycznym jakiejś głównej klasy, która jest ładowana przed wszystkim.

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;
    }
}
PROCESSOR_ARCHITECTURE faktycznie odzwierciedla proces, a nie maszynę; próbowaćStart->Runing%SYSTEMROOT%\SysWOW64\cmd /V:ON /q /c "echo This process is: !PROCESSOR_ARCHITECTURE! && pause" Fowl
„mógł to być proces x86 na systemie operacyjnym x64” - to nie ma sensu. Z pewnością jedyną rzeczą, która decyduje o tym, w jaki sposób, jako programista, wybierasz kompilację własnej aplikacji? Jeśli nie skompilujesz się jako „jakikolwiek procesor”, to po co w pierwszej kolejności? Nyerguds
Czy ktoś wie, czy to działa dla niezarządzanych zespołów? Nie mogę dostać tegoAssemblyResolve zdarzenie do uruchomienia dla niezarządzanych zespołów. 9ee1
To jest biblioteka. Może być używany przez różne aplikacje. Jeśli ktoś, kto tworzy rzeczywisty exe, zdecyduje, że nie użyje żadnego procesora, powinniśmy się nim zająć z wdziękiem. Dlatego też, zgodnie z ogólną zasadą, zawsze używaj świadków tego procesu, a nie gospodarza. Założenia są złe :) Yurik
2

korale narzędzie do wymuszenia załadowania exe AnyCPU jako pliku wykonywalnego x86 lub x64, ale nie spełnia to całkowicie wymogu wdrożenia kopiowania plików, chyba że wybierzesz exe do skopiowania na podstawie celu.

1

rzyłem prosty przykład podobny do świetnego przykładu Milana Gardiana. Utworzony przykład dynamicznie ładuje bibliotekę Managed C ++ do biblioteki C # dll skompilowanej dla platformy Any CPU. Rozwiązanie korzysta z pakietu nuget InjectModuleInitializer, aby subskrybować zdarzenie AssemblyResolve przed załadowaniem zależności zespołu.

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

3

IBM spss dla x64 i x86. Rozwiązano również ścieżki dla obsługi nieobsługiwanej przez dll załadowane przez zespoły w moim przypadku było w przypadku dll spss.

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

Różnica w stosunku do rozwiązań wykorzystujących Resolver polega na tym, że w moim przypadku SPSS sam zespół zawijał i ładował 3 dll C, zawierający rzeczywisty kod SPSS. Dll C zostały skompilowane dla x86 lub x64 (tak więc zarówno x86, jak i x64 miały swój własny zestaw 3 C dll). Tutaj Resolver nie będzie działał (wybierze tylko prawidłowy zespół owijający, ale nie będzie działał dla dll w opakowaniach montażowych). wvd_vegt

Powiązane pytania