web-dev-qa-db-de.com

Verwenden einer 32-Bit- oder 64-Bit-DLL in C # DllImport

Hier ist die Situation, ich verwende eine C-basierte DLL in meiner dot.net-Anwendung. Es gibt 2 DLLs, eine 32-Bit-Version mit dem Namen MyDll32.dll und die andere 64-Bit-Version mit dem Namen MyDll64.dll.

Es gibt eine statische Variable, die den Dateinamen DLL: string DLL_FILE_NAME enthält.

und es wird folgendermaßen verwendet:

[DllImport(DLL_FILE_NAME, CallingConvention=CallingConvention.Cdecl, EntryPoint=Func1")]
private static extern int is_Func1(int var1, int var2);

Soweit einfach.

Wie Sie sich vorstellen können, wird die Software mit aktivierter Option "Beliebige CPU" kompiliert.

Ich habe auch den folgenden Code, um zu bestimmen, ob das System die 64-Bit-Datei oder die 32-Bit-Datei verwenden soll.

#if WIN64
        public const string DLL_FILE_NAME = "MyDll64.dll";
#else
        public const string DLL_FILE_NAME = "MyDll32.dll";        
#endif

Inzwischen sollte das Problem auftreten. DLL_FILE_NAME wird in der Kompilierungszeit und nicht in der Ausführungszeit definiert, sodass die richtige DLL nicht entsprechend dem Ausführungskontext geladen wird.

Was wäre der richtige Weg, um mit diesem Problem umzugehen? Ich möchte keine zwei Ausführungsdateien (eine für 32bit und die andere für 64bit)? Wie kann ich DLL_FILE_NAME festlegen vorher wird es in der DllImport-Anweisung verwendet?

58
Gilad

Ich habe die einfachste Möglichkeit gefunden, dies zu tun, indem ich die beiden Methoden mit unterschiedlichen Namen importiere und die richtige aufrufe. Die DLL wird erst geladen, wenn der Aufruf erfolgt ist.

[DllImport("MyDll32.dll", EntryPoint = "Func1", CallingConvention = CallingConvention.Cdecl)]
private static extern int Func1_32(int var1, int var2);

[DllImport("MyDll64.dll", EntryPoint = "Func1", CallingConvention = CallingConvention.Cdecl)]
private static extern int Func1_64(int var1, int var2);

public static int Func1(int var1, int var2) {
    return IntPtr.Size == 8 /* 64bit */ ? Func1_64(var1, var2) : Func1_32(var1, var2);
}

Wenn Sie viele Importe haben, kann die manuelle Pflege natürlich recht mühsam werden.

55

Hier ist eine weitere Alternative, die erfordert, dass die beiden DLLs den gleichen Namen haben und in verschiedenen Ordnern abgelegt sind. Zum Beispiel:

  • win32/MyDll.dll
  • win64/MyDll.dll

Der Trick besteht darin, die DLL mit LoadLibrary manuell zu laden, bevor die CLR dies tut. Dann wird ein MyDll.dll ist bereits geladen und benutzt es.

Dies kann einfach im statischen Konstruktor der übergeordneten Klasse durchgeführt werden.

static class MyDll
{
    static MyDll()
    {            
        var myPath = new Uri(typeof(MyDll).Assembly.CodeBase).LocalPath;
        var myFolder = Path.GetDirectoryName(myPath);

        var is64 = IntPtr.Size == 8;
        var subfolder = is64 ? "\\win64\\" : "\\win32\\";

        LoadLibrary(myFolder + subfolder + "MyDll.dll");
    }

    [DllImport("kernel32.dll")]
    private static extern IntPtr LoadLibrary(string dllToLoad);

    [DllImport("MyDll.dll")]
    public static extern int MyFunction(int var1, int var2);
}

EDIT 2017/02/01 : Verwenden Sie Assembly.CodeBase, damit es auch funktioniert, wenn Schattenkopie aktiviert ist.

51
Benoit Blanchon

In diesem Fall sollte ich wie folgt vorgehen (2 Ordner erstellen, x64 und x86 + die entsprechende DLL MIT DEM GLEICHEN NAMEN in beide Ordner einfügen):

using System;
using System.Runtime.InteropServices;
using System.Reflection;
using System.IO;

class Program {
    static void Main(string[] args) {
        var path = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
        path = Path.Combine(path, IntPtr.Size == 8 ? "x64" : "x86");
        bool ok = SetDllDirectory(path);
        if (!ok) throw new System.ComponentModel.Win32Exception();
    }
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool SetDllDirectory(string path);
}

Es gibt eine statische Variable, die den Dateinamen DLL enthält

Es ist keine statische Variable. Es ist eine Konstante zur Kompilierungszeit. Sie können eine Kompilierzeitkonstante zur Laufzeit nicht ändern.

Was wäre der richtige Weg, um mit diesem Problem umzugehen?

Ehrlich gesagt würde ich nur empfehlen, x86 als Ziel festzulegen und die 64-Bit-Version insgesamt zu vergessen und Ihre Anwendung auf WOW64 ausführen zu lassen, es sei denn, Ihre Anwendung muss zwingend als x64 ausgeführt werden.

Wenn Sie x64 benötigen, können Sie:

  • Ändern Sie die DLLs so, dass sie denselben Namen haben, z. B. MyDll.dll und setzen Sie zum Zeitpunkt der Installation/Bereitstellung die richtige ein. (Wenn das Betriebssystem x64 ist, stellen Sie die 64-Bit-Version der DLL bereit, andernfalls die x86-Version.).

  • Haben Sie zwei separate Builds, einen für x86 und einen für x64.

7
vcsjones

Was Sie beschreiben, wird als "Side-by-Side-Assembly" bezeichnet (zwei Versionen derselben Assembly, eine 32-Bit- und eine 64-Bit-Version).

Hier Hier finden Sie eine exemplarische Vorgehensweise für genau Ihr Szenario (.NET DLL umschließt C++/CLI DLL verweist auf eine native DLL)).

EMPFEHLUNG:

Erstellen Sie es einfach als x86 und machen Sie es fertig ... oder haben Sie zwei Builds (einen x86 und einen x64) ... da die obigen Techniken ziemlich kompliziert sind ...

2
Yahia

ein alternativer Ansatz kann sein

public static class Sample
{
    public Sample()
    {

        string StartupDirEndingWithSlash = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName) + "\\";
        string ResolvedDomainTimeFileName = StartupDirEndingWithSlash + "ABCLib_Resolved.dll";
        if (!File.Exists(ResolvedDomainTimeFileName))
        {
            if (Environment.Is64BitProcess)
            {
                if (File.Exists(StartupDirEndingWithSlash + "ABCLib_64.dll"))
                    File.Copy(StartupDirEndingWithSlash + "ABCLib_64.dll", ResolvedDomainTimeFileName);
            }
            else
            {
                if (File.Exists(StartupDirEndingWithSlash + "ABCLib_32.dll"))
                    File.Copy(StartupDirEndingWithSlash + "ABCLib_32.dll", ResolvedDomainTimeFileName);
            }
        }
    }

    [DllImport("ABCLib__Resolved.dll")]
    private static extern bool SomeFunctionName(ref int FT);
}
1
user2381106

Ich habe einen der von vcsjones beabsichtigten Ansätze verwendet:

"Ändern Sie die DLLs so, dass sie denselben Namen haben, wie z. B." MyDll.dll ", und richten Sie zum Zeitpunkt der Installation/Bereitstellung die richtige ein."

Für diesen Ansatz müssen zwei Build-Plattformen verwaltet werden. Weitere Informationen finden Sie unter diesem Link: https://stackoverflow.com/a/6446638/38368

0
Danny Varod

Der Trick, den ich für V8.Net benutze, ist folgender:

  1. Erstellen Sie ein neues C # -Projekt "Proxy-Schnittstelle" mit allen Definitionen, um zwischen den verschiedenen Architekturen zu wechseln. In meinem Fall hieß das Projekt V8.Net-ProxyInterface; Beispiel:
 public unsafe static class V8NetProxy
    {
    #if x86
            [DllImport("V8_Net_Proxy_x86")]
    #Elif x64
            [DllImport("V8_Net_Proxy_x64")]
    #else
            [DllImport("V8_Net_Proxy")] // (dummy - NOT USED!)
    #endif
            public static extern NativeV8EngineProxy* CreateV8EngineProxy(bool enableDebugging, void* debugMessageDispatcher, int debugPort);

DIESES ist das Projekt, auf das Sie verweisen werden. Verweisen Sie NICHT auf die nächsten beiden:

  1. Erstellen Sie zwei weitere Projekte, um x64- und x86-Versionen der Bibliothek zu generieren. Dies ist SEHR EINFACH: Kopieren und Einfügen, um die Datei .csproj Im selben Ordner zu duplizieren und umzubenennen. In meinem Fall wurde die Projektdatei in V8.Net-ProxyInterface-x64 Und V8.Net-ProxyInterface-x86 Umbenannt, und dann habe ich die Projekte zu meiner Lösung hinzugefügt. Öffnen Sie die Projekteinstellungen für jede von ihnen in Visual Studio und stellen Sie sicher, dass der Name für Assembly Name Entweder x64 oder x86 enthält. Zu diesem Zeitpunkt haben Sie 3 Projekte: das erste "Platzhalter" -Projekt und die 2 architekturspezifischen. Für die 2 neuen Projekte:

    a) Öffnen Sie die Projekteinstellungen für die x64-Schnittstelle, wechseln Sie zur Registerkarte Build, wählen Sie All Platforms für Platform oben aus und geben Sie x64 in Conditional compilation symbols.

    b) Öffnen Sie die Projekteinstellungen für die x86-Schnittstelle, wechseln Sie zur Registerkarte Build, wählen Sie oben All Platforms für Platform aus und geben Sie x86 in Conditional compilation symbols.

  2. Öffnen Sie Build->Configuration Manager... Und stellen Sie sicher, dass x64 Als Plattform für x64-Projekte und x86 Für x86-Projekte für BEIDE Debug UND Release Konfigurationen.

  3. Vergewissern Sie sich, dass die beiden neuen Schnittstellenprojekte (für x64 und x86) am selben Speicherort Ihres Host-Projekts ausgegeben werden (siehe Projekteinstellung Build->Output path).

  4. Der letzte Zauber: In einem statischen Konstruktor für meinen Motor binde ich schnell den Assembly Resolver an:

static V8Engine()
{
    AppDomain.CurrentDomain.AssemblyResolve += Resolver;
}

Bei der Methode Resolver lade ich die Datei nur basierend auf der aktuellen Plattform, die vom aktuellen Prozess angegeben wird (Hinweis: Dieser Code ist eine abgespeckte Version und nicht getestet):

var currentExecPath = Assembly.GetExecutingAssembly().Location;
var platform = Environment.Is64BitProcess ? "x64" : "x86";
var filename = "V8.Net.Proxy.Interface." + platform + ".dll"
return Assembly.LoadFrom(Path.Combine(currentExecPath , fileName));

Wechseln Sie schließlich im Projektmappen-Explorer zu Ihrem Host-Projekt, erweitern Sie References, wählen Sie das erste Dummy-Projekt aus, das Sie in Schritt 1 erstellt haben, klicken Sie mit der rechten Maustaste darauf, um die Eigenschaften zu öffnen, und setzen Sie Copy Local Auf false. Auf diese Weise können Sie mit EINEM Namen für jede P/Invoke-Funktion entwickeln und mit dem Resolver herausfinden, welche Funktion tatsächlich geladen werden soll.

Beachten Sie, dass der Assembly Loader nur bei Bedarf ausgeführt wird. Es wird (in meinem Fall) nur beim ersten Zugriff auf die Motorklasse automatisch vom CLR-System ausgelöst. Wie sich das auf Sie auswirkt, hängt davon ab, wie Ihr Host-Projekt gestaltet ist.

0
James Wilkins