Frage an .net, 64bit, c# – Verwenden von Side-by-Side-Assemblys zum Laden der x64- oder x32-Version einer DLL

58

Wir haben zwei Versionen einer verwalteten C ++ - Assembly, eine für x86 und eine für x64. Diese Assembly wird von einer .NET-Anwendung aufgerufen, die für AnyCPU kompatibel ist. Wir stellen unseren Code über eine Dateikopie-Installation bereit und möchten dies auch weiterhin tun.

Ist es möglich, ein Side-by-Side-Assembly-Manifest zum Laden einer x86- oder x64-Assembly zu verwenden, wenn eine Anwendung ihre Prozessorarchitektur dynamisch auswählt? Oder gibt es eine andere Möglichkeit, dies in einer Dateikopie-Bereitstellung zu erreichen (z. B. ohne Verwendung des GAC)?

Deine Antwort

5   die antwort
63

mit der plattformspezifische Assemblys aus einer als AnyCPU kompilierten ausführbaren Datei geladen werden können. Die verwendete Technik kann wie folgt zusammengefasst werden:

Stellen Sie sicher, dass der standardmäßige .NET-Assembly-Lademechanismus ("Fusion" -Engine) weder die x86- noch die x64-Version der plattformspezifischen Assembly findetInstallieren Sie einen benutzerdefinierten Assembly-Resolver in der aktuellen AppDomain, bevor die Hauptanwendung versucht, die plattformspezifische Assembly zu ladenWenn die Hauptanwendung die plattformspezifische Assembly benötigt, gibt die Fusion Engine (aufgrund von Schritt 1) ​​auf und ruft unseren benutzerdefinierten Resolver (aufgrund von Schritt 2) auf. Im benutzerdefinierten Resolver ermitteln wir die aktuelle Plattform und verwenden die verzeichnisbasierte Suche, um die entsprechende DLL zu laden.

Um diese Technik zu demonstrieren, füge ich ein kurzes, befehlszeilenbasiertes Tutorial hinzu. Ich habe die resultierenden Binärdateien unter Windows XP x86 und dann unter Vista SP1 x64 getestet (indem ich die Binärdateien genau wie bei Ihrer Bereitstellung kopiert habe).

Anmerkung 1: "csc.exe" ist ein Cis-Compiler. In diesem Lernprogramm wird davon ausgegangen, dass es sich in Ihrem Pfad befindet (meine Tests verwendeten "C: \ WINDOWS \ Microsoft.NET \ Framework \ v3.5 \ csc.exe").

Anmerkung 2: Ich empfehle Ihnen, einen temporären Ordner für die Tests zu erstellen und die Befehlszeile (oder PowerShell) auszuführen, deren aktuelles Arbeitsverzeichnis auf diesen Speicherort festgelegt ist, z.

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

Schritt 1: Die plattformspezifische Assembly wird durch eine einfache C # -Klassenbibliothek dargestellt:

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

Schritt 2: Wir kompilieren plattformspezifische Assemblys mit einfachen Befehlszeilenbefehlen:

(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

Schritt 3: Das Hauptprogramm besteht aus zwei Teilen. "Bootstrapper" enthält den Haupteinstiegspunkt für die ausführbare Datei und registriert einen benutzerdefinierten Assembly-Resolver in der aktuellen Anwendungsdomäne:

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

"Programm" ist die "echte" Implementierung der Anwendung (beachten Sie, dass App.Run am Ende von Bootstrapper.Main aufgerufen wurde):

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

Schritt 4: Kompilieren Sie die Hauptanwendung in der Befehlszeile:

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

Schritt 5: Wir sind jetzt fertig. Die Struktur des von uns erstellten Verzeichnisses sollte wie folgt aussehen:

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

Wenn Sie jetzt program.exe auf einer 32-Bit-Plattform ausführen, wird platform \ x86 \ library.dll geladen. Wenn Sie program.exe auf einer 64-Bit-Plattform ausführen, wird platform \ amd64 \ library.dll geladen. Beachten Sie, dass ich Console.ReadLine () am Ende der Worker.Run-Methode hinzugefügt habe, damit Sie mithilfe des Task-Managers / Prozess-Explorers geladene DLLs untersuchen können, oder mithilfe von Visual Studio / Windows Debugger eine Verbindung zum Prozess herstellen können, um das anzuzeigen Callstack etc.

Wenn program.exe ausgeführt wird, wird unser benutzerdefinierter Assembly-Resolver an die aktuelle Anwendungsdomäne angehängt. Sobald .NET mit dem Laden der Program-Klasse beginnt, wird eine Abhängigkeit von der 'library'-Assembly angezeigt. Daher wird versucht, diese zu laden. Es wurde jedoch keine solche Assembly gefunden (da wir sie in den Unterverzeichnissen platform / * versteckt haben). Glücklicherweise kennt unser angepasster Resolver unsere Tricks und versucht, basierend auf der aktuellen Plattform, die Assembly aus dem entsprechenden Plattform / * -Unterverzeichnis zu laden.

PROCESSOR_ARCHITECTURE ist richtig - Es spiegelt tatsächlich den Prozess wider, nicht die Maschine. Fowl
Bitte aktualisieren Sie, um Environment.Is64BitProcess zu verwenden, da es sich möglicherweise von der CPU auf dem Computer unterscheidet. Ansonsten - sehr gut beantwortet - verwenden wir etwas Ähnliches. Yurik
Weiß jemand, ob dies für nicht verwaltete Assemblys funktioniert? Ich kann nicht scheinen, das zu bekommenAssemblyResolve Ereignis wird für nicht verwaltete Assemblys ausgelöst. 9ee1
Wir verwenden einen ähnlichen Ansatz, aber das Ereignis wird im statischen Konstruktor angehängt. In einigen Fällen geschieht dies, bevor .NET versucht, eine andere Assembly zu laden. Yurik
3

he Laden einer IBM-SPSS-Assembly für x64 und x86. Es löste auch Pfade für Nicht-Assembly-Support-DLLs, die von den Assemblys geladen wurden. In meinem Fall war dies bei den SPSS-DLLs der Fall.

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

Ein Unterschied zu den Lösungen mit Resolver besteht darin, dass in meinem Fall von SPSS die Assembly selbst 3 C-DLLs mit dem eigentlichen SPSS-Code umhüllt und lädt. Die C-DLLs wurden für x86 oder x64 kompiliert (daher verfügten sowohl x86 als auch x64-Assembly über einen eigenen Satz von 3 C-DLLs). Hier funktioniert ein Resolver nicht (er wählt nur die richtige Umhüllungsbaugruppe aus, funktioniert jedoch nicht für DLLs, die die Baugruppen umhüllen). wvd_vegt
23

ähnlich wie @Milan, aber mit einigen wichtigen Änderungen:

Funktioniert für ALLE DLLs, die nicht gefunden wurdenKann ein- und ausgeschaltet werden

AppDomain.CurrentDomain.SetupInformation.ApplicationBase wird anstelle von verwendetPath.GetFullPath() weil das aktuelle Verzeichnis unterschiedlich sein kann, z. In Hosting-Szenarien lädt Excel möglicherweise Ihr Plugin, das aktuelle Verzeichnis wird jedoch nicht auf Ihre DLL festgelegt.

Environment.Is64BitProcess wird anstelle von verwendetPROCESSOR_ARCHITECTUREDa wir nicht davon abhängen sollten, was das Betriebssystem ist, sondern wie dieser Prozess gestartet wurde, könnte es sich um einen x86-Prozess auf einem x64-Betriebssystem handeln. Verwenden Sie vor .NET 4IntPtr.Size == 8 stattdessen.

Rufen Sie diesen Code in einem statischen Konstruktor einer Hauptklasse auf, die vor allen anderen geladen wird.

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 spiegelt tatsächlich den Prozess wider, nicht die Maschine; VersuchenStart->Runing%SYSTEMROOT%\SysWOW64\cmd /V:ON /q /c "echo This process is: !PROCESSOR_ARCHITECTURE! && pause" Fowl
"Es könnte ein x86-Prozess auf einem x64-Betriebssystem gewesen sein" - das macht keinen Sinn. Sicherlich ist das Einzige, was davon abhängt, wie Sie als Programmierer Ihre eigene Anwendung kompilieren? Wenn Sie nicht als "irgendeine CPU" kompilieren, was nützt das überhaupt? Nyerguds
Dies ist eine Bibliothek. Es kann von verschiedenen Apps genutzt werden. Wenn jemand, der die eigentliche Exe erstellt, beschließt, "keine CPU" zu verwenden, sollten wir sie mit Sorgfalt handhaben. Verwenden Sie daher als Faustregel immer die Bitness des Prozesses, nicht den Host. Annahmen sind böse :) Yurik
Weiß jemand, ob dies für nicht verwaltete Assemblys funktioniert? Ich kann nicht scheinen, das zu bekommenAssemblyResolve Ereignis wird für nicht verwaltete Assemblys ausgelöst. 9ee1
2

Corflags Dienstprogramm zum Erzwingen des Ladens einer AnyCPU-Exe als x86- oder x64-Programmdatei, das jedoch die Anforderungen für die Bereitstellung von Dateikopien nur dann vollständig erfüllt, wenn Sie die zu kopierende Exe auf der Grundlage des Ziels auswählen.

1

. Ich habe ein einfaches Beispiel geschaffen, das dem großartigen Beispiel von Milan Gardian ähnelt. Das Beispiel, das ich erstellt habe, lädt dynamisch eine verwaltete C ++ - DLL in eine C # -DLL, die für die Any CPU-Plattform kompiliert wurde. Die Lösung verwendet das InjectModuleInitializer-Nuget-Paket, um das AssemblyResolve-Ereignis zu abonnieren, bevor die Abhängigkeiten der Assembly geladen werden.

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

Verwandte Fragen