Jak określić ścieżkę [DllImport] w czasie wykonywania?

Jak określić ścieżkę [DllImport] w czasie wykonywania?

Wbrew sugestiom niektórych innych odpowiedzi, używając DllImport atrybut jest nadal poprawnym podejściem.

Szczerze nie rozumiem, dlaczego nie możesz postępować tak jak wszyscy na świecie i określić krewnego ścieżka do biblioteki DLL. Tak, ścieżka, w której zostanie zainstalowana Twoja aplikacja, różni się na komputerach różnych osób, ale jest to w zasadzie uniwersalna zasada, jeśli chodzi o wdrażanie. DllImport mechanizm został zaprojektowany z myślą o tym.

W rzeczywistości nie jest to nawet DllImport który sobie z tym radzi. To natywne reguły ładowania DLL Win32, które zarządzają rzeczami, niezależnie od tego, czy używasz poręcznych zarządzanych opakowań (organizator P/Invoke wywołuje po prostu LoadLibrary ). Zasady te są tutaj szczegółowo wyliczone, ale najważniejsze z nich są tutaj zaczerpnięte:

Tak więc, chyba że nazywasz swoją bibliotekę DLL taką samą nazwą jak biblioteka systemowa (czego oczywiście nigdy nie powinieneś robić w żadnych okolicznościach), domyślna kolejność wyszukiwania zacznie szukać w katalogu, z którego aplikacja została załadowana. Jeśli umieścisz tam bibliotekę DLL podczas instalacji, zostanie ona znaleziona. Wszystkie skomplikowane problemy znikną, jeśli użyjesz tylko ścieżek względnych.

Po prostu napisz:

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

Ale jeśli to nie działają z dowolnego powodu i musisz zmusić aplikację do szukania w innym katalogu dla biblioteki DLL, możesz zmodyfikować domyślną ścieżkę wyszukiwania za pomocą SetDllDirectory funkcjonować.
Zwróć uwagę, że zgodnie z dokumentacją:

Tak długo, jak wywołasz tę funkcję przed wywołaniem funkcji importowanej z biblioteki DLL po raz pierwszy, możesz zmodyfikować domyślną ścieżkę wyszukiwania używaną do lokalizowania bibliotek DLL. Zaletą jest oczywiście możliwość przekazania dynamicznego wartość tej funkcji, która jest obliczana w czasie wykonywania. Nie jest to możliwe z DllImport atrybut, więc nadal będziesz używać ścieżki względnej (tylko nazwa biblioteki DLL) i polegać na nowej kolejności wyszukiwania, aby ją znaleźć.

Będziesz musiał P/Wywołaj tę funkcję. Deklaracja wygląda tak:

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

Nawet lepiej niż sugestia Ran, aby użyć GetProcAddress , po prostu zadzwoń do LoadLibrary przed wezwaniami do DllImport funkcji (tylko z nazwą pliku bez ścieżki) i automatycznie użyją załadowanego modułu.

Użyłem tej metody, aby wybrać w czasie wykonywania, czy załadować 32-bitową, czy 64-bitową natywną bibliotekę DLL bez konieczności modyfikowania wielu funkcji P/Invoke-d. Umieść kod ładujący w konstruktorze statycznym dla typu, który ma zaimportowane funkcje, a wszystko będzie działać poprawnie.


Jeśli potrzebujesz pliku .dll, którego nie ma w ścieżce ani w lokalizacji aplikacji, to nie sądzę, że możesz to zrobić, ponieważ DllImport jest atrybutem, a atrybuty są tylko metadanymi ustawionymi dla typów, członków i innych elementów języka.

Alternatywą, która może pomóc Ci osiągnąć to, co myślę, że próbujesz, jest użycie natywnego LoadLibrary poprzez P/Invoke, aby załadować plik .dll z żądanej ścieżki, a następnie użyj GetProcAddress aby uzyskać odwołanie do potrzebnej funkcji z tego pliku .dll. Następnie użyj ich, aby utworzyć pełnomocnika, którego możesz wywoływać.

Aby ułatwić korzystanie, możesz ustawić ten delegat na pole w swojej klasie, tak aby korzystanie z niego wyglądało jak wywołanie metody członkowskiej.

EDYTUJ

Oto fragment kodu, który działa i pokazuje, o co mi chodziło.

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

Uwaga:nie zawracałem sobie głowy używaniem FreeLibrary , więc ten kod nie jest kompletny. W prawdziwej aplikacji powinieneś zadbać o zwolnienie załadowanych modułów, aby uniknąć wycieku pamięci.