Come posso specificare un percorso in fase di runtime?

In effetti, ho ottenuto una DLL C ++ (funzionante) che voglio importare nel mio progetto C # per chiamarne le funzioni.

Funziona quando si specifica il percorso completo della DLL, in questo modo:

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

Il problema è che sarà un progetto installabile, quindi la cartella dell’utente non sarà la stessa (ex: pierre, paul, jack, mamma, papà, …) a seconda del computer / sessione in cui dovrebbe essere eseguito.

Quindi mi piacerebbe che il mio codice fosse un po ‘più generico, come questo:

 /* 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); 

Il grosso problema è che “DllImport” desidera un parametro “const string” per la directory della DLL.

Quindi la mia domanda è :: cosa si potrebbe fare in questo caso?

Contrariamente ai suggerimenti di alcune delle altre risposte, l’utilizzo dell’attributo DllImport è ancora l’approccio corretto.

Onestamente non capisco perché non puoi fare come tutti gli altri al mondo e specificare un percorso relativo alla tua DLL. Sì, il percorso in cui verrà installata la tua applicazione è diverso sui computer di altre persone, ma questa è fondamentalmente una regola universale per quanto riguarda la distribuzione. Il meccanismo DllImport è stato progettato pensando a questo.

In realtà, non è nemmeno DllImport . Sono le regole native di caricamento della DLL Win32 che governano le cose, indipendentemente dal fatto che tu stia utilizzando i comodi wrapper gestiti (il marshaller P / Invoke chiama solo LoadLibrary ). Queste regole sono enumerate in dettaglio qui , ma quelle importanti sono tratte qui:

Prima che il sistema cerchi una DLL, verifica quanto segue:

  • Se una DLL con lo stesso nome modulo è già stata caricata in memoria, il sistema utilizza la DLL caricata, indipendentemente dalla directory in cui si trova. Il sistema non ricerca la DLL.
  • Se la DLL è presente nell’elenco delle DLL conosciute per la versione di Windows su cui è in esecuzione l’applicazione, il sistema utilizza la sua copia della DLL conosciuta (e le DLL dipendenti della DLL nota, se presenti). Il sistema non cerca la DLL.

Se SafeDllSearchMode è abilitato (impostazione predefinita), l’ordine di ricerca è il seguente:

  1. La directory da cui è stata caricata l’applicazione.
  2. La directory di sistema. Utilizzare la funzione GetSystemDirectory per ottenere il percorso di questa directory.
  3. La directory di sistema a 16 bit. Non esiste una funzione che ottenga il percorso di questa directory, ma viene cercata.
  4. La directory di Windows. Utilizzare la funzione GetWindowsDirectory per ottenere il percorso di questa directory.
  5. La directory corrente.
  6. Le directory elencate nella variabile di ambiente PATH . Si noti che questo non include il percorso per applicazione specificato dalla chiave di registro App Paths. La chiave App Paths non viene utilizzata durante il calcolo del percorso di ricerca DLL.

Quindi, a meno che tu non stia nominando la tua DLL come una DLL di sistema (che ovviamente non dovresti fare mai, in nessuna circostanza), l’ordine di ricerca predefinito inizierà a cercare nella directory da cui è stata caricata la tua applicazione. Se inserisci la DLL lì durante l’installazione, verrà trovata. Tutti i problemi complicati scompaiono se usi solo percorsi relativi.

Scrivi e basta:

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

Ma se ciò non funziona per qualsiasi motivo, e devi forzare l’applicazione a cercare in una directory diversa per la DLL, puoi modificare il percorso di ricerca predefinito usando la funzione SetDllDirectory .
Si noti che, come da documentazione:

Dopo aver chiamato SetDllDirectory , il percorso di ricerca DLL standard è:

  1. La directory da cui è stata caricata l’applicazione.
  2. La directory specificata dal parametro lpPathName .
  3. La directory di sistema. Utilizzare la funzione GetSystemDirectory per ottenere il percorso di questa directory.
  4. La directory di sistema a 16 bit. Non esiste una funzione che ottenga il percorso di questa directory, ma viene cercata.
  5. La directory di Windows. Utilizzare la funzione GetWindowsDirectory per ottenere il percorso di questa directory.
  6. Le directory elencate nella variabile di ambiente PATH .

Quindi, finché si chiama questa funzione prima di chiamare la funzione importata dalla DLL per la prima volta, è ansible modificare il percorso di ricerca predefinito utilizzato per individuare le DLL. Il vantaggio, ovviamente, è che è ansible passare un valore dinamico a questa funzione calcasting in fase di esecuzione. Ciò non è ansible con l’attributo DllImport , quindi utilizzerai comunque un percorso relativo (solo il nome della DLL), e fai affidamento sul nuovo ordine di ricerca per trovarlo per te.

Dovrai P / Richiamare questa funzione. La dichiarazione assomiglia a questo:

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

Ancora meglio del suggerimento di Ran di utilizzare GetProcAddress, basta effettuare la chiamata a LoadLibrary prima di qualsiasi chiamata alle funzioni DllImport (con solo un nome file senza un percorso) e useranno automaticamente il modulo caricato.

Ho usato questo metodo per scegliere a runtime se caricare una DLL nativa a 32-bit o 64-bit senza dover modificare una serie di funzioni P / Invoke-d. Attacca il codice di caricamento in un costruttore statico per il tipo con le funzioni importate e funzionerà tutto bene.

Se hai bisogno di un file .dll che non si trova sul percorso o nella posizione dell’applicazione, allora non penso che tu possa fare proprio questo, perché DllImport è un attributo e gli attributi sono solo metadati impostati su tipi, membri e altri elementi del linguaggio.

Un’alternativa che può aiutarti a realizzare ciò che penso tu stia provando, è usare la LoadLibrary nativa attraverso P / Invoke, per caricare un file .dll dal percorso che ti serve, e quindi usare GetProcAddress per ottenere un riferimento alla funzione hai bisogno di quella .dll. Quindi usali per creare un delegato che puoi invocare.

Per renderlo più facile da usare, puoi impostare questo delegato in un campo della tua class, in modo che utilizzarlo assomigli al chiamare un metodo membro.

MODIFICARE

Ecco uno snippet di codice che funziona e mostra cosa intendevo.

 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(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( @"c:\windows\system32\user32.dll", "MessageBoxA"); } private delegate int MessageBoxDelegate( IntPtr hwnd, string title, string message, int buttons); ///  /// This is the dynamic P/Invoke alternative ///  static private MessageBoxDelegate MessageBox; ///  /// Example for a method that uses the "dynamic P/Invoke" ///  public int ShowMessage() { // 3 means "yes/no/cancel" buttons, just to show that it works... return MessageBox(IntPtr.Zero, "Hello world", "Loaded dynamically", 3); } } 

Nota: non mi sono preoccupato di usare FreeLibrary , quindi questo codice non è completo. In un’applicazione reale, è necessario fare attenzione a rilasciare i moduli caricati per evitare perdite di memoria.

Finché si conosce la directory in cui è ansible trovare le librerie C ++ in fase di esecuzione, questo dovrebbe essere semplice. Posso vedere chiaramente che questo è il caso nel tuo codice. Il tuo myDll.dll sarebbe presente nella directory myLibFolder all’interno della cartella temporanea dell’utente corrente.

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

Ora puoi continuare a usare l’istruzione DllImport usando una stringa const come mostrato di seguito:

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

Appena in fase di esecuzione prima di chiamare la funzione DLLFunction (presente nella libreria C ++) aggiungere questa riga di codice nel codice C #:

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

Questo semplicemente ordina al CLR di cercare le librerie C ++ non gestite nel percorso della directory che hai ottenuto in fase di esecuzione del tuo programma. Directory.SetCurrentDirectory chiamata a Directory.SetCurrentDirectory imposta la directory di lavoro corrente dell’applicazione nella directory specificata. Se il tuo myDLL.dll è presente al percorso rappresentato dal percorso assemblyProbeDirectory , verrà caricato e la funzione desiderata verrà richiamata tramite p / invoke.

DllImport funzionerà correttamente senza il percorso completo specificato fintanto che la dll si trova da qualche parte nel percorso di sistema. Potrebbe essere ansible aggiungere temporaneamente la cartella dell’utente al percorso.

Se tutto fallisce, inserisci semplicemente la DLL nella cartella windows\system32 . Il compilatore lo troverà. Specificare la DLL da cui caricare: DllImport("user32.dll"... , impostare EntryPoint = "my_unmanaged_function" per importare la funzione non gestita desiderata nell’app C #:

  using System; using System.Runtime.InteropServices; class Example { // Use DllImport to import the Win32 MessageBox function. [DllImport ("user32.dll", CharSet = CharSet.Auto)] public static extern int MessageBox (IntPtr hWnd, String text, String caption, uint type); static void Main() { // Call the MessageBox function using platform invoke. MessageBox (new IntPtr(0), "Hello, World!", "Hello Dialog", 0); } } 

Sorgente e ancora più esempi di DllImport : http://msdn.microsoft.com/en-us/library/aa288468(v=vs.71).aspx