![]() |
Wyjście Spis treści Poprzedni Następny
Autor:
©Iczelion |
©2008 mgr
Jerzy Wałaszek
|
Na tej lekcji poznamy, czym są biblioteki DLL (Dynamic Link Library) oraz jak je się tworzy.
Pobierz plik z przykładem {z tego archiwum}.
Jeśli programujesz wystarczająco długo, to odkryjesz, iż w tworzonych przez ciebie programach występują wspólne procedury. Przepisywanie ich od początku przy każdym nowym programie jest zwykłym marnowaniem czasu. Dawniej, w czasach gdy królował system DOS, programiści umieszczali te często używane procedury w jednej lub kilku bibliotekach. Gdy chcieli użyć funkcji bibliotecznych, to po prostu dołączali odpowiednią bibliotekę do pliku obiektowego, a konsolidator wydobywał pożądane funkcje z biblioteki i dołączał je do końcowego pliku wykonywalnego. Taki proces nosi nazwę statycznej konsolidacji. Dobrym przykładem są biblioteki języka C. Wadą tej metody jest to, iż w każdym programie korzystającym z tych funkcji są one fizycznie obecne. Marnowane jest twoje miejsce na dysku przechowując identyczne kopie funkcji. Dla programów w systemie DOS metoda ta jest jednakże do przyjęcia, gdyż zwykle w pamięci komputera jest uruchomiony tylko jeden program. Zatem nie jest marnowana cenna pamięć operacyjna.
W systemie Windows sytuacja staje się bardziej krytyczna, ponieważ możesz mieć uruchomione kilka różnych programów jednocześnie. Pamięć zostanie szybko pożarta, jeśli twój program jest dosyć duży. System Windows rozwiązuje ten typ problemu wprowadzając dynamicznie dołączane biblioteki - DLL. Dynamicznie dołączana biblioteka jest pewnego rodzaju pulą wspólnych funkcji. System Windows nie załaduje do pamięci kilku kopii biblioteki DLL, zatem nawet gdy uruchomisz kilka egzemplarzy tego samego programu jednocześnie, to w pamięci będzie tylko jedna wykorzystywana przez ten program biblioteka DLL. Powinienem tutaj podać małe wyjaśnienie. W rzeczywistości wszystkie procesy korzystające z tej samej biblioteki DLL będą posiadały swoje własne kopie tej biblioteki. Będzie to wyglądało tak, jakby w pamięci przebywało wiele kopii biblioteki DLL. System Windows dokonuje tej sztuczki za pomocą stronicowania pamięci (podmienianie bloków za pomocą zmiany deskryptora) i wszystkie procesy współdzielą ten sam kod biblioteki DLL. Zatem w fizycznej pamięci komputera istnieje tylko jedna kopia kodu DLL. Jednakże każdy proces będzie posiadał swoją własną, unikalną sekcję danych tej biblioteki DLL.
W przeciwieństwie do starej biblioteki statycznej program łączy się z biblioteką DLL w czasie wykonania. Dlatego nosi ona nazwę biblioteki dołączanej dynamicznie. Również w czasie wykonania programu możesz usunąć z pamięci bibliotekę DLL, gdy już nie będzie potrzebna. Jeśli tylko ten program używa danej biblioteki DLL, to zostanie ona natychmiast usunięta z pamięci. Lecz jeżeli dana biblioteka DLL jest wciąż używana przez jakiś inny program, to pozostanie w pamięci, aż ostatni program korzystający z jej usług zwolni ją.
Jednakże konsolidator ma utrudnione zadanie przy wykonywaniu ustalania adresów dla końcowego pliku wykonywalnego. Skoro nie może on "wydobyć" funkcji i wstawić ich do końcowego pliku wykonywalnego, to w jakiś sposób musi umieścić w tym pliku wystarczającą informację o bibliotece DLL i jej funkcjach, aby program mógł ją załadować i wykorzystać w trakcie działania.
Tutaj przychodzi z pomocą biblioteka importu, która zawiera niezbędne informacje o bibliotece DLL, którą reprezentuje. Konsolidator może wydobyć potrzebne mu informacje z bibliotek importu i umieścić je w pliku wykonywalnym. Gdy program ładujący systemu Windows umieszcza nasz program w pamięci, to zobaczy, iż należy go połączyć z biblioteką DLL, zatem wyszuka ją i odwzoruje w obszar adresowy procesu jak również dokona ustaleń adresów wywołań funkcji zawartych w tej bibliotece DLL.
Możesz zdecydować się na samodzielne załadowanie biblioteki DLL bez polegania na programie ładującym z systemu Windows. Metoda ta ma swoje wady i zalety:
Znając zalety i wady wywołania LoadLibrary, przejdziemy teraz do szczegółów tworzenia biblioteki DLL.
Poniższy kod jest szkieletem biblioteki DLL.
Plik DLLSkeleton.asm
.386
.MODEL FLAT, STDCALL
OPTION CASEMAP:NONE
INCLUDE \masm32\include\windows.inc
.CODE
DllEntry PROC hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
mov eax, TRUE
ret
DllEntry ENDP
;------------------------------------------------------------------------- ; To jest pusta funkcja. Nic nie robi. Wstawiłem ją tutaj, aby ci pokazać,
; gdzie należy umieszczać własne funkcje w bibliotece DLL ;------------------------------------------------------------------------- TestFunction PROC
ret
TestFunction ENDP
END DllEntry
Plik DLLSskeleton.def
LIBRARY DLLSkeleton EXPORTS TestFunction
Powyższy program jest szkieletem biblioteki DLL. Każda biblioteka DLL musi posiadać funkcję wejściową (entrypoint function). System Windows wywołuje tę funkcję zawsze, gdy:
DllEntry PROC hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
mov eax, TRUE
ret
DllEntry ENDP
Funkcję wejścia możesz nazwać dowolnie, pod warunkiem, iż tę samą nazwę umieścisz w końcowej dyrektywie END <nazwa funkcji wejścia>. Funkcja ta przyjmuje trzy parametry, z których tylko dwa pierwsze są istotne.
Jeśli chcesz, aby biblioteka DLL była wciąż aktywna, to zwracasz w rejestrze eax wartość TRUE. Jeśli zwrócisz FALSE, to biblioteka nie zostanie załadowana. Na przykład jeśli twój kod inicjalizacji musi zarezerwować nieco pamięci, a nie może tego dokonać z powodzeniem, to funkcja wejścia powinna zwrócić wartość FALSE, aby poinformować system, iż biblioteka DLL nie może pracować. Swoje funkcje możesz umieszczać zarówno przed jak i za funkcją wejścia. Lecz jeśli chcesz, aby możne je było wywoływać z innych programów, musisz wstawić ich nazwy na listę eksportu w pliku definicji modułu o rozszerzeniu DEF.
Biblioteka DLL wymaga pliku definicji modułu w fazie projektowania. Przyjrzyjmy się mu teraz.
LIBRARY DLLSkeleton EXPORTS TestFunction
Zwykle musisz umieścić tutaj ten pierwszy wiersz. Polecenie LIBRARY definiuje wewnętrzną nazwę modułu biblioteki DLL. Nazwa ta powinna być taka sama jak nazwa pliku biblioteki DLL.
Polecenie EXPORTS informuje konsolidator, które funkcje z biblioteki DLL są eksportowane, tj. wywoływalne z innych programów. W tym przykładzie chcemy, aby inne moduły były w stanie wywołać funkcję TestFunction, zatem umieszczamy jej nazwę za poleceniem EXPORTS.
Następna zmiana występuje w przełącznikach konsolidatora. Musisz tam umieścić przełączniki /DLL oraz /DEF<nazwa pliku def> w sposób następujący:
link /DLL /SUBSYSTEM:WINDOWS /DEF:DLLSkeleton.def /LIBPATH:c:\masm32\lib DLLSkeleton.obj
Przełączniki asemblera pozostają niezmienione, tj./c /coff /Cp. Zatem po konsolidacji pliku obiektowego otrzymasz bibliotekę DLL oraz bibliotekę eksportu, którą możesz wykorzystać połączenia z programami wykorzystującymi funkcje w bibliotece DLL.
Poniżej przedstawiam schematyczny program, który korzysta z wygenerowanej wcześniej biblioteki DLL. W programie umieszczamy bibliotekę eksportu, zatem odpowiednia biblioteka DLL zostanie automatycznie połączona z naszym programem w momencie jego ładowania do pamięci komputera przez system Windows. Bibliotekę DLL należy umieścić w tym samym katalogu, co program - w przeciwnym wypadku system Windows wyświetli komunikat o braku odpowiedniej biblioteki DLL i program nie zostanie uruchomiony.
Plik USEDLL1.ASM
.386
.MODEL FLAT, STDCALL
OPTION CASEMAP:NONE
INCLUDE \masm32\include\windows.inc
INCLUDE \masm32\include\kernel32.inc
INCLUDELIB \masm32\lib\kernel32.lib
INCLUDE \masm32\include\user32.inc
INCLUDELIB \masm32\lib\user32.lib
INCLUDELIB DLLSkeleton.lib
TestFunction PROTO
.DATA
MsgBoxCaption DB "Test biblioteki DLL", 0
MsgBoxText DB "Funkcja testowa została wywołana!", 0
.CODE
start:
INVOKE TestFunction
INVOKE MessageBox, NULL, ADDR MsgBoxText, ADDR MsgBoxCaption, MB_OK
INVOKE ExitProcess, NULL
END start
Na potrzeby tego przykładu opracowałem prostą bibliotekę DLL, której kod przedstawiam poniżej:
Plik DLLSkeleton.asm
.386
.MODEL FLAT, STDCALL
OPTION CASEMAP:NONE
INCLUDE \masm32\include\windows.inc
INCLUDE \masm32\include\user32.inc
INCLUDE \masm32\include\kernel32.inc
INCLUDELIB \masm32\lib\user32.lib
INCLUDELIB \masm32\lib\kernel32.lib
.DATA
AppName DB "Szkielet biblioteki DLL", 0
HelloMsg DB "Cześć, wywołujesz funkcję z tej biblioteki DLL", 0
LoadMsg DB "Biblioteka DLL jest ładowana", 0
UnloadMsg DB "Biblioteka DLL jest usuwana", 0
ThreadCreated DB "W procesie powstał nowy wątek", 0
ThreadDestroyed DB "Z procesu usunięto wątek", 0
.CODE
DllEntry PROC hInstance:HINSTANCE, reason:DWORD, reserved1:DWORD
.IF reason==DLL_PROCESS_ATTACH
INVOKE MessageBox, NULL, ADDR LoadMsg, ADDR AppName, MB_OK
.ELSEIF reason==DLL_PROCESS_DETACH
INVOKE MessageBox, NULL, ADDR UnloadMsg, ADDR AppName, MB_OK
.ELSEIF reason==DLL_THREAD_ATTACH
INVOKE MessageBox, NULL, ADDR ThreadCreated, ADDR AppName, MB_OK
.ELSE ; DLL_THREAD_DETACH
INVOKE MessageBox, NULL, ADDR ThreadDestroyed, ADDR AppName, MB_OK
.ENDIF
mov eax, TRUE
ret
DllEntry ENDP
TestHello PROC
INVOKE MessageBox, NULL, ADDR HelloMsg, ADDR AppName, MB_OK
ret
TestHello ENDP
END DllEntry
Plik DLLSkeleton.def
LIBRARY DLLSkeleton EXPORTS TestHello
Biblioteka DLL wyświetla różne okienka informacyjne przy ładowaniu do pamięci, usuwaniu z pamięci oraz tworzeniu i usuwaniu wątku w procesie, który z niej korzysta. W bibliotece zawarto tylko jedną funkcję TestHello wyświetlającą okienko informacyjne z napisem powitalnym.
A oto przykładowa aplikacja, która samodzielnie ładuje tę bibliotekę i wywołuje zawartą w niej funkcję TestHello.
Plik USEDLL.ASM
.386
.MODEL FLAT, STDCALL
OPTION CASEMAP:NONE
INCLUDE \masm32\include\windows.inc
INCLUDE \masm32\include\user32.inc
INCLUDE \masm32\include\kernel32.inc
INCLUDELIB \masm32\lib\kernel32.lib
INCLUDELIB \masm32\lib\user32.lib
.DATA
LibName DB "DLLSkeleton.dll", 0
FunctionName DB "TestHello", 0
DllNotFound DB "Nie można załadować biblioteki DLL", 0
AppName DB "Ładowanie biblioteki DLL", 0
FunctionNotFound DB "Funkcja TestHello nie odnaleziona", 0
.DATA?
hLib DD ?
TestHelloAddr DD ?
.CODE
start:
INVOKE LoadLibrary, ADDR LibName
; Wywołaj LoadLibrary z nazwą żądanej biblioteki DLL.
; Jeśli wywołanie to się powiedzie, to zwróci uchwyt
; do biblioteki DLL. Jeśli nie, to zwróci NULL.
; Uchwyt biblioteki DLL możesz przekazać do GetProcAddress
; lub dowolnej innej funkcji, która wymaga jako parametr
; uchwytu do biblioteki DLL.
.IF eax==NULL
INVOKE MessageBox, NULL,ADDR DllNotFound,ADDR AppName, MB_OK
.ELSE
mov hLib, eax
INVOKE GetProcAddress, hLib, ADDR FunctionName
; Gdy otrzymasz uchwyt do biblioteki DLL, przekazujesz go
; do funkcji GetProcAddress z adresem nazwy funkcji w tej
; bibliotece, którą chcesz wywołać. Jeśli wywołanie się
; powiedzie to jako wartość zwrotną otrzymasz adres funkcji.
; W przeciwnym razie otrzymasz wartość NULL. Adres funkcji
; nie ulega zmianom, chyba że usuniesz i ponownie załadujesz
; tę bibliotekę DLL. Zatem możesz zachować go w zmiennej
; globalnej do późniejszego wykorzystania.
.IF eax==NULL
INVOKE MessageBox, NULL,ADDR FunctionNotFound,ADDR AppName, MB_OK
.ELSE
mov TestHelloAddr, eax
; Następnie możesz wywołać daną funkcję z biblioteki DLL za pomocą
; prostego wywołania z adresem zmiennej jako argument zawierającej
; adres funkcji bibliotecznej
call [TestHelloAddr]
.ENDIF
INVOKE FreeLibrary, hLib
; Gdy już nie potrzebujesz danej biblioteki DLL, usuń ją
; za pomocą funkcji FreeLibrary.
.ENDIF
INVOKE ExitProcess, NULL
END start
Jak widać użycie funkcji LoadLibrary wymaga nieco więcej pracy, jednakże jest bardziej wszechstronne.
|
Autorem
kursu jest
Iczelion. Kurs
programowania Windows znalazł się na serwerze I LO w Tarnowie za pisemną
zgodą autora. |
![]() | I Liceum Ogólnokształcące |
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe