¿Cómo puedo especificar una ruta [DllImport] en tiempo de ejecución?


De hecho, tengo una DLL de C++ (que funciona) que quiero importar a mi proyecto de C# para llamar a sus funciones.

Funciona cuando especifico la ruta completa a la DLL, así:

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

El problema es que va a ser un proyecto instalable, por lo que la carpeta del usuario no será la misma (por ejemplo: pierre, paul, jack, mamá, papá,...) dependiendo de la computadora / sesión donde se ejecutaría.

Así que me gustaría que mi código fuera un poco más genérico, así:

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

El gran el trato es que" DllImport "desea un parámetro" const string " para el directorio del DLL.

Así que mi pregunta es :: ¿Qué se podría hacer en este caso ?

Author: Jsncrdnl, 2012-01-12

6 answers

Contrariamente a las sugerencias de algunas de las otras respuestas, usar el atributo DllImport sigue siendo el enfoque correcto.

Honestamente no entiendo por qué no puedes hacer como todos los demás en el mundo y especificar una ruta relativa a tu DLL. Sí, la ruta en la que se instalará su aplicación difiere en los equipos de diferentes personas, pero eso es básicamente una regla universal cuando se trata de implementación. El mecanismo DllImport está diseñado con esto en mente.

De hecho, ni siquiera es DllImport quien lo maneja. Son las reglas nativas de carga de DLL Win32 las que gobiernan las cosas, independientemente de si está utilizando los prácticos wrappers administrados (el marshaller P / Invoke solo llama LoadLibrary). Esas reglas se enumeran en gran detalle aquí , pero las importantes se extraen aquí: {[18]]}

Antes de que el sistema busque una DLL, comprueba lo siguiente:

  • Si una DLL con el mismo módulo el nombre ya está cargado en la memoria, el sistema utiliza la DLL cargada, sin importar en qué directorio se encuentre. El sistema no busca la DLL.
  • Si la DLL está en la lista de DLL conocidos para la versión de Windows en la que se está ejecutando la aplicación, el sistema utiliza su copia de la DLL conocida (y las DLL dependientes de la DLL conocida, si las hay). El sistema no busca la DLL.

Si SafeDllSearchMode está habilitado (el valor predeterminado), el orden de búsqueda es el siguiente sigue:

  1. El directorio desde el que se cargó la aplicación.
  2. El directorio del sistema. Utilice la función GetSystemDirectory para obtener la ruta de este directorio.
  3. El directorio del sistema de 16 bits. No hay ninguna función que obtenga la ruta de este directorio, pero se busca.
  4. El directorio de Windows. Utilice la función GetWindowsDirectory para obtener la ruta de este directorio.
  5. El directorio actual.
  6. Los directorios que se enumeran en el PATH variable de entorno. Tenga en cuenta que esto no incluye la ruta de acceso por aplicación especificada por la clave de registro de rutas de aplicación. La clave App Paths no se utiliza al calcular la ruta de búsqueda DLL.

Por lo tanto, a menos que esté nombrando su DLL lo mismo que una DLL del sistema (lo que obviamente no debería hacer, nunca, bajo ninguna circunstancia), el orden de búsqueda predeterminado comenzará a buscar en el directorio desde el que se cargó su aplicación. Si coloca el DLL allí durante el instalar, se encontrará. Todos los problemas complicados desaparecen si solo usa rutas relativas.

Simplemente escribe:

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

Pero si no funciona por cualquier razón, y necesita forzar a la aplicación a buscar la DLL en un directorio diferente, puede modificar la ruta de búsqueda predeterminada utilizando el SetDllDirectory function .
Tenga en cuenta que, según la documentación:

Después de llamar a SetDllDirectory, la ruta de búsqueda estándar de DLL es:

  1. El directorio desde el que se cargó la aplicación.
  2. El directorio especificado por el parámetro lpPathName.
  3. El directorio del sistema. Utilice la función GetSystemDirectory para obtener la ruta de este directorio.
  4. El directorio del sistema de 16 bits. No hay ninguna función que obtenga la ruta de este directorio, pero se busca.
  5. El directorio de Windows. Utilice la función GetWindowsDirectory para obtener la ruta de este directorio.
  6. Los directorios que se enumeran en el PATH variable de entorno.

