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

De hecho, obtuve una DLL C ++ (en funcionamiento) que quiero importar a mi proyecto C # para llamar a sus funciones.

Funciona cuando especifico la ruta completa a la DLL, como esta:

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, mum, dad, …) en función de la computadora / sesión en la que se ejecutará.

Entonces me gustaría que mi código sea 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 problema es que “DllImport” desea un parámetro “const string” para el directorio de la DLL.

Entonces mi pregunta es :: ¿Qué se puede hacer en este caso?

Contrariamente a las sugerencias de algunas de las otras respuestas, el uso del 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 las computadoras de las 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 la DLL de Win32 las que gobiernan las cosas, independientemente de si está utilizando las prácticas envolturas administradas (el intérprete de comandos P / Invoke simplemente llama a LoadLibrary ). Esas reglas se enumeran con gran detalle aquí , pero las importantes se resumen aquí:

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

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

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

  1. El directorio desde el que se cargó la aplicación.
  2. El directorio del sistema Use 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. Use la función GetWindowsDirectory para obtener la ruta de este directorio.
  5. El directorio actual.
  6. Los directorios que se enumeran en la PATH entorno PATH . Tenga en cuenta que esto no incluye la ruta por aplicación especificada por la clave de registro App Paths. La clave App Paths no se usa al calcular la ruta de búsqueda de DLL.

Entonces, a menos que nombre su DLL de la misma manera que una DLL del sistema (que obviamente no debería estar haciendo, bajo ninguna circunstancia), la orden de búsqueda predeterminada comenzará a buscar en el directorio desde el que se cargó su aplicación. Si coloca la DLL allí durante la instalación, se encontrará. Todos los problemas complicados desaparecen si solo usa rutas relativas.

Solo escribe:

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

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

Después de llamar a SetDllDirectory , la ruta de búsqueda DLL estándar 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 Use 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. Use la función GetWindowsDirectory para obtener la ruta de este directorio.
  6. Los directorios que se enumeran en la PATH entorno PATH .

Por lo tanto, siempre que llame a esta función antes de llamar a la función importada de la DLL por primera vez, puede modificar la ruta de búsqueda predeterminada utilizada para localizar las DLL. El beneficio, por supuesto, es que puede pasar un valor dynamic a esta función que se computa en tiempo de ejecución. Eso no es posible con el atributo DllImport , por lo que aún usará una ruta relativa (el nombre de la DLL solamente) y confiará en la nueva orden de búsqueda para encontrarla.

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

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

Incluso mejor que la sugerencia de Ran de usar GetProcAddress, simplemente realice la llamada a LoadLibrary antes de cualquier llamada a las funciones de 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 archivo DLL nativo de 32 bits o de 64 bits sin tener que modificar un grupo 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 funcionará bien.

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

Una alternativa que puede ayudarlo a lograr lo que creo que está intentando es utilizar LoadLibrary nativo a través de P / Invoke, para cargar un .dll desde la ruta que necesita, y luego usar GetProcAddress para obtener una referencia a la función. necesitas de eso .dll. A continuación, utilícelos para crear un delegado que pueda invocar.

Para que sea más fácil de usar, puede configurar este delegado en un campo de su clase, de modo que usarlo sea como 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(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: No me molesté en utilizar 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 pérdida de memoria.

Siempre que conozca el directorio donde se pueden encontrar sus bibliotecas C ++ en tiempo de ejecución, esto debería ser simple. Puedo ver claramente que este es el caso en tu 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 el momento de la ejecución antes de llamar a la función DLLFunction (presente en la biblioteca C ++), agregue esta línea de código en el código C #:

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

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

DllImport funcionará bien sin la ruta completa especificada, siempre que el dll esté ubicado en algún lugar de la ruta del sistema. Puede agregar temporalmente la carpeta del usuario a la ruta.

Si todo falla, simplemente ponga el archivo DLL en la carpeta windows\system32 . El comstackdor lo encontrará. Especifique la DLL desde la que cargar: DllImport("user32.dll"... , establezca EntryPoint = "my_unmanaged_function" para importar su función no administrada deseada a su aplicación 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 e incluso más ejemplos de DllImport : http://msdn.microsoft.com/en-us/library/aa288468(v=vs.71).aspx