Rozdział XVII - Biblioteki Dołączane Dynamicznie - DLL


Na tej lekcji poznamy, czym są biblioteki DLL (Dynamic Link Library) oraz jak je się tworzy.

Pobierz plik z przykładem {z tego archiwum}.

 

Teoria

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.

 

Przykład

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.

 

Automatyczne dołączanie biblioteki 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

Ręczne ładowanie biblioteki DLL

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.
Tłumaczenie z języka angielskiego, opracowanie HTML i konwersję przykładów programów wykonał mgr Jerzy Wałaszek.



List do administratora Serwisu Edukacyjnego Nauczycieli I LO

Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz  ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

 

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień szeroko opisywanych w podręcznikach.



   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2017 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.