Łączenie pliku wykonywalnego z biblioteką DLL

Plik wykonywalny łączy się z biblioteką DLL (lub ładuje) na jeden z dwóch sposobów:

  • Niejawne łączenie, w którym system operacyjny ładuje bibliotekę DLL w tym samym czasie co plik wykonywalny, który go używa. Plik wykonywalny klienta wywołuje wyeksportowane funkcje biblioteki DLL w taki sam sposób, jak w przypadku, gdy funkcje były statycznie połączone i zawarte w pliku wykonywalnym. Łączenie niejawne jest czasami nazywane statycznym ładowaniem lub łączeniem dynamicznym w czasie ładowania.

  • Jawne łączenie, w którym system operacyjny ładuje bibliotekę DLL na żądanie w czasie wykonywania. Plik wykonywalny używający biblioteki DLL przez jawne łączenie musi jawnie załadować i zwolnić bibliotekę DLL. Musi również skonfigurować wskaźnik funkcji, aby uzyskać dostęp do każdej funkcji używanej z biblioteki DLL. W przeciwieństwie do wywołań funkcji w statycznie połączonej bibliotece lub niejawnie połączonej biblioteki DLL plik wykonywalny klienta musi wywoływać wyeksportowane funkcje w jawnie połączonej bibliotece DLL za pośrednictwem wskaźników funkcji. Jawne łączenie jest czasami nazywane dynamicznym ładowaniem lub łączeniem dynamicznym w czasie wykonywania.

Plik wykonywalny może użyć metody łączenia, aby połączyć się z tą samą biblioteką DLL. Ponadto te metody nie wykluczają się wzajemnie; jeden plik wykonywalny może niejawnie łączyć się z biblioteką DLL, a drugi może zostać jawnie dołączony.

Określanie metody łączenia do użycia

Niezależnie od tego, czy używać linków niejawnych, czy jawnych linków, należy podjąć decyzję dotyczącą architektury dla aplikacji. Istnieją zalety i wady każdej metody.

Łączenie niejawne

Niejawne łączenie występuje, gdy kod aplikacji wywołuje wyeksportowaną funkcję DLL. Gdy kod źródłowy wywołującego pliku wykonywalnego jest kompilowany lub zmontowany, wywołanie funkcji DLL generuje odwołanie do funkcji zewnętrznej w kodzie obiektu. Aby rozwiązać to odwołanie zewnętrzne, aplikacja musi połączyć się z biblioteką importu (plik lib) udostępnioną przez twórcę biblioteki DLL.

Biblioteka importu zawiera tylko kod, aby załadować bibliotekę DLL i zaimplementować wywołania funkcji w bibliotece DLL. Znalezienie funkcji zewnętrznej w bibliotece importu informuje konsolidator, że kod tej funkcji znajduje się w bibliotece DLL. Aby rozwiązać zewnętrzne odwołania do bibliotek DLL, konsolidator po prostu dodaje informacje do pliku wykonywalnego, który informuje system, gdzie należy znaleźć kod DLL podczas uruchamiania procesu.

Gdy system uruchamia program zawierający dynamicznie połączone odwołania, używa informacji w pliku wykonywalnym programu w celu zlokalizowania wymaganych bibliotek DLL. Jeśli nie można zlokalizować biblioteki DLL, system kończy proces i wyświetla okno dialogowe, które zgłasza błąd. W przeciwnym razie system mapuje moduły DLL na przestrzeń adresową procesu.

Jeśli którakolwiek z bibliotek DLL ma funkcję punktu wejścia do inicjowania i zakończenia kodu, takiego jak DllMain, system operacyjny wywołuje funkcję. Jeden z parametrów przekazywanych do funkcji punktu wejścia określa kod, który wskazuje, że biblioteka DLL jest dołączana do procesu. Jeśli funkcja punktu wejścia nie zwraca wartości TRUE, system kończy proces i zgłasza błąd.

Na koniec system modyfikuje kod wykonywalny procesu w celu udostępnienia adresów początkowych dla funkcji DLL.

Podobnie jak w pozostałej części kodu programu, moduł ładujący mapuje kod DLL na przestrzeń adresową procesu podczas uruchamiania procesu. System operacyjny ładuje go do pamięci tylko wtedy, gdy jest to konieczne. W związku z tym PRELOAD atrybuty i LOADONCALL kodu używane przez pliki .def do kontrolowania ładowania w poprzednich wersjach systemu Windows nie mają już znaczenia.

