DLL - szybki przewodnik
Łączenie dynamiczne to mechanizm, który łączy aplikacje z bibliotekami w czasie wykonywania. Biblioteki pozostają we własnych plikach i nie są kopiowane do plików wykonywalnych aplikacji. Biblioteki DLL łączą się z aplikacją, gdy aplikacja jest uruchomiona, a nie podczas jej tworzenia. Biblioteki DLL mogą zawierać łącza do innych bibliotek DLL.
Często biblioteki DLL są umieszczane w plikach o różnych rozszerzeniach, takich jak .EXE, .DRV lub .DLL.
Zalety DLL
Poniżej podano kilka zalet posiadania plików DLL.
Zużywa mniej zasobów
Pliki DLL nie są ładowane do pamięci RAM razem z głównym programem; nie zajmują miejsca, chyba że jest to wymagane. Gdy potrzebny jest plik DLL, jest ładowany i uruchamiany. Na przykład, jeśli użytkownik programu Microsoft Word edytuje dokument, plik DLL drukarki nie jest wymagany w pamięci RAM. Jeśli użytkownik zdecyduje się wydrukować dokument, aplikacja Word spowoduje załadowanie i uruchomienie pliku DLL drukarki.
Promuje architekturę modułową
Biblioteka DLL pomaga promować tworzenie programów modułowych. Pomaga w tworzeniu dużych programów wymagających wielu wersji językowych lub programu wymagającego architektury modułowej. Przykładem programu modułowego jest program księgowy posiadający wiele modułów, które mogą być dynamicznie ładowane w czasie wykonywania.
Pomaga w łatwym wdrażaniu i instalacji
Gdy funkcja w bibliotece DLL wymaga aktualizacji lub poprawki, wdrożenie i instalacja biblioteki DLL nie wymaga ponownego połączenia programu z biblioteką DLL. Ponadto, jeśli wiele programów używa tej samej biblioteki DLL, wszystkie z nich skorzystają z aktualizacji lub poprawki. Ten problem może występować częściej, gdy używasz biblioteki DLL innej firmy, która jest regularnie aktualizowana lub naprawiana.
Aplikacje i biblioteki DLL mogą automatycznie łączyć się z innymi bibliotekami DLL, jeśli powiązanie DLL jest określone w sekcji IMPORTS pliku definicji modułu jako część kompilacji. W przeciwnym razie można je jawnie załadować za pomocą funkcji Windows LoadLibrary.
Ważne pliki DLL
COMDLG32.DLL - Steruje oknami dialogowymi.
GDI32.DLL - Zawiera liczne funkcje do rysowania grafiki, wyświetlania tekstu i zarządzania czcionkami.
KERNEL32.DLL - Zawiera setki funkcji do zarządzania pamięcią i różnymi procesami.
USER32.DLL- Zawiera liczne funkcje interfejsu użytkownika. Zaangażowany w tworzenie okien programów i ich wzajemne interakcje.
Najpierw omówimy kwestie i wymagania, które należy wziąć pod uwagę podczas tworzenia własnych bibliotek DLL.
Rodzaje bibliotek DLL
Podczas ładowania biblioteki DLL do aplikacji dwie metody łączenia umożliwiają wywołanie wyeksportowanych funkcji DLL. Dwie metody łączenia to:
- dynamiczne łączenie w czasie ładowania oraz
- dynamiczne łączenie w czasie wykonywania.
Dynamiczne łączenie w czasie ładowania
W dynamicznym łączeniu w czasie ładowania aplikacja wykonuje jawne wywołania wyeksportowanych funkcji DLL, takich jak funkcje lokalne. Aby korzystać z dynamicznego łączenia w czasie ładowania, podczas kompilowania i łączenia aplikacji należy podać plik nagłówkowy (.h) i plik biblioteki importu (.lib). Gdy to zrobisz, konsolidator dostarczy systemowi informacje wymagane do załadowania biblioteki DLL i rozpoznania lokalizacji wyeksportowanych funkcji DLL w czasie ładowania.
Dynamiczne łączenie w czasie wykonywania
W dynamicznym łączeniu w czasie wykonywania aplikacja wywołuje funkcję LoadLibrary lub funkcję LoadLibraryEx w celu załadowania biblioteki DLL w czasie wykonywania. Po pomyślnym załadowaniu biblioteki DLL użyj funkcji GetProcAddress, aby uzyskać adres wyeksportowanej funkcji DLL, którą chcesz wywołać. Gdy używasz dynamicznego łączenia w czasie wykonywania, nie potrzebujesz pliku biblioteki importu.
Poniższa lista opisuje kryteria aplikacji dotyczące wyboru między dynamicznym łączeniem w czasie ładowania a dynamicznym łączeniem w czasie wykonywania:
Startup performance : Jeśli wydajność początkowego uruchamiania aplikacji jest ważna, należy użyć dynamicznego łączenia w czasie wykonywania.
Ease of use: W dynamicznym łączeniu w czasie ładowania wyeksportowane funkcje DLL są podobne do funkcji lokalnych. Ułatwia wywoływanie tych funkcji.
Application logic: W dynamicznym łączeniu w czasie wykonywania aplikacja może się rozgałęziać, aby załadować różne moduły zgodnie z wymaganiami. Jest to ważne, gdy tworzysz wersje wielojęzyczne.
Punkt wejścia biblioteki DLL
Podczas tworzenia biblioteki DLL można opcjonalnie określić funkcję punktu wejścia. Funkcja punktu wejścia jest wywoływana, gdy procesy lub wątki dołączają się do biblioteki DLL lub odłączają się od biblioteki DLL. Funkcji punktu wejścia można użyć do zainicjowania lub zniszczenia struktur danych zgodnie z wymaganiami biblioteki DLL.
Ponadto, jeśli aplikacja jest wielowątkowa, można użyć lokalnego magazynu wątków (TLS) do przydzielenia pamięci, która jest prywatna dla każdego wątku w funkcji punktu wejścia. Poniższy kod jest przykładem funkcji punktu wejścia DLL.
BOOL APIENTRY DllMain(
HANDLE hModule, // Handle to DLL module DWORD ul_reason_for_call, LPVOID lpReserved ) // Reserved
{
switch ( ul_reason_for_call )
{
case DLL_PROCESS_ATTACHED:
// A process is loading the DLL.
break;
case DLL_THREAD_ATTACHED:
// A process is creating a new thread.
break;
case DLL_THREAD_DETACH:
// A thread exits normally.
break;
case DLL_PROCESS_DETACH:
// A process unloads the DLL.
break;
}
return TRUE;
}
Gdy funkcja punktu wejścia zwraca wartość FALSE, aplikacja nie uruchomi się, jeśli używasz dynamicznego łączenia w czasie ładowania. Jeśli używasz dynamicznego łączenia w czasie wykonywania, tylko pojedyncza biblioteka DLL nie zostanie załadowana.
Funkcja punktu wejścia powinna wykonywać tylko proste zadania inicjalizacji i nie powinna wywoływać żadnych innych funkcji ładowania lub kończenia bibliotek DLL. Na przykład w funkcji punktu wejścia nie należy bezpośrednio ani pośrednio wywoływać funkcjiLoadLibrary funkcja lub LoadLibraryExfunkcjonować. Ponadto nie powinieneś dzwonić doFreeLibrary funkcja, gdy proces się kończy.
WARNING: W aplikacjach wielowątkowych upewnij się, że dostęp do globalnych danych DLL jest zsynchronizowany (bezpieczny wątkowo), aby uniknąć możliwego uszkodzenia danych. Aby to zrobić, użyj protokołu TLS, aby zapewnić unikalne dane dla każdego wątku.
Eksportowanie funkcji DLL
Aby wyeksportować funkcje DLL, można dodać słowo kluczowe funkcji do wyeksportowanych funkcji DLL lub utworzyć plik definicji modułu (.def), który zawiera listę wyeksportowanych funkcji DLL.
Aby użyć słowa kluczowego funkcji, musisz zadeklarować każdą funkcję, którą chcesz wyeksportować, za pomocą następującego słowa kluczowego:
__declspec(dllexport)
Aby użyć wyeksportowanych funkcji DLL w aplikacji, musisz zadeklarować każdą funkcję, którą chcesz zaimportować, za pomocą następującego słowa kluczowego:
__declspec(dllimport)
Zwykle użyjesz jednego pliku nagłówkowego z rozszerzeniem define oświadczenie i plik ifdef oświadczenie, aby oddzielić oświadczenie eksportowe i oświadczenie importu.
Możesz również użyć pliku definicji modułu, aby zadeklarować wyeksportowane funkcje DLL. W przypadku korzystania z pliku definicji modułu nie trzeba dodawać słowa kluczowego funkcji do wyeksportowanych funkcji DLL. W pliku definicji modułu deklarujesz rozszerzenieLIBRARY oświadczenie i EXPORTSinstrukcja dla biblioteki DLL. Poniższy kod jest przykładem pliku definicji.
// SampleDLL.def
//
LIBRARY "sampleDLL"
EXPORTS
HelloWorld
Napisz przykładową bibliotekę DLL
W programie Microsoft Visual C ++ 6.0 można utworzyć bibliotekę DLL, wybierając plik Win32 Dynamic-Link Library typ projektu lub MFC AppWizard (dll) typ projektu.
Poniższy kod jest przykładem biblioteki DLL, która została utworzona w programie Visual C ++ przy użyciu typu projektu Win32 Dynamic-Link Library.
// SampleDLL.cpp
#include "stdafx.h"
#define EXPORTING_DLL
#include "sampleDLL.h"
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
{
return TRUE;
}
void HelloWorld()
{
MessageBox( NULL, TEXT("Hello World"),
TEXT("In a DLL"), MB_OK);
}
// File: SampleDLL.h
//
#ifndef INDLL_H
#define INDLL_H
#ifdef EXPORTING_DLL
extern __declspec(dllexport) void HelloWorld() ;
#else
extern __declspec(dllimport) void HelloWorld() ;
#endif
#endif
Wywołanie przykładowej biblioteki DLL
Poniższy kod jest przykładem projektu aplikacji Win32, który wywołuje wyeksportowaną funkcję DLL w bibliotece DLL SampleDLL.
// SampleApp.cpp
#include "stdafx.h"
#include "sampleDLL.h"
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
HelloWorld();
return 0;
}
NOTE : W dynamicznym łączeniu w czasie ładowania należy połączyć bibliotekę importu SampleDLL.lib, która jest tworzona podczas budowania projektu SampleDLL.
W dynamicznym łączeniu w czasie wykonywania do wywołania funkcji wyeksportowanej biblioteki DLL SampleDLL.dll używany jest kod podobny do następującego kodu.
...
typedef VOID (*DLLPROC) (LPTSTR);
...
HINSTANCE hinstDLL;
DLLPROC HelloWorld;
BOOL fFreeDLL;
hinstDLL = LoadLibrary("sampleDLL.dll");
if (hinstDLL != NULL)
{
HelloWorld = (DLLPROC) GetProcAddress(hinstDLL, "HelloWorld");
if (HelloWorld != NULL)
(HelloWorld);
fFreeDLL = FreeLibrary(hinstDLL);
}
...
Podczas kompilowania i łączenia aplikacji SampleDLL system operacyjny Windows wyszukuje bibliotekę DLL SampleDLL w następujących lokalizacjach w następującej kolejności:
Folder aplikacji
Bieżący folder
Folder systemowy Windows (plik GetSystemDirectory funkcja zwraca ścieżkę do folderu systemowego Windows).
Folder systemu Windows (plik GetWindowsDirectory funkcja zwraca ścieżkę do folderu Windows).
Aby korzystać z biblioteki DLL, należy ją zarejestrować poprzez wprowadzenie odpowiednich odniesień do Rejestru. Czasami zdarza się, że odniesienie do rejestru zostaje uszkodzone i funkcje biblioteki DLL nie mogą być już używane. Bibliotekę DLL można ponownie zarejestrować, otwierając Start-Run i wprowadzając następujące polecenie:
regsvr32 somefile.dll
To polecenie zakłada, że plik somefile.dll znajduje się w katalogu lub folderze w ścieżce PATH. W przeciwnym razie należy użyć pełnej ścieżki do biblioteki DLL. Plik DLL można również wyrejestrować za pomocą przełącznika „/ u”, jak pokazano poniżej.
regsvr32 /u somefile.dll
Służy do włączania i wyłączania usługi.
Dostępnych jest kilka narzędzi ułatwiających rozwiązywanie problemów z bibliotekami DLL. Niektóre z nich omówiono poniżej.
Dependency Walker
Narzędzie Dependency Walker (depends.exe) może rekurencyjnie skanować wszystkie zależne biblioteki DLL używane przez program. Kiedy otwierasz program w Dependency Walker, Dependency Walker wykonuje następujące testy:
- Sprawdza brakujące biblioteki DLL.
- Sprawdza, czy pliki programów lub biblioteki DLL są nieprawidłowe.
- Sprawdza, czy funkcje importu i funkcje eksportu są zgodne.
- Sprawdza cykliczne błędy zależności.
- Sprawdza moduły, które są nieprawidłowe, ponieważ są przeznaczone dla innego systemu operacyjnego.
Używając Dependency Walker, możesz udokumentować wszystkie biblioteki DLL używane przez program. Może to pomóc w zapobieganiu i rozwiązywaniu problemów z bibliotekami DLL, które mogą wystąpić w przyszłości. Dependency Walker znajduje się w następującym katalogu podczas instalowania programu Microsoft Visual Studio 6.0:
drive\Program Files\Microsoft Visual Studio\Common\Tools
DLL Universal Problem Solver
Narzędzie DLL Universal Problem Solver (DUPS) służy do kontroli, porównywania, dokumentowania i wyświetlania informacji DLL. Poniższa lista opisuje narzędzia, które tworzą narzędzie DUPS:
Dlister.exe - To narzędzie wylicza wszystkie biblioteki DLL na komputerze i rejestruje informacje w pliku tekstowym lub w pliku bazy danych.
Dcomp.exe - To narzędzie porównuje biblioteki DLL wymienione w dwóch plikach tekstowych i tworzy trzeci plik tekstowy zawierający różnice.
Dtxt2DB.exe - To narzędzie ładuje pliki tekstowe utworzone za pomocą narzędzia Dlister.exe i narzędzia Dcomp.exe do bazy danych dllHell.
DlgDtxt2DB.exe - To narzędzie udostępnia wersję graficznego interfejsu użytkownika (GUI) narzędzia Dtxt2DB.exe.
Podczas pisania biblioteki DLL należy pamiętać o następujących wskazówkach:
Użyj odpowiedniej konwencji wywoływania (C lub stdcall).
Pamiętaj o prawidłowej kolejności argumentów przekazywanych do funkcji.
NIGDY nie zmieniaj rozmiaru tablic ani nie łącz ciągów za pomocą argumentów przekazanych bezpośrednio do funkcji. Pamiętaj, że parametry, które przekazujesz, są danymi LabVIEW. Zmiana rozmiaru tablicy lub łańcucha może spowodować awarię poprzez nadpisanie innych danych przechowywanych w pamięci LabVIEW. MOŻESZ zmieniać rozmiar tablic lub łączyć ciągi, jeśli przekażesz uchwyt LabVIEW Array Handle lub LabVIEW String Handle i używasz kompilatora Visual C ++ lub kompilatora Symantec do kompilowania biblioteki DLL.
Podczas przekazywania ciągów do funkcji wybierz prawidłowy typ ciągu do przekazania. Uchwyt ciągowy C, Pascal lub LabVIEW.
Łańcuchy Pascal są ograniczone do 255 znaków.
Ciągi C są zakończone wartością NULL. Jeśli funkcja DLL zwraca dane liczbowe w formacie łańcucha binarnego (na przykład przez GPIB lub port szeregowy), może zwrócić wartości NULL jako część ciągu danych. W takich przypadkach przekazywanie tablic krótkich (8-bitowych) liczb całkowitych jest najbardziej niezawodne.
Jeśli pracujesz z tablicami lub ciągami danych, ZAWSZE przekazuj bufor lub tablicę, która jest wystarczająco duża, aby pomieścić wszelkie wyniki umieszczone w buforze przez funkcję, chyba że przekazujesz je jako uchwyty LabVIEW, w takim przypadku możesz zmienić ich rozmiar za pomocą CIN działa w kompilatorze Visual C ++ lub Symantec.
Wyświetl listę funkcji DLL w sekcji EXPORTS pliku definicji modułu, jeśli używasz _stdcall.
Wyświetla listę funkcji DLL, które inne aplikacje wywołują w sekcji EXPORTS pliku definicji modułu lub w celu uwzględnienia słowa kluczowego _declspec (dllexport) w deklaracji funkcji.
Jeśli używasz kompilatora C ++, wyeksportuj funkcje z instrukcją extern .C. {} W swoim pliku nagłówkowym, aby zapobiec manipulowaniu nazwami.
Jeśli piszesz własną bibliotekę DLL, nie należy jej rekompilować, gdy jest ona ładowana do pamięci przez inną aplikację. Przed ponowną kompilacją biblioteki DLL upewnij się, że wszystkie aplikacje korzystające z tej konkretnej biblioteki DLL zostały wyładowane z pamięci. Zapewnia, że sama biblioteka DLL nie jest ładowana do pamięci. Możesz nie odbudować poprawnie, jeśli o tym zapomnisz, a Twój kompilator nie ostrzeże Cię.
Przetestuj biblioteki DLL za pomocą innego programu, aby upewnić się, że funkcja (i biblioteka DLL) działają poprawnie. Testowanie go za pomocą debugera kompilatora lub prostego programu w języku C, w którym można wywołać funkcję z biblioteki DLL, pomoże ci określić, czy możliwe trudności są nieodłącznie związane z biblioteką DLL lub LabVIEW.
Widzieliśmy, jak napisać bibliotekę DLL i jak utworzyć program „Hello World”. Ten przykład musiał dać ci wyobrażenie o podstawowej koncepcji tworzenia biblioteki DLL.
Tutaj podamy opis tworzenia bibliotek DLL przy użyciu Delphi, Borland C ++ i ponownie VC ++.
Weźmy te przykłady jeden po drugim.
Jak pisać i wywoływać biblioteki DLL w Delphi
Tworzenie bibliotek DLL z Borland C ++ Builder IDE
Tworzenie bibliotek DLL w Microsoft Visual C ++ 6.0