it-swarm.com.de

Wie kann ich zur Laufzeit einen [DllImport] -Pfad angeben?

Tatsächlich habe ich ein C++ (funktioniert) DLL, das ich in mein C # -Projekt importieren möchte, um dessen Funktionen aufzurufen.

Es funktioniert, wenn ich den vollständigen Pfad zur DLL wie folgt angebe:

string str = "C:\\Users\\userName\\AppData\\Local\\myLibFolder\\myDLL.dll";
[DllImport(str, CallingConvention = CallingConvention.Cdecl)]
public static extern int DLLFunction(int Number1, int Number2);

Das Problem ist, dass es sich um ein installierbares Projekt handelt, sodass der Ordner des Benutzers je nach Computer/Sitzung, auf dem es ausgeführt wird, nicht derselbe ist (z. B. Pierre, Paul, Jack, Mama, Papa ...).

Deshalb möchte ich, dass mein Code ein bisschen allgemeiner ist:

/* 
goes right to the temp folder of the user 
    "C:\\Users\\userName\\AppData\\Local\\temp"
then go to parent folder
    "C:\\Users\\userName\\AppData\\Local"
and finally go to the DLL's folder
    "C:\\Users\\userName\\AppData\\Local\\temp\\myLibFolder"
*/

string str = Path.GetTempPath() + "..\\myLibFolder\\myDLL.dll"; 
[DllImport(str, CallingConvention = CallingConvention.Cdecl)]
public static extern int DLLFunction(int Number1, int Number2);

Die große Sache ist, dass "DllImport" einen "const string" -Parameter für das DLL-Verzeichnis wünscht.

Meine Frage lautet also: Was könnte in diesem Fall getan werden?

126
Jsncrdnl

Entgegen den Vorschlägen einiger anderer Antworten ist die Verwendung des Attributs DllImport immer noch der richtige Ansatz.

Ich verstehe ehrlich gesagt nicht, warum Sie nicht wie jeder andere auf der Welt einen relativen Pfad zu Ihrer DLL angeben können. Ja, der Pfad, in dem Ihre Anwendung installiert wird, ist auf den Computern der verschiedenen Benutzer unterschiedlich. Dies ist jedoch im Grunde eine universelle Regel für die Bereitstellung. Der DllImport-Mechanismus wurde unter Berücksichtigung dieses Aspekts entwickelt.

Tatsächlich ist es nicht einmal DllImport, der damit umgeht. Es sind die systemeigenen Win32-Laderegeln DLL, die die Dinge regeln, unabhängig davon, ob Sie die handlichen verwalteten Wrapper verwenden (der P/Invoke-Marshaller ruft nur LoadLibrary auf). Diese Regeln werden sehr detailliert aufgezählt hier , aber die wichtigsten sind hier aufgeführt:

Bevor das System nach einer DLL sucht, überprüft es Folgendes:

  • Wenn bereits eine DLL mit demselben Modulnamen in den Speicher geladen ist, verwendet das System die geladene DLL, unabhängig davon, in welchem ​​Verzeichnis sie sich befindet. Das System sucht nicht nach der DLL.
  • Wenn sich die DLL in der Liste der bekannten DLLs für die Windows-Version befindet, auf der die Anwendung ausgeführt wird, verwendet das System ihre Kopie der bekannten DLL (und der abhängigen DLLs der bekannten DLLs, falls vorhanden). . Das System sucht nicht nach der DLL.

Wenn SafeDllSearchMode aktiviert ist (Standardeinstellung), lautet die Suchreihenfolge wie folgt:

  1. Das Verzeichnis, aus dem die Anwendung geladen wurde.
  2. Das Systemverzeichnis. Verwenden Sie die Funktion GetSystemDirectory, um den Pfad dieses Verzeichnisses abzurufen.
  3. Das 16-Bit-Systemverzeichnis. Es gibt keine Funktion, die den Pfad dieses Verzeichnisses ermittelt, aber es wird gesucht.
  4. Das Windows-Verzeichnis. Verwenden Sie die Funktion GetWindowsDirectory, um den Pfad dieses Verzeichnisses abzurufen.
  5. Das aktuelle Verzeichnis.
  6. Die Verzeichnisse, die in der Umgebungsvariablen PATH aufgelistet sind. Beachten Sie, dass dies nicht den anwendungsspezifischen Pfad enthält, der im Registrierungsschlüssel App Paths angegeben ist. Der Schlüssel App Paths wird bei der Berechnung des Suchpfads DLL nicht verwendet.