Así que siempre que llame a esta función antes de llamar a la función importada desde la DLL por primera vez, puede modificar la ruta de búsqueda predeterminada utilizada para localizar DLL. El beneficio, por supuesto, es que puede pasar un dinámico valor a esta función que se calcula en tiempo de ejecución. Eso no es posible con el atributo DllImport, por lo que seguirá utilizando una ruta relativa (solo el nombre de la DLL) allí, y confiará en el nuevo orden de búsqueda para encontrar es para ti.

Tendrás que P/Invocar esta función. La declaración se ve así:

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool SetDllDirectory(string lpPathName);
 150
Author: Cody Gray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-14 11:31:14

Incluso mejor que la sugerencia de Ran de usar GetProcAddress, simplemente haga la llamada a LoadLibrary antes de cualquier llamada a las funciones DllImport (con solo un nombre de archivo sin una ruta) y usarán el módulo cargado automáticamente.

He utilizado este método para elegir en tiempo de ejecución si cargar un DLL nativo de 32 bits o 64 bits sin tener que modificar un montón de funciones P/Invoke-d. Pegue el código de carga en un constructor estático para el tipo que tiene las funciones importadas y todo funciona bien.

 28
Author: MikeP,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-12 14:46:24

Si necesita un .archivo dll que no está en la ruta o en la ubicación de la aplicación, entonces no creo que pueda hacer precisamente eso, porque DllImport es un atributo, y los atributos son solo metadatos que se establece en tipos, miembros y otros elementos del lenguaje.

Una alternativa que puede ayudarle a lograr lo que creo que está tratando, es utilizar el nativo LoadLibrary a través de P/Invoke, con el fin de cargar a .dll de la ruta que necesita, y luego use GetProcAddress para obtener una referencia a la función de la que necesita que .DLL. A continuación, utilícelos para crear un delegado que pueda invocar.

Para que sea más fácil de usar, puede establecer este delegado en un campo en su clase, de modo que usarlo parezca llamar a un método miembro.

EDITAR

Aquí hay un fragmento de código que funciona y muestra lo que quise decir.

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

Nota: No me molesté en usar FreeLibrary, por lo que este código no está completo. En una aplicación real, debe tener cuidado de liberar los módulos cargados para evitar una memoria fuga.

 22
Author: Ran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-12 16:01:39

Mientras conozca el directorio donde se pueden encontrar sus bibliotecas de C++ en tiempo de ejecución, esto debería ser simple. Puedo ver claramente que este es el caso en su código. Su myDll.dll estaría presente dentro del directorio myLibFolder dentro de la carpeta temporal del usuario actual.

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

Ahora puede continuar usando la instrucción DllImport usando una cadena const como se muestra a continuación:

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

Justo en tiempo de ejecución antes de llamar a la función DLLFunction (presente en la biblioteca de C++) agregue esta línea de código en C# código:

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

Esto simplemente indica al CLR que busque las bibliotecas de C++ no administradas en la ruta del directorio que obtuvo en tiempo de ejecución de su programa. Directory.SetCurrentDirectory call establece el directorio de trabajo actual de la aplicación en el directorio especificado. Si su myDLL.dll está presente en path representado por assemblyProbeDirectory path, entonces se cargará y la función deseada se llamará a través de p/invoke.

 2
Author: RBT,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-20 08:44:15

DllImport funcionará bien sin la ruta completa especificada siempre y cuando la dll esté ubicada en algún lugar de la ruta del sistema. Es posible que pueda agregar temporalmente la carpeta del usuario a la ruta.

 0
Author: Mike W,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-12 13:53:09

Si todo falla, simplemente coloque el DLL en la carpeta windows\system32. El compilador lo encontrará. Especifique la DLL desde la que cargar con: DllImport("user32.dll"..., establezca EntryPoint = "my_unmanaged_function" para importar la función no administrada deseada a su aplicación de 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);    
   }
}

Fuente y aún más DllImport ejemplos: http://msdn.microsoft.com/en-us/library/aa288468 (v=vs.71). aspx

 -13
Author: Software_Designer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-12 14:14:23