159 votes

Comment puis-je spécifier un chemin [DllImport] au moment de l'exécution ?

En fait, j'ai une DLL C++ (fonctionnelle) que je veux importer dans mon projet C# pour appeler ses fonctions.

Cela fonctionne lorsque je spécifie le chemin complet de la DLL, comme ceci :

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

Le problème est que ce sera un projet installable, donc le dossier de l'utilisateur ne sera pas le même (ex : pierre, paul, jack, mum, dad, ...) selon l'ordinateur/session sur lequel il sera exécuté.

J'aimerais donc que mon code soit un peu plus générique, comme ceci :

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

Le problème est que "DllImport" désire un paramètre "const string" pour le répertoire de la DLL.

Ma question est donc : : Que peut-on faire dans ce cas ?

18 votes

Il suffit de déployer la DLL dans le même dossier que l'EXE pour n'avoir rien d'autre à faire que de spécifier le nom de la DLL sans le chemin. D'autres schémas sont possibles, mais ils sont tous gênants.

2 votes

Le problème est qu'il s'agit d'une extension Excel de MS Office, donc je ne pense pas que mettre la dll dans le répertoire de l'exe soit la meilleure solution...

9 votes

Votre solution est la mauvaise. Ne placez pas de fichiers dans les dossiers Windows ou système. Ils ont choisi ces noms pour une raison : parce qu'ils sont destinés aux fichiers système de Windows. Vous ne créez pas l'un de ces dossiers parce que vous ne travaillez pas pour Microsoft dans l'équipe Windows. Rappelez-vous ce que vous avez appris à l'école maternelle sur l'utilisation sans autorisation de choses qui ne vous appartiennent pas, et placez vos fichiers ailleurs qu'à cet endroit.

196voto

Cody Gray Points 102261

Contrairement à ce que suggèrent certaines des autres réponses, l'utilisation de l'option DllImport L'attribut est toujours l'approche correcte.

Honnêtement, je ne comprends pas pourquoi vous ne pouvez pas faire comme tout le monde dans le monde et spécifier un relatif le chemin d'accès à votre DLL. Certes, le chemin dans lequel votre application sera installée diffère selon les ordinateurs, mais il s'agit là d'une règle universelle en matière de déploiement. Le site DllImport est conçu dans cette optique.

En fait, ce n'est même pas DllImport qui s'en occupe. Ce sont les règles de chargement des DLL Win32 natives qui régissent les choses, que vous utilisiez ou non les enveloppes gérées pratiques (le marshaller P/Invoke appelle simplement LoadLibrary ). Ces règles sont énumérées de manière très détaillée ici mais les plus importants sont extraits ici :

Avant que le système ne recherche une DLL, il vérifie les éléments suivants :

  • Si une DLL portant le même nom de module est déjà chargée en mémoire, le système utilise la DLL chargée, quel que soit le répertoire dans lequel elle se trouve. Le système ne recherche pas la DLL.
  • Si la DLL figure dans la liste des DLL connues pour la version de Windows sur laquelle l'application est exécutée, le système utilise sa copie de la DLL connue (et les DLL dépendantes de la DLL connue, le cas échéant). Le système ne recherche pas la DLL.

Si SafeDllSearchMode est activé (par défaut), l'ordre de recherche est le suivant :

  1. Le répertoire à partir duquel l'application a été chargée.
  2. Le répertoire du système. Utilisez le GetSystemDirectory pour obtenir le chemin de ce répertoire.
  3. Le répertoire du système 16 bits. Il n'existe pas de fonction permettant d'obtenir le chemin de ce répertoire, mais il est recherché.
  4. Le répertoire de Windows. Utilisez le GetWindowsDirectory pour obtenir le chemin de ce répertoire.
  5. Le répertoire actuel.
  6. Les répertoires qui figurent dans la liste des PATH variable d'environnement. Notez que cela n'inclut pas le chemin par application spécifié par la clé de registre App Paths. La clé App Paths n'est pas utilisée lors du calcul du chemin de recherche des DLL.

Ainsi, à moins que vous ne donniez à votre DLL le même nom qu'une DLL système (ce que vous ne devriez évidemment pas faire, jamais, en aucune circonstance), l'ordre de recherche par défaut commencera à chercher dans le répertoire à partir duquel votre application a été chargée. Si vous placez la DLL à cet endroit pendant l'installation, elle sera trouvée. Tous ces problèmes compliqués disparaissent si vous utilisez simplement des chemins relatifs.

Ecris juste :

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

Mais si cela n'a pas pour une raison quelconque, et que vous devez forcer l'application à rechercher la DLL dans un autre répertoire, vous pouvez modifier le chemin de recherche par défaut à l'aide de la commande SetDllDirectory fonction .
Notez que, selon la documentation :

Après avoir appelé SetDllDirectory Le chemin de recherche standard des DLL est le suivant

  1. Le répertoire à partir duquel l'application a été chargée.
  2. Le répertoire spécifié par l'option lpPathName paramètre.
  3. Le répertoire du système. Utilisez le GetSystemDirectory pour obtenir le chemin de ce répertoire.
  4. Le répertoire du système 16 bits. Il n'existe pas de fonction permettant d'obtenir le chemin de ce répertoire, mais il est recherché.
  5. Le répertoire de Windows. Utilisez le GetWindowsDirectory pour obtenir le chemin de ce répertoire.
  6. Les répertoires qui figurent dans la liste des PATH variable d'environnement.