Jawne łączenie

Większość aplikacji używa linków niejawnych, ponieważ jest to najłatwiejsza metoda łączenia. Istnieją jednak czasy, w których konieczne jest jawne łączenie. Oto kilka typowych powodów używania jawnego łączenia:

  • Aplikacja nie zna nazwy biblioteki DLL, która jest ładowana do czasu wykonywania. Na przykład aplikacja może uzyskać nazwę biblioteki DLL i wyeksportowane funkcje z pliku konfiguracji podczas uruchamiania.

  • Proces korzystający z niejawnego łączenia jest przerywany przez system operacyjny, jeśli biblioteka DLL nie zostanie znaleziona podczas uruchamiania procesu. Proces korzystający z jawnego łączenia nie został zakończony w tej sytuacji i może podjąć próbę odzyskania po błędzie. Na przykład proces może powiadomić użytkownika o błędzie i określić inną ścieżkę do biblioteki DLL.

  • Proces korzystający z niejawnego łączenia jest również przerywany, jeśli którakolwiek z bibliotek DLL, z którą jest połączona, ma funkcję, która kończy się niepowodzeniem DllMain . Proces korzystający z jawnego łączenia nie jest w tej sytuacji zakończony.

  • Aplikacja, która niejawnie łączy się z wieloma bibliotekami DLL, może działać wolno, ponieważ system Windows ładuje wszystkie biblioteki DLL podczas ładowania aplikacji. Aby zwiększyć wydajność uruchamiania, aplikacja może używać tylko niejawnego łączenia dla bibliotek DLL wymaganych natychmiast po załadowaniu. Może używać jawnego łączenia w celu załadowania innych bibliotek DLL tylko wtedy, gdy są potrzebne.

  • Jawne łączenie eliminuje konieczność łączenia aplikacji przy użyciu biblioteki importu. Jeśli zmiany w bibliotece DLL spowodują zmianę eksportu, aplikacje nie muszą ponownie połączyć, jeśli wywołają GetProcAddress nazwę funkcji, a nie wartość porządkową. Aplikacje korzystające z linków niejawnych muszą nadal ponownie łączyć się ze zmienioną biblioteką importu.

