Como posso especificar um caminho em tempo de execução?

Na verdade, eu tenho uma DLL em C ++ (em funcionamento) que quero importar para meu projeto em C # para chamar suas funções.

Ele funciona quando eu especificar o caminho completo para a DLL, assim:

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

O problema é que vai ser um projeto instalável, então a pasta do usuário não será a mesma (ex: pierre, paul, jack, mãe, pai, …) dependendo do computador / session onde ele será executado.

Então eu gostaria que meu código fosse um pouco mais genérico, assim:

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

O grande problema é que “DllImport” deseja um parâmetro “const string” para o diretório da DLL.

Então, minha pergunta é: O que poderia ser feito neste caso?

    Ao contrário das sugestões de algumas das outras respostas, o uso do atributo DllImport ainda é a abordagem correta.

    Eu sinceramente não entendo porque você não pode fazer como todo mundo no mundo e especificar um caminho relativo para sua DLL. Sim, o caminho no qual seu aplicativo será instalado difere em computadores de pessoas diferentes, mas isso é basicamente uma regra universal quando se trata de implantação. O mecanismo DllImport é projetado com isso em mente.

    Na verdade, não é nem mesmo o DllImport que lida com isso. São as regras nativas de carregamento de Win32 DLL que governam as coisas, independentemente de você estar usando os wrappers gerenciados (o marshaller P / Invoke apenas chama LoadLibrary ). Essas regras são enumeradas em grande detalhe aqui , mas as mais importantes são extraídas aqui:

    Antes de o sistema procurar por uma DLL, ele verifica o seguinte:

    • Se uma DLL com o mesmo nome de módulo já estiver carregada na memory, o sistema usará a DLL carregada, não importa em qual diretório esteja. O sistema não pesquisa a DLL.
    • Se a DLL estiver na lista de DLLs conhecidas para a versão do Windows em que o aplicativo está sendo executado, o sistema usa sua cópia da DLL conhecida (e as DLLs dependentes da DLL conhecida, se houver). O sistema não procura pela DLL.

    Se SafeDllSearchMode estiver ativado (o padrão), a ordem de pesquisa será a seguinte:

    1. O diretório do qual o aplicativo foi carregado.
    2. O diretório do sistema. Use a function GetSystemDirectory para obter o caminho desse diretório.
    3. O diretório do sistema de 16 bits. Não existe nenhuma function que obtenha o caminho desse diretório, mas é pesquisada.
    4. O diretório do Windows. Use a function GetWindowsDirectory para obter o caminho desse diretório.
    5. O diretório atual.
    6. Os diretórios listados na variável de ambiente PATH . Observe que isso não inclui o caminho por aplicativo especificado pela chave do Registro App Paths. A chave App Paths não é usada ao calcular o caminho de pesquisa da DLL.

    Portanto, a menos que você esteja nomeando sua DLL da mesma forma que uma DLL do sistema (que obviamente você nunca deveria estar fazendo, em nenhuma circunstância), a ordem de pesquisa padrão começará a procurar no diretório a partir do qual seu aplicativo foi carregado. Se você colocar a DLL durante a instalação, ela será encontrada. Todos os problemas complicados desaparecem se você usar apenas caminhos relativos.

    Apenas escreva:

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

    Mas se isso não funcionar por qualquer motivo, e você precisar forçar o aplicativo a procurar em um diretório diferente para a DLL, você poderá modificar o caminho de pesquisa padrão usando a function SetDllDirectory .
    Note que, conforme a documentação:

    Depois de chamar SetDllDirectory , o caminho de pesquisa padrão da DLL é:

    1. O diretório do qual o aplicativo foi carregado.
    2. O diretório especificado pelo parâmetro lpPathName .
    3. O diretório do sistema. Use a function GetSystemDirectory para obter o caminho desse diretório.
    4. O diretório do sistema de 16 bits. Não existe nenhuma function que obtenha o caminho desse diretório, mas é pesquisada.
    5. O diretório do Windows. Use a function GetWindowsDirectory para obter o caminho desse diretório.
    6. Os diretórios listados na variável de ambiente PATH .

    Portanto, contanto que você chamar essa function antes de chamar a function importada da DLL pela primeira vez, você pode modificar o caminho de pesquisa padrão usado para localizar as DLLs. O benefício, é claro, é que você pode passar um valor dynamic para essa function que é calculada em tempo de execução. Isso não é possível com o atributo DllImport , portanto você ainda usará um caminho relativo (somente o nome da DLL) e contará com a nova ordem de pesquisa para encontrá-lo para você.

    Você terá que P / Invocar essa function. A declaração é assim:

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

    Ainda melhor do que a sugestão de Ran de usar GetProcAddress, basta fazer a chamada para LoadLibrary antes de qualquer chamada para as funções DllImport (com apenas um nome de arquivo sem um caminho) e eles usarão o módulo carregado automaticamente.

    Eu usei esse método para escolher em tempo de execução se carregar uma DLL nativa de 32 bits ou 64 bits sem ter que modificar um monte de funções P / Invoke-d. Cole o código de carregamento em um construtor estático para o tipo que tem as funções importadas e tudo funcionará bem.

    Se você precisar de um arquivo .dll que não esteja no caminho ou no local do aplicativo, não acho que você possa fazer isso, porque DllImport é um atributo e os atributos são apenas metadados definidos em tipos, membros e outros elementos da linguagem.

    Uma alternativa que pode ajudá-lo a realizar o que eu acho que você está tentando, é usar o LoadLibrary nativo através de P / Invoke, para carregar um .dll do caminho que você precisa e use GetProcAddress para obter uma referência para a function você precisa desse arquivo .dll. Em seguida, use-os para criar um delegado que você possa invocar.

    Para facilitar o uso, você pode definir esse delegado para um campo em sua class, para que usá-lo pareça chamar um método de membro.

    EDITAR

    Aqui está um trecho de código que funciona e mostra o que eu quis dizer.

     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: Eu não me incomodei em usar FreeLibrary , então este código não está completo. Em um aplicativo real, você deve ter o cuidado de liberar os módulos carregados para evitar um memory leaks.

    Contanto que você saiba o diretório onde suas bibliotecas C ++ podem ser encontradas em tempo de execução, isso deve ser simples. Eu posso ver claramente que este é o caso em seu código. Seu myDll.dll estaria presente dentro do diretório myLibFolder dentro da pasta temporária do usuário atual.

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

    Agora você pode continuar usando a instrução DllImport usando uma string const como mostrada abaixo:

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

    Apenas em tempo de execução antes de chamar a function DLLFunction (presente na biblioteca C ++), adicione esta linha de código no código C #:

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

    Isso simplesmente instrui o CLR a procurar as bibliotecas C ++ não gerenciadas no caminho do diretório que você obteve no tempo de execução do seu programa. Directory.SetCurrentDirectory chamada Directory.SetCurrentDirectory define o diretório de trabalho atual do aplicativo para o diretório especificado. Se o seu myDLL.dll estiver presente no caminho representado pelo caminho assemblyProbeDirectory , ele será carregado e a function desejada será chamada por meio de p / invoke.

    DllImport funcionará bem sem o caminho completo especificado, desde que a dll esteja localizada em algum lugar no caminho do sistema. Você poderá adicionar temporariamente a pasta do usuário ao caminho.

    Se tudo falhar, basta colocar a DLL na pasta windows\system32 . O compilador irá encontrá-lo. Especifique a DLL para carregar com: DllImport("user32.dll"... , defina EntryPoint = "my_unmanaged_function" para importar sua function não gerenciada desejada para seu aplicativo 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); } } 

    Fonte e ainda mais exemplos de DllImport : http://msdn.microsoft.com/en-us/library/aa288468(v=vs.71).aspx