DZWON

Są tacy, którzy czytają tę wiadomość przed tobą.
Zapisz się, aby otrzymywać najnowsze artykuły.
E-mail
Nazwa
Nazwisko
Jak chcesz czytać dzwonek?
Bez spamu

Jak zapewne wiecie, biblioteki dołączane dynamicznie (DLL) używają konwencji języka C podczas deklarowania eksportowanych obiektów, natomiast C++ używa nieco innego systemu generowania nazw kompilacji, więc nie można po prostu eksportować funkcji - metody klasy C++ a następnie użyć ich w kodzie aplikacji klienckiej (dalej klient oznacza aplikację korzystającą z biblioteki DLL). Można to jednak zrobić za pomocą interfejsów dostępnych zarówno dla biblioteki DLL, jak i aplikacji klienckiej. Ta metoda jest jednocześnie bardzo mocna i elegancka. klient widzi tylko abstrakcyjny interfejs, a rzeczywista klasa implementująca wszystkie funkcje może być dowolna. Technologia COM firmy Microsoft (Component Model obiektowy) opiera się na podobnym pomyśle (oczywiście plus dodatkowa funkcjonalność). W tym artykule pokazano, jak używać podejścia „klasowego” z interfejsem podobnym do modelu COM do wczesnego (w czasie kompilacji) i późnego (w czasie wykonywania) wiązania.

Jeśli kiedykolwiek pracowałeś z biblioteką DLL, wiesz już, że biblioteka DLL ma specjalną funkcję DllMain(). Ta funkcja jest podobna do WinMain lub main() w tym sensie, że jest rodzajem punktu wejścia DLL. System operacyjny automatycznie wywołuje tę funkcję, jeśli biblioteka DLL jest ładowana i rozładowywana. Zwykle ta funkcja nie jest używana do niczego innego.

Istnieją dwie metody łączenia biblioteki DLL z projektem — wczesne (w czasie kompilacji) i późne (w czasie wykonywania) łączenie. Metody różnią się sposobem ładowania biblioteki DLL i sposobem wywoływania funkcji zaimplementowanych i eksportowanych z biblioteki DLL.

Wczesne wiązanie (w czasie kompilacji)

Dzięki tej metodzie łączenia system operacyjny automatycznie ładuje bibliotekę DLL podczas uruchamiania programu. Wymagane jest jednak, aby opracowywany projekt zawierał plik .lib (plik biblioteczny) odpowiadający tej bibliotece DLL. Ten plik definiuje wszystkie wyeksportowane obiekty DLL. Deklaracje mogą zawierać zwykłe funkcje lub klasy języka C. Wszystko, co klient musi zrobić, to użyć tego pliku .lib i dołączyć plik nagłówka DLL - a system operacyjny automatycznie załaduje ten plik DLL. Jak widać, ta metoda wygląda na bardzo łatwą w użyciu, ponieważ wszystko jest przejrzyste. Powinieneś jednak zauważyć, że kod klienta musi zostać ponownie skompilowany za każdym razem, gdy zmieni się kod DLL i zostanie odpowiednio wygenerowany nowy plik .lib. To, czy jest to wygodne dla Twojej aplikacji, zależy od Ciebie. Biblioteka DLL może deklarować funkcje, które chce wyeksportować za pomocą dwóch metod. Standardową metodą jest użycie plików .def. Taki plik .def to tylko lista funkcji wyeksportowanych z biblioteki DLL.

// ======================================================== ============= // plik .def BIBLIOTEKA myfirstdll.dll OPIS „Mój pierwszy DLL” EKSPORTY MyFunction // ================== = ========================================= // Nagłówek DLL, który zostanie uwzględniony w kodzie klienta bool MyFunction (int parms); // ======================================================== ============ // implementacja funkcji w DLL bool MyFunction (int parms) (// rób co jest potrzebne ............)

Myślę, że można tego nie powiedzieć w ten przykład eksportowana jest tylko jedna funkcja MyFunction. Druga metoda deklarowania eksportowanych obiektów jest specyficzna, ale znacznie potężniejsza: możesz eksportować nie tylko funkcje, ale także klasy i zmienne. Rzućmy okiem na fragment kodu wygenerowany podczas tworzenia VisualC++ DLL AppWizard. Komentarze zawarte w listingu wystarczą, aby zrozumieć, jak to wszystko działa.

// ======================================================== ============= // Nagłówek DLL do uwzględnienia w kodzie klienta / * Następny blok ifdef - metoda standardowa tworzenie makra, które ułatwia eksport DLL. Wszystkie pliki tej biblioteki DLL są kompilowane z określonym kluczem MYFIRSTDLL_EXPORTS. Ten klucz nie jest zdefiniowany dla żadnego z projektów korzystających z tej biblioteki DLL. W związku z tym każdy projekt, w którym znajduje się ten plik, widzi funkcje MYFIRSTDLL_API jako importowane z biblioteki DLL, podczas gdy sama biblioteka DLL widzi te same funkcje jako eksportowane. * / #ifdef MYFIRSTDLL_EXPORTS #define MYFIRSTDLL_API __declspec (dllexport) #else #define MYFIRSTDLL_API __declspec (dllimport) #endif // Klasa jest eksportowana z klasy test2.dll MYFIRSTDLL_Fll_API (CMyFid); zewnętrzny MYFIRSTDLL_API int nMyFirstDll; MYFIRSTDLL_API int fnMyFunction (unieważnione);

Podczas kompilacji DLL klucz MYFIRSTDLL_EXPORTS jest zdefiniowany, więc słowo kluczowe __declspec (dllexport) jest zastępowane przed deklaracjami wyeksportowanych obiektów. A gdy kod klienta jest kompilowany, ten klucz jest niezdefiniowany, a obiekty są poprzedzone prefiksem __declspec (dllimport), aby klient wiedział, które obiekty są importowane z biblioteki DLL.

W obu przypadkach klient musi jedynie dodać plik myfirstdll.lib do projektu i dołączyć plik nagłówkowy, który deklaruje obiekty importowane z biblioteki DLL, a następnie używać tych obiektów (funkcji, klas i zmiennych) dokładnie tak, jakby były zostały zdefiniowane i wdrożone lokalnie w projekcie. Przyjrzyjmy się teraz innej metodzie korzystania z bibliotek DLL, która często jest wygodniejsza i bardziej wydajna.

Późne wiązanie (podczas działania programu)

Gdy używane jest późne wiązanie, biblioteka DLL nie jest ładowana automatycznie po uruchomieniu programu, ale bezpośrednio w kodzie, w którym jest potrzebna. Nie ma potrzeby używania żadnych plików .lib, więc aplikacja kliencka nie musi być ponownie kompilowana po zmianie biblioteki DLL. To połączenie jest potężne właśnie dlatego, że TY decydujesz, kiedy i którą bibliotekę DLL załadować. Na przykład piszesz grę, która używa DirectX i OpenGL. Możesz po prostu dołączyć cały niezbędny kod do pliku wykonywalnego, ale wtedy po prostu nie będzie można czegoś wymyślić. Możesz też umieścić kod DirectX w jednej bibliotece DLL, a kod OpenGL w innej i statycznie połączyć je z projektem. Ale teraz cały kod jest współzależny, więc jeśli napisałeś nową bibliotekę DLL, która zawiera kod DirectX, będziesz musiał również ponownie skompilować plik wykonywalny. Jedyną wygodą jest to, że nie musisz się martwić o ładowanie (chociaż nie wiadomo, czy jest to wygoda, jeśli ładujesz obie biblioteki DLL, zabierając pamięć, a w rzeczywistości potrzebujesz tylko jednej z nich). I wreszcie, moim zdaniem, najlepszy pomysł jest umożliwienie plikowi wykonywalnemu decydowanie, którą bibliotekę DLL załadować podczas uruchamiania. Na przykład, jeśli program stwierdzi, że system nie obsługuje akceleracji OpenGL, lepiej załadować DLL z kodem DirectX, w przeciwnym razie załadować OpenGL. W związku z tym późne wiązanie oszczędza pamięć i zmniejsza zależność między biblioteką DLL a plikiem wykonywalnym. Jednak w tym przypadku na eksportowane obiekty nałożone jest ograniczenie — można wyeksportować tylko funkcje w stylu C. Nie można załadować klas i zmiennych, jeśli program używa późnego wiązania. Zobaczmy, jak obejść to ograniczenie za pomocą interfejsów.

Biblioteka DLL zaprojektowana do późnego łączenia zazwyczaj używa pliku .def, aby określić, które obiekty chcesz wyeksportować. Jeśli nie chcesz używać pliku .def, możesz po prostu użyć prefiksu __declspec (dllexport) przed eksportowanymi funkcjami. Obie metody robią to samo. Klient ładuje bibliotekę DLL, przekazując nazwę pliku DLL do funkcji Win32 LoadLibrary() Funkcja ta zwraca uchwyt HINSTANCE, który służy do pracy z biblioteką DLL i który jest wymagany do wyładowania biblioteki DLL z pamięci, gdy nie jest już potrzebna . Po załadowaniu biblioteki DLL klient może uzyskać wskaźnik do dowolnej funkcji za pomocą funkcji GetProcAddress(), używając jako parametru nazwy wymaganej funkcji.

// ======================================================== ============= // plik .def BIBLIOTEKA myfirstdll.dll OPIS „Mój pierwszy DLL” EKSPORTY MyFunction // ================== = ========================================= / * Implementacja funkcji w DLL * / bool MyFunction (int parms) (// zrób coś ............) // ====================== == ==================================== // Kod klienta / * Deklaracja funkcji jest tak naprawdę potrzebna tylko dla w celu zdefiniowania parametrów. Deklaracje funkcji są zwykle zawarte w pliku nagłówkowym dostarczonym z biblioteką DLL. Słowo kluczowe extern C w deklaracji funkcji mówi kompilatorowi, aby używał konwencji nazewnictwa języka C * / extern "C" bool MyFunction (int parms); typedef bool (* MOJAFUNKCJA) (parametry int); MOJAFUNKCJA pfnMojaFunkcja = 0; // wskaźnik do MyFunction WSKAZÓWKA hMyDll = :: LoadLibrary ("myfirstdll.dll"); if (hMyDll! = NULL) (// Określ adres funkcji pfnMyFunc = (MYFUNCTION) :: GetProcAddress (hMyDll, "MyFunction"); // Jeśli się nie powiedzie, wyładuj bibliotekę DLL if (pfnMyFunc == 0) (:: FreeLibrary (hMyDll) ; return;) // Wywołaj funkcję bool result = pfnMyFunc (parms); // Rozładuj bibliotekę DLL, jeśli już jej nie potrzebujemy :: FreeLibrary (hMyDll);)

Jak widać, kod jest dość prosty. Zobaczmy teraz, jak można zaimplementować pracę z „klasami”. Jak wspomniano wcześniej, jeśli używane jest późne wiązanie, nie ma bezpośredniego sposobu importowania klas z biblioteki DLL, więc musimy zaimplementować „funkcjonalność” klasy przy użyciu interfejsu, który zawiera wszystkie funkcje publiczne z wyjątkiem konstruktora i destruktora. Interfejs będzie zwykłą strukturą C/C++ zawierającą tylko wirtualne abstrakcyjne funkcje składowe. Rzeczywista klasa w bibliotece DLL odziedziczy po tej strukturze i zaimplementuje całą funkcjonalność zdefiniowaną w interfejsie. Teraz, aby uzyskać dostęp do tej klasy z aplikacji klienckiej, wystarczy wyeksportować funkcje w stylu C odpowiadające instancji klasy i powiązać je ze zdefiniowanym przez nas interfejsem, aby klient mógł z nich korzystać. Aby zaimplementować taką metodę, potrzebne są jeszcze dwie funkcje, z których jedna utworzy interfejs, a druga usunie interfejs po zakończeniu pracy z nim. Przykład realizacji tego pomysłu podano poniżej.

// ======================================================== ============ // plik .def BIBLIOTEKA myinterface.dll OPIS "implementuje interfejs I_MyInterface EKSPORTY GetMyInterface FreeMyInterface // ================== = ========================================= // Plik nagłówkowy używany w bibliotekach DLL i klient, // który deklaruje interfejs // I_MyInterface.h struct I_MyInterface (virtual bool Init (int parms) = 0; virtual bool Release () = 0; virtual void DoStuff () = 0;); / * Deklaracje eksportowanych funkcji Dll i definicje typów dla wskaźników funkcji dla łatwego ładowania i manipulowania funkcjami Zwróć uwagę na prefiks extern "C", który mówi kompilatorowi, aby używał funkcji w stylu C * / extern "C" (HRESULT GetMyInterface (I_MyInterface ** pInterface); typedef HRESULT (* GETINTERFACE) (I_MyInterface ** pInterface); HRESULT FreeMyInterface (I_MyInterface ** pInterface); typedef HRESULT (* FREEINTERFACE) (I_MyInterface ** pInterface);) // ==== === ================================ ============== // Implementacja interfejsu w Dll // MyInterface.h class CMyClass: public I_MyInterface (public: bool Init (int parms); Wydanie logiczne (); nieważne DoStuff (); CMyClass (); ~ CMMojaKlasa (); // dowolni inni członkowie klasy ............ private: // dowolni członkowie klasy ............); // ======================================================== ============ // Wyeksportowane funkcje, które tworzą i niszczą interfejs // Dllmain.h HRESULT GetMyInterface (I_MyInterface ** pInterface) (if (! * PInterface) (* pInterface = new CMyClass; return S_OK;) return E_FAIL;) HRESULT FreeMyInterface (I_MyInterface ** pInterface) (jeśli (! * PInterface) return E_FAIL; usuń * pInterface; * pInterface = 0; return S_OK;) // ========== =========================================================== // Kod klienta // Deklaracje interfejsu i wywołania funkcji GETINTERFACE pfnInterface = 0; // wskaźnik do funkcji GetMyInterface I_MyInterface * pInterface = 0; // wskaźnik do struktury MyInterface HINSTANCE hMyDll = :: LoadLibrary ("myinterface.dll"); if (hMyDll! = NULL) (// Określ adres funkcji pfnInterface = (GETINTERFACE) :: GetProcAddress (hMyDll, "GetMyInterface"); // Usuń bibliotekę DLL, jeśli poprzednia operacja nie powiodła się if (pfnInterface == 0) ( :: FreeLibrary (hMyDll); return;) // Wywołaj funkcję HRESULT hr = pfnInterface (& pInterface); // Rozładuj, jeśli nie powiedzie się, jeśli (FAILED (hr)) (:: FreeLibrary (hMyDll); return;) // interfejs jest załadowany, możesz wywołać funkcje pInterface-> Init (1); pInterface-> DoStuff (); pInterface-> Release (); // Uwolnij interfejs FREEINTERFACE pfnFree = (FREEINTERFACE) :: GetProcAddress (hMyDll, "FreeMyInterface" ); jeśli (pfnFree! = 0) pfnFree (& hMyDll); // Wyładuj bibliotekę DLL :: FreeLibrary (hMyDll); )

Ta informacja wystarczy, abyś poczuł całą wygodę korzystania z interfejsów. Miłego programowania!

Od urodzenia (lub nieco później) system operacyjny Windows wykorzystywał biblioteki DLL (Dynamic Link Library), które zawierały implementacje najczęściej używanych funkcji. Spadkobiercy Windows - NT i Windows 95, a także OS / 2 - również polegają na bibliotekach DLL, które zapewniają znaczną część ich funkcjonalności.

Przyjrzyjmy się kilku aspektom tworzenia i używania bibliotek DLL:

  • Jak statycznie łączyć biblioteki DLL
  • jak dynamicznie ładować biblioteki DLL;
  • jak tworzyć biblioteki DLL;
  • Jak tworzyć rozszerzenia DLL MFC.

Wykorzystanie biblioteki DLL

Prawie niemożliwe do stworzenia Aplikacja Windows który nie używa bibliotek DLL. DLL zawiera wszystkie funkcje Win32 API i niezliczone inne funkcje systemów operacyjnych Win32.

Mówiąc ogólnie, biblioteki DLL są po prostu zbiorami funkcji zebranych w biblioteki. Jednak w przeciwieństwie do ich statycznych kuzynów (pliki .lib), biblioteki DLL nie są bezpośrednio połączone z plikami wykonywalnymi za pomocą konsolidatora. Plik wykonywalny zawiera tylko informacje o ich lokalizacji. W momencie wykonywania programu ładowana jest cała biblioteka. Dzięki temu różne procesy mogą współużytkować te same biblioteki w pamięci. Takie podejście pozwala zmniejszyć ilość pamięci wymaganej dla wielu aplikacji korzystających z wielu bibliotek współdzielonych, a także kontrolować rozmiar plików EXE.

Jeśli jednak biblioteka jest używana tylko przez jedną aplikację, lepiej jest uczynić ją zwykłą, statyczną. Oczywiście, jeśli zawarte w nim funkcje będą używane tylko w jednym programie, wystarczy wstawić do niego odpowiedni plik źródłowy.

Najczęściej projekt jest połączony z biblioteką DLL statycznie lub niejawnie podczas fazy łączenia. System operacyjny kontroluje ładowanie DLL podczas wykonywania programu. Jednak biblioteka DLL może być ładowana jawnie lub dynamicznie w trakcie działania aplikacji.

Importuj biblioteki

Gdy biblioteka DLL jest połączona statycznie, nazwa pliku .lib jest określana wśród innych parametrów konsolidatora w wiersz poleceń lub na karcie Link w oknie dialogowym Project Settings w Developer Studio. Jednak plik .lib używany przez podczas niejawnego łączenia biblioteki DLL, nie jest zwykłą biblioteką statyczną. Takie pliki .lib nazywają się importuj biblioteki(import biblioteki). Nie zawierają samego kodu biblioteki, a jedynie odniesienia do wszystkich funkcji wyeksportowanych z pliku DLL, w którym wszystko jest przechowywane. W rezultacie biblioteki importu są zwykle mniejsze niż pliki DLL. Do metod ich tworzenia wrócimy później. Przyjrzyjmy się teraz innym zagadnieniom związanym z niejawnym łączeniem bibliotek dynamicznych.

Negocjacja interfejsu

Korzystając z własnych lub zewnętrznych bibliotek, należy zwrócić uwagę na dopasowanie wywołania funkcji do jej prototypu.

Gdyby świat był doskonały, programiści nie musieliby się martwić o dopasowanie interfejsów funkcji przy łączeniu bibliotek – wszystkie byłyby takie same. Świat jest jednak daleki od ideału i wielu duże programy są pisane przy użyciu różnych bibliotek bez C++.

Domyślnie w Visual C ++ interfejsy funkcji są zgodne z regułami C ++. Oznacza to, że parametry są odkładane na stos od prawej do lewej, program wywołujący jest odpowiedzialny za usunięcie ich ze stosu, gdy funkcja kończy działanie i rozwija swoją nazwę. Manglowanie nazw umożliwia linkerowi rozróżnienie przeciążonych funkcji, tj. funkcje o tej samej nazwie, ale z różnymi listami argumentów. Jednak w starej bibliotece C nie ma funkcji o rozszerzonych nazwach.

Chociaż wszystkie inne reguły wywoływania funkcji w C są takie same jak te dotyczące wywoływania funkcji w C++, nazwy funkcji nie są rozszerzane w bibliotekach C. Są one dodawane tylko z podkreśleniem (_) z przodu.

Jeśli potrzebujesz podłączyć bibliotekę C do aplikacji C++, wszystkie funkcje z tej biblioteki będą musiały być zadeklarowane jako zewnętrzne w formacie C:

Extern "С" int MyOldCFunction (int myParam);

Deklaracje funkcji bibliotecznych są zwykle umieszczane w pliku nagłówkowym biblioteki, chociaż nagłówki większości bibliotek C nie są przeznaczone do użycia w projektach C++. W takim przypadku należy utworzyć kopię pliku nagłówkowego i zawrzeć w nim modyfikator extern "C" do deklaracji wszystkich użytych funkcji bibliotecznych. Modyfikator extern „C” można również zastosować do całego bloku, do którego za pomocą dyrektywy #tinclude dołączony jest stary plik nagłówkowy C. W ten sposób zamiast modyfikować każdą funkcję osobno, wystarczy trzy wiersze:

Zewnętrzne „C” (#include „MyCLib.h”)

Programy dla starszych wersji systemu Windows również używały konwencji wywoływania funkcji język PASCAL dla funkcji Windows API... Nowsze programy powinny używać modyfikatora winapi przekonwertowanego na _stdcall. Chociaż nie jest to standardowy interfejs funkcji C lub C++, służy do wywoływania funkcji Windows API. Jednak zazwyczaj wszystko to jest już uwzględnione w standardowych nagłówkach Windows.

Ładowanie niejawnego łącza DLL

Podczas uruchamiania aplikacja próbuje znaleźć wszystkie pliki DLL, które są niejawnie połączone z aplikacją i umieścić je w obszarze pamięci zajmowanym przez ten proces. System operacyjny wyszukuje pliki DLL w następującej kolejności.

  • Katalog, w którym znajduje się plik EXE.
  • Bieżący katalog procesu.
  • Katalog systemu Windows.

Jeśli biblioteka DLL nie zostanie znaleziona, aplikacja wyświetli okno dialogowe z komunikatem o jej braku i przeszukanych ścieżkach. Następnie proces zostaje zatrzymany.

Jeśli potrzebna biblioteka zostanie znaleziona, jest umieszczana w pamięci RAM procesu, gdzie pozostaje do końca. Aplikacja może teraz uzyskać dostęp do funkcji zawartych w bibliotece DLL.

Dynamiczne ładowanie i rozładowywanie DLL

Zamiast dynamicznego linkowania systemu Windows do biblioteki DLL, gdy aplikacja jest po raz pierwszy ładowana do pamięci RAM, można połączyć program z modułem biblioteki w czasie wykonywania (w ten sposób nie trzeba używać biblioteki importu podczas procesu tworzenia aplikacji). W szczególności można określić, która biblioteka DLL jest dostępna dla użytkownika lub pozwolić użytkownikowi wybrać, którą bibliotekę DLL załadować. W ten sposób możesz używać różnych bibliotek DLL, które implementują te same funkcje i wykonują różne działania. Na przykład aplikacja zaprojektowana do niezależnego przesyłania danych może zdecydować w czasie wykonywania, czy załadować bibliotekę DLL dla TCP/IP, czy innego protokołu.

Ładowanie zwykłej biblioteki DLL

Pierwszą rzeczą do zrobienia podczas dynamicznego ładowania biblioteki DLL jest umieszczenie modułu biblioteki w pamięci procesu. Ta operacja jest wykonywana za pomocą funkcji :: LoadLibrary który ma jeden argument - nazwę modułu do załadowania. Odpowiedni fragment programu powinien wyglądać tak:

WSKAZÓWKA hMyDll; :: if ((hMyDll = :: LoadLibrary ("MyDLL")) == NULL) (/ * Nie można załadować DLL * /) else (/ * aplikacja może korzystać z funkcji DLL przez hMyDll * /)

System Windows uważa bibliotekę dll za standardowe rozszerzenie pliku biblioteki, chyba że określisz inne rozszerzenie. Jeśli ścieżka jest również określona w nazwie pliku, to tylko ona będzie używana do wyszukiwania pliku. W przeciwnym razie Windows będzie szukał pliku w taki sam sposób, jak w przypadku niejawnie połączonych bibliotek DLL, zaczynając od katalogu, z którego ładowany jest plik exe, i kontynuując zgodnie z wartością PATH.

Gdy system Windows znajdzie plik, jego pełna ścieżka zostanie porównana ze ścieżką bibliotek DLL załadowanych już przez ten proces. Jeśli zostanie znaleziona tożsamość, zamiast ładowania kopii aplikacji, zwracany jest uchwyt do już połączonej biblioteki.

Jeśli plik zostanie znaleziony, a biblioteka zostanie pomyślnie załadowana, funkcja :: LoadLibrary zwraca swój uchwyt, który jest używany do dostępu do funkcji bibliotecznych.

Przed skorzystaniem z funkcji bibliotecznych należy uzyskać ich adres. Aby to zrobić, najpierw użyj dyrektywy typedef aby zdefiniować typ wskaźnika funkcji i zdefiniować zmienną tego nowego typu, na przykład:

// typ PFN_MyFunction zadeklaruje wskaźnik do funkcji // akceptując wskaźnik do bufora znaków i dając wartość typu int typedef int (WINAPI * PFN_MyFunction) (char *); :: PFN_MyFunction pfnMyFunction;

Następnie musisz uzyskać deskryptor biblioteki, za pomocą którego możesz określić adresy funkcji, na przykład adres funkcji o nazwie MyFunction:

HMyDll = :: LoadLibrary ("MyDLL"); pfnMojaFunkcja = (PFN_MojaFunkcja) :: GetProcAddress (hMyDll, "MojaFunkcja"); :: int iKod = (* pfnMojaFunkcja) ("Cześć");

Adres funkcji jest określany za pomocą funkcji :: GetProcAdres, należy podać nazwę biblioteki i nazwę funkcji. Ten ostatni należy przekazać w formie, w jakiej jest eksportowany z biblioteki DLL.

Możesz również odwołać się do funkcji według liczby porządkowej, według której jest eksportowana (w tym przypadku do utworzenia biblioteki należy użyć pliku def, zostanie to omówione później):

PfnMyFunction = (PFN_MyFunction) :: GetProcAddress (hMyDll, MAKEINTRESOURCE (1));

Po zakończeniu pracy z biblioteką dołączaną dynamicznie można ją wyładować z pamięci procesu za pomocą funkcji :: Darmowa biblioteka:

:: FreeLibrary (hMyDll);

Ładowanie rozszerzeń DLL MFC

Podczas ładowania rozszerzeń MFC dla bibliotek DLL (które są omówione bardziej szczegółowo poniżej) zamiast funkcji Załaduj bibliotekę oraz Darmowa biblioteka używane są funkcje Biblioteka AfxLoad oraz AfxFreeLibrary... Te ostatnie są prawie identyczne z funkcjami API Win32. Gwarantują tylko dodatkowo, że struktury MFC zainicjowane przez rozszerzenie DLL nie zostaną naruszone przez inne wątki.

Zasoby DLL

Ładowanie dynamiczne dotyczy również zasobów DLL, których MFC używa do ładowania standardowych zasobów aplikacji. Aby to zrobić, musisz najpierw wywołać funkcję Załaduj bibliotekę i umieść bibliotekę DLL w pamięci. Następnie korzystając z funkcji AfxSetResourceHandle należy przygotować okno programu do odbioru zasobów z nowo załadowanej biblioteki. W przeciwnym razie zasoby będą ładowane z plików połączonych z plikiem wykonywalnym procesu. Takie podejście jest wygodne, gdy trzeba użyć różnych zestawów zasobów, na przykład dla różnych języków.

Komentarz. Korzystanie z funkcji Załaduj bibliotekę możesz także załadować pliki wykonywalne do pamięci (nie uruchamiaj ich w celu wykonania!). Wykonywalny deskryptor może być następnie użyty podczas wywoływania funkcji Znajdź zasób oraz LoadResource aby znaleźć i załadować zasoby aplikacji. Wyładuj moduły z pamięci również za pomocą funkcji Darmowa biblioteka.

Przykład zwykłej biblioteki DLL i metod ładowania

Oto kod źródłowy biblioteki dołączanej dynamicznie o nazwie MyDLL, która zawiera jedną funkcję, MyFunction, która po prostu wyświetla komunikat.

Najpierw w pliku nagłówkowym definiowana jest makrostała EKSPORT... Korzystanie z tego słowo kluczowe definiując funkcję, biblioteka DLL pozwala powiedzieć konsolidatorowi, że funkcja jest dostępna do użycia przez inne programy, powodując dodanie jej do biblioteki importu. Dodatkowo taka funkcja, podobnie jak procedura okna, musi być zdefiniowana za pomocą stałej ODDZWONIĆ:

MojaDLL.h#define EXPORT extern "C" __declspec (dllexport) EXPORT int CALLBACK MyFunction (char * str);

Plik biblioteki różni się również nieco od zwykłych plików C dla systemu Windows. Zamiast funkcji WinMain jest funkcja DllMain... Ta funkcja służy do wykonywania inicjalizacji, która zostanie omówiona później. Aby biblioteka pozostała w pamięci po jej załadowaniu i można było wywołać jej funkcje, zwracana przez nią wartość musi być TRUE:

MojaDLL.c #zawiera #include "MyDLL.h" int WINAPI DllMain (HINSTANCE hInstance, DWORD fdReason, PVOID pvReserved) (powrót TRUE;) EXPORT int CALLBACK MyFunction (char * str) (MessageBox (NULL, str, "Funkcja z DLL", MB_OK); powrót 1;)

Po przetłumaczeniu i połączeniu tych plików pojawiają się dwa pliki - MyDLL.dll (sama biblioteka dołączana dynamicznie) i MyDLL.lib (jego biblioteka importu).

Przykład niejawnego połączenia DLL przez aplikację

Podajmy teraz kod źródłowy prostej aplikacji korzystającej z funkcji MyFunction z biblioteki MyDLL.dll:

#włączać #include "MyDLL.h" int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) (int iCode = MyFunction ("Hello"); zwróć 0;)

Ten program wygląda regularne programy dla systemu Windows, którym tak naprawdę jest. Należy jednak zauważyć, że jego tekst źródłowy, oprócz wywołania funkcji MyFunction z biblioteki DLL, zawiera również plik nagłówkowy tej biblioteki, MyDLL.h. Na etapie budowania aplikacji konieczne jest również połączenie się z nią importuj bibliotekę MyDLL.lib (proces niejawnego łączenia biblioteki DLL z modułem wykonywalnym).

Niezwykle ważne jest, aby zrozumieć, że sam kod MyFunction nie jest zawarty w pliku MyApp.exe. Zamiast tego ma po prostu link do pliku MyDLL.dll i link do MyFunction, który znajduje się w tym pliku. Plik MyApp.exe wymaga do uruchomienia pliku MyDLL.dll.

Plik nagłówkowy MojaDLL.h jest zawarty w pliku źródłowym programu MojaAplikacja.c w taki sam sposób, jak zawarty jest tam plik windows.h. Dołączanie biblioteki importu MyDLL.lib do łączenia jest podobne do dołączania wszystkich bibliotek importu Windows. Kiedy MyApp.exe jest uruchomiony, łączy się z MyDLL.dll, tak jak wszystkie standardowe biblioteki DLL systemu Windows.

Przykład dynamicznego ładowania biblioteki DLL przez aplikację

Podajmy teraz pełny kod źródłowy prostej aplikacji korzystającej z funkcji MyFunction z biblioteki MyDLL.dll przy użyciu dynamicznego ładowania biblioteki:

#włączać typedef int (WINAPI * PFN_MyFunction) (znak *); int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) (HINSTANCE hMyDll; if ((hMyDll = LoadLibrary ("MyDLL")) == NULL) return 1; pFN_MyFunction ="; * pfnMyFunction) ("Witaj"); FreeLibrary (hMyDll); zwróć 0;)

Tworzenie DLL

Teraz, gdy wiesz, jak działają biblioteki DLL w aplikacjach, przyjrzyjmy się, jak je utworzyć. Podczas tworzenia aplikacji zaleca się umieszczenie funkcji, do których dostęp uzyskuje się przez kilka procesów, w bibliotece DLL. Pozwala to na bardziej efektywne wykorzystanie pamięci w systemie Windows.

Najłatwiejszym sposobem utworzenia nowego projektu DLL jest użycie AppWizard, który automatycznie wykonuje wiele operacji. W przypadku prostych bibliotek DLL, takich jak te omówione w tym rozdziale, należy wybrać typ projektu Win32 Dynamic-Link Library. Do nowego projektu zostaną przypisane wszystkie niezbędne parametry do utworzenia biblioteki DLL. Pliki źródłowe trzeba będzie dodać do projektu ręcznie.

Jeśli planujesz w pełni wykorzystać funkcjonalność MFC, takich jak dokumenty i widoki, lub zamierzasz utworzyć serwer automatyzacji OLE, lepiej wybrać typ projektu MFC AppWizard (dll). W takim przypadku, oprócz przypisania do projektu parametrów do podłączenia bibliotek dynamicznych, kreator wykona dodatkową pracę. Projekt doda niezbędne referencje do bibliotek MFC oraz plików źródłowych zawierających opis i implementację w DLL obiektu klasy aplikacji pochodzącej z CWinApp.

Czasami wygodnie jest najpierw utworzyć projekt MFC AppWizard (dll) jako aplikację testową, a następnie utworzyć bibliotekę DLL jako jej składnik. W rezultacie biblioteka DLL zostanie utworzona automatycznie, jeśli to konieczne.

Funkcja DllMain

Większość bibliotek DLL to po prostu zbiory funkcji, które są praktycznie niezależne od siebie, które są eksportowane i używane w aplikacjach. Oprócz funkcji przeznaczonych do eksportu, każda biblioteka DLL ma funkcję DllMain... Ta funkcja jest przeznaczona do inicjowania i czyszczenia bibliotek DLL. Zastąpiła funkcje LibMain oraz WEP używane w poprzednich wersjach systemu Windows. Struktura najprostszej funkcji DllMain może wyglądać tak:

BOOL WINAPI DllMain (HANDLE hInst, DWORD dwReason, LPVOID IpReserved) (BOOL bAllWentWell = TRUE; switch (dwReason) (przypadek DLL_PROCESS_ATTACH: // Inicjalizacja procesu. Break; przypadek DLL_THREAD_ATTACH: // Inicjowanie/ DLL_ATTACH: // Inicjalizacja wątku Przerwij struktury przepływu.break; case DLL_PROCESS_DETACH: // Wyczyść struktury procesu.break;) if (bAllWentWell) zwróć TRUE; w przeciwnym razie zwróć FALSE;)

Funkcjonować DllMain zadzwonił w kilku przypadkach. Powód jego wywołania określa parametr dwReason, którym może być jedna z poniższych wartości.

Przy pierwszym załadowaniu biblioteki DLL przez proces wywoływana jest funkcja DllMain z dwReason równym DLL_PROCESS_ATTACH. Za każdym razem, gdy proces tworzy nowy wątek, DllMainO jest wywoływane z dwReason równym DLL_THREAD_ATTACH (z wyjątkiem pierwszego wątku, ponieważ w tym przypadku dwReason jest równe DLL_PROCESS_ATTACH).

Pod koniec procesu z biblioteką DLL funkcja DllMain wywołane z dwReason równym DLL_PROCESS_DETACH. Gdy wątek (inny niż pierwszy) zostanie zniszczony, dwReason będzie równe DLL_THREAD_DETACH.

Wszystkie operacje inicjowania i czyszczenia procesów i wątków, których potrzebuje biblioteka DLL, muszą być wykonywane na podstawie wartości dwReason, jak pokazano w poprzednim przykładzie. Inicjowanie procesu jest zwykle ograniczone do alokacji zasobów współużytkowanych przez wątki, takich jak ładowanie plików współużytkowanych i inicjowanie bibliotek. Inicjalizacja wątku służy do konfigurowania trybów specyficznych tylko dla tego wątku, na przykład do inicjowania pamięci lokalnej.

Biblioteka DLL może zawierać zasoby, które nie należą do aplikacji wywołującej. Jeśli funkcje DLL działają na zasobach DLL, byłoby oczywiście przydatne przechowywanie deskryptora hInst gdzieś w ukrytym miejscu i używanie go podczas ładowania zasobów z biblioteki DLL. Wskaźnik IpReserved jest zarezerwowany dla wewnętrznego za pomocą systemu Windows... Stąd wniosek nie powinien tego żądać. Możesz tylko sprawdzić jego znaczenie. Jeśli biblioteka DLL została załadowana dynamicznie, będzie miała wartość NULL. Po załadowaniu statycznym ten wskaźnik nie będzie miał wartości null.

Jeśli się powiedzie, funkcja DllMain musi zwrócić TRUE. Jeśli wystąpi błąd, zwracana jest wartość FALSE i dalsza akcja jest przerywana.

Komentarz. Jeśli nie napiszesz własnej funkcji DllMain(), kompilator dołączy standardową wersję, która po prostu zwróci TRUE.

Eksportowanie funkcji z DLL

Aby aplikacja mogła uzyskać dostęp do funkcji DLL, każda z nich musi zajmować wiersz w tabeli eksportowanych funkcji biblioteki DLL. Istnieją dwa sposoby dodania funkcji do tej tabeli w czasie kompilacji.

__Declspec (dllexport) metoda

Funkcję można wyeksportować z biblioteki DLL, umieszczając modyfikator __declspec (dllexport) na początku jej opisu. Ponadto MFC zawiera kilka makr, które definiują __declspec (dllexport), w tym AFX_CLASS_EXPORT, AFX_DATA_EXPORT i AFX_API_EXPORT.

Metoda __declspec jest używana rzadziej niż druga metoda, która działa z plikami definicji modułów (.def) i pozwala na lepszą kontrolę nad procesem eksportu.

Pliki definicji modułów

Składnia plików .def w Visual C++ jest dość prosta, głównie ze względu na złożone parametry używane w wcześniejsze wersje Windows w Win32 nie ma już zastosowania. Jak wynika z poniższego prostego przykładu, plik .def zawiera nazwę i opis biblioteki, a także listę wyeksportowanych funkcji:

MojaDLL.def BIBLIOTEKA „MyDLL” OPIS „MyDLL - przykładowa biblioteka DLL” EKSPORTY MyFunction @ 1

W wierszu eksportu funkcji możesz określić jej numer porządkowy, poprzedzając ją symbolem @. Ten numer będzie następnie używany w odniesieniu do PobierzProcAdres(). W rzeczywistości kompilator przypisuje kolejne numery do wszystkich eksportowanych obiektów. Jednak sposób, w jaki to robi, jest nieco nieprzewidywalny, chyba że liczby te zostaną wyraźnie przypisane.

Możesz użyć parametru NONAME w linii eksportu. Uniemożliwia kompilatorowi uwzględnienie nazwy funkcji w tabeli eksportu DLL:

MyFunction @ 1 NONAME

Czasami może to zaoszczędzić dużo miejsca w pliku DLL. Aplikacje korzystające z biblioteki importu do niejawnego łączenia biblioteki DLL nie „zauważą” różnicy, ponieważ niejawne łączenie automatycznie używa numerów sekwencji. Aplikacje, które dynamicznie ładują biblioteki DLL, będą musiały zostać przekazane PobierzProcAdres numer sekwencyjny, a nie nazwa funkcji.

Korzystając z powyższego, plik def opisujący eksportowane funkcje biblioteki DLL może na przykład być inny:

#define EXPORT extern "C" __declspec (dllexport) EXPORT int CALLBACK MyFunction (char * str); coś takiego: extern "C" int CALLBACK MyFunction (char * str);

Eksportowanie klas

Tworzenie pliku .def do eksportowania nawet prostych klas z biblioteki dynamicznej może być dość trudne. Będziesz musiał jawnie wyeksportować każdą funkcję, która może być używana przez zewnętrzną aplikację.

Jeśli spojrzysz na plik alokacji pamięci zaimplementowany w klasie, zauważysz w nim kilka bardzo nietypowych funkcji. Okazuje się, że istnieją niejawne konstruktory i destruktory, funkcje deklarowane w makrach MFC, w szczególności _DECLARE_MESSAGE_MAP, a także funkcje napisane przez programistę.

Chociaż możliwe jest wyeksportowanie każdej z tych funkcji osobno, istnieje prostszy sposób. Jeśli użyjesz modyfikatora makra AFX_CLASS_EXPORT w deklaracji klasy, kompilator sam zajmie się eksportem. niezbędne funkcje umożliwienie aplikacji korzystania z klasy zawartej w bibliotece DLL.

Pamięć DLL

W przeciwieństwie do bibliotek statycznych, które zasadniczo stają się częścią kodu aplikacji, biblioteki dołączane dynamicznie w 16-bitowych wersjach systemu Windows radzą sobie z pamięcią w nieco inny sposób. W Win 16 pamięć DLL znajdowała się poza przestrzenią adresową zadania. Umieszczenie bibliotek dynamicznych w pamięci globalnej umożliwiło współdzielenie ich z różnymi zadaniami.

W Win32 biblioteka DLL znajduje się w obszarze pamięci procesu, który ją ładuje. Każdy proces otrzymuje osobną kopię „globalnej” pamięci DLL, która jest ponownie inicjowana za każdym razem, gdy ładuje ją nowy proces. Oznacza to, że biblioteka dołączana dynamicznie nie może być współużytkowana w pamięci współdzielonej, tak jak w Winl6.

A jednak, wykonując kilka skomplikowanych manipulacji na segmencie danych biblioteki DLL, możesz utworzyć obszar pamięci współdzielonej dla wszystkich procesów korzystających z tej biblioteki.

Załóżmy, że masz tablicę liczb całkowitych, która powinna być używana przez wszystkie procesy, które ładują tę bibliotekę DLL. Można to zaprogramować w następujący sposób:

#pragma data_seg (". myseg") int sharedlnts; // inne zmienne powszechne zastosowanie#pragma data_seg() #pragma komentarz (lib, "msvcrt" "-SEKCJA: .myseg, rws");

Wszystkie zmienne zadeklarowane między dyrektywami #pragma data_seg() są umieszczane w segmencie .myseg. Dyrektywa #pragma comment() nie jest zwykłym komentarzem. Nakazuje bibliotece wykonawczej C oznaczenie nowej partycji jako odczytu/zapisu/udostępniania.

Pełna kompilacja DLL

Jeśli projekt DLL jest tworzony przy użyciu AppWizard, a plik .def jest odpowiednio modyfikowany, jest to wystarczające. Jeśli tworzysz pliki projektu ręcznie lub w inny sposób bez użycia AppWizard, musisz dołączyć opcję / DLL w wierszu polecenia konsolidatora. Tworzy to bibliotekę DLL zamiast samodzielnego pliku wykonywalnego.

Jeśli w pliku .def znajduje się wiersz LIBRART, nie trzeba jawnie określać opcji /DLL w wierszu polecenia konsolidatora.

Istnieje wiele trybów specjalnych dla MFC dotyczących korzystania z biblioteki DLL MFC. Zagadnieniu temu poświęcony jest kolejny rozdział.

DLL i MFC

Programista nie musi używać MFC podczas tworzenia bibliotek dołączanych dynamicznie. Jednak zastosowanie MFC otwiera szereg bardzo ważnych możliwości.

Istnieją dwa poziomy użycia struktury MFC w bibliotece DLL. Pierwsza z nich to zwykła biblioteka łączy dynamicznych oparta na MFC, Biblioteka MFC(zwykła biblioteka DLL MFC). Może używać MFC, ale nie może przekazywać wskaźników do obiektów MFC między bibliotekami DLL i aplikacjami. Drugi poziom realizowany jest w dynamiczne rozszerzenia MFC(DLL rozszerzeń MFC). Korzystanie z tego rodzaju biblioteki dołączanej dynamicznie wymaga dodatkowej pracy konfiguracyjnej, ale pozwala na swobodną wymianę wskaźników do obiektów MFC między biblioteką DLL a aplikacją.

Wspólne biblioteki DLL MFC

Zwykłe biblioteki DLL MFC umożliwiają używanie MFC w bibliotekach dołączanych dynamicznie. Jednak aplikacje, które uzyskują dostęp do takich bibliotek, nie muszą być budowane na MFC. W zwykłych bibliotekach DLL można używać MFC w dowolny sposób, w tym tworzyć nowe klasy w bibliotece DLL na podstawie klas MFC i eksportować je do aplikacji.

Jednak zwykłe biblioteki DLL nie mogą wymieniać wskaźników do klas pochodnych MFC z aplikacjami.

Jeśli Twoja aplikacja musi wymieniać wskaźniki do obiektów klas MFC lub ich pochodnych z bibliotekami DLL, musisz użyć rozszerzenia DLL opisanego w następnej sekcji.

Wspólna architektura DLL jest przeznaczona do użytku przez inne środowiska programistyczne, takie jak Visual Basic i PowerBuilder.

Podczas tworzenia zwykłej biblioteki DLL MFC za pomocą AppWizard, nowy projekt typu Kreator aplikacji MFC (dll)... W pierwszym oknie dialogowym Kreatora aplikacji należy wybrać jeden z trybów dla zwykłych bibliotek dołączanych dynamicznie: „Zwykła biblioteka DLL z łączem statystycznym MFC” lub „Zwykła biblioteka DLL korzystająca ze współdzielonej biblioteki MFC DLL”. Pierwsza zapewnia statyczne, a druga dynamiczne łączenie bibliotek MFC. Możesz później zmienić tryb połączenia MFC na bibliotekę DLL, korzystając z pola kombi na karcie Ogólne w oknie dialogowym Ustawienia projektu.

Zarządzanie informacjami o stanie MFC

Każdy moduł przetwarzający MFC zawiera informacje o swoim stanie. W związku z tym informacje o stanie biblioteki DLL różnią się od informacji o stanie aplikacji wywołującej. W związku z tym wszelkie funkcje wyeksportowane z biblioteki, do których dostęp uzyskuje się bezpośrednio z aplikacji, muszą informować MFC, jakich informacji o stanie należy użyć. W zwykłej bibliotece DLL MFC, która używa bibliotek DLL MFC, umieść następujący wiersz na początku wyeksportowanej funkcji przed wywołaniem dowolnego podprogramu MFC:

AFX_MANAGE_STATE (AfxGetStaticModuleState ());

Instrukcja ta określa użycie odpowiedniej informacji o stanie podczas wykonywania funkcji wywołującej podprogram.

Dynamiczne rozszerzenia MFC

MFC umożliwia tworzenie bibliotek DLL, których aplikacje nie są traktowane jako zbiór oddzielnych funkcji, ale jako rozszerzenia MFC. Za pomocą tego rodzaju biblioteki DLL można tworzyć nowe klasy, które pochodzą z klas MFC i używać ich w swoich aplikacjach.

Aby umożliwić swobodną wymianę wskaźników obiektów MFC między aplikacją a biblioteką DLL, należy utworzyć dynamiczne rozszerzenie MFC. Biblioteki DLL tego typu łączą się z bibliotekami DLL MFC w taki sam sposób, jak każda aplikacja korzystająca z dynamicznego rozszerzenia MFC.

Najłatwiejszym sposobem utworzenia nowego dynamicznego rozszerzenia MFC jest użycie Kreatora aplikacji do ustawienia typu projektu Kreator aplikacji MFC (dll) iw kroku 1 włącz tryb "MFC Extension DLL". Spowoduje to przypisanie do nowego projektu wszystkich wymaganych atrybutów rozszerzenia dynamicznego MFC. Dodatkowo zostanie utworzona funkcja DllMain dla biblioteki DLL, która wykonuje szereg określonych operacji w celu zainicjowania rozszerzenia DLL. Należy pamiętać, że biblioteki dynamiczne tego typu nie zawierają i nie powinny zawierać obiektów pochodzących od CWinApp.

Inicjowanie rozszerzeń dynamicznych

Aby „dopasować się” do struktury MFC, dynamiczne rozszerzenia MFC wymagają dodatkowych początkowe ustawienia... Odpowiednie operacje są wykonywane przez funkcję DllMain... Spójrzmy na przykład tej funkcji stworzonej przez AppWizard.

Statyczny AFX_EXTENSION_MODULE MyExtDLL = (NULL, NULL); extern "C" int APIENTRY DllMain (HINSTANCE hinstance, DWORD dwReason, LPVOID IpReserved) (if (dwReason == DLL_PROCESS_ATTACH) (TRACED ("MYEXT.DLL Initializing! \ n"); // Rozszerzenie DLL jednorazowa inicjalizacja, rozszerzenie AfulexIn (it wskazówka); // Wstaw tę bibliotekę DLL do łańcucha zasobów new CDynLinkLibrary (MyExtDLL);) else if (dwReason == DLL_PROCESS_DETACH) (TRACED ("MYEXT.DLL zakończenie! \ n");) return 1; // ok)

Najważniejszą częścią tej funkcji jest połączenie AfxInitExtensionModuł... Jest to inicjalizacja biblioteki dołączanej dynamicznie, umożliwiająca jej poprawne działanie jako część struktury MFC. Argumentami tej funkcji są deskryptor DLL przekazany do DllMain i struktura AFX_EXTENSION_MODULE, która zawiera informacje o bibliotece DLL MFC.

Nie jest konieczne jawne inicjowanie struktury AFX_EXTENSION_MODULE. Jednak trzeba to zadeklarować. Konstruktor zajmie się inicjalizacją. CDynLinkLibrary... W DLL musisz stworzyć klasę CDynLinkLibrary... Jego konstruktor nie tylko zainicjuje strukturę AFX_EXTENSION_MODULE, ale także doda nową bibliotekę do listy bibliotek DLL, z którymi może pracować MFC.

Wczytuję dynamiczne rozszerzenia MFC

Począwszy od wersji 4.0 MFC umożliwia dynamiczne ładowanie i rozładowywanie bibliotek DLL, w tym rozszerzeń. Aby poprawnie wykonać te operacje na tworzonej bibliotece DLL, w jej funkcji DllMain w momencie rozłączenia z procesem należy dodać połączenie AfxTermExtensionModuł... Struktura AFX_EXTENSION_MODULE już użyta powyżej jest przekazywana do ostatniej funkcji jako parametr. Aby to zrobić, w tekście DllMain musisz dodać następujące wiersze.

Jeśli (dwReason == DLL_PROCESS_DETACH) (AfxTermExtensionModule (MyExtDLL);)

Pamiętaj również, że nowa biblioteka DLL jest rozszerzeniem dynamicznym i musi być ładowana i rozładowywana dynamicznie za pomocą funkcji Biblioteka AfxLoad oraz AfxFreeLibrary,ale nie Załaduj bibliotekę oraz Darmowa biblioteka.

Eksport funkcji z rozszerzeń dynamicznych

Przyjrzyjmy się teraz, jak wyeksportować funkcje i klasy z rozszerzenia dynamicznego do aplikacji. Chociaż możesz ręcznie dodać wszystkie rozszerzone nazwy do pliku DEF, lepiej jest używać modyfikatorów dla deklaracji eksportowanych klas i funkcji, takich jak AFX_EXT_CLASS i AFX_EXT_API, na przykład:

Class AFX_EXT_CLASS CMyClass: public CObject (// Twoja deklaracja klasy) void AFX_EXT_API MyFunc ();

Istnieją dwa sposoby ładowania biblioteki DLL: jawne łączenie i niejawne łączenie.

Niejawne łączenie dodaje funkcje załadowanej biblioteki DLL do sekcji importu pliku wywołującego. Podczas uruchamiania takiego pliku loader system operacyjny analizuje sekcję importu i łączy wszystkie określone biblioteki. Ze względu na swoją prostotę metoda ta jest bardzo popularna; ale prostota jest prostotą, a niejawne łączenie ma pewne wady i ograniczenia:

wszystkie połączone biblioteki DLL są zawsze ładowane, nawet jeśli podczas całej sesji program nigdy nie wywołuje żadnej z nich;

jeśli brakuje przynajmniej jednej z wymaganych bibliotek DLL (lub biblioteka DLL nie eksportuje przynajmniej jednej wymaganej funkcji) - ładowanie pliku wykonywalnego jest przerywane komunikatem "Nie można znaleźć biblioteki dynamicznego łącza" (lub coś w tym stylu) - nawet jeśli brak tej biblioteki DLL nie jest krytyczny dla wykonania programu. Na przykład, Edytor tekstu może działać całkiem dobrze w minimalnej konfiguracji - bez modułu drukowania, wyprowadzania tabel, wykresów, formuł i innych pomniejszych komponentów, ale jeśli te biblioteki DLL są ładowane z ukrytym linkiem - ci się to podoba lub nie, będziesz musiał je "ciągnąć" .

DLL jest wyszukiwany w następującej kolejności: w katalogu zawierającym plik wywołujący; w bieżącym katalogu procesu; v katalog systemowy% Windows% System%; w głównym katalogu% Windows%; w katalogach określonych w zmiennej PATH. Nie można określić innej ścieżki wyszukiwania (a może raczej, ale będzie to wymagało wprowadzenia zmian w rejestrze systemowym, a zmiany te wpłyną na wszystkie procesy działające w systemie - co nie jest dobre).

Jawne łączenie usuwa wszystkie te wady - kosztem pewnej złożoności kodu. Sam programista będzie musiał zadbać o załadowanie biblioteki DLL i podłączenie wyeksportowanych funkcji (nie zapominając o kontroli nad błędami, w przeciwnym razie pewnego dnia system się zawiesi). Z drugiej strony, jawne łączenie umożliwia ładowanie bibliotek DLL w razie potrzeby i daje programiście możliwość niezależnej obsługi sytuacji, w których brakuje bibliotek DLL. Możesz iść dalej - nie ustawiaj jawnie nazwy DLL w programie, tylko przeskanuj taki a taki katalog w poszukiwaniu bibliotek dynamicznych i podłącz wszystkie znalezione do aplikacji. Tak działa mechanizm obsługi wtyczek w popularnym menedżerze plików FAR (i nie tylko).

Istnieją trzy sposoby ładowania biblioteki DLL:

a) dorozumiana;

c) odroczone.

Rozważ niejawne ładowanie biblioteki DLL. Aby zbudować aplikację, która opiera się na niejawnym ładowaniu DLL, musisz mieć:

Biblioteka zawiera plik z opisami użytych obiektów DLL (prototypy funkcji, deklaracje klas i typów). Ten plik jest używany przez kompilator.

Plik LIB z listą zaimportowanych identyfikatorów. Ten plik należy dodać do ustawień projektu (do listy bibliotek używanych przez linker).

Projekt jest kompilowany w zwykły sposób. Wykorzystując moduły obiektowe i plik LIB, a także biorąc pod uwagę referencje do importowanych identyfikatorów, linker (linker, linker) generuje ładowalny moduł EXE. W tym module linker umieszcza również sekcję importu, która zawiera listę nazw wszystkich wymaganych modułów DLL. Dla każdej biblioteki DLL sekcja importu określa, do których nazw funkcji i zmiennych odwołuje się kod pliku wykonywalnego. Program ładujący systemu operacyjnego użyje tych informacji.

Co dzieje się w fazie realizacji wniosku klienta? Po uruchomieniu modułu EXE program ładujący systemu operacyjnego wykonuje następujące operacje:

1. Tworzy wirtualną przestrzeń adresową dla nowego procesu i projektuje na nią moduł wykonywalny;

2. Analizuje sekcję importu, identyfikując wszystkie niezbędne moduły DLL, a także rzutując je na przestrzeń adresową procesu.

Biblioteka DLL może importować funkcje i zmienne z innej biblioteki DLL. Oznacza to, że może mieć własną sekcję importu, dla której musisz powtórzyć te same kroki. W rezultacie zainicjowanie procesu może zająć dość dużo czasu.

Po zmapowaniu modułu EXE i wszystkich bibliotek DLL do przestrzeni adresowej procesu, jego wątek podstawowy jest gotowy do wykonania i aplikacja zaczyna działać.

Wadami niejawnego ładowania są obowiązkowe ładowanie biblioteki, nawet jeśli jej funkcje nie będą wywoływane, a zatem obowiązkowy wymóg obecności biblioteki podczas łączenia.

Jawne ładowanie DLL eliminuje wady wymienione powyżej kosztem pewnej złożoności kodu. Programista musi sam zadbać o załadowanie biblioteki DLL i podłączenie wyeksportowanych funkcji. Z drugiej strony jawne ładowanie umożliwia ładowanie biblioteki DLL w razie potrzeby i umożliwia programowi obsługę sytuacji, które pojawiają się w przypadku braku biblioteki DLL.

W przypadku jawnego ładowania proces pracy z biblioteką DLL przebiega w trzech etapach:

1. Ładowanie DLL za pomocą funkcji Załaduj bibliotekę(lub jego rozszerzony odpowiednik LoadLibraryEx). W przypadku pomyślnego załadowania funkcja zwraca deskryptor hLib typu HMODULE, który umożliwia dalszy dostęp do tej biblioteki DLL.

2. Wywołania funkcji PobierzProcAdres aby uzyskać wskaźniki do wymaganych funkcji lub innych obiektów. Jako pierwszy parametr funkcja PobierzProcAdres otrzymuje deskryptor hLib, jako drugi parametr - adres ciągu z nazwą importowanej funkcji. Wynikowy wskaźnik jest następnie używany przez klienta. Na przykład, jeśli jest to wskaźnik funkcji, wywoływana jest wymagana funkcja.

3. Gdy załadowana biblioteka dynamiczna nie jest już potrzebna, zaleca się jej zwolnienie przez wywołanie funkcji Bezpłatna biblioteka. Zwolnienie biblioteki nie oznacza, że ​​system operacyjny natychmiast usunie ją z pamięci. Opóźnienie rozładowania jest przewidziane w przypadku, gdy ta sama biblioteka DLL po pewnym czasie jest ponownie potrzebna przez jakiś proces. Ale jeśli występują problemy z pamięcią RAM, system Windows najpierw usuwa z pamięci zwolnione biblioteki.

Rozważ leniwe ładowanie biblioteki DLL. Biblioteka DLL opóźnionego ładowania to niejawnie połączona biblioteka DLL, która nie jest ładowana, dopóki kod nie odwołuje się do wyeksportowanego z niej identyfikatora. Takie biblioteki DLL mogą być przydatne w następujących sytuacjach:

Jeśli aplikacja korzysta z wielu bibliotek DLL, jej inicjalizacja może zająć dużo czasu, co jest wymagane przez program ładujący, aby wyświetlić wszystkie biblioteki DLL w przestrzeni adresowej procesu. Biblioteki DLL z leniwym ładowaniem rozwiązują ten problem, rozprowadzając ładowanie DLL podczas wykonywania aplikacji.

Jeśli aplikacja została zaprojektowana do pracy w różnych wersjach systemu operacyjnego, niektóre funkcje mogą pojawiać się tylko w późniejszych wersjach systemu operacyjnego i nie mogą być używane w bieżącej wersji. Ale jeśli program nie wywołuje określonej funkcji, biblioteka DLL nie jest do tego potrzebna i może bezpiecznie kontynuować pracę. Odnosząc się do nieistniejącego

funkcja może przewidywać wysłanie ostrzeżenia do użytkownika.

W celu zaimplementowania metody lazy loading dodatkowo wymagane jest dodanie do listy bibliotek konsolidujących nie tylko niezbędnej biblioteki importu MyLib.lib, ale także systemowej biblioteki importu delayimp.lib. Dodatkowo należy dodać flagę / delayload: MyLib.dll w opcjach linkera.

Te ustawienia wymuszają na konsolidatorze wykonanie następujących operacji:

Zaimplementuj specjalną funkcję w module EXE
delayLoadHelper;

Usuń MyLib.dll z sekcji importu modułu wykonywalnego, aby program ładujący systemu operacyjnego nie próbował niejawnie załadować tej biblioteki na etapie ładowania aplikacji;

Dodaj nową sekcję odroczonego importu do pliku EXE z listą funkcji zaimportowanych z MyLib.dll;

Konwertuj wywołania funkcji z DLL na wywołania
delayLoadhelper.

Na etapie wykonywania aplikacji wywołanie funkcji z biblioteki DLL jest realizowane poprzez wywołanie delayLoadHelper. Ta funkcja, korzystając z informacji z sekcji importu z opóźnieniem, wywołuje najpierw LoadLibrary, a następnie GetprocAddress. Po otrzymaniu adresu funkcji DLL, delayLoadHelper sprawia, że ​​w przyszłości ta funkcja DLL jest wywoływana bezpośrednio. Zauważ, że każda funkcja w bibliotece DLL jest konfigurowana indywidualnie przy pierwszym wywołaniu.

Stosowanie DLL(Dynamic Link Library) jest szeroko rozpowszechniona w programowaniu Windows. DLL w rzeczywistości część kodu pliku wykonywalnego z rozszerzeniem DLL... Każdy program może zadzwonić DLL.

Korzyść DLL następująco:

  • Ponowne wykorzystanie kodu.
  • Udostępnianie kodu między aplikacjami.
  • Kod podziału.
  • Poprawa zużycia zasobów w systemie Windows.

Tworzenie DLL

W menu Plik wybierz Nowy -> Inne. W oknie dialogowym na karcie Nowy wybierać Kreator DLL... Automatycznie zostanie utworzony moduł - pusty szablon na przyszłość DLL.

Składnia DLL

Budować DLL, Wybierz Projekt -> Buduj Nazwa Projektu .

Widoczność funkcji i procedur

Funkcje i procedury mogą być lokalne i eksportowane z DLL.

Lokalny

Lokalne funkcje i procedury mogą być używane wewnętrznie DLL... Są widoczne tylko wewnątrz biblioteki i żaden program nie może ich wywołać z zewnątrz.

Wyeksportowane

Wyeksportowane funkcje i procedury mogą być używane poza DLL... Inne programy mogą wywoływać takie funkcje i procedury.

Powyższy kod źródłowy używa funkcji eksportowanej. Nazwa funkcji następuje po słowie zastrzeżonym Eksport.

Ładowanie DLL

W Delphi istnieją dwa rodzaje ładowania DLL:

Ładowanie statyczne

Po uruchomieniu aplikacja jest ładowana automatycznie. Pozostaje w pamięci przez cały czas wykonywania programu. Bardzo łatwy w użyciu. Po prostu dodaj słowo zewnętrzny po deklaracji funkcji lub procedury.

Funkcja SummaTotal (współczynnik: liczba całkowita): liczba całkowita; Zarejestruj się; zewnętrzny „Przykład.dll”;

Gdyby DLL nie zostanie znaleziony, program będzie nadal działał.

ładowane do pamięci w razie potrzeby. Jego implementacja jest bardziej skomplikowana, ponieważ sam musisz go ładować i rozładowywać z pamięci. Pamięć jest wykorzystywana bardziej ekonomicznie, dzięki czemu aplikacja działa szybciej. Sam programista musi upewnić się, że wszystko działa poprawnie. Do tego potrzebujesz:

  • Zadeklaruj typ opisywanej funkcji lub procedury.
  • Załaduj bibliotekę do pamięci.
  • Uzyskaj adres funkcji lub procedury w pamięci.
  • Wywołaj funkcję lub procedurę.
  • Wyładuj bibliotekę z pamięci.

Deklaracja typu opisująca funkcję

wpisz TSumaTotal = function (współczynnik: liczba całkowita): liczba całkowita;

Ładowanie biblioteki

dll_instance: = LoadLibrary ("Przykład_dll.dll");

Uzyskaj wskaźnik do funkcji

@SummaTotal: = GetProcAddress (dll_instance, "SummaTotal");

Wywołanie funkcji

Label1.Caption: = IntToStr (SummaTotal (5));

Wyładowanie biblioteki z pamięci

FreeLibrary (dll_instance);

Połączenie dynamiczne DLL wymaga więcej pracy, ale łatwiej zarządzać zasobami w pamięci. Jeśli musisz użyć DLL w programie, preferowane jest ładowanie statyczne. Nie zapomnij użyć bloku spróbuj ... z wyjątkiem oraz spróbuj ... w końcu aby uniknąć błędów podczas wykonywania programu.

Eksportowanie ciągów

Stworzone przez DLL z za pomocą Delphi, może być używany w programach napisanych w innych językach programowania. Z tego powodu nie możemy używać żadnego typu danych. Typy istniejące w Delphi mogą nie istnieć w innych językach. Wskazane jest używanie natywnych typów danych z systemu Linux lub Windows. Nasz DLL może być używany przez innego programistę, który używa innego języka programowania.

Może być użyty smyczki oraz tablice dynamiczne v DLL napisane w Delphi, ale do tego trzeba podłączyć moduł UdostępnijMem do sekcji używa v DLL i program, który będzie z niego korzystał. Ponadto ta reklama musi być pierwsza w sekcji używa każdy plik projektu.

Rodzaje strunowy, jak wiemy, C, C++ i inne języki nie istnieją, dlatego warto używać zamiast nich PChar.

Przykładowa biblioteka DLL

biblioteka Przykład_dll; używa SysUtils, klas; var (Zadeklaruj zmienne) k1: liczba całkowita; k2: liczba całkowita; (Deklarujemy funkcję) function SummaTotal (współczynnik: liczba całkowita): liczba całkowita; Zarejestruj się; współczynnik początku: = współczynnik * k1 + współczynnik; współczynnik: = współczynnik * k2 + współczynnik; wynik: = współczynnik; kończyć się; (Eksportujemy funkcję do dalszego wykorzystania przez program) export SummaTotal; (Inicjalizacja zmiennych) rozpocznij k1: = 2; k2: = 5; kończyć się.

Przykład wywołania funkcji z biblioteki DLL

jednostka Jednostka1; interfejs wykorzystuje Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; typ TForm1 = klasa (TForm) Button1: TButton; procedura Button1Click (Nadawca: TObject); prywatne (deklaracje prywatne) publiczne (deklaracje publiczne) koniec; wpisz TSummaTotal = function (współczynnik: integer): integer; var Form1: TForm1; implementacja ($R*.dfm) procedury TForm1.Button1Click (Sender: TObject); var dll_instance: Thandle; SummaTotal: TsummaTotal; begin dll_instance: = LoadLibrary ("Example_dll.dll"); @SummaTotal: = GetProcAddress (dll_instance, "SummaTotal"); Label1.Caption: = IntToStr (SummaTotal (5)); FreeLibrary (dll_instance); kończyć się; kończyć się.

DZWON

Są tacy, którzy czytają tę wiadomość przed tobą.
Zapisz się, aby otrzymywać najnowsze artykuły.
E-mail
Nazwa
Nazwisko
Jak chcesz czytać dzwonek?
Bez spamu