Wenn Sie also Ihre DLL nicht wie ein System DLL benennen (was Sie natürlich unter keinen Umständen tun sollten), beginnt die Standardsuchreihenfolge mit der Suche im Verzeichnis von dem aus Ihre Anwendung geladen wurde. Wenn Sie während der Installation die DLL dort ablegen, wird sie gefunden. Alle komplizierten Probleme verschwinden, wenn Sie nur relative Pfade verwenden.

Einfach schreiben:

[DllImport("MyAppDll.dll")] // relative path; just give the DLL's name
static extern bool MyGreatFunction(int myFirstParam, int mySecondParam);

Aber wenn das nicht aus irgendeinem Grund funktioniert und Sie die Anwendung zwingen müssen, in einem anderen Verzeichnis nach der DLL zu suchen, können Sie den Standardsuchpfad mit der Funktion SetDllDirectory ändern .
Beachten Sie, dass gemäß der Dokumentation:

Nach dem Aufruf von SetDllDirectory lautet der Standard-Suchpfad DLL:

  1. Das Verzeichnis, aus dem die Anwendung geladen wurde.
  2. Das durch den Parameter lpPathName angegebene Verzeichnis.
  3. Das Systemverzeichnis. Verwenden Sie die Funktion GetSystemDirectory, um den Pfad dieses Verzeichnisses abzurufen.
  4. Das 16-Bit-Systemverzeichnis. Es gibt keine Funktion, die den Pfad dieses Verzeichnisses ermittelt, aber es wird gesucht.
  5. Das Windows-Verzeichnis. Verwenden Sie die Funktion GetWindowsDirectory, um den Pfad dieses Verzeichnisses abzurufen.
  6. Die Verzeichnisse, die in der Umgebungsvariablen PATH aufgelistet sind.

Solange Sie diese Funktion aufrufen, bevor Sie die aus der DLL importierte Funktion zum ersten Mal aufrufen, können Sie den Standardsuchpfad ändern, der zum Suchen von DLLs verwendet wird. Der Vorteil ist natürlich, dass Sie dieser Funktion, die zur Laufzeit berechnet wird, einen Wert dynamic übergeben können. Dies ist mit dem Attribut DllImport nicht möglich. Daher verwenden Sie weiterhin einen relativen Pfad (nur den Namen der DLL) und verlassen sich auf die neue Suchreihenfolge, um sie für Sie zu finden.

Diese Funktion muss per P/Invoke aufgerufen werden. Die Erklärung sieht so aus:

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool SetDllDirectory(string lpPathName);
161
Cody Gray

Noch besser als Rans Vorschlag, GetProcAddress zu verwenden, rufen Sie einfach LoadLibrary auf, bevor Sie die DllImport -Funktionen aufrufen (nur mit einem Dateinamen ohne Pfad) benutze das geladene Modul automatisch.

Ich habe diese Methode verwendet, um zur Laufzeit zu wählen, ob eine native 32-Bit- oder 64-Bit-Datei geladen werden soll DLL), ohne eine Reihe von P/Invoke-d-Funktionen ändern zu müssen in einem statischen Konstruktor für den Typ, der die importierten Funktionen hat und alles wird gut funktionieren.

33
MikeP

Wenn Sie eine DLL-Datei benötigen, die sich nicht im Pfad oder am Speicherort der Anwendung befindet, können Sie dies meines Erachtens nicht tun, da DllImport ein Attribut ist und es sich bei den Attributen nur um festgelegte Metadaten handelt über Typen, Mitglieder und andere Sprachelemente.

Eine Alternative, die Ihnen dabei helfen kann, das zu erreichen, was Sie meines Erachtens versuchen, besteht darin, die native LoadLibrary über P/Invoke zu verwenden, um eine DLL aus dem benötigten Pfad zu laden, und dann GetProcAddress, um einen Verweis auf die Funktion zu erhalten, die Sie von dieser DLL benötigen. Verwenden Sie diese dann, um einen Delegaten zu erstellen, den Sie aufrufen können.

Um die Verwendung zu vereinfachen, können Sie diesen Delegaten dann auf ein Feld in Ihrer Klasse festlegen, sodass die Verwendung so aussieht, als würde eine Member-Methode aufgerufen.

EDIT