Ainsi, tant que vous appelez cette fonction avant d'appeler pour la première fois la fonction importée de la DLL, vous pouvez modifier le chemin de recherche par défaut utilisé pour localiser les DLL. L'avantage, bien sûr, c'est que vous pouvez passer une fonction dynamique à cette fonction qui est calculée au moment de l'exécution. Ce n'est pas possible avec la fonction DllImport Vous continuerez donc à utiliser un chemin d'accès relatif (le nom de la DLL uniquement) et vous compterez sur le nouvel ordre de recherche pour la trouver pour vous.

Vous devrez P/Invoke cette fonction. La déclaration ressemble à ceci :

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

19 votes

Une autre amélioration mineure pourrait consister à supprimer l'extension du nom de la DLL. Windows ajoutera automatiquement .dll et d'autres systèmes ajouteront l'extension appropriée sous Mono (par ex. .so sur Linux). Cela peut aider si la portabilité est une préoccupation.

8 votes

+1 pour le SetDllDirectory . Vous pouvez aussi simplement changer Environment.CurrentDirectory et tous les chemins relatifs seront évalués à partir de ce chemin !

2 votes

Avant même que ce message ne soit posté, l'OP a précisé qu'il créait un plugin, donc placer les DLL dans les fichiers de programme de Microsoft est une sorte d'échec. De même, modifier le DllDirectory ou le CWD du processus n'est pas une bonne idée, car cela pourrait faire échouer le processus. Maintenant AddDllDirectory d'autre part...

41voto

MikeP Points 4823

Encore mieux que la suggestion de Ran d'utiliser GetProcAddress il suffit de faire l'appel à LoadLibrary avant tout appel à la DllImport (avec seulement un nom de fichier sans chemin) et elles utiliseront le module chargé automatiquement.

J'ai utilisé cette méthode pour choisir, au moment de l'exécution, de charger une DLL native 32 bits ou 64 bits sans avoir à modifier un grand nombre de fonctions P/Invoke. Placez le code de chargement dans un constructeur statique pour le type qui a les fonctions importées et tout fonctionnera bien.

1 votes

Je ne sais pas si le fonctionnement est garanti. Ou si c'est simplement le cas sur la version actuelle du framework.

3 votes

@Code : Ça me semble garanti : Ordre de recherche de la bibliothèque de liens dynamiques . Plus précisément, "Facteurs qui affectent la recherche", point 1.

0 votes

Joli. Ma solution a un petit avantage supplémentaire, car même le nom de la fonction ne doit pas être statique et connu au moment de la compilation. Si vous avez 2 fonctions avec la même signature et un nom différent, vous pouvez les invoquer en utilisant ma solution FunctionLoader code.

30voto

Ran Points 4401

Si vous avez besoin d'un fichier .dll qui ne se trouve pas dans le chemin d'accès ou à l'emplacement de l'application, je ne pense pas que vous puissiez faire cela, car DllImport est un attribut, et les attributs sont uniquement des métadonnées qui sont définies sur les types, les membres et d'autres éléments de langage.

Une alternative qui peut vous aider à accomplir ce que je pense que vous essayez de faire, est d'utiliser la fonction native LoadLibrary via P/Invoke, afin de charger une .dll à partir du chemin dont vous avez besoin, puis d'utiliser GetProcAddress pour obtenir une référence à la fonction dont vous avez besoin à partir de ce fichier .dll. Utilisez-les ensuite pour créer un délégué que vous pouvez invoquer.

Pour faciliter son utilisation, vous pouvez ensuite définir ce délégué comme un champ de votre classe, de sorte que son utilisation ressemble à l'appel d'une méthode membre.

EDIT

Voici un extrait de code qui fonctionne et qui montre ce que je voulais dire.

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

Note : Je n'ai pas pris la peine d'utiliser FreeLibrary donc ce code n'est pas complet. Dans une application réelle, vous devez prendre soin de libérer les modules chargés pour éviter une fuite de mémoire.

0 votes

Il existe une contrepartie gérée pour LoadLibrary (dans la classe Assembly).

0 votes

Si vous aviez un exemple de code, ce serait plus facile pour moi de comprendre ! ^^ (En fait, c'est un peu brumeux)

1 votes

@Luca Piccioni : Si vous vouliez dire Assembly.LoadFrom, cela ne charge que les assemblages .NET, pas les bibliothèques natives. Que vouliez-vous dire ?

0voto

Mike W Points 1158

DllImport fonctionnera sans que le chemin d'accès complet soit spécifié, à condition que la dll soit située quelque part dans le chemin d'accès du système. Vous pouvez peut-être ajouter temporairement le dossier de l'utilisateur au chemin.

0 votes

J'ai essayé de le placer dans le système des variables d'environnement MAIS il est toujours considéré comme non constant (logique, je pense).

-16voto

Software_Developer Points 3082

Si tout échoue, il suffit de placer la DLL dans le répertoire windows\system32 dossier . Le compilateur le trouvera. Spécifiez la DLL à charger à partir de : DllImport("user32.dll"... ensemble EntryPoint = "my_unmanaged_function" pour importer la fonction non gérée de votre choix dans votre application 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);    
   }
}

Source et encore plus DllImport exemples : http://msdn.microsoft.com/en-us/library/aa288468(v=vs.71).aspx

0 votes

Ok, je suis d'accord avec votre solution d'utiliser le dossier win32 (la façon la plus simple de le faire) mais comment donnez-vous accès à ce dossier au débogueur de Visual Studio (et aussi à l'application compilée) ? (Sauf à l'exécuter manuellement en tant qu'administrateur)

0 votes

Si cela est utilisé pour autre chose qu'une aide au débogage, cela ne passerait pas l'examen (sécurité ou autre) dans mon livre.

22 votes

C'est une solution assez terrible. Le dossier système est destiné à système DLLs. Maintenant, vous avez besoin de privilèges d'administrateur et vous vous appuyez sur de mauvaises pratiques juste parce que vous êtes paresseux.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X