DZWONEK

Są tacy, którzy czytają te wiadomości przed tobą.
Subskrybuj, aby otrzymywać świeże artykuły.
E-mail
Imię
Nazwisko
Jak chcesz przeczytać Dzwon
Bez spamu

Jak zapewne wiesz, dynamicznie połączone biblioteki (DLL) używają konwencji języka C podczas deklarowania eksportowanych obiektów, podczas gdy C ++ używa nieco innego systemu do generowania nazw podczas kompilacji, więc nie można po prostu eksportować funkcji - metod klasy C ++ a następnie użyj ich w kodzie aplikacji klienta (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 wydajna i elegancka. klient widzi tylko abstrakcyjny interfejs, a rzeczywista klasa implementująca wszystkie funkcje może być dowolna. Technologia Component Object Model (COM) firmy Microsoft oparta jest na podobnym pomyśle (plus oczywiście dodatkowa funkcjonalność). W tym artykule pokażemy, jak stosować podejście „klasowe” przy użyciu interfejsu podobnego do modelu COM na wczesnym etapie (na etapie kompilacji) oraz późne (podczas działania programu) wiązanie.

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 do DLL. System operacyjny automatycznie wywołuje tę funkcję w przypadku załadowania i rozładowania biblioteki DLL. Zwykle ta funkcja nie jest używana do niczego innego.

Istnieją dwie metody podłączenia biblioteki DLL do projektu - wcześniejsze (na etapie kompilacji programu) i późniejsze (podczas wykonywania programu). Metody różnią się sposobem ładowania biblioteki DLL oraz sposobem wywoływania funkcji zaimplementowanych i wyeksportowanych z biblioteki DLL.

Wczesne wiązanie (w czasie kompilacji)

Dzięki tej metodzie wiązania system operacyjny automatycznie ładuje bibliotekę DLL podczas uruchamiania programu. Wymagane jest jednak dołączenie pliku .lib (pliku biblioteki) odpowiadającego tej bibliotece DLL do projektu programistycznego. Ten plik definiuje wszystkie wyeksportowane obiekty DLL. Deklaracje mogą zawierać zwykłe funkcje lub klasy C. Klient musi tylko użyć tego pliku.lib i dołączyć plik nagłówkowy biblioteki DLL - a system operacyjny automatycznie załaduje tę bibliotekę DLL. Jak widać, metoda ta wygląda na bardzo łatwą w użyciu, ponieważ wszystko jest przezroczyste. Powinieneś jednak zauważyć, że kod klienta wymaga ponownej kompilacji za każdym razem, gdy zmienia się kod DLL i odpowiednio generowany jest nowy plik.lib. To, czy jest to wygodne dla twojej aplikacji, zależy od Ciebie. Biblioteka DLL może zadeklarować funkcje, które chce wyeksportować na dwa sposoby. Standardową metodą jest użycie plików .def. Taki plik .def to po prostu lista funkcji wyeksportowanych z biblioteki DLL.

// \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // .def plik BIBLIOTEKA myfirstdll.dll OPIS „Moja pierwsza biblioteka DLL” EKSPORUJE MyFunction // \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // Nagłówek DLL, który należy uwzględnić kod klienta bool MyFunction (int parms); // \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // implementacja funkcji w bibliotece DLL bool MyFunction (int parms) (// zrobić wszystko, czego potrzebujemy ............)

Myślę, że nie można powiedzieć, że w tym przykładzie eksportowana jest tylko jedna funkcja MyFunction. Druga metoda deklarowania eksportowanych obiektów jest specyficzna, ale o wiele bardziej wydajna: możesz eksportować nie tylko funkcje, ale także klasy i zmienne. Spójrzmy na fragment kodu wygenerowany podczas tworzenia biblioteki DLL AppWizard przez VisualC ++. Komentarze zawarte na liście są wystarczające, aby zrozumieć, jak to wszystko działa.

// \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // Nagłówek DLL, który powinien być zawarty w kodzie klienta / * Następny blok ifdef to standardowa metoda tworzenia makra, która ułatwia eksportowanie z DLL. Wszystkie pliki tej biblioteki DLL są kompilowane z określonym kluczem MYFIRSTDLL_EXPORTS. Ten klucz nie jest zdefiniowany dla żadnego projektu używającego tej biblioteki DLL. Zatem 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 funkcje jako eksportowane. * / #ifdef MYFIRSTDLL_EXPORTS # zdefiniuj MYFIRSTDLL_API __declspec (dllexport) #else #define MYFIRSTDLL_API __declspec (dllimport) #endif // Klasa jest eksportowana z klasy test2.dll MYFIRSTDLL_API CMyllfirst metody.); extern MYFIRSTDLL_API int nMyFirstDll; MYFIRSTDLL_API int fnMyFunction (void);

Podczas kompilacji DLL definiowany jest klucz MYFIRSTDLL_EXPORTS, więc słowo kluczowe __declspec (dllexport) jest zastępowane przed deklaracjami eksportowanych obiektów. A kiedy kod klienta jest kompilowany, ten klucz jest niezdefiniowany, a przed obiektami pojawia się przedrostek __declspec (dllimport), dzięki czemu klient wie, które obiekty są importowane z biblioteki DLL.

W obu przypadkach wszystko, co klient musi zrobić, to dodać plik myfirstdll.lib do projektu i dołączyć plik nagłówka, który deklaruje obiekty zaimportowane z biblioteki DLL, a następnie użyć tych obiektów (funkcji, klas i zmiennych) w taki sam sposób, jakby były zdefiniowane i wdrożone lokalnie w projekcie. Teraz spójrzmy na inną metodę korzystania z DLL, która jest często 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 podczas uruchamiania programu, ale bezpośrednio w kodzie, w którym jest potrzebna. Nie trzeba używać żadnych plików .lib, więc aplikacja kliencka nie wymaga ponownej kompilacji podczas zmiany biblioteki DLL. Takie wiązanie jest skuteczne właśnie dlatego, że decydujesz, kiedy i którą bibliotekę DLL załadować. Na przykład piszesz grę, która korzysta z DirectX i OpenGL. Możesz po prostu dołączyć cały niezbędny kod do pliku wykonywalnego, ale wtedy po prostu coś nie będzie w stanie znaleźć. Lub możesz umieścić kod DirectX w jednej bibliotece DLL, a kod OpenGL w innej i połączyć je statycznie z projektem. Ale teraz cały kod jest współzależny, więc jeśli napisałeś nową bibliotekę DLL zawierającą kod DirectX, będziesz musiał ponownie skompilować plik wykonywalny. Jedyną wygodą będzie to, że nie musisz się martwić o ładowanie (chociaż nie wiadomo, czy jest to wygodne, jeśli ładujesz obie biblioteki DLL, zajmując pamięć, ale w rzeczywistości potrzebna jest tylko jedna). I wreszcie, moim zdaniem, najlepszym pomysłem jest pozwolenie, aby plik wykonywalny decydował, którą bibliotekę DLL należy 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ładuj OpenGL. Dlatego 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 nakładane jest ograniczenie - można eksportować tylko funkcje w stylu C. Klasy i zmienne nie mogą być ładowane, jeśli program używa późnego wiązania. Zobaczmy, jak obejść to ograniczenie za pomocą interfejsów.

Biblioteka DLL zaprojektowana do późnego wiązania zwykle używa pliku .def do identyfikacji obiektów, które chce wyeksportować. Jeśli nie chcesz używać pliku .def, możesz po prostu użyć przedrostka __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 jest używany do pracy z biblioteką DLL i który jest niezbędny do zwolnienia 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 nazwy wymaganej funkcji jako parametru.

// \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // plik .def BIBLIOTEKA myfirstdll.dll OPIS „Moja pierwsza biblioteka DLL” EKSPORUJE MyFunction // \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d / * Implementacja funkcji w DLL * / bool MyFunction (int parms) (// zrób coś ............) // \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // Kod klienta / * Deklaracja funkcji jest naprawdę konieczna tylko dla w celu ustalenia parametrów. Deklaracje funkcji są zwykle zawarte w pliku nagłówkowym dołączonym do biblioteki DLL. Słowo kluczowe extern C w deklaracji funkcji mówi kompilatorowi, aby używał konwencji nazewnictwa zmiennych C. * / Extern "C" bool MyFunction (int parms); typedef bool (* MYFUNCTION) (int parms); MYFUNCTION pfnMyFunc \u003d 0; // wskaźnik do MyFunction HINSTANCE hMyDll \u003d :: LoadLibrary ("myfirstdll.dll"); if (hMyDll! \u003d NULL) (// Określ adres funkcji pfnMyFunc \u003d (MYFUNCTION) :: GetProcAddress (hMyDll, „MyFunction”); // Jeśli nie powiedzie się, zwolnij bibliotekę DLL if (pfnMyFunc \u003d\u003d 0) (:: FreeLibrary (hMyDll) ; return;) // Wywołaj funkcję bool result \u003d pfnMyFunc (parms); // Zwolnij bibliotekę DLL, jeśli już jej nie potrzebujemy :: FreeLibrary (hMyDll);)

Jak widać, kod jest dość prosty. Zobaczmy teraz, jak można wdrożyć pracę z „klasami”. Jak wspomniano wcześniej, jeśli używane jest późne wiązanie, nie ma bezpośredniego sposobu importowania klas z bibliotek DLL, dlatego musimy zaimplementować „funkcjonalność” klasy za pomocą interfejsu zawierającego 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 wszystkie funkcje zdefiniowane 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 z interfejsem, który zdefiniowaliśmy, aby klient mógł z nich korzystać. Aby zaimplementować tę 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.

// \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // plik .def BIBLIOTEKA myinterface.dll OPIS "implementuje interfejs I_MyInterface EKSPORTU GetMyInterface FreeMyInterface // \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // Plik nagłówka używany w bibliotece DLL i do klienta //, który deklaruje interfejs // I_MyInterface.h struct I_MyInterface (virtual bool Init (int parms) \u003d 0; virtual bool Release () \u003d 0; virtual void DoStuff () \u003d 0;); / * Deklaracje eksportowanych bibliotek Dll i definicje typów wskaźników funkcji dla łatwego ładowania i pracy z funkcjami: Zwróć uwagę na zewnętrzny prefiks „C”, który informuje kompilator, że używane są funkcje 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);) \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // // Implementacja interfejsu w bibliotece Dll // MyInterface.h klasa CMyClass: public I_MyInterface (public: bool Init (int parms); bool Release (); void DoStuff (); CMyClass (); ~ CMyClass (); // każdy inny członek klasy ............ prywatny: // każdy członek klasy ............); // \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // Wyeksportowane funkcje tworzące i niszczące interfejs // Dllmain.h HRESULT GetMyInterface (I_MyInterface ** pInterface) (if (! * PInterface) (* pInterface \u003d new CMyClass; return S_OK;) return E_FAIL;) HRESULT FreeMyInterface (I_MyInterface ** pInterface) (if (! * PInterface) return E_FAIL; usuń * pInterface; * pInterface \u003d 0; zwróć S_OK;) // \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // Kod klienta // Deklaracje interfejsu i wywołania funkcji GETINTERFACE pfnInterface \u003d 0; // wskaźnik do funkcji GetMyInterface I_MyInterface * pInterface \u003d 0; // wskaźnik do struktury MyInterface HINSTANCE hMyDll \u003d :: LoadLibrary ("myinterface.dll"); if (hMyDll! \u003d NULL) (// Określ adres funkcji pfnInterface \u003d (GETINTERFACE) :: GetProcAddress (hMyDll, „GetMyInterface”); // Zwolnij bibliotekę DLL, jeśli poprzednia operacja nie powiodła się, jeśli (pfnInterface \u003d\u003d 0) (:: FreeLibrary (hMyDll); return;) // Wywołaj funkcję HRESULT hr \u003d pfnInterface (& pInterface); // Zwolnij, jeśli nie powiedzie się, jeśli (FAILED (hr)) (:: FreeLibrary (hMyDll); return;) // Interfejs jest załadowany, możesz wywołać funkcje pInterface-\u003e Init (1); pInterface-\u003e DoStuff (); pInterface-\u003e Release (); // Uwolnij interfejs FREEINTERFACE pfnFree \u003d (FREEINTERFACE) :: GetProcAddress (hMyDll, „FreeMyInterface”); if (pfnFree! \u003d 0) pfnFree (& hMyDll); // Pobierz DLL :: FreeLibrary (hMyDll); )

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

Od urodzenia (lub nieco później) system operacyjny Windows korzystał z bibliotek linków dynamicznych (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ż zależą od bibliotek DLL pod względem zapewniania znacznej części ich funkcjonalności.

Rozważ szereg aspektów 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.

Korzystanie z DLL

Utworzenie aplikacji Windows, która nie korzysta z bibliotek DLL, jest prawie niemożliwe. DLL zawiera wszystkie funkcje Win32 API i niezliczone inne funkcje systemów operacyjnych Win32.

Ogólnie rzecz biorąc, biblioteki DLL są po prostu kolekcjami funkcji wkompilowanych w biblioteki. Jednak w przeciwieństwie do statycznego rodzeństwa (pliki. Lib) biblioteki DLL nie są bezpośrednio dołączane do plików wykonywalnych za pomocą edytora łączy. 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 zmniejsza ilość pamięci potrzebnej kilku aplikacjom korzystającym z wielu bibliotek współdzielonych, a także kontroluje rozmiar plików .exe.

Jeśli jednak biblioteka jest używana tylko przez jedną aplikację, lepiej jest uczynić ją normalną, statyczną. Oczywiście, jeśli funkcje zawarte w jego składzie będą używane tylko w jednym programie, możesz po prostu wstawić do niego odpowiedni plik z tekstem źródłowym.

Najczęściej projekt łączy się z biblioteką DLL statycznie lub pośrednio na etapie kompilacji. Ładowanie biblioteki DLL podczas wykonywania programu jest kontrolowane przez system operacyjny. Jednak biblioteki DLL mogą być ładowane jawnie lub dynamicznie podczas działania aplikacji.

Importuj biblioteki

W przypadku statycznego połączenia DLL nazwa pliku .lib jest określana między innymi parametrami edytora łączy w wierszu polecenia lub na karcie Łącze w oknie dialogowym Ustawienia projektu w Developer Studio. Jednak użyty plik .lib podczas niejawnego łączenia biblioteki DLL, - To nie jest zwykła biblioteka statyczna. Takie pliki .lib są nazywane importuj biblioteki (importuj biblioteki). Nie zawierają one samego kodu biblioteki, a jedynie łącza do wszystkich funkcji eksportowanych z pliku DLL, w którym wszystko jest przechowywane. W rezultacie biblioteki importu są zwykle mniejsze niż pliki DLL. Wrócimy do metod ich tworzenia później. Teraz spójrzmy na inne kwestie dotyczące niejawnego połączenia bibliotek dynamicznych.

Negocjacje interfejsu

Korzystając z własnych bibliotek lub bibliotek niezależnych programistów, należy zwrócić uwagę na dopasowanie wywołania funkcji do jego prototypu.

Gdyby świat był doskonały, programiści nie musieliby martwić się koordynacją interfejsów funkcji podczas podłączania bibliotek - wszystkie byłyby takie same. Jednak świat jest daleki od ideału, a wiele dużych programów jest pisanych przy użyciu różnych bibliotek bez C ++.

Domyślnie w Visual C ++ interfejsy funkcji są spójne zgodnie z regułami C ++. Oznacza to, że parametry są wypychane na stos od prawej do lewej, program wywołujący jest odpowiedzialny za usunięcie ich ze stosu podczas wychodzenia z funkcji i rozwinięcie jej nazwy. Zmiana nazwy pozwala edytorowi linków rozróżniać przeciążone funkcje, tj. działa z tą samą nazwą, ale różnymi listami argumentów. Jednak stara biblioteka C nie ma rozszerzonych nazw.

Chociaż wszystkie inne reguły wywoływania funkcji w C są identyczne z regułami wywoływania funkcji w C ++, w bibliotekach C nazwy funkcji nie są rozszerzane. Znak podkreślenia (_) jest dodawany do nich tylko z przodu.

Jeśli musisz podłączyć bibliotekę C do aplikacji C ++, musisz zadeklarować wszystkie funkcje z tej biblioteki jako zewnętrzne w formacie C:

Extern „C” int MyOldCFunction (int myParam);

Deklaracje funkcji bibliotecznych są zwykle umieszczane w pliku nagłówkowym tej biblioteki, chociaż nagłówki większości bibliotek C nie są przeznaczone do użytku w projektach C ++. W takim przypadku konieczne jest utworzenie kopii pliku nagłówka i dołączenie do niego zewnętrznego modyfikatora „C”, aby zadeklarować wszystkie używane funkcje biblioteki. Zewnętrzny modyfikator „C” można również zastosować do całego bloku, do którego podłączony jest stary plik nagłówka C, za pomocą dyrektywy #tinclude. Dlatego zamiast modyfikować każdą funkcję osobno, można wykonać tylko trzy linie:

Extern „C” (#include „MyCLib.h”)

Programy dla starszych wersji Windows również używały konwencji wywoływania funkcji. pASKAL dla funkcji Windows API. W nowych programach powinieneś używać modyfikatora winapi, przekonwertowanego na _stdcall. Chociaż nie jest to standardowy interfejs funkcji C lub C ++, jest używany do uzyskiwania dostępu do funkcji Windows API. Jednak zwykle wszystko to jest już brane pod uwagę w standardowych nagłówkach Windows.

Pobierz niejawnie dołączoną bibliotekę DLL

Podczas uruchamiania aplikacja próbuje znaleźć wszystkie pliki DLL, które są domyślnie połączone z aplikacją i umieścić je w obszarze pamięci RAM zajmowanym przez ten proces. Wyszukiwanie plików DLL według systemu operacyjnego odbywa się w następującej kolejności.

  • Katalog, w którym znajduje się plik .exe.
  • Bieżący katalog procesów.
  • 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 się kończy.

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

Dynamiczne ładowanie i rozładowywanie DLL

Zamiast dynamicznego łączenia systemu Windows z biblioteką DLL przy pierwszym ładowaniu aplikacji do pamięci RAM, można skojarzyć program z modułem biblioteki podczas wykonywania programu (w ten sposób nie trzeba używać biblioteki importu podczas procesu tworzenia aplikacji). W szczególności możesz określić, które biblioteki DLL są dostępne dla użytkownika lub pozwolić użytkownikowi wybrać, które biblioteki zostaną załadowane. W ten sposób można używać różnych bibliotek DLL, które implementują te same funkcje, które wykonują różne działania. Na przykład aplikacja zaprojektowana do niezależnego przesyłania danych będzie mogła podczas wykonywania zdecydować, czy załadować biblioteki DLL dla TCP / IP, czy dla innego protokołu.

Pobierz zwykłą bibliotekę DLL

Pierwszą rzeczą do zrobienia podczas dynamicznego ładowania biblioteki DLL jest umieszczenie modułu biblioteki w pamięci procesu. Ta operacja jest wykonywana przy użyciu funkcji :: LoadLibraryposiadanie jednego argumentu to nazwa modułu do załadowania. Odpowiedni fragment programu powinien wyglądać następująco:

HINSTANCE hMyDll; :: if ((hMyDll \u003d :: LoadLibrary („MyDLL”)) \u003d\u003d NULL) (/ * DLL nie załadował się * /) else (/ * aplikacja ma prawo do korzystania z funkcji DLL poprzez hMyDll * /)

Domyślne rozszerzenie pliku biblioteki Windows to.dll, chyba że podasz inne rozszerzenie. Jeśli nazwa ścieżki jest również podana w nazwie pliku, wówczas tylko ona będzie używana do wyszukiwania pliku. W przeciwnym razie system Windows wyszuka plik 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 wykryje plik, jego pełna ścieżka zostanie porównana ze ścieżką bibliotek DLL już załadowanych przez ten proces. Jeśli zostanie znaleziona tożsamość, zamiast ładowania kopii aplikacji zwracany jest uchwyt już podłączonej biblioteki.

Jeśli plik zostanie znaleziony i biblioteka załaduje się pomyślnie, funkcja :: LoadLibraryzwraca uchwyt, który służy do uzyskiwania dostępu do funkcji biblioteki.

Przed użyciem funkcji bibliotecznych musisz uzyskać ich adres. Aby to zrobić, najpierw użyj dyrektywy typedef aby określić typ wskaźnika funkcji i zdefiniować zmienną tego nowego typu, na przykład:

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

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

HMyDll \u003d :: LoadLibrary („MyDLL”); pfnMyFunction \u003d (PFN_MyFunction) :: GetProcAddress (hMyDll, „MyFunction”); :: int iCode \u003d (* pfnMyFunction) („Hello”);

Adres funkcji określa się za pomocą funkcji :: GetProcAddress, powinien przekazać 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 po numerze seryjnym, pod którym jest ona eksportowana (dodatkowo do utworzenia biblioteki należy użyć pliku def, co zostanie omówione później):

PfnMyFunction \u003d (PFN_MyFunction) :: GetProcAddress (hMyDll, MAKEINTRESOURCE (1));

Po zakończeniu pracy z biblioteką linków dynamicznych możesz ją zwolnić z pamięci procesu za pomocą funkcji :: Freelibrary:

:: FreeLibrary (hMyDll);

Pobierz rozszerzenia biblioteki dynamicznej MFC

Podczas ładowania rozszerzeń MFC dla bibliotek DLL (opisanych bardziej szczegółowo poniżej) zamiast funkcji Loadlibraryi Freelibraryfunkcje są używane AfxLoadLibrary i AfxFreeLibrary. Te ostatnie są prawie identyczne z funkcjami Win32 API. Gwarantują one tylko, że struktury MFC inicjowane przez rozszerzenie .dll nie są uszkodzone przez inne wątki.

Zasoby DLL

Ładowanie dynamiczne dotyczy również zasobów DLL używanych przez MFC do ładowania standardowych zasobów aplikacji. Aby to zrobić, musisz najpierw wywołać funkcję Loadlibraryi umieść dll w pamięci. Następnie za pomocą funkcji AfxSetResourceHandle Musisz przygotować okno programu do odbierania zasobów z nowo załadowanej biblioteki. W przeciwnym razie zasoby zostaną załadowane z plików podłączonych do pliku wykonywalnego procesu. Takie podejście jest wygodne, jeśli trzeba użyć różnych zestawów zasobów, na przykład dla różnych języków.

Komentarz. Korzystanie z funkcji Loadlibrary Możesz także załadować pliki wykonywalne do pamięci (nie uruchamiaj ich!). Aby uzyskać dostęp do funkcji, można następnie użyć wykonywalnego deskryptora modułu. Findresourcei Loadresource do wyszukiwania i pobierania zasobów aplikacji. Zwolnij moduły z pamięci za pomocą funkcji Freelibrary.

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

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

Po pierwsze, stała makr jest zdefiniowana w pliku nagłówkowym. EKSPORT. Użycie tego słowa kluczowego podczas definiowania funkcji dynamicznie połączonej biblioteki pozwala powiedzieć linkerowi, że ta funkcja jest dostępna do użytku przez inne programy, w wyniku czego wprowadza ją do biblioteki importu. Ponadto taką funkcję, podobnie jak procedurę okna, należy określić za pomocą stałej Oddzwonić:

Mydll.h # zdefiniować EXPORT extern "C" __declspec (dllexport) EXPORT int CALLBACK MyFunction (char * str);

Plik biblioteki różni się również nieco od zwykłych plików w języku C dla systemu Windows. W nim zamiast funkcji Winmain jest funkcja Dllmain. Ta funkcja służy do inicjalizacji, która zostanie omówiona później. Aby biblioteka pozostała po załadowaniu do pamięci i wywołała swoje funkcje, konieczne jest, aby jej zwracana wartość była PRAWDA:

MyDLL.c #include #include „MyDLL.h” int WINAPI DllMain (HINSTANCE hInstance, DWORD fdReason, PVOID pvReserved) (return TRUE;) EXPORT int CALLBACK MyFunction (char * str) (MessageBox (NULL, str, „Funkcja z DLL”, MB_OK); zwrot 1;)

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

Przykład niejawnego połączenia DLL według aplikacji

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

#zawierać #include „MyDLL.h” int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) (int iCode \u003d MyFunction („Hello”); return 0;)

Ten program wygląda regularne programy dla systemu Windows, którym w istocie jest. Niemniej jednak należy zauważyć, że oprócz wywołania funkcji MyFunction z biblioteki DLL plik nagłówkowy tej biblioteki MyDLL.h znajduje się w jej tekście źródłowym. Konieczne jest również połączenie z nim na etapie budowy aplikacji importuj bibliotekę MyDLL.lib (proces niejawnego łączenia biblioteki DLL z modułem wykonywalnym).

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

Plik nagłówkowy MyDLL.h jest zawarty w pliku źródłowym MyApp.c w taki sam sposób, jak plik windows.h. Włączenie biblioteki importu MyDLL.lib do kompilacji jest podobne do włączenia wszystkich bibliotek importu systemu Windows. Gdy program MyApp.exe jest uruchomiony, łączy się z biblioteką MyDLL.dll w taki sam sposób, jak w przypadku wszystkich standardowych bibliotek linków dynamicznych Windows.

Przykład dynamicznego ładowania biblioteki DLL przez aplikację

Teraz podajemy pełny kod źródłowy prostej aplikacji, która korzysta z funkcji MyFunction z biblioteki MyDLL.dll przy użyciu dynamicznego ładowania biblioteki:

#zawierać typedef int (WINAPI * PFN_MyFunction) (char *); int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) (HINSTANCE hMyDll; if (((hMyDll \u003d LoadLibrary („MyDLL”)) \u003d\u003d NULL) funkcja 1 funkcja PFN_funkcja , „MyFunction”); int iCode \u003d (* pfnMyFunction) („Hello”); FreeLibrary (hMyDll); return 0;)

Tworzenie DLL

Po zapoznaniu się ze sposobem działania bibliotek DLL w aplikacjach przyjrzymy się sposobom ich tworzenia. Podczas opracowywania aplikacji funkcje, do których dostęp ma kilka procesów, najlepiej umieścić w bibliotece DLL. Umożliwia to bardziej wydajne wykorzystanie pamięci w systemie Windows.

Najprostszym sposobem jest utworzenie nowego projektu DLL za pomocą AppWizard, który automatycznie wykonuje wiele operacji. W przypadku prostych bibliotek DLL, takich jak omówione w tym rozdziale, należy wybrać typ projektu biblioteki DLL biblioteki dynamicznej. Do nowego projektu zostaną przypisane wszystkie parametry niezbędne do utworzenia biblioteki DLL. Pliki źródłowe będą musiały zostać dodane do projektu ręcznie.

Jeśli planujesz w pełni wykorzystać funkcje MFC, takie jak dokumenty i prezentacje, lub zamierzasz utworzyć serwer automatyzacji OLE, lepiej wybrać typ projektu MFC AppWizard (dll). W takim przypadku oprócz przypisania parametrów do projektu w celu podłączenia bibliotek dynamicznych kreator wykona dodatkową pracę. Niezbędne linki do bibliotek MFC i plików źródłowych zawierających opis i implementację w DLL obiektu obiektu klasy aplikacji pochodzącego z Cwininpp.

Czasami wygodnie jest najpierw utworzyć projekt typu MFC AppWizard (dll) jako aplikację testową, a następnie DLL jako jego integralną część. W rezultacie biblioteka DLL zostanie utworzona automatycznie, jeśli to konieczne.

Funkcja Dllmain

Większość bibliotek DLL to po prostu kolekcje praktycznie niezależnych funkcji eksportowanych i używanych przez aplikacje. Oprócz funkcji przeznaczonych do eksportu każda biblioteka DLL ma funkcję Dllmain. Ta funkcja służy do inicjalizacji i czyszczenia biblioteki DLL. Zastąpiła funkcje Libmain i WEPuż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 \u003d TRUE; przełącznik (dwReason) (case DLL_PROCESS_ATTACH: // Zainicjuj process.break; case DLL_THREAD_ATTACH: // Initialize_DETREACH. Strumień struktur. break; case DLL_PROCESS_DETACH: // Wyczyść struktury procesów. break;) if (bAllWentWell) zwraca TRUE; w przeciwnym razie zwraca FALSE;)

Funkcjonować Dllmainwywoływany w kilku przypadkach. Przyczynę jego wywołania określa parametr dwReason, który może przyjąć jedną z następujących wartości.

Przy pierwszym załadowaniu biblioteki DLL proces wywołuje funkcję Dllmain z dwRasason równym DLL_PROCESS_ATTACH. Za każdym razem, gdy proces tworzy nowy wątek, DllMainO jest wywoływany z dwReason równym DLL_THREAD_ATTACH (z wyjątkiem pierwszego wątku, ponieważ w tym przypadku dwReason jest równy DLL_PROCESS_ATTACH).

Na koniec procesu z funkcją DLL Dllmain wywoływane z parametrem dwReason równym DLL_PROCESS_DETACH. Gdy strumień zostanie zniszczony (z wyjątkiem pierwszego), dwReason będzie równy DLL_THREAD_DETACH.

Wszystkie operacje inicjowania i czyszczenia procesów i wątków, których potrzebuje biblioteka DLL, są konieczne na podstawie wartości dwReason, jak pokazano w poprzednim przykładzie. Inicjowanie procesów zwykle ogranicza się do alokacji zasobów współdzielonych przez wątki, w szczególności pobierania współdzielonych plików i inicjowania bibliotek. Inicjalizacja wątków służy do konfigurowania trybów specyficznych dla danego wątku, na przykład do inicjowania pamięci lokalnej.

Biblioteki DLL mogą zawierać zasoby, które nie należą do aplikacji wywołującej tę bibliotekę. Jeśli funkcje DLL działają z zasobami DLL, oczywiście dobrze byłoby zapisać uchwyt hInst gdzieś w ustronnym miejscu i używać go podczas ładowania zasobów z DLL. Wskaźnik IpReserved zarezerwowany dla wewnętrznego za pomocą systemu Windows. Dlatego aplikacja nie powinna tego udawać. Możesz tylko sprawdzić jego znaczenie. Jeśli biblioteka DLL została załadowana dynamicznie, będzie miała wartość NULL. Przy obciążeniu statycznym wskaźnik ten będzie niezerowy.

Jeśli się powiedzie, funkcja Dllmain powinien zwrócić PRAWDA. W przypadku błędu zwracana jest wartość FAŁSZ, a dalsze działania są przerywane.

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

Eksportowanie funkcji z biblioteki DLL

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

__Declspec metoda (dllexport)

Możesz wyeksportować funkcję z biblioteki DLL, ustawiając modyfikator __declspec (dllexport) na początku jego 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 nie jest używana tak często jak druga metoda, która działa z plikami definicji modułów (.def) i pozwala lepiej kontrolować proces eksportu.

Pliki definicji modułów

Składnia plików z rozszerzeniem .def w programie Visual C ++ jest dość prosta, głównie dlatego, że złożone parametry używane we wcześniejszych wersjach systemu Windows nie są już używane w systemie Win32. Jak wynika z poniższego prostego przykładu, plik .def zawiera nazwę i opis biblioteki, a także listę eksportowanych funkcji:

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

W wierszu eksportu funkcji możesz podać jej numer seryjny, poprzedzając ją symbolem @. Ten numer zostanie następnie użyty w odniesieniu do GetProcAddress (). W rzeczywistości kompilator przypisuje numery seryjne wszystkim eksportowanym obiektom. Jednak sposób, w jaki to robi, jest częściowo nieprzewidywalny, chyba że wyraźnie przypiszesz te liczby.

Możesz użyć parametru NONAME w ciągu eksportu. Zapobiega włączaniu przez kompilator nazwy funkcji do 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 połączenia biblioteki DLL nie zauważą różnicy, ponieważ niejawne połączenia automatycznie używają numerów sekwencyjnych. Aplikacje, które dynamicznie ładują biblioteki DLL, będą musiały przejść GetProcAddressnumer seryjny, a nie nazwa funkcji.

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

# zdefiniować EXPORT extern "C" __declspec (dllexport) EXPORT int CALLBACK MyFunction (char * str); podobny do tego: extern "C" int CALLBACK MyFunction (char * str);

Eksport klasy

Utworzenie pliku .def w celu wyeksportowania nawet prostych klas z biblioteki dynamicznej może być dość trudne. Konieczne będzie jawne wyeksportowanie każdej funkcji, z której może korzystać aplikacja zewnętrzna.

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

Chociaż każdą z tych funkcji można wyeksportować osobno, istnieje łatwiejszy sposób. Jeśli użyjesz modyfikatora makra AFX_CLASS_EXPORT w deklaracji klasy, kompilator zajmie się eksportowaniem wymagane funkcjeumożliwiając aplikacji korzystanie z klasy zawartej w DLL.

Pamięć dll

W przeciwieństwie do bibliotek statycznych, które zasadniczo stają się częścią kodu aplikacji, biblioteki linków dynamicznych w 16-bitowych wersjach systemu Windows działały 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, w którym jest ładowana. Każdy proces jest dostarczany z osobną kopią „globalnej” pamięci DLL, która jest ponownie inicjowana za każdym razem, gdy ładuje ją nowy proces. Oznacza to, że dynamiczna biblioteka nie może być współużytkowana w pamięci współdzielonej, tak jak to było w Winl6.

Niemniej jednak, po przeprowadzeniu serii skomplikowanych manipulacji na segmencie danych DLL, możliwe jest utworzenie wspólnego obszaru pamięci dla wszystkich procesów korzystających z tej biblioteki.

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

#pragma data_seg (". myseg") int sharedlnts; // inne zmienne publiczne #pragma data_seg () #pragma comment (lib, "msvcrt" "-SECTION: .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 działającego systemu C oznaczenie nowej partycji jako do odczytu, zapisu i współdzielenia.

Pełna kompilacja DLL

Jeśli projekt biblioteki dynamicznej został utworzony za pomocą AppWizard i odpowiednio zmodyfikowano plik .def, wystarczy. Jeśli pliki projektu są tworzone ręcznie lub w inny sposób bez pomocy AppWizard, parametr / DLL powinien zostać zawarty w linii poleceń edytora linków. W rezultacie zostanie utworzona biblioteka DLL zamiast samodzielnego pliku wykonywalnego.

Jeśli w pliku .def znajduje się wiersz LIBRART, nie trzeba jawnie określać parametru / DLL w wierszu polecenia edytora łączy.

Istnieje wiele trybów specjalnych dla MFC dotyczących korzystania z biblioteki dynamicznej bibliotek MFC. Poniższa sekcja poświęcona jest temu zagadnieniu.

DLL i MFC

Programista nie musi używać MFC podczas tworzenia bibliotek dynamicznych. Jednak korzystanie z MFC otwiera szereg bardzo ważnych możliwości.

Istnieją dwa poziomy korzystania ze struktury MFC w bibliotece DLL. Pierwszy to zwykła biblioteka dynamiczna oparta na MFC, Biblioteka MFC DLL (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 jest zaimplementowany w dynamiczne rozszerzenia MFC (DLL rozszerzenia MFC). Korzystanie z tego typu biblioteki dynamicznej wymaga pewnych dodatkowych działań konfiguracyjnych, ale umożliwia swobodną wymianę wskaźników do obiektów MFC między biblioteką DLL a aplikacją.

Zwykła biblioteka MFC DLL

Konwencjonalne biblioteki DLL MFC umożliwiają używanie MFC w bibliotekach dynamicznych. Jednak aplikacje uzyskujące dostęp do takich bibliotek nie muszą być budowane na podstawie 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 z klasami pochodzącymi z MFC z aplikacjami.

Jeśli aplikacja musi wymieniać się ze wskaźnikami DLL na obiekty klas MFC lub ich pochodnych, należy użyć rozszerzenia DLL opisanego w następnym rozdziale.

Architektura konwencjonalnych bibliotek DLL została zaprojektowana do użytku w innych środowiskach programistycznych, takich jak Visual Basic i PowerBuilder.

Podczas tworzenia zwykłej biblioteki DLL MFC za pomocą AppWizard, nowego projektu tego typu MFC AppWizard (dll). W pierwszym oknie dialogowym kreatora aplikacji musisz wybrać jeden z trybów dla zwykłych bibliotek dynamicznych: „Zwykła biblioteka DLL z statystycznie połączonym MFC” lub „Zwykła biblioteka DLL przy użyciu współdzielonej biblioteki DLL MFC”. Pierwszy zapewnia statyczne, a drugi - dynamiczne połączenie bibliotek MFC. Następnie tryb łączenia MFC z DLL można zmienić za pomocą pola kombi na karcie Ogólne okna dialogowego Ustawienia projektu.

Zarządzanie informacjami o statusie MFC

Każdy moduł procesu MFC zawiera informacje o jego statusie. W związku z tym informacje o stanie biblioteki DLL różnią się od informacji o stanie aplikacji, która ją wywołała. Dlatego wszelkie funkcje wyeksportowane z biblioteki, do których dostęp jest uzyskiwany bezpośrednio z aplikacji, powinny informować MFC, jakich informacji o stanie należy użyć. W zwykłej bibliotece DLL MFC korzystającej z dynamicznych bibliotek MFC, przed wywołaniem dowolnej procedury MFC, na początku eksportowanej funkcji należy umieścić następujący wiersz:

AFX_MANAGE_STATE (AfxGetStaticModuleState ());

Ten operator określa użycie odpowiednich informacji o stanie podczas wykonywania funkcji, która uzyskuje dostęp do tej procedury.

Dynamiczne rozszerzenia MFC

MFC pozwala tworzyć takie biblioteki DLL, które są postrzegane przez aplikacje nie jako zestaw oddzielnych funkcji, ale jako rozszerzenia MFC. Korzystając z tego typu bibliotek DLL, możesz tworzyć nowe klasy pochodzące z klas MFC i używać ich w swoich aplikacjach.

Aby umożliwić swobodną wymianę wskaźników do obiektów MFC między aplikacją a biblioteką DLL, musisz utworzyć dynamiczne rozszerzenie MFC. Ten typ DLL łączy się z bibliotekami dynamicznymi MFC w taki sam sposób, jak każda aplikacja korzystająca z dynamicznego rozszerzenia MFC.

Aby utworzyć nowe dynamiczne rozszerzenie MFC, najprostszym sposobem jest użycie kreatora aplikacji w celu przypisania typu do projektu MFC AppWizard (dll) i w kroku 1 włącz tryb „MFC Extension DLL”. W rezultacie nowy projekt otrzyma wszystkie niezbędne atrybuty dynamicznego rozszerzenia MFC. Ponadto zostanie utworzona funkcja. Dllmain dla biblioteki DLL, która wykonuje szereg określonych operacji w celu zainicjowania rozszerzenia DLL. Należy zauważyć, że biblioteki dynamiczne tego typu nie zawierają i nie powinny zawierać obiektów pochodzących z Cwininpp.

Inicjowanie rozszerzeń dynamicznych

Aby „dopasować” się do struktury MFC, dynamiczne rozszerzenia MFC wymagają dodatkowych początkowe ustawienia. Odpowiednie operacje są wykonywane przez funkcję Dllmain. Rozważ przykład tej funkcji utworzonej przez AppWizard.

Statyczny AFX_EXTENSION_MODULE MyExtDLL \u003d (NULL, NULL); extern "C" int APIENTRY DllMain (instancja HINSTANCE, DWORD dwReason, LPVOID IpReserved) (if (dwReason \u003d\u003d DLL_PROCESS_ATTACH) (ŚLEDZONY ("Inicjalizacja MYEXT.DLL! \\ n"); // Rozszerzenie DLL jednorazowa inicjalizacja AfxInitExtensionMDule , hinstance;

Najważniejszą częścią tej funkcji jest połączenie. AfxInitExtensionModule. Jest to inicjalizacja biblioteki dynamicznej, która pozwala jej działać poprawnie jako część struktury MFC. Argumentami tej funkcji są deskryptor biblioteki DLL przekazywany do DllMain i struktura AFX_EXTENSION_MODULE, która zawiera informacje o bibliotece dynamicznej podłączonej do MFC.

Nie ma potrzeby jawnego inicjowania struktury AFX_EXTENSION_MODULE. Jednak trzeba to ogłosić. Konstruktor będzie zaangażowany w inicjalizację CDynLinkLibrary. W DLL musisz utworzyć klasę CDynLinkLibrary. Jego konstruktor nie tylko zainicjuje strukturę AFX_EXTENSION_MODULE, ale także doda nową bibliotekę do listy bibliotek DLL, z którymi MFC może współpracować.

Pobierz dynamiczne rozszerzenia MFC

Począwszy od wersji 4.0, MFC umożliwia dynamiczne ładowanie i zwalnianie bibliotek DLL, w tym rozszerzeń. Do poprawnego wykonania tych operacji na utworzonej bibliotece DLL w jej funkcji Dllmain w momencie odłączenia się od procesu musisz dodać połączenie AfxTermExtensionModule. Ostatnia użyta powyżej struktura AFX_EXTENSION_MODULE jest przekazywana jako parametr do ostatniej funkcji. Aby to zrobić w tekście Dllmain Należy dodać następujące wiersze.

If (dwReason \u003d\u003d DLL_PROCESS_DETACH) (AfxTermExtensionModule (MyExtDLL);)

Ponadto należy pamiętać, że nowa biblioteka DLL jest rozszerzeniem dynamicznym i należy ją dynamicznie ładować i rozładowywać za pomocą funkcji AfxLoadLibrary i AfxFreeLibrary,ale nie Loadlibrary i Freelibrary.

Eksportowanie funkcji z rozszerzeń dynamicznych

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

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

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

Dzięki niejawnemu łączeniu funkcje załadowanej biblioteki DLL są dodawane do sekcji importu pliku wywołującego. Po uruchomieniu takiego pliku moduł ładujący systemu operacyjnego analizuje sekcję importu i łączy wszystkie określone biblioteki. Ze względu na swoją prostotę ta metoda jest bardzo popularna; ale prostota - prostota i niejawny układ ma pewne wady i ograniczenia:

wszystkie podłączone biblioteki DLL są zawsze ładowane, nawet jeśli podczas całej sesji program nigdy nie uzyskuje dostępu do żadnej z nich;

jeśli brakuje co najmniej jednej wymaganej biblioteki DLL (lub biblioteka DLL nie eksportuje co najmniej jednej wymaganej funkcji) - ładowanie pliku wykonywalnego jest przerywane komunikatem „Nie można znaleźć biblioteki linków dynamicznych” (lub coś w tym rodzaju) - nawet jeśli brak tej biblioteki DLL nie jest krytyczny wykonać program. Na przykład edytor tekstowy może działać całkiem dobrze w minimalnej konfiguracji - bez modułu drukowania, tabel wyjściowych, wykresów, wzorów i innych mniejszych składników, ale jeśli te biblioteki DLL są załadowane z niejawnym układem - jeśli chcesz lub nie, musisz je ciągnąć.

wyszukiwanie DLL odbywa się w następującej kolejności: w katalogu zawierającym plik wywołujący; w bieżącym katalogu procesów; w katalogu systemowym% Windows% System%; w głównym katalogu% Windows%; w katalogach określonych w zmiennej PATH. Nie można ustawić innej ścieżki wyszukiwania (a raczej jest to możliwe, ale w tym celu konieczne będzie wprowadzenie zmian w rejestrze systemu, a zmiany te wpłyną na wszystkie procesy działające w systemie - co nie jest dobre).

Wyraźny układ eliminuje wszystkie te niedociągnięcia - kosztem pewnej złożoności kodu. Sam programista będzie musiał zadbać o załadowanie biblioteki DLL i podłączenie eksportowanych funkcji (nie zapominając o kontroli błędów, w przeciwnym razie pewnego dnia system zakończy się awarią systemu). Jednak wyraźny układ umożliwia ładowanie bibliotek DLL w razie potrzeby i daje programiście możliwość samodzielnej obsługi sytuacji z brakującą biblioteką DLL. Możesz iść dalej - nie ustawiaj jawnie nazwy DLL w programie, ale skanuj taki i 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) dorozumiany;

c) odroczony.

Rozważ niejawne ładowanie biblioteki DLL. Aby zbudować aplikację zaprojektowaną do niejawnego ładowania bibliotek DLL, musisz mieć:

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

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

Kompilacja projektu odbywa się w zwykły sposób. Korzystając z modułów obiektowych i pliku LIB, a także biorąc pod uwagę linki do importowanych identyfikatorów, linker (linker, edytor linków) tworzy ładujący moduł EXE. W tym module linker umieszcza również sekcję importu, która zawiera nazwy wszystkich niezbędnych modułów DLL. Dla każdej biblioteki DLL sekcja importu wskazuje, do których nazw funkcji i zmiennych odwołuje się kod wykonywalnego pliku. Informacje te zostaną wykorzystane przez bootloader systemu operacyjnego.

Co dzieje się w środowisku wykonawczym aplikacji klienckiej? Po uruchomieniu modułu EXE moduł ładujący systemu operacyjnego wykonuje następujące operacje:

1. Tworzy wirtualną przestrzeń adresową dla nowego procesu i rzutuje 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. Może więc mieć własną sekcję importu, dla której konieczne jest powtórzenie tych samych działań. W rezultacie proces inicjalizacji może zająć sporo czasu.

Po zmapowaniu modułu EXE i wszystkich modułów DLL na przestrzeń adresową procesu jego główny strumień jest gotowy do wykonania i aplikacja zaczyna działać.

Wadami niejawnego ładowania są obowiązkowe ładowanie biblioteki, nawet jeśli dostęp do jej funkcji nie nastąpi, i odpowiednio obowiązkowy wymóg obecności biblioteki podczas łączenia.

Jawne ładowanie biblioteki DLL eliminuje powyższe wady wynikające z komplikacji kodu. Programista musi zadbać o załadowanie biblioteki DLL i połączenie eksportowanych funkcji. Ale jawne ładowanie umożliwia ładowanie bibliotek DLL w razie potrzeby i umożliwia programowi obsługę sytuacji wynikających z braku bibliotek DLL.

W przypadku jawnego obciążenia proces pracy z bibliotekami DLL przebiega w trzech etapach:

1. Ładowanie biblioteki DLL za pomocą funkcji Loadlibrary(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 GetProcAddressaby uzyskać wskaźniki do wymaganych funkcji lub innych obiektów. Jako pierwszy parametr funkcja GetProcAddressotrzymuje uchwyt hLib, jako drugi parametr, adres ciągu z nazwą importowanej funkcji. Następnie wynikowy wskaźnik jest używany przez klienta. Na przykład, jeśli jest to wskaźnik do funkcji, wówczas wywoływana jest żądana funkcja.

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

Rozważ odroczenie ładowania biblioteki DLL. Biblioteka DLL ładowania z opóźnieniem to niejawnie połączona biblioteka DLL, która nie ładuje się, dopóki kod nie uzyska dostępu do wyeksportowanego z niej identyfikatora. Takie biblioteki DLL mogą być przydatne w następujących sytuacjach:

Jeśli aplikacja korzysta z kilku bibliotek DLL, zainicjowanie jej może zająć dużo czasu, co wymaga od modułu ładującego zmapowania wszystkich bibliotek DLL na przestrzeń adresową procesu. Opóźnione ładowanie bibliotek DLL może rozwiązać ten problem, dystrybuując ładowanie bibliotek DLL podczas wykonywania aplikacji.

Jeśli aplikacja jest przeznaczona 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 jej nie potrzebuje i może bezpiecznie kontynuować pracę. Odnosząc się do nieistniejącego

funkcje mogą ostrzegać użytkownika.

Aby wdrożyć metodę opóźnionego ładowania, musisz dodać nie tylko niezbędną bibliotekę importu MyLib.lib do listy bibliotek linkera, ale także bibliotekę systemu importu delayimp.lib. Ponadto należy dodać flagę / delayload: MyLib.dll w opcjach linkera.

Wymienione ustawienia zmuszają konsolidatora do wykonania następujących operacji:

Zaimplementuj specjalną funkcję w module EXE
delayLoadHelper;

Usuń MyLib.dll z sekcji importu modułu wykonywalnego, aby moduł ł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 importowanych 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 przez wywołanie delayLoadHelper. Ta funkcja, wykorzystując informacje z sekcji odroczonego importu, najpierw wywołuje LoadLibrary, a następnie GetprocAddress. Po otrzymaniu adresu funkcji DLL funkcja delayLoadHelper sprawia, że \u200b\u200bw przyszłości ta funkcja DLL będzie wywoływana bezpośrednio. Należy pamiętać, że każda funkcja w bibliotece DLL jest konfigurowana indywidualnie przy pierwszym wywołaniu.

Za pomocą Dll (biblioteka linków dynamicznych) jest powszechna w programowaniu Windows. Dll faktycznie część kodu pliku wykonywalnego z rozszerzeniem Dll. Każdy program może wywoływać Dll.

Korzyść Dll następująco:

  • Ponowne użycie kodu.
  • Współdzielenie kodu między aplikacjami.
  • Separacja kodów
  • Poprawa zużycia zasobów w systemie Windows.

Tworzenie DLL

W menu Plik wybierz Nowy -\u003e Inne. W oknie dialogowym karta Nowy Wybierz Kreator DLL. Moduł zostanie utworzony automatycznie - pusty szablon na przyszłość Dll.

Składnia DLL

Budować DllWybierz Projekt -\u003e Kompilacja 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 w bibliotece i żaden program nie może ich wywoływać z zewnątrz.

Wyeksportowano

Wyeksportowane funkcje i procedury mogą być używane na zewnątrz Dll. Inne programy mogą wywoływać takie funkcje i procedury.

Powyższy kod źródłowy używa wyeksportowanej funkcji. Nazwa funkcji następuje po słowie zarezerwowanym. Eksportuje.

Ładowanie DLL

Istnieją dwa rodzaje bootowania w Delphi Dll:

Obciążenie statyczne

Po uruchomieniu aplikacja ładuje się automatycznie. Pozostaje w pamięci przez czas trwania programu. Bardzo łatwy w użyciu. Po prostu dodaj słowo zewnętrzny po zadeklarowaniu funkcji lub procedury.

Funkcja SummaTotal (współczynnik: liczba całkowita): liczba całkowita; zarejestrować; zewnętrzny „Example.dll”;

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

w razie potrzeby ładowane do pamięci. Jego implementacja jest bardziej skomplikowana, ponieważ sam musisz ją załadować i rozładować z pamięci. Pamięć jest wykorzystywana bardziej ekonomicznie, więc aplikacja działa szybciej. Sam programista musi upewnić się, że wszystko działa poprawnie. Aby to zrobić, potrzebujesz:

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

Deklaracja typu funkcji

typ TSumaTotal \u003d funkcja (współczynnik: liczba całkowita): liczba całkowita;

Ładowanie biblioteki

dll_instance: \u003d LoadLibrary („Example_dll.dll”);

Otrzymujemy wskaźnik do funkcji

@SummaTotal: \u003d GetProcAddress (dll_instance, „SummaTotal”);

Wywołanie funkcji

Label1.Caption: \u003d IntToStr (SummaTotal (5));

Zwalnianie 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ć urządzenia spróbuj ... z wyjątkiem i spróbuj ... w końcuaby uniknąć błędów podczas wykonywania programu.

Eksport wierszy

Stworzone przez Dll z używając Delphi, może być stosowany w programach napisanych w innych językach programowania. Z tego powodu nie możemy użyć żadnego typu danych. Typy istniejące w Delphi mogą nie być dostępne w innych językach. Wskazane jest stosowanie rodzimych 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 i tablice dynamiczne w Dllnapisane w Delphi, ale w tym celu musisz podłączyć moduł Sharemem do sekcji wykorzystuje w Dll i program, który go wykorzysta. Ponadto ta lista powinna być pierwsza w sekcji. wykorzystuje każdy plik projektu.

Rodzajów strunowy, jak wiemy, C, C ++ i inne języki nie istnieją, dlatego zaleca się użycie zamiast tego Pchar.

Przykład biblioteki DLL

biblioteka Example_dll; używa SysUtils, klas; var (Deklaruj zmienne) k1: liczba całkowita; k2: liczba całkowita; (Deklaracja funkcji) funkcja SummaTotal (współczynnik: liczba całkowita): liczba całkowita; zarejestrować; współczynnik początkowy: \u003d współczynnik * k1 + współczynnik; współczynnik: \u003d współczynnik * k2 + współczynnik; wynik: \u003d współczynnik; koniec; (Eksportujemy funkcję do dalszego wykorzystania przez program) eksportuje SummaTotal; (Inicjalizacja zmiennych) rozpocząć k1: \u003d 2; k2: \u003d 5; koniec.

Przykład wywołania funkcji z biblioteki DLL

jednostka Unit1; interfejs wykorzystuje Windows, Wiadomości, SysUtils, Warianty, Klasy, Grafikę, Kontrolki, Formularze, Dialogi, StdCtrls; typ TForm1 \u003d klasa (TForm) Button1: TButton; procedura Button1Click (Sender: TObject); prywatne (prywatne deklaracje) publiczne (publiczne deklaracje) koniec; typ TSummaTotal \u003d funkcja (współczynnik: liczba całkowita): liczba całkowita; var Form1: TForm1; procedura implementacji ($ R * .dfm) TForm1.Button1Click (Sender: TObject); var dll_instance: Thandle; SummaTotal: TSummaTotal; początek dll_instance: \u003d LoadLibrary („Example_dll.dll”); @SummaTotal: \u003d GetProcAddress (dll_instance, „SummaTotal”); Label1.Caption: \u003d IntToStr (SummaTotal (5)); FreeLibrary (dll_instance); koniec; koniec.

DZWONEK

Są tacy, którzy czytają te wiadomości przed tobą.
Subskrybuj, aby otrzymywać świeże artykuły.
E-mail
Imię
Nazwisko
Jak chcesz przeczytać Dzwon
Bez spamu