Laden...

32bit/64bit unmanaged DLL "importen" - wie?

Erstellt von CaptainIglo vor 15 Jahren Letzter Beitrag vor 2 Jahren 8.510 Views
Information von gfoidl vor 2 Jahren

Dies ist ein Thread, auf den aus der FAQ verwiesen wird. Bitte keine weitere Diskussion, sondern nur wichtige Ergänzungen und diese bitte knapp und präzise. Vielen Dank!

C
CaptainIglo Themenstarter:in
366 Beiträge seit 2005
vor 15 Jahren
32bit/64bit unmanaged DLL "importen" - wie?

Hallo,

habe hier ein Project in welchem ich eine externe C++-dll verwende, welche in der 32bit und 64bit Variante vorliegt (32bit geht unter 64bit nicht) und per DllImport verwednet wird.

Kann ich meiner Anwendung irgendwie sagen, dass sie anstatt "xyz.dll", je nach System "x32\xyz.dll" bzw. "x64\xyz.dll" lädt?

mfg
Capt.Iglo

1.457 Beiträge seit 2004
vor 15 Jahren

Hallo CaptainIglo,

Wenn dein Projekt via AnyCPU kompiliert wird, dann IMHO garnicht. Du müsstest zwei Konfigurationen anlegen und via DllImport je nach Konfiguration die eine oder andere DLL miteinbinden.

6.911 Beiträge seit 2009
vor 11 Jahren

Hallo zusammen,

nachfolgender Vorschlag bezieht sich auf unmanaged (od. native) DLLs.

Kann ich meiner Anwendung irgendwie sagen, dass sie anstatt "xyz.dll", je nach System "x32\xyz.dll" bzw. "x64\xyz.dll" lädt?

Statt x32 wird üblicherweise x86 verwendet, aber das ändert nichts an der Lösung.

Die Suchreihenfolge für native DLLs ist:

Zitat von: Dynamic-Link Library Search Order (Windows)
1.The directory from which the application loaded. 1.The system directory. 1.The Windows directory. 1.The current directory. 1.The directories that are listed in the PATH environment variable. Note that this does not include the per-application path specified by the App Paths registry key. The App Paths key is not used when computing the DLL search path.

Davon können wir Punkt 4 und 5 beeinflussen. Punkt 4 ist aber nicht sicher, da nicht garantiert ist, dass dieses Verzeichnis während der Lebensdauer des Prozesses nicht geändert wird. D.h. wir müssen den Weg über die Umgebungsvariable PATH gehen.

Beispielsweise so:


string path = Environment.GetEnvironmentVariable("PATH") ?? string.Empty;
string dllDir = System.IO.Path.GetDirectoryName(typeof(NativeMethods).Assembly.Location);

if (Environment.Is64BitProcess)
	dllDir = System.IO.Path.Combine(dllDir, "x64");
else
	dllDir = System.IO.Path.Combine(dllDir, "x86");

path += ";" + dllDir;

Environment.SetEnvironmentVariable("PATH", path);

Da in Environment.SetEnvironmentVariable kein EnvironmentVariableTarget angegeben wurde, gilt diese Umgebungsvariable nur für den aktuellen Prozess.

Dieser Code sollte ausgeführt werden, bevor erstmals auf eine mit DllImport versehenen Methode zugegriffen wird. Z.B. in im Einstiegspunkt des Prozesses (Main) od. im statischen Konstruktor der (wie im Beispiel) NativeMethods-Klasse.

Das .net-Projekt ist mit der Konfiguration "Any CPU" zu erstellen.

Wenn das Ganze auch für NuGet verwendet werden soll, so ist es praktisch sich Microsoft.SqlServer.Compact als Vorlage zu nehmen. Die Script-Files im tools-Ordner sind entsprechend anzupassen.

Managed DLLs (also .net-DLLs) sollten idealerweise mit der Konfiguration "Any CPU" erstellt werden, da für die CLR nur die Bittigkeit der EXE von Bedeutung ist. D.h. wenn die EXE 32bit ist, so wird von der CLR die DLL auch als 32bit behandelt. Für 64bit analog. Für fremde DLLs muss die Bittigkeit der EXE zu jener der DLLs passen, andernfalls erhält man zur Laufzeit eine BadImageFormatException. Siehe hierzu auch BadImageFormatException bei Verwendung einer Assembly.
Bei managed DLLs macht es keinen Sinn eine 32bit- und eine 64bit-Version anzubieten und diese zur Laufzeit entsprechend einzubinden, da bei managed DLLs Any CPU die bessere Wahl ist.

mfG Gü

Speedski, nativ, native, DLL, unmanaged, DllImport, 32bit, 64bit, 32/64, 32/64bit

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

502 Beiträge seit 2004
vor 11 Jahren

Nur als Ergänzung: Das Laden von nativen DLLs lässt sich auch mit einem anderen Ansatz lösen - ohne setzen von Path-Variablen etc.
Folgende Vorgehensweise ist dafür nötig:

a) Native Windows Calls einbinden


[DllImport("Kernel32.dll", SetLastError = true)]
public static extern IntPtr LoadLibraryEx(string dllFilePath, IntPtr hReservedNull, LoadLibraryFlags dwFlags);

[DllImport("Kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool FreeLibrary(IntPtr hModule);

[DllImport("Kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

b) den Pfad zur richtigen Dll bestimmen (ggf. abhängig von Environment.Is64BitProcess)
c) die DLL laden und den Zeiger darauf merken

 LoadLibraryEx("FullPathToLibrary.dll", IntPtr.Zero, LoadLibraryFlags.LOAD_WITH_ALTERED_SEARCH_PATH)

d) Für jede benötigte Funktion jeweils den Einsprungpunkt holen und einen entsprechenden Delegate darauf erezugen lassen

private static TDelegate GetFunctionDelegate<TDelegate>(IntPtr dllHandle, string functionName)
           where TDelegate : class
        {
            IntPtr function = NativeCalls.GetProcAddress(dllHandle, functionName);
            var funcPointer = Marshal.GetDelegateForFunctionPointer(function, typeof(TDelegate));
            return funcPointer as TDelegate;
        }

TDelegate muss dafür definiert sein als Delegate mit der Signatur der zu importierenden Funktion.
Am Ende kann die DLL dann auch noch mit FreeLibrary wieder freigegeben werden.

Vorteile hiervon: *Es wird alles dynamisch geladen *Der Pfad zur DLL kann frei angegeben werden (ohne PATH-Variable und andere Unwägbarkeiten) *Der Zeitpunkt des Ladens (und Entladens) der DLL kann exakt bestimmt werden *Es ist (wenn nötig) möglich die aufzurufenden Methoden dynamisch zu bestimmen (Zugriff via String, der den Namen enthält) *Verschiedene Funktionen mit gleicher Signatur können über den selben Delegate-Typ importiert werden (keine mehrfachen Importe via DllImport-Attribut nötig)

Nachteil: Mann muss einiges "von Hand lösen" - z.B. via Marshal.GetLastWin32Error() immer die nativen Calls überprüfen und das Ergebnis ggf. in eine ".net-konforme" Exception packen, was beim "normalen" DllImport schon automatisch mit dabei ist.

Bart Simpson

Praxis ist wenn alles funktioniert und keiner weiss warum.
Theorie ist wenn man alles weiss, aber nichts funktioniert.

Bei uns wird Theorie und Praxis vereint: Nichts funktioniert und keiner weiss warum...

6.911 Beiträge seit 2009
vor 2 Jahren

Hallo zusammen,

zu den oben genannten Möglichkeiten gibt es seit .NET Core 3.1 eine weitere basierend auf NativeLibrary.

Damit das Ganze nicht so trocken ist, hab ich ein Demoprojekt gebastelt, welches drei Varianten zeigt:* P/Invoke mittels DllImport (Projekt Managed/NetFull)

  • LoadLibrary mittels Win-API (Projekt Managed/NetFull_LoadLibrary)
  • NativeLibrary mit Function Pointers (C# 9) (Projekt Managed/NetCore)

Alle drei Projekte machen im Grunde nichts anderes als


Console.WriteLine($"Hello from managed, I'm {(Environment.Is64BitProcess ? "64" : "32")} bit");
Console.WriteLine(NativeLib.GetNativeInfo());

Dazu gibt es eine native Dll, als Cmake Projekt, welche in 32 bit und 64 bit vorliegt bzw. so kompiliert werden kann.
Die .NET-Projekte (Managed) entscheiden dann zur Laufzeit je nach Bittigkeit welche native Dll geladen werden soll.

Das Demo ist bewusst einfach gehalten, ausgebaut und verkompliziert kann das beliebig werden.
Auch mag es bzgl. nativen Zugriff andere Klassen-Organisationen geben, aber momentan finde das im Demo gezeigte Vorgehen ganz praktikabel, v.a. bei etwas umfangreicheren und mehreren nativen Dlls.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"