Poniżej przedstawiono dwa zagrożenia jawnego łączenia, o których należy pamiętać:

  • Jeśli biblioteka DLL ma DllMain funkcję punktu wejścia, system operacyjny wywołuje funkcję w kontekście wątku o nazwie LoadLibrary. Funkcja punktu wejścia nie jest wywoływana, jeśli biblioteka DLL jest już dołączona do procesu z powodu poprzedniego wywołania LoadLibrary , które nie ma odpowiedniego wywołania FreeLibrary funkcji. Jawne łączenie może powodować problemy, jeśli biblioteka DLL używa DllMain funkcji do inicjowania każdego wątku procesu, ponieważ wszystkie wątki, które już istnieją, gdy LoadLibrary (lub AfxLoadLibrary) nie są inicjowane.

  • Jeśli biblioteka DLL deklaruje dane statycznego zakresu jako __declspec(thread), może to spowodować błąd ochrony, jeśli jawnie zostanie połączony. Po załadowaniu biblioteki DLL przez wywołanie metody LoadLibrarydo metody powoduje błąd ochrony za każdym razem, gdy kod odwołuje się do tych danych. (Dane dotyczące zakresu statycznego obejmują zarówno globalne, jak i lokalne elementy statyczne). Dlatego podczas tworzenia biblioteki DLL należy unikać używania magazynu lokalnego wątku. Jeśli nie możesz, poinformuj użytkowników bibliotek DLL o potencjalnych pułapkach dynamicznego ładowania biblioteki DLL. Aby uzyskać więcej informacji, zobacz Using thread local storage in a dynamic-link library (Windows SDK) (Korzystanie z magazynu lokalnego wątku w bibliotece dynamicznej (Windows SDK).

Jak używać linków niejawnych

Aby używać biblioteki DLL przez łączenie niejawne, pliki wykonywalne klienta muszą uzyskać te pliki od dostawcy biblioteki DLL:

  • Co najmniej jeden plik nagłówka (pliki h), który zawiera deklaracje wyeksportowanych danych, funkcji i klas języka C++ w dll. Klasy, funkcje i dane wyeksportowane przez bibliotekę DLL muszą być oznaczone __declspec(dllimport) w pliku nagłówkowym. Aby uzyskać więcej informacji, zobacz dllexport, dllimport.

  • Biblioteka importu do połączenia z plikiem wykonywalnym. Konsolidator tworzy bibliotekę importu podczas tworzenia biblioteki DLL. Aby uzyskać więcej informacji, zobacz pliki LIB jako dane wejściowe konsolidatora.

  • Rzeczywisty plik DLL.

Aby używać danych, funkcji i klas w dll przez niejawne łączenie, każdy plik źródłowy klienta musi zawierać pliki nagłówka, które je deklarują. Z perspektywy kodowania wywołania wyeksportowanych funkcji są tak samo jak każde inne wywołanie funkcji.

Aby utworzyć plik wykonywalny klienta, należy połączyć się z biblioteką importu biblioteki DLL. Jeśli używasz zewnętrznego pliku makefile lub systemu kompilacji, określ bibliotekę importu wraz z innymi plikami obiektów lub bibliotekami, które łączysz.

System operacyjny musi mieć możliwość zlokalizowania pliku DLL podczas ładowania pliku wykonywalnego wywołującego. Oznacza to, że podczas instalowania aplikacji należy wdrożyć lub zweryfikować istnienie biblioteki DLL.

Aby używać biblioteki DLL przez jawne łączenie, aplikacje muszą wykonać wywołanie funkcji, aby jawnie załadować bibliotekę DLL w czasie wykonywania. Aby jawnie połączyć się z biblioteką DLL, aplikacja musi:

  • Wywołaj metodę LoadLibraryEx lub podobną funkcję, aby załadować bibliotekę DLL i uzyskać uchwyt modułu.

  • Wywołaj metodę GetProcAddress , aby uzyskać wskaźnik funkcji do każdej wyeksportowanej funkcji wywoływanej przez aplikację. Ponieważ aplikacje nazywają funkcje DLL za pomocą wskaźnika, kompilator nie generuje odwołań zewnętrznych, więc nie ma potrzeby łączenia z biblioteką importu. Należy jednak mieć instrukcję typedef or using definiującą podpis wywołania wyeksportowanych funkcji, które są wywoływane.

  • Wywołaj metodę FreeLibrary po zakończeniu pracy z biblioteką DLL.

Na przykład ta przykładowa funkcja wywołuje metodę LoadLibrary ładowania biblioteki DLL o nazwie "MyDLL", wywołuje GetProcAddress wskaźnik do funkcji o nazwie "DLLFunc1", wywołuje funkcję i zapisuje wynik, a następnie wywołuje FreeLibrary metodę w celu zwolnienia biblioteki DLL.

#include "windows.h"

typedef HRESULT (CALLBACK* LPFNDLLFUNC1)(DWORD,UINT*);

HRESULT LoadAndCallSomeFunction(DWORD dwParam1, UINT * puParam2)
{
    HINSTANCE hDLL;               // Handle to DLL
    LPFNDLLFUNC1 lpfnDllFunc1;    // Function pointer
    HRESULT hrReturnVal;

    hDLL = LoadLibrary("MyDLL");
    if (NULL != hDLL)
    {
        lpfnDllFunc1 = (LPFNDLLFUNC1)GetProcAddress(hDLL, "DLLFunc1");
        if (NULL != lpfnDllFunc1)
        {
            // call the function
            hrReturnVal = lpfnDllFunc1(dwParam1, puParam2);
        }
        else
        {
            // report the error
            hrReturnVal = ERROR_DELAY_LOAD_FAILED;
        }
        FreeLibrary(hDLL);
    }
    else
    {
        hrReturnVal = ERROR_DELAY_LOAD_FAILED;
    }
    return hrReturnVal;
}

W przeciwieństwie do tego przykładu w większości przypadków należy wywołać metodę LoadLibrary i FreeLibrary tylko raz w aplikacji dla danej biblioteki DLL. Jest to szczególnie prawdziwe, jeśli zamierzasz wywołać wiele funkcji w dll lub wywołać funkcje DLL wielokrotnie.

Co chcesz dowiedzieć się więcej?

Zobacz też

Tworzenie bibliotek DLL języka C/C++ w programie Visual Studio