Hier ist ein Codeausschnitt, der funktioniert und zeigt, was ich damit gemeint habe.

class Program
{
    static void Main(string[] args)
    {
        var a = new MyClass();
        var result = a.ShowMessage();
    }
}

class FunctionLoader
{
    [DllImport("Kernel32.dll")]
    private static extern IntPtr LoadLibrary(string path);

    [DllImport("Kernel32.dll")]
    private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

    public static Delegate LoadFunction<T>(string dllPath, string functionName)
    {
        var hModule = LoadLibrary(dllPath);
        var functionAddress = GetProcAddress(hModule, functionName);
        return Marshal.GetDelegateForFunctionPointer(functionAddress, typeof (T));
    }
}

public class MyClass
{
    static MyClass()
    {
        // Load functions and set them up as delegates
        // This is just an example - you could load the .dll from any path,
        // and you could even determine the file location at runtime.
        MessageBox = (MessageBoxDelegate) 
            FunctionLoader.LoadFunction<MessageBoxDelegate>(
                @"c:\windows\system32\user32.dll", "MessageBoxA");
    }

    private delegate int MessageBoxDelegate(
        IntPtr hwnd, string title, string message, int buttons); 

    /// <summary>
    /// This is the dynamic P/Invoke alternative
    /// </summary>
    static private MessageBoxDelegate MessageBox;

    /// <summary>
    /// Example for a method that uses the "dynamic P/Invoke"
    /// </summary>
    public int ShowMessage()
    {
        // 3 means "yes/no/cancel" buttons, just to show that it works...
        return MessageBox(IntPtr.Zero, "Hello world", "Loaded dynamically", 3);
    }
}

Hinweis: Ich habe mich nicht darum gekümmert, FreeLibrary zu verwenden, daher ist dieser Code nicht vollständig. In einer realen Anwendung sollten Sie darauf achten, die geladenen Module freizugeben, um einen Speicherverlust zu vermeiden.

24
Ran

Solange Sie das Verzeichnis kennen, in dem sich Ihre C++ - Bibliotheken zur Laufzeit befinden, sollte dies einfach sein. Ich kann deutlich sehen, dass dies in Ihrem Code der Fall ist. Ihre myDll.dll befindet sich im Verzeichnis myLibFolder im temporären Ordner des aktuellen Benutzers.

string str = Path.GetTempPath() + "..\\myLibFolder\\myDLL.dll"; 

Jetzt können Sie die DllImport-Anweisung mit einer const-Zeichenfolge wie folgt weiterverwenden:

[DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int DLLFunction(int Number1, int Number2);

Fügen Sie zur Laufzeit, bevor Sie die Funktion DLLFunction aufrufen (in der C++ - Bibliothek vorhanden), diese Codezeile in C # -Code ein:

string assemblyProbeDirectory = Path.GetTempPath() + "..\\myLibFolder\\myDLL.dll"; 
Directory.SetCurrentDirectory(assemblyProbeDirectory);

Dies weist die CLR einfach an, nach den nicht verwalteten C++ - Bibliotheken im Verzeichnispfad zu suchen, den Sie zur Laufzeit Ihres Programms abgerufen haben. Directory.SetCurrentDirectory call setzt das aktuelle Arbeitsverzeichnis der Anwendung auf das angegebene Verzeichnis. Wenn dein myDLL.dll ist am Pfad vorhanden, der durch assemblyProbeDirectory path dargestellt wird, dann wird es geladen und die gewünschte Funktion wird durch p/invoke aufgerufen.

5
RBT

legen Sie den DLL-Pfad in der Konfigurationsdatei fest

<add key="dllPath" value="C:\Users\UserName\YourApp\myLibFolder\myDLL.dll" />

führen Sie die folgenden Schritte aus, bevor Sie die DLL in Ihrer App aufrufen

string dllPath= ConfigurationManager.AppSettings["dllPath"];    
   string appDirectory = Path.GetDirectoryName(dllPath);
   Directory.SetCurrentDirectory(appDirectory);

dann ruf die dll auf und du kannst sie wie folgt benutzen

 [DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int DLLFunction(int Number1, int Number2);
3
Sajithd

DllImport funktioniert einwandfrei ohne den angegebenen vollständigen Pfad, solange sich die DLL irgendwo im Systempfad befindet. Möglicherweise können Sie den Ordner des Benutzers vorübergehend zum Pfad hinzufügen.

0
Mike W