r06 04 qla3mfniflkmarunvvl5sj4owxh65d7c7ekfkoy QLA3MFNIFLKMARUNVVL5SJ4OWXH65D7C7EKFKOY


Rozdział 6.
Biblioteki DLL
Niniejszy rozdział poświęcony będzie bibliotekom DLL, które stanowią podstawowy element konstrukcyjny
aplikacji dla Windows i w ogóle całego systemu Windows. Zaprezentujemy tworzenie bibliotek DLL w Delphi
oraz ich integrację z aplikacjami wywołującymi. Pokażemy również, jak w środowisku Win32 wykorzystać
bibliotekę DLL w roli obszaru komunikacyjnego pomiędzy procesami; praktyka taka była dosyć powszechna w
16-bitowych wersjach Windows. Straciła rację bytu w Win32 ze względu na skrajnie odmienny sposób obsługi
bibliotek DLL, można ją jednak zasymulować za pomocą innych środków, co z pewnością ułatwi zadanie
programistom przenoszącym do Delphi 6 aplikacje 16-bitowe.
Czym w istocie jest biblioteka DLL
Biblioteka DLL (Dynamic Link Library) jest modułem wykonywalnym, zawierającym kod, dane, a także zasoby,
które mogą być udostępniane aplikacjom. Charakterystyczny jest sposób łączenia bibliotek DLL z aplikacjami.
Aączność aplikacji z wykorzystywaną przez nią biblioteką DLL nawiązywana jest dopiero w czasie jej
wykonywania, co nazywane bywa popularnie  póznym wiązaniem (late binding)  w przeciwieństwie do
 wczesnego wiązania (early binding), wykonywanego przez konsolidator jeszcze na etapie tworzenia modułu
wykonywalnego. Oprócz niewątpliwej elastyczności  w zakresie kompletowania kodu, stosownie do potrzeb
wynikających z bieżącego stanu wykonywanej aplikacji  podejście takie posiada jeszcze jedną ważna zaletę:
umożliwia współdzielenie tego samego egzemplarza biblioteki DLL przez kilka (lub wszystkie!)
wykonywalnych aplikacji, co pozwala uniknąć kosztownego nieraz dublowania obszernych fragmentów kodu w
poszczególnych modułach wykonywalnych .EXE. W ten właśnie sposób wykorzystywane są podstawowe
biblioteki DLL systemu Win32  Kernel32.dll, User32.dll i GDI32.dll. Biblioteka Kernel32 odpo-
wiedzialna jest za zarządzanie pamięcią, procesami i wątkami; biblioteka User32 obsługuje interfejs
użytkownika i zarządza tworzeniem okien oraz obsługą komunikatów, natomiast w bibliotece GDI32 znajdują
się procedury i funkcje podsystemu graficznego (GDI). Ponadto standardowe kontrolki Windows
zaimplementowane są w bibliotece ComDlg32.dll, zaś nadzór nad rejestrem i bezpieczeństwem systemu
sprawuje biblioteka AdvAPI32.dll.
Niezależność bibliotek DLL od modułów wynikowych aplikacji wywołujących rodzi jeszcze jedną niezmiernie
istotną konsekwencję  jest nią modularność tworzonych aplikacji (lub kompletnych systemów); w prawidłowo
zaprojektowanym systemie poszczególne moduły (którymi są właśnie biblioteki DLL) odpowiedzialne są za
realizację jego poszczególnych elementów funkcjonalnych, między innymi  obsługę poszczególnych
urządzeń, wykorzystywaną czcionkę, kształt okien dialogowych itp. Unowocześnienie takiego systemu
sprowadza się zazwyczaj do wymiany lub dołączenia nowej biblioteki DLL.
Format wewnętrzny biblioteki DLL jest niemalże identyczny z formatem modułu wykonywalnego .EXE; jednak
w przeciwieństwie do niego, biblioteka DLL nie pełni nigdy roli samodzielnej, a jedynie   służebną w
stosunku do aplikacji nadrzędnych. Większość plików będących bibliotekami DLL posiada (oczywiste)
rozszerzenie .DLL, jednak pod względem fizycznym bibliotekami DLL są także sterowniki urządzeń *.drv,
pliki *.ocx implementujące kontrolki ActiveX, pliki czcionek *.fon (te ostatnie nie zawierają w ogóle kodu
wykonywalnego) i niektóre pliki systemowe *.sys.
Notatka
Bibliotekami DLL są także pakiety (packages) Delphi i C++Buildera  zajmiemy się nimi szczegółowo w
drugim tomie niniejszej książki.
Połączenie biblioteki DLL z korzystającą z niej aplikacją następuje w procesie tzw. łączenia dynamicznego
(dynamic linking), którym zajmiemy się dokładniej w dalszej części rozdziału. Mówiąc ogólnie, w momencie,
gdy aplikacja wywołująca odwołuje się do danej biblioteki DLL (nieobecnej jeszcze w pamięci), system ładuje
bibliotekę na swą globalną stertę, posługując się mechanizmem plików odwzorowanych (memory-mapped files).
Biblioteka ta jest następnie  mapowana w przestrzeń adresową aplikacji wywołującej. W ten sposób każda z
aplikacji, korzystając ze wspólnego egzemplarza biblioteki, zachowuje się tak, jak gdyby posługiwała się
oddzielną kopią jej kodu, danych i zasobów. Jest to sytuacja odmienna w stosunku do Win16, gdzie wszystkie
aplikacje, działając we wspólnej przestrzeni adresowej, mogły komunikować się poprzez pojedynczą bibliotekę
DLL.
Opisany powyżej scenariusz jest jednak tylko wyidealizowaną wersją rzeczywistości  wyidealizowaną w tym
sensie, iż współdzielenie pojedynczej kopii biblioteki DLL przez wiele procesów nie zawsze jest możliwe. Jego
wykonalność zależna jest od jednego z parametrów biblioteki, mianowicie jej bazowego adresu ładowania
(preferred base address).
Bazowy adres ładowania modułu
Jednym z elementów każdego modułu wykonywalnego są tzw. elementy relokowalne (relocatable items), to
znaczy takie, których wartość zależy od położenia modułu w pamięci operacyjnej. Konsolidator, tworząc
ostateczną (zapisywaną na dysku) postać biblioteki DLL nadaje jej relokowalnym elementom taką wartość, jak
gdyby biblioteka ta ulokowana była w obszarze rozpoczynającym się od adresu określonego w parametrze
Image base na karcie Linker opcji projektu (tworzącego tę bibliotekę). Jeżeli w przestrzeni adresowej
aplikacji istnieje wolny obszar rozpoczynający się od tegoż adresu i wystarczająco duży, by pomieścić
bibliotekę, cały proces połączenia jej z aplikacją sprowadza się do pamięciowego odwzorowania jej pliku
(mechanizm plików odwzorowanych opisany jest szczegółowo na stronach 580  598 książki  Delphi 4.
Vademecum profesjonalisty ). Jeżeli obszar taki nie istnieje, biblioteka ulokowana zostaje w innym miejscu
przestrzeni adresowej aplikacji, wymaga to jednak utworzenia w pamięci jej kopii i  przeliczenia (fixup) jej
elementów relokowalnych.
Trafny dobór bazowego adresu ładowania przyczynia się więc zarówno do oszczędności pamięci (nie jest
tworzona kopia pamięciowa biblioteki), jak i czasu (elementy relokowalne mają już żądaną wartość). Domyślną
wartością bazowego adresu ładowania w projekcie nowo tworzonej aplikacji, .EXE, jak również biblioteki DLL
jest $400000; jeżeli nie zmienimy któregoś z tych ustawień, bazowy adres ładowania biblioteki wypadnie w
obszarze zajętym przez aplikację i konieczne będzie tworzenie kopii biblioteki. Zaleca się zmianę bazowego
adresu ładowania biblioteki na wartość z zakresu od $40000000 do $7FFFFFF0  w Windows 95/98/NT/2000
obszar ten nie jest nigdy wykorzystywany przez samą aplikację.
Nieco terminologii&
W dalszej części rozdziału  i w rozdziałach następnych  wielokrotnie używać będziemy kilku pojęć,
związanych z procesami i wykorzystywanymi przez nie modułami, głównie bibliotekami DLL. Jako że ich
potoczne znaczenie nie jest z natury rzeczy tak precyzyjne, jak w ścisłej terminologii Win32, przedstawimy teraz
tę ostatnią kategorię znaczeniową. A więc:
" Aplikacja to program znajdujący się w pliku z rozszerzeniem .EXE, nadający się do uruchomienia w
systemie Windows.
" Plik wykonywalny to plik zawierający kod wykonywalny: do plików wykonywalnych zaliczają się pliki
*.EXE i biblioteki dynamiczne.
" Instancja biblioteki to po prostu fakt jej obecności w ramach danego procesu, reprezentowany przez
uchwyt (handle). Pojęcie instancji odnosi się również do uruchomionej aplikacji  jeśli uruchomimy ją
w kilku  egzemplarzach , każdy z nich jest osobną instancją.
" Moduł  wraz ze zmianą sposobu korzystania z modułów w Win32 (w stosunku do 16-bitowych wersji
Windows) uległa zatarciu różnica pomiędzy modułem i jego instancją  każde odwołanie się aplikacji
do modułu wymaga utworzenia jego instancji (w pamięci wirtualnej procesu), fizycznie reprezentowanej
przez unikatowy uchwyt. Dla przypomnienia  w środowisku 16-bitowym każdy moduł załadowany do
pamięci mógł być rozpatrywany w oderwaniu od wykorzystujących go procesów (a więc  w
oderwaniu od swych instancji), gdyż posiadał własny adres w pamięci wspólnej dla wszystkich proce-
sów; w Win32 każdy moduł istnieje jedynie w kontekście przestrzeni adresowej wykorzystującego go
procesu. Pomimo to Microsoft w dalszym ciągu wykorzystuje pojęcie modułu w swej dokumentacji,
przy czytaniu której należy być świadomym tego, co napisano powyżej1.
" Zadanie (task)  Windows jest systemem wielozadaniowym z wywłaszczaniem (preemptive
multitasking), zatem każde zadanie działa niezależnie od pozostałych, także niezależnie ubiegając się o
zasoby systemowe. Co prawda obiektami środowiska Windows 95/NT ubiegającymi się o czas procesora
są nie zadania, lecz ich wątki, jednak priorytet wątku zależy przede wszystkim od klasy priorytetowej
zadania, do którego ów wątek należy (pisaliśmy o tym w rozdziale 5.). Każde zadanie w Windows
reprezentowane jest przez oddzielny uchwyt.
Aączenie statyczne kontra łączenie dynamiczne
Podczas kompletowania przez konsolidator (linker) modułu wynikowego .EXE, wszystkie niezbędne procedury i
funkcje znajdujące się w modułach (i ewentualnie w pliku *.DPR) zostają włączone do jego kodu. Po
załadowaniu go do pamięci, każda z tych procedur i funkcji posiada ściśle określone położenie (w przestrzeni
adresowej aplikacji), a ich wywołania odbywają się bez ingerencji systemu. Ten rodzaj konsolidacji nosi nazwę
łączenia statycznego (static linking), bo odbywa się ona bez jakiegokolwiek związku z faktycznym przebiegiem
przyszłego wykonania programu, którego przecież nie sposób (na ogół) przewidzieć.
Wskazówka
Proces łączenia modułów w aplikację w Delphi obejmuje co prawda pewne czynności optymalizacyjne (tzw.
smart linking) polegające na niewłączaniu do pliku wykonywalnego ewidentnie nieużywanych fragmentów
kodu  w tym procedur i funkcji, do których nie istnieją odwołania; nie zmienia to jednak w niczym opisanej
idei łączenia statycznego.
Załóżmy, że dwie aplikacje wykorzystują jakiś uniwersalny moduł zródłowy; po ich skompilowaniu i
skonsolidowaniu wszystkie wykorzystywane procedury (funkcje) tego modułu zostaną oczywiście włączone do
obydwu plików wykonywalnych; przy równoczesnym uruchomieniu obydwu aplikacji wiele funkcji i procedur
będzie obecnych w pamięci w dwóch egzemplarzach; efekt ten spotęguje się przy uruchomieniu następnych
aplikacji korzystających z tego modułu.
Oprócz opisanego efektu powielenia procedur i funkcji należy być świadomym tego, iż niektóre elementy
aplikacji tworzone są niejako  na zapas i istnieje mała szansa, iż elementy te w ogóle zostaną wykorzystane;
jako przykład mogą tu posłużyć różnorodne procedury obsługi sytuacji wyjątkowych.
1
W języku polskim sprawa jest nieco bardziej skomplikowana, gdyż termin  moduł jest ogólnie przyjętym określeniem
modułu w sensie tu opisanym (module), jak również np. modułu zródłowego Delphi (unit); w tym rozdziale termin
 moduł używany więc będzie tylko w tym ostatnim znaczeniu (przyp. tłum.).
Przy łączeniu dynamicznym (dynamic linking) każda z funkcji i procedur zawartych w bibliotece istnieje tylko w
jednym egzemplarzu (przynajmniej teoretycznie  patrz opis bazowego adresu ładowania), zaś ładowanie samej
biblioteki następuje w momencie bądz ładowania do pamięci samej aplikacji, bądz dopiero na wyraznie żądanie
tej ostatniej.
W pierwszym przypadku mamy do czynienia z tzw. ładowaniem automatycznym lub niejawnym (implicit
loading). Załóżmy, iż biblioteka o nazwie MaxLib.dll zawiera funkcję zadeklarowaną następująco:
function Max(i1, i2: integer): integer;
Wynikiem tej funkcji jest wartość większej z dwóch liczb podanych jako parametry wywołania. Najprostszym
sposobem udostępnienia tej funkcji aplikacjom jest stworzenie modułu importującego ją z biblioteki, zwanego z
tej racji modułem importowym. Oto przykład treści modułu importującego funkcję Max:
unit MaxUnit;
interface
function Max(i1, i2: integer): integer;
implementation
function Max; external 'MAXLIB';
end.
Od zwykłego modułu różni się on tym, iż nie ma w nim implementacji funkcji Max(); funkcja ta jest
zaimplementowana w bibliotece DLL, do której odsyła dyrektywa external. Wykorzystanie modułu jest
natomiast jak najbardziej typowe  wystarczy umieścić jego nazwę w dyrektywie uses. W momencie
ładowania aplikacji do pamięci zostanie załadowana także biblioteka MaxLib.dll, a przekazanie sterowania do
funkcji Max() odbywać się będzie automatycznie przy każdym jej wywołaniu.
Drugim z omawianych wariantów łączenia dynamicznego  ładowaniem jawnym (explicit loading) biblioteki
na wyrazne żądanie aplikacji  zajmiemy się nieco pózniej.
Korzyści płynące z używania DLL
Rozpatrując wykorzystanie bibliotek DLL w kategoriach technologicznych, natychmiast dostrzeżesz wielorakie
korzyści wynikające z różnorodnych aspektów ich implementacji. W tym miejscu zajmiemy się dwoma
najważniejszymi  współdzieleniem zasobów przez aplikacje oraz ukryciem szczegółów implementacyjnych.
Współdzielenie kodu, zasobów i danych przez wiele
aplikacji
Jak wcześniej wspominaliśmy, zastosowanie bibliotek DLL umożliwia na ogół zredukowanie
zapotrzebowania na pamięć, gdyż jeden egzemplarz kodu może być jednocześnie wykorzystywany przez wiele
aplikacji; bezdyskusyjna jest także korzyść wynikająca z mniejszych rozmiarów modułów wykonywalnych.
Jednak oprócz współdzielenia kodu, możliwe jest również współdzielenie zawartych w DLL zasobów  bitmap,
czcionek, ikon itp.
Problem współdzielenia danych biblioteki DLL wymaga odrębnego komentarza. W środowisku 16-bitowym
każda biblioteka posiadała swój własny segment danych globalnych i zmiennych statycznych, toteż jej dane
mogły stanowić  niekiedy nawet nieoczekiwanie dla projektanta i użytkownika  obszar interferencji kilku
aplikacji, swego rodzaju  skrzynkę kontaktową . Było to konsekwencją faktu, iż wszystkie aplikacje
funkcjonowały we wspólnej przestrzeni adresowej. W środowisku Win32 sprawa uległa radykalnej zmianie:
każdy proces działa we własnej przestrzeni adresowej, w którą odwzorowywany jest również obszar danych
wykorzystywanej biblioteki DLL; ponieważ przestrzenie adresowe poszczególnych procesów są z założenia
rozłączne, więc nie jest możliwa wymiana danych przez jej obszar danych. Ponadto dwa różne procesy mogą
(chociaż nie muszą) posługiwać się dwiema odrębnymi kopiami biblioteki (w pamięci wirtualnej) co
wyjaśniliśmy już nieco wcześniej.
Skoro jednak wszystkie wątki danego procesu działają w tej samej przestrzeni adresowej, jest możliwa wymiana
danych przez obszar stanowiący (uwaga) odwzorowanie globalnego segmentu danych biblioteki DLL w
przestrzeń adresową procesu. Należy jednak pamiętać o tym, iż niekontrolowany dostęp do zmiennych
globalnych może doprowadzić do ich dezorganizacji, trzeba więc zastosować w takiej sytuacji mechanizmy
synchronizacyjne, które omówiliśmy ze szczegółami w rozdziale 5.
Dwie aplikacje (lub większa ich liczba) mogą się jednak komunikować ze sobą poprzez wspólny obszar pamięci
(shared memory area), stanowiący odwzorowanie tego samego pliku dyskowego (w przestrzeniach adresowych
poszczególnych aplikacji); funkcje implementujące taki obszar wymiany mogą znajdować się właśnie w
bibliotece DLL. Zajmiemy się tym zagadnieniem w dalszej części rozdziału.
Ukrycie szczegółów implementacyjnych
Zgodnie z ukształtowanym przez dziesięciolecia standardem programowania, każda utworzona aplikacja posiada
dwa oblicza: użytkowe, wynikające po prostu z wykonywanych przez nią czynności oraz projektowe,
przejawiające się w jej kodzie zródłowym. Dla końcowego użytkownika aplikacji zwykle dostępny jest jedynie
aspekt użytkowy  aplikacja sprzedawana jest w postaci pliku wykonywalnego .EXE, projektant zaś zatrzymuje
dla siebie kod zródłowy, będący często wynikiem olbrzymiej pracy i wysiłku intelektualnego (i tym samym
posiadający nieporównanie większą wartość niż końcowy moduł wykonywalny).
Motywy ukrycia szczegółów implementacyjnych aplikacji są więc, jak widać, racjonalne, jednak jest to możliwe
do zrealizowania zasadniczo tylko w przypadku udostępniania kompletnej aplikacji. Rozpowszechnianie
binarnych fragmentów aplikacji nie zawsze było możliwe bez wykorzystania łączenia dynamicznego; najlepiej
wiedzą o tym programiści rozpowszechniający np. moduły nowych komponentów jedynie w postaci plików
*.dcu  nawet drobna zmiana w części publicznej któregoś z modułów wymaganych przez odnośny moduł
komponentu skutkuje wówczas protestami kompilatora z powodu niezgodności wersji modułów. Opisany
problem nie występuje w przypadku łączenia dynamicznego. Biblioteka DLL jest zamkniętą całością, nie
podlegającą już kompilacji. Co więcej, biblioteki DLL mają uniwersalną postać, niezależną od języka, w którym
zostały zaprogramowane, możliwe jest więc wykorzystanie w Delphi bibliotek DLL stworzonych np. w Turbo
Asemblerze, C++, czy innym języku zdolnym generować kod 32-bitowy  i vice versa: biblioteki tworzone w
Delphi mogą być wykorzystywane w aplikacjach stworzonych w wymienionych językach. Dla użytkownika
biblioteki DLL konieczna jest jedynie znajomość jej interfejsu, czyli uzewnętrznionych procedur i funkcji. Jako
przykład można podać moduł windows.pas, zawierający deklarację funkcji ClientToScreen(): część
publiczna modułu zawiera deklarację jej nagłówka :
function ClientToScreen(hWnd: HWND; var lpPoint: TPoint):BOOL; stdcall;
zaś cała  implementacja funkcji wygląda w sposób następujący:
function ClientToScreen; external user32 name 'ClientToScreen';
a jej szczegóły ukryte są, jak widać, w bibliotece User32.DLL.
Tworzenie i wykorzystywanie bibliotek DLL
Po opisaniu roli, jaką spełniają biblioteki DLL w aplikacjach Windows, nadszedł czas na szczegóły związane z
ich tworzeniem i wykorzystaniem przez Delphi i inne środowiska  zilustrujemy je konkretnymi przykładami.
Prosty przykład  poznaj siłę swych pieniędzy
Niniejszy przykład opiera się na sztandarowym pomyśle dydaktyki wykorzystania DLL, ilustrującym
rozmienianie pewnej kwoty pieniędzy na cztery rodzaje monet amerykańskich: 25-centowej (Quarter), 10-
centowej (Dime), 5-centowej (Nickel) i 1-centowej (Penny)2.
Biblioteka DLL
Funkcja realizująca rozmienianie pieniędzy nosi nazwę PenniesToCoins i znajduje się w bibliotece
PenniesLib.dll. Kod zródłowy głównego pliku jej projektu PenniesLib.dpr prezentujemy na wydruku
6.1.
Wydruk 6.1. Plik główny projektu biblioteki PenniesLib
library PenniesLib;
{$DEFINE PENNIESLIB}
uses
SysUtils,
Classes,
PenniesInt;
function PenniesToCoins(TotPennies: word; CoinsRec: PCoinsRec): word; StdCall;
begin
Result := TotPennies;
{ oblicz liczbę monet poszczególnych rodzajów }
with CoinsRec^ do
begin
Quarters := TotPennies div 25;
TotPennies := TotPennies - Quarters * 25;
Dimes := TotPennies div 10;
TotPennies := TotPennies - Dimes * 10;
Nickels := TotPennies div 5;
TotPennies := TotPennies - Nickels * 5;
Pennies := TotPennies;
end;
end;
{ eksportuj funkcję przez nazwę }
exports
PenniesToCoins;
end.
Biblioteka PenniesLib wykorzystuje moduł PenniesInt, który jednocześnie jest jej modułem importowym;
w module tym znajduje się jednak definicja typu PCoinsRec, wykorzystywanego przez funkcję
PenniesToCoins(). Dyrektywa exports służy do wskazania funkcji, które mają być dostępne na zewnątrz
biblioteki (czyli z niej  wyeksportowane ).
2
Zostały zachowane oryginalne nazwy monet (przyp. tłum.).
Moduł importowy biblioteki
Jak już wspomnieliśmy, pomostem pomiędzy biblioteką DLL a wykorzystującą ją aplikacją są przy łączeniu
domyślnym moduły importowe. Poza tym, iż definiują interfejs dla funkcji eksportowanych3 przez bibliotekę
DLL, zawierają zazwyczaj definicje struktur danych wykorzystywanych zarówno przez bibliotekę, jak i
aplikacje wywołujące; w prezentowanym przykładzie strukturą taką jest rekord TCoinsRec. Kod zródłowy
modułu importowego biblioteki PenniesLib przedstawiamy na wydruku 6.2.
Wydruk 6.2. PenniesInt.pas  moduł importowy biblioteki PenniesLib
unit PenniesInt;
{ Moduł importowy biblioteki PENNIES.DLL }
interface
type
{ poniższy rekord przechowuje liczbę monet każdego rodzaju }
PCoinsRec = ^TCoinsRec;
TCoinsRec = record
Quarters,
Dimes,
Nickels,
Pennies: word;
end;
{$IFNDEF PENNIESLIB}
function PenniesToCoins(TotPennies: word; CoinsRec: PCoinsRec): word; StdCall;
{$ENDIF}
implementation
{$IFNDEF PENNIESLIB}
{ definicja importowanej funkcji }
function PenniesToCoins; external 'PENNIESLIB.DLL' name 'PenniesToCoins';
{$ENDIF}
end.
Funkcja PenniesToCoins() posiada dwa parametry wywołania: rozmienianą kwotę pieniędzy oraz wskaznik
do rekordu TCoinsRec reprezentującego stan monet po rozmienieniu kwoty. Wynikowa liczba monet stanowi
wynik funkcji.
Na uwagę zasługuje też symbol kompilacji warunkowej PENNIESLIB. Jak wyjaśniliśmy wcześniej, moduł
PenniesInt spełnia dwojakiego rodzaju rolę: definiuje typ TCoinsRec oraz importuje z biblioteki funkcję
PenniesToCoins(). W pierwszym przypadku jest on częścią projektu tworzącego bibliotekę i deklaracja oraz
3
Funkcje te są eksportowane przez bibliotekę, równie dobrze mogą być jednak uważane jako importowane przez moduł
importowy (przyp. tłum.).
definicja funkcji PenniesToCoins() są zupełnie niepotrzebne. Elementy te nie są widoczne dla kompilatora,
ponieważ wspomniany symbol PENNIESLIB jest w tym przypadku zdefiniowany. Natomiast w sytuacji, gdy
moduł PenniesInt pełni rolę modułu importowego, jest on częścią projektu aplikacji i istotna jest cała jego
treść.
Zwróć także uwagę na postać dyrektywy external w definicji funkcji PenniesToCoins(). Specyfikuje ona
importowanie przez nazwę  w bibliotece o nazwie PENNIES.LIB poszukiwana jest funkcja o nazwie
PenniesToCoins.
Wskazówka
Definiowanie symboli warunkowych obowiązujących w całym projekcie może odbywać się na dwa sposoby:
za pomocą dyrektywy {$DEFINE lub za pomocą opcji projektu, na karcie Directories/Conditionals.
Pamiętaj, iż po zmianie opcji projektu należy skompilować ów projekt w trybie Build  kompilacja w trybie
Make nie uwzględnia tych modułów, których treść nie została zmodyfikowana w sposób jawny.
Notatka
Moduł PenniesInt ilustruje jeden ze sposobów importowania funkcji (procedury)  na podstawie jej nazwy:
external nazwa_biblioteki name nazwa_funkcji
Alternatywą jest import oparty na indeksach przypisanych procedurom (funkcjom) w dyrektywie exports
biblioteki DLL:
external nazwa_biblioteki index indeks_funkcji
Choć import na podstawie indeksu funkcji jest rozwiązaniem efektywniejszym, jest zdecydowanie odradzany
ze względu na wygodę użytkownika: zapamiętanie indeksu konkretnej funkcji w konkretnej bibliotece DLL jest
trudniejsze niż zapamiętanie jej nazwy, ponadto pozycja funkcji może zostać zmieniona podczas
unowocześniania modułu, natomiast nazwa jest znacznie mniej podatna na tego typu zmiany.
Dla użytkownika końcowego, wykorzystującego funkcję PenniesToCoins() na potrzeby aplikacji tworzonych
w Delphi, niezbędne są więc co najmniej dwa pliki: biblioteka PenniesLib.dll i skompilowany moduł
PenniesInt.dcu, bądz też jego wersja zródłowa PenniesInt.pas, najlepiej z usuniętymi sekwencjami
{$IFNDEF PENNIESLIB & ENDIF}. Bibliotekę PenniesLib.dll można oczywiście wykorzystywać w
innych językach programowania (np. w C++Builderze), sposoby importowania funkcji PenniesToCoins()
będą jednak charakterystyczne dla tychże języków.
Formularze modalne w bibliotekach DLL
Pokażemy teraz, jak  zamknąć w bibliotece DLL utworzony w Delphi formularz, przeznaczony do
wyświetlenia w sposób modalny. Formularz stanie się dzięki temu dostępny dla dowolnego 32-bitowego
środowiska programowania w Windows, na przykład C++Buildera, Visual Basica itp. Formularz ten zawiera
komponent TCalendar. Aplikacja, wywołując importowaną z biblioteki funkcję ShowCalendar(), powoduje
modalne wyświetlenie formularza  użytkownik ma wówczas możliwość wyboru konkretnej daty, która po
zamknięciu formularza zwracana jest jako wynik wspomnianej funkcji.
Na wydruku 6.3 prezentujemy moduł zródłowy wspomnianego formularza wyświetlanego w sposób modalny;
jest on częścią projektu biblioteki o nazwie CalendarLib.dll.
Wydruk 6.3. Moduł zródłowy formularza wyświetlanego w sposób modalny
unit DLLFrm;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, Grids, Calendar;
type
TDLLForm = class(TForm)
calDllCalendar: TCalendar;
procedure calDllCalendarDblClick(Sender: TObject);
end;
{ deklaracja eksportowanej funkcji }
function ShowCalendar(AHandle: THandle; ACaption: String): TDateTime; StdCall;
implementation
{$R *.DFM}
function ShowCalendar(AHandle: THandle; ACaption: String): TDateTime;
var
DLLForm: TDllForm;
begin
// kopiuj uchwyt aplikacji do obiektu Application biblioteki DLL
Application.Handle := AHandle;
DLLForm := TDLLForm.Create(Application);
try
DLLForm.Caption := ACaption;
DLLForm.ShowModal;
Result := DLLForm.calDLLCalendar.CalendarDate; // przekaż wybraną datę
// jako wynik
finally
DLLForm.Free;
end;
end;
procedure TDLLForm.calDllCalendarDblClick(Sender: TObject);
begin
Close;
end;
end.
Zwróć uwagę, iż obiekt formularza jest wewnętrznym obiektem funkcji ShowCalendar()  wskazująca go
zmienna jest zmienną lokalną funkcji; tworzona automatycznie definicja globalnej zmiennej formularza została
 ręcznie usunięta z treści modułu.
Pierwszą czynnością, którą wykonuje funkcja ShowCalendar(), jest właściwe ustawienie tzw. uchwytu
aplikacyjnego. Każdy stworzony w Delphi moduł wykonywalny, zarówno plik .EXE, jak i biblioteka .DLL,
zawiera swój własny obiekt Application. Właściwość Handle tego obiektu zawiera uchwyt reprezentujący
aplikację w systemie; uchwyt ten wykorzystywany jest do komunikowania się aplikacji z niskopoziomowymi
funkcjami Win32 API. Ponieważ wyświetlony formularz funkcjonuje jako modalne okno aplikacji, właściwość
Handle obiektu Application biblioteki DLL musi zawierać uchwyt aplikacji głównej, nie uchwyt biblioteki;
ponadto obiekt Application biblioteki musi być właścicielem tworzonego obiektu formularza. Wygląda na to,
iż tworzony formularz jest niejako  na siłę kojarzony z aplikacją macierzystą  to prawda, lecz zaniedbanie tej
czynności skutkowałoby jego błędnym zachowaniem się, szczególnie w przypadku próby jego minimalizacji.
Utworzony obiekt formularza jest następnie wyświetlany w sposób modalny. Jest on zamykany (i zwalniany)
wskutek dwukrotnego kliknięcia komponentu kalendarza, zaś wybrana ostatnio data zwracana jest jako wynik
funkcji.
Ostrzeżenie
Jeżeli którakolwiek z procedur (funkcji) eksportowanych z biblioteki używa jako parametrów (i ew. w
charakterze wyniku) długich łańcuchów lub tablic dynamicznych, konieczne jest umieszczenie nazwy
ShareMem na pierwszym miejscu dyrektywy uses pliku *.dpr związanego z biblioteką; dotyczy to
wszystkich długich łańcuchów i tablic dynamicznych, także tych zagnieżdżonych w rekordach i klasach.
Moduł ShareMem.Pas jest modułem importowym biblioteki Borlndmm.dll. Jej użycie jest konieczne w
sytuacji, gdy przekazywany długi łańcuch (lub tablica dynamiczna) zmienia swój moduł-właściciela  czyli np.
definiowany jest w module .EXE, lecz obsługiwany w ramach funkcji znajdującej się w bibliotece .DLL.
Przekazywanie do innych modułów wskazników do długich łańcuchów, stanowiących np. wynik ich rzutowania
na typ PChar, nie powoduje zmiany własności (wywoływana funkcja nie wykonuje na otrzymanym wskazniku
żadnych operacji charakterystycznych dla długich łańcuchów), nie wymaga więc używania biblioteki
Borlndmm.dll.
Wyjątkiem od opisanej zasady jest przekazywanie długich łańcuchów i tablic dynamicznych pomiędzy
modułami tworzonymi z udziałem pakietów  nie wymaga to używania biblioteki Borlndmm.dll, bo alokator
pamięci jest wówczas wspólny dla wszystkich takich modułów.
Należy ponadto zaznaczyć, iż biblioteka Borlndmm.dll nadaje się do użycia jedynie przez moduły
stworzone w Delphi i C++Builderze; biblioteki przeznaczone dla innych środowisk nie powinny w ogóle
eksportować procedur i funkcji używających w charakterze parametrów (i wyniku) długich łańcuchów i tablic
dynamicznych, z prostej przyczyny  są to obiekty charakterystyczne dla Delphi i C++Buildera i inne
środowiska nie  mają pojęcia o ich obsłudze!
Formularze niemodalne w bibliotekach DLL
Pokażemy teraz, jak można wykorzystać ten sam formularz w sposób niemodalny. Zawierająca go biblioteka
DLL powinna posiadać przynajmniej dwa podprogramy, wykonujące dwie podstawowe czynności, to jest 
tworzenie i zwalnianie formularza. Przykładowy projekt o nazwie CalendarMLlib.Dpr realizuje taką właśnie
bibliotekę; wspomniane funkcje noszą nazwy (odpowiednio) ShowCalendar() oraz CloseCalendar().
Moduł zródłowy formularza, nieznacznie różniącego się od tego wyświetlanego w wersji modalnej,
przedstawiamy na wydruku 6.4.
Wydruk 6.4. Moduł zródłowy formularza wyświetlanego w sposób niemodalny
unit DLLFrm;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, Grids, Calendar;
type
TDLLForm = class(TForm)
calDllCalendar: TCalendar;
end;
{ deklaracja eksportowanych funkcji }
function ShowCalendar(AHandle: THandle; ACaption: String): Longint; stdCall;
procedure CloseCalendar(AFormRef: Longint); stdcall;
implementation
{$R *.DFM}
function ShowCalendar(AHandle: THandle; ACaption: String): Longint;
var
DLLForm: TDllForm;
begin
// kopiuj uchwyt aplikacji do obiektu Application biblioteki DLL
Application.Handle := AHandle;
DLLForm := TDLLForm.Create(Application);
Result := Longint(DLLForm);
DLLForm.Caption := ACaption;
DLLForm.Show;
end;
procedure CloseCalendar(AFormRef: Longint);
begin
if AFormRef > 0
then
TDLLForm(AFormRef).Release;
end;
end.
Funkcja ShowCalendar() przypisuje uchwyt aplikacji wywołującej właściwości Application.Handle,
tworzy egzemplarz formularza, nadaje mu żądany tytuł i w końcu wyświetla go w sposób niemodalny.
Wynikiem zwracanym przez funkcję jest wskaznik do utworzonego egzemplarza, dla  uniwersalności
rzutowany tutaj na liczbę typu Longint (32-bitowa liczba całkowita jest w Win32 bardziej uniwersalna niż
pascalowy pointer). Nie należy jednak utożsamiać tej liczby całkowitej z typowym uchwytem Windows 
użycie jej jako argumentu procedury CloseHandle() na pewno nie spowoduje zamknięcia formularza, a
dodatkowo może powodować inne niepożądane efekty. Chcąc zamknąć formularz, musimy przekazać tę liczbę
jako argument wywołania funkcji CloseCalendar(). Zwalnia ona obiekt formularza za pomocą jego metody
Release()  metoda ta wywołuje destruktor Destroy(), uprzednio jednak doprowadza do obsłużenia
wszystkich zdarzeń i komunikatów związanych z formularzem.
Wykorzystywanie bibliotek DLL
w aplikacjach Delphi
Na początku niniejszego rozdziału wspomnieliśmy o istnieniu dwóch sposobów wykorzystywania bibliotek DLL
w aplikacjach  domyślnego i jawnego. Obecnie zaprezentujemy obydwa te sposoby na przykładzie aplikacji
testowych, działających na podstawie stworzonych przed chwilą bibliotek.
Automatyczne ładowanie biblioteki DLL
Pierwsza aplikacja  Pennies.dpr  wykorzystuje funkcję PenniesToCoins() znajdującą się w bibliotece
PenniesLib.Dll. Formularz aplikacji zawiera jedną kontrolkę edycyjną TMaskEdit, jeden przycisk TButton
i dziewięć etykiet TLabel. Użytkownik wprowadza żądaną kwotę do kontrolki edycyjnej, po czym naciska
wspomniany przycisk  w odpowiedzi aplikacja oblicza liczbę monet każdego rodzaju i wyświetla wynik pod
postacią czterech etykiet. Kod formularza głównego aplikacji przedstawia wydruk 6.5.
Wydruk 6.5. Formularz główny projektu wykorzystującego bibliotekę PenniesLib.dll
unit MainFrm;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Mask;
type
TMainForm = class(TForm)
lblTotal: TLabel;
lblQlbl: TLabel;
lblDlbl: TLabel;
lblNlbl: TLabel;
lblPlbl: TLabel;
lblQuarters: TLabel;
lblDimes: TLabel;
lblNickels: TLabel;
lblPennies: TLabel;
btnMakeChange: TButton;
meTotalPennies: TMaskEdit;
procedure btnMakeChangeClick(Sender: TObject);
procedure meTotalPenniesChange(Sender: TObject);
end;
var
MainForm: TMainForm;
implementation
uses
PenniesInt; // moduł importowy biblioteki
{$R *.DFM}
procedure TMainForm.btnMakeChangeClick(Sender: TObject);
var
CoinsRec: TCoinsRec;
TotPennies: word;
begin
// Wywołaj funkcję zawartą w bibliotece DLL
TotPennies := PenniesToCoins(StrToInt(meTotalPennies.Text), @CoinsRec);
with CoinsRec do
begin
{ Wyświetl informację wynikową }
lblQuarters.Caption := IntToStr(Quarters);
lblDimes.Caption := IntToStr(Dimes);
lblNickels.Caption := IntToStr(Nickels);
lblPennies.Caption := IntToStr(Pennies);
end
end;
// A. Grażyński
procedure TMainForm.meTotalPenniesChange(Sender: TObject);
// Usuń z formularza informację wynikową
// na czas wprowadzania kwoty wejściowej
begin
lblQuarters.Caption := '';
lblDimes.Caption := '';
lblNickels.Caption := '';
lblPennies.Caption := '';
end;
end.
Biblioteka DLL ładowana jest automatycznie w momencie rozpoczęcia aplikacji, a funkcja PenniesToCoins()
osiągalna jest za pośrednictwem modułu importowego PenniesInt.pas; moduł ten zawiera ponadto definicję
wykorzystywanego przez aplikację rekordu TCoinsRec. Tak naprawdę używanie modułów importowych nie
jest obowiązkowe  funkcję PenniesToCoins() można by zaimportować z biblioteki w sposób bezpośredni,
poprzez umieszczenie w sekcji implementation modułu MainFrm następującej definicji:
function PenniesToCoins(TotPennies: word; CoinsRec: PCoinsRec): word; StdCall;
external 'PENNIESLIB.DLL';
Należałoby oczywiście umieścić w module MainFrm także definicję rekordu TCoinsRec, gdyż konsekwencją
pominięcia modułu importowego jest konieczność przeniesienia (do aplikacji wywołującej) zawartych w nim
deklaracji struktur danych. Opłaca się to jednak tylko w przypadku bardzo prostych bibliotek,
wykorzystywanych przez niewiele aplikacji.
Wskazówka
Wielu bibliotekom DLL, rozpowszechnianym przez niezależnych wytwórców, towarzyszą często nie moduły
importowe Pascala, lecz biblioteki importowe (import libraries) dla języka C i C++. Przetłumaczenie biblioteki
importowej na równoważny moduł importowy nie jest zbyt skomplikowane, zwłaszcza, jeżeli posłużymy się
tabelą 2.5 (z rozdziału 2.) zawierającą zestawienie równoważnych typów danych.
Aadowanie biblioteki DLL na żądanie
Choć automatyczne ładowanie bibliotek DLL jest bardzo wygodne i proste w obsłudze, nie zawsze jest
rozwiązaniem najwłaściwszym. Załóżmy, że aplikacja, do której  na wszelki wypadek przyłącza się domyślnie
kilkadziesiąt bibliotek DLL, zawierających ogółem kilka tysięcy funkcji, w typowym zastosowaniu ogranicza się
do wywołania zaledwie kilku z nich  powraca więc pod inną postacią podstawowy mankament łączenia
statycznego. Ponadto rzadko się zdarza, by wykorzystywane przez aplikację biblioteki DLL potrzebne były
wszystkie na raz; zazwyczaj w danej chwili potrzebna jest tylko jedna z nich, a (potencjalnie) duże obiekty i
struktury definiowane w pozostałych bibliotekach niepotrzebnie zajmują pamięć.
Najwłaściwszym wyjściem z tej sytuacji jest z pewnością odłożenie ładowania biblioteki do czasu, kiedy stanie
się ona faktycznie potrzebna  a więc zastosowanie łączenia jawnego, bardziej co prawda elastycznego, ale
wymagającego bardziej zaawansowanej obsługi.
Na załączonym krążku CD-ROM, w podkatalogu ModalDLL, znajduje się projekt CalendarTest.dpr
dokonujący modalnego wyświetlenia formularza znajdującego się w bibliotece DLL. Projekt ten stanowi
jednocześnie ilustrację jawnego łączenia biblioteki DLL z aplikacją; kod jego modułu głównego przedstawiamy
na wydruku 6.6.
Wydruk 6.6. Ilustracja jawnego łączenia biblioteki DLL
unit MainFfm;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;
type
// zdefiniuj typ odpowiadający typowi importowanej funkcji
TShowCalendar = function (AHandle: THandle; ACaption: String): TDateTime; StdCall;
// zdefiniuj nową klasę wyjątku związaną z błędem ładowania biblioteki
EDLLLoadError = class(Exception);
TMainForm = class(TForm)
lblDate: TLabel;
btnGetCalendar: TButton;
procedure btnGetCalendarClick(Sender: TObject);
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TMainForm.btnGetCalendarClick(Sender: TObject);
var
LibHandle : THandle;
ShowCalendar: TShowCalendar;
begin
{ spróbuj załadować bibliotekę }
LibHandle := LoadLibrary('CALENDARLIB.DLL');
try
{ zerowa wartość LibHandle oznacza, że ładowanie nie powiodło się;
wygeneruj wyjątek
}
if LibHandle = 0
then
raise EDLLLoadError.Create('Błąd ładowania biblioteki DLL');
{ jeżeli ładowanie powiodło się, wykonanie programu jest kontynuowane;
uzyskaj adres żądanej funkcji
}
@ShowCalendar := GetProcAddress(LibHandle, 'ShowCalendar');
{
jeżeli udało się uzyskać adres funkcji, wywołaj ją i wyświetl
zwracany przez nią wynik; jeżeli nie udało się uzyskać adresu
funkcji, wygeneruj wyjątek
}
if not (@ShowCalendar = nil)
then
lblDate.Caption := DateToStr(ShowCalendar(Application.Handle, Caption))
else
RaiseLastWin32Error;
finally
FreeLibrary(LibHandle); // zwolnij bibliotekę DLL
end;
end;
end.
Pierwszą czynnością podczas jawnego łączenia jest załadowanie biblioteki. Dokonuje tego funkcja API
LoadLibrary():
Function LoadLibrary(lpLibFileName: PChar): HMODULE; stdcall;
Jej niezerowy wynik oznacza pomyślne załadowanie biblioteki i jest jednocześnie uchwytem jej instancji.
Kolejna czynność to uzyskanie adresu procedury ShowCalendar(): czynność tę wykonuje funkcja
GetProcAddress() na podstawie uchwytu instancji biblioteki oraz nazwy szukanej funkcji:
function GetProcAddress(Module: HMODULE; lpProcName: LPCSTR):FARPROC; stdcall;
Wynik zwracany przez funkcję GetProcAddress() jest wskaznikiem amorficznym (FARPROC znaczy tyle
samo co pointer), więc wywołanie funkcji wymaga jego rzutowania na typ zgodny z deklaracją wywoływanej
funkcji:
TShowCalendar = function (AHandle: THandle; ACaption: String): TDateTime; StdCall;
Zwolnienie ( rozładowanie ) biblioteki DLL następuje w wyniku wywołania funkcji FreeLibrary():
function FreeLibrary(Module: HMODULE):BOOL; stdcall;
Wspominaliśmy już wcześniej, iż system operacyjny stara się minimalizować liczbę załadowanych egzemplarzy
biblioteki DLL, dzieląc jej pojedynczą kopię pomiędzy kilka aplikacji zawsze, gdy jest to możliwe. W związku z
tym wywołanie funkcji LoadLibrary() niekoniecznie musi skutkować fizycznym ładowaniem biblioteki, lecz
może sprowadzać się do zwiększenia (o 1) licznika odwołań związanego z jej załadowanym egzemplarzem; na
podobnej zasadzie funkcja FreeLibrary() zmniejsza o 1 wartość wspomnianego licznika; fizyczne zwolnienie
biblioteki następuje tylko wówczas, gdy wynikiem dekrementacji licznika jest jego zerowa wartość.
Opisany scenariusz (załadowanie biblioteki, uzyskanie adresu funkcji, wywołanie funkcji, zwolnienie biblioteki)
realizowany jest w ramach procedury wywoływanej kliknięciem jedynego przycisku znajdującego się na
formularzu. Niemożność załadowania biblioteki lub uzyskania adresu funkcji powoduje wygenerowanie
wyjątku; bezwarunkowe zwolnienie (ewentualnie) załadowanej biblioteki zapewnione zostało przez
umieszczenie wywołania funkcji FreeLibrary() w ramach bloku finally.
Zwróć uwagę, iż każde wywołanie funkcji ShowCalendar() wiąże się z ładowaniem i zwalnianiem biblioteki
(dokładniej  wywołaniem LoadLibrary() i FreeLibrary()). Nie stanowi to problemu w przypadku
wywołania jednokrotnego, lecz przy wywołaniu wielokrotnym skutkować może pewnym wydłużeniem czasu
realizacji programu.
Procedura inicjująco-kończąca biblioteki DLL
Każda biblioteka dołączana do procesu lub odłączana od niego może być o tym fakcie powiadamiana przez
Win32; ponadto w czasie, gdy jest ona przyłączona do procesu, może być powiadamiana o utworzeniu albo
zwolnieniu każdego wątku. Owo powiadamianie realizowane jest za pośrednictwem tzw. procedury inicjująco-
kończącej (DLL Entry/Exit Function). Mechanizm ten może być z wielu względów użyteczny, gdyż ułatwia
wykonywanie typowych operacji inicjujących i kończących, związanych z obecnością biblioteki w procesie 
np. nadawanie wartości zmiennym globalnym, rejestrację klas, tworzenie i usuwanie plików roboczych itp.
Sprawowanie przez bibliotekę kontroli nad gospodarką wątkami procesu może być natomiast pomocne ze
względu na możliwość wielodostępnego jej wykorzystywania.
Definiowanie procedury inicjująco-kończącej
Z każdą instancją biblioteki DLL związana jest w Delphi globalna zmienna o nazwie DLLProc, zawierająca
wskazanie na zdefiniowaną przez użytkownika procedurę inicjująco-kończącą. Początkową wartością tej
zmiennej jest NIL, co oznacza, że biblioteka realizuje jedynie standardowy dla Delphi sposób powiadamiania,
czyli wykonanie bloku begin& end pliku .DPR projektu tworzącego bibliotekę. Przypisując wspomnianej
zmiennej funkcję zdefiniowaną przez użytkownika, możemy poszerzyć owo powiadamianie na trzy pozostałe
przypadki.
Procedura inicjująco-kończąca powinna posiadać pojedynczy parametr typu DWORD. Jego wartość informuje o
tym, która z czterech możliwych przyczyn powiadamiania spowodowała wywołanie procedury, zgodnie z
opisem w poniższej tabeli.
Tabela 6.1. Przyczyny wywołania procedury inicjująco-kończącej
Wartość parametru Przyczyna
DLL_PROCESS_ATTACH
Biblioteka DLL została włączona do przestrzeni
adresowej procesu bądz przez załadowanie domyślne,
bądz w wyniku pierwszego wywołania
LoadLibrary().
DLL_PROCESS_DETACH
Biblioteka została odłączona od procesu bądz na skutek
jego zakończenia, bądz też w wyniku wyzerowania
licznika odwołań spowodowanego
przez funkcję FreeLibrary().
DLL_THREAD_ATTACH
Proces utworzył nowy wątek; procedura inicjująco-
kończąca wywoływana jest w kontekście nowo
utworzonego wątku.
DLL_THREAD_DETACH
Zakończył się wątek procesu; procedura inicjująco-
kończąca wywoływana jest w kontekście kończącego
się wątku.
Ostrzeżenie
Zakończenie wątku za pomocą wywołania procedury TerminateThread() nie gwarantuje wywołania z
parametrem DLL_THREAD_DETACH.
Przykład zastosowania procedury inicjująco-kończącej przedstawia wydruk 6.7. Prezentowany plik znajduje się
na załączonym krążku CD-ROM pod nazwą DLLEntryLib.dpr.
Wydruk 6.7. Zastosowanie procedury inicjująco-kończącej biblioteki DLL w Delphi
library DLLEntryLib;
uses
SysUtils,
Windows,
Dialogs,
Classes;
procedure DLLEntryExitProc(dwReason: DWord);
begin
case dwReason of
DLL_PROCESS_ATTACH: begin
ShowMessage('Przyłączenie do procesu');
end;
DLL_PROCESS_DETACH: begin
ShowMessage('Odłączenie od procesu');
end;
DLL_THREAD_ATTACH: begin
ShowMessage('Uruchomienie wątku');
end;
DLL_THREAD_DETACH: begin
ShowMessage('Zatrzymanie wątku');
end;
end;
end;
begin
{ przypisz procedurę inicjująco-kończącą do odpowiedniej zmiennej }
DllProc := @DLLEntryExitProc;
{ wywołaj procedurę inicjująco-kończącą z parametrem DLL_PROCESS_ATTACH }
DllProc(DLL_PROCESS_ATTACH);
end.
Zdefiniowana przez użytkownika procedura inicjująco-kończąca przypisywana jest zmiennej DLLProc; odbywa
się to w ramach bloku inicjującego begin& end. Blok ten jest w Delphi wykonywany zamiast standardowego
wywołania procedury inicjująco-kończącej z parametrem DLL_PROCESS_ATTACH, by więc pozostać w zgodzie
ze standardami Win32, należy wywołanie to zrealizować samodzielnie. Funkcjonowanie samej procedury
inicjująco-kończącej sprowadza się tu do wypisania komunikatu informującego o przyczynie wywołania.
Aby zaobserwować funkcjonowanie procedury inicjująco-kończącej jakiejś biblioteki, należy bibliotekę tę
wykorzystać w jakimś projekcie realizującym aplikację wielowątkową. Projekt taki, o nazwie
DllEntryTest.dpr, znajduje się na załączonym krążku CD-ROM; kod jego modułu głównego przedstawiamy
na wydruku 6.8.
Wydruk 6.8. Moduł główny projektu aplikacji ilustrującej funkcjonowanie procedury inicjująco-kończącej
biblioteki DLL
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ComCtrls, Gauges;
type
{ zdefiniuj klasę wątku }
TTestThread = class(TThread)
procedure Execute; override;
procedure SetCaptionData;
end;
TMainForm = class(TForm)
btnLoadLib: TButton;
btnFreeLib: TButton;
btnCreateThread: TButton;
btnFreeThread: TButton;
lblCount: TLabel;
procedure btnLoadLibClick(Sender: TObject);
procedure btnFreeLibClick(Sender: TObject);
procedure btnCreateThreadClick(Sender: TObject);
procedure btnFreeThreadClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
LibHandle : THandle;
TestThread : TTestThread;
Counter : Integer;
GoThread : Boolean;
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TTestThread.Execute;
begin
FreeOnTerminate := TRUE; // A. Grażyński
while MainForm.GoThread do
begin
Synchronize(SetCaptionData);
Inc(MainForm.Counter);
end;
end;
procedure TTestThread.SetCaptionData;
begin
MainForm.lblCount.Caption := IntToStr(MainForm.Counter);
end;
procedure TMainForm.btnLoadLibClick(Sender: TObject);
{ załadowanie biblioteki }
begin
if LibHandle = 0 then
begin
LibHandle := LoadLibrary('DLLENTRYLIB.DLL');
if LibHandle = 0
then
raise Exception.Create('Błąd ładowania biblioteki');
end
else
MessageDlg('Biblioteka jest już załadowana', mtWarning, [mbok], 0);
end;
procedure TMainForm.btnFreeLibClick(Sender: TObject);
{ zwolnienie biblioteki }
begin
if not (LibHandle = 0) then
begin
FreeLibrary(LibHandle);
LibHandle := 0;
end;
end;
procedure TMainForm.btnCreateThreadClick(Sender: TObject);
{ tworzenie nowego wątku; powinno to powodować wywołanie procedury
inicjująco-kończącej KAŻDEJ załadowanej biblioteki DLL z parametrem
DLL_THREAD_ATTACH
}
begin
if TestThread = nil then
begin
GoThread := True;
TestThread := TTestThread.Create(False);
end;
end;
procedure TMainForm.btnFreeThreadClick(Sender: TObject);
{ kończenie wątku; powinno to powodować wywołanie procedury
inicjująco-kończącej KAŻDEJ załadowanej biblioteki DLL z parametrem
DLL_THREAD_DETACH
}
begin
if TestThread <> nil then
begin
GoThread := False;
// TestThread.Free; // A. Grażyński
TestThread := nil;
Counter := 0;
end;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
LibHandle := 0;
TestThread := nil;
end;
end.
Formularz projektu zawiera cztery przyciski, związane z czterema przyczynami wywoływania procedury
inicjująco-kończącej. Procedura zdarzeniowa związana z pierwszym z przycisków dokonuje załadowania
biblioteki za pomocą funkcji LoadLibrary(). Uchwyt biblioteki przechowywany jest w jednym z pól
formularza (LibHandle); jego niezerowa wartość oznacza, że biblioteka została już załadowana i następne
kliknięcia wspomnianego przycisku należy po prostu zignorować. Kliknięcie drugiego ze wspomnianych
przycisków stanowi polecenie zwolnienia biblioteki; procedura zdarzeniowa przycisku sprawdza wówczas, czy
pole LibHandle ma niezerową wartość i jeżeli tak, to przekazuje tę wartość jako parametr wywołania funkcji
FreeLibrary().
Kolejne dwa przyciski związane są z tworzeniem i kończeniem wątku. Wątek reprezentowany jest przez klasę
TTestThread. Jej metoda Execute() dokonuje nieustannej inkrementacji i wyświetlania na formularzu
wartości licznika (będącego polem Counter formularza)  trwa to dopóty, dopóki pole GoThread nie osiągnie
wartości False; wartość tę nadaje mu procedura obsługi przycisku kończącego wątek. W aplikacji da się
uruchomić co najwyżej jeden wątek poboczny  jego obiekt wskazywany jest przez pole TestThread
formularza. Zwróć uwagę, iż zarówno inkrementacja licznika, jak i jego wyświetlanie, realizowane są za pomocą
metody Synchronize().
Nasza przykładowa procedura inicjująco-kończąca ogranicza swą pracę do wyświetlania stosownych
komunikatów, w rzeczywistych aplikacjach może ona jednak wykonywać niebagatelne zadania, w rodzaju
przydzielania i zwalniania zasobów przeznaczonych dla procesu oraz dla poszczególnych wątków.
Notatka
Wywołania procedury inicjująco-kończącej z parametrami DLL_THREAD_ATTACH i DLL_THREAD_DETACH
mają miejsce tylko wtedy, gdy podczas  odpowiednio  tworzenia oraz zwalniania wątku dana biblioteka
jest przyłączona do procesu.
W poniższej sekwencji
1. Utworzenie wątku
2. Załadowanie biblioteki DLL
3. Zwolnienie wątku
4. Zwolnienie biblioteki DLL
tylko zwolnienie wątku (3.) zauważone będzie przez bibliotekę ładowaną w punkcie 2. Wynika stąd ważny
wniosek, iż dla danej biblioteki DLL wywołania z parametrami DLL_THREAD_ATTACH oraz
DLL_THREAD_DETACH wcale nie muszą się bilansować! Nie mogą więc one pełnić roli  inicjująco-kończącej
w stosunku do poszczególnych wątków (przyp. tłum.).
Obsługa wyjątków w bibliotekach DLL
W 16-bitowym środowisku Delphi 1 wyjątki stanowiły zjawisko specyficzne dla języka programowania i
musiały zostać obsłużone przed powrotem do modułu wywołującego:
procedure SomeDLLProc;
begin
try
&
except
on Exception do
&
// nie pozostawiaj wyjątków nieobsłużonych ani ich nie ponawiaj
end;
end;
 Wydostanie się wyjątku na zewnątrz biblioteki DLL powodowało, iż aplikacja wywołująca zastawała stos w
nieprawidłowym stanie, co prawie zawsze stanowiło zagrożenie dla aplikacji i systemu operacyjnego. Sytuacja
ta zmieniła się diametralnie wraz z pojawieniem się Delphi 2  wyjątki aplikacji są odtąd mapowane w wyjątki
Win32, są więc zjawiskami systemowymi, nie zaś specyficznymi dla konkretnego języka programowania.
Niezbędne do tego czynności wykonywane są w części inicjacyjnej modułu SysUtils, a więc jego włączenie
do aplikacji jest konieczne do tego, by wyjątki zaistniałe wewnątrz biblioteki DLL mogły się z niej bezpiecznie
wydostawać.
Ostrzeżenie
Większość aplikacji Win32 nie została jednak zaprojektowana w taki sposób, by obsługiwać wyjątki Win32,
więc nawet bezpieczne wydostanie się wyjątku z biblioteki DLL może spowodować awaryjne zakończenie
aplikacji. Dla pewności, zalecane jest więc obsługiwanie przez bibliotekę DLL wszystkich generowanych w
ramach niej wyjątków.
Ponadto najlepsza nawet aplikacja stworzona w środowisku innym niż Delphi nie będzie w stanie obsłużyć
wyjątków specyficznych dla klas Object Pascala; wyjątki te są najczęściej sygnalizowane jako wyjątki Win32 o
kodzie $0EEDFACE. Wyjątek Win32 reprezentowany jest przez następującą strukturę zdefiniowaną w module
SysUtils:
PExceptionRecord = ^TExceptionRecord;
TExceptionRecord = record
ExceptionCode: Cardinal;
ExceptionFlags: Cardinal;
ExceptionRecord: PExceptionRecord;
ExceptionAddress: Pointer;
NumberParameters: Cardinal;
ExceptionInformation: array[0..14] of Cardinal;
end;
Pierwszy element tablicy ExceptionInformation zawiera wówczas adres wyjątku, drugi natomiast 
adres obiektu Delphi reprezentującego wyjątek. Bardziej szczegółowe informacje na temat struktury
TExceptionRecord znajdują się w systemie pomocy Win32 pod hasłem  EXCEPTION_RECORD .
Wyjątki a klauzula Safecall
Opatrzenie procedury (funkcji) klauzulą Safecall powoduje, iż jakikolwiek nieobsłużony w jej ramach
wyjątek zostanie przekazany do obsługi przez program wywołujący. Technologicznie odbywa się to przez
otoczenie całej treści funkcji ukrytą konstrukcją try& except, co spowoduje przejęcie wyjątków
nieobsłużonych na niższych poziomach zagnieżdżenia. W bloku except tej konstrukcji przechwycony wyjątek
konwertowany jest (przez procedurę SafecallErrorProc()) na liczbę całkowitą (typ HRESULT) zwracaną
jako wynik funkcji. Klauzula Safecall implikuje także konwencję wywołania stdcall, tak więc przykładowa
funkcja zadeklarowana jako
function Foo(i: integer): string; Safecall;
może być (koncepcyjnie, nie w sensie składni) traktowana na równi z następującą funkcją:
function Foo(i: integer):string; HRESULT; stdcall;
Klauzula Safecall jest szczególnie użyteczna w technologii COM; w sytuacji, gdy (nieobsłużony) wyjątek nie
powinien  wydostać się z wywoływanej funkcji, jest on konwertowany na liczbę całkowitą, zawierającą
informację o błędzie. Swoją drogą, jest to pewnego rodzaju sposób na nieobsługiwane wyjątki generowane w
ramach bibliotek DLL.
Funkcje zwrotne
Funkcją zwrotną (callback function) nazywana jest funkcja (lub procedura) stanowiąca część aplikacji, ale
wywoływana asynchronicznie przez bibliotekę DLL. Kierunek tego wywołania jest więc niejako odwrotny w
stosunku do naturalnego wywoływania, przez aplikację, funkcji zawartych w bibliotekach; wywołanie zwrotne
musi być jednak połączone z  normalnym wywołaniem, w ramach którego do biblioteki przekazywany jest
adres funkcji zwrotnej.
Biblioteka Win32 API naszpikowana jest wręcz funkcjami korzystającymi z odwołań zwrotnych; jednym z
przykładów wykorzystania odwołań zwrotnych są wszelkiego rodzaju  enumeracje , czyli wywołania określonej
funkcji zwrotnej w stosunku do każdego obiektu określonej grupy, na przykład w stosunku do wszystkich okien
 pierwszego poziomu (top level), bez uwzględniania okien potomnych (child windows). Enumeracja  po
oknach pierwszego poziomu wykonywana jest przez funkcję EnumWindows():
function EnumWindows(lpEnumFunc: TFNWndEnumProc; lParam: LPARAM): BOOL; stdcall;
Pierwszy parametr jest wskaznikiem do funkcji zwrotnej, drugi natomiast niesie informację dodatkową,
nieistotną dla Win32 API, wykorzystywaną wewnętrznie przez funkcję zwrotną.
Funkcja zwrotna, określona przez pierwszy parametr, wywołana zostanie jednokrotnie dla każdego okna.
Powinna być ona dwuparametrową funkcją zwracającą wynik typu Boolean; jako pierwszy parametr
przekazany zostanie do niej uchwyt odnośnego okna, jako drugi  wartość określona przez drugi parametr
wywołania funkcji EnumWindows():
type
TFNWndEnumProc = function (Hw: Hwnd; lp: lParam): Boolean; stdcall;
Wynik zwracany przez funkcję zwrotną decyduje o tym, czy enumeracja ma być kontynuowana (True), czy też
należy ją zatrzymać (False).
Ilustracją enumeracji prowadzonej po oknach jest projekt o nazwie CallBack.dpr znajdujący się na
załączonym krążku CD-ROM; jego moduł główny prezentujemy na wydruku 6.9.
Wydruk 6.9. Moduł główny projektu ilustrującego enumerację po oknach pierwszego poziomu
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ComCtrls;
type
// zdefiniuj rekord zawierający informację o oknie
TWindowInfo = class
WindowName, // nazwa okna
WindowClass: String; // nazwa klasy okna
end;
TMainForm = class(TForm)
lbWinInfo: TListBox;
btnGetWinInfo: TButton;
hdWinInfo: THeaderControl;
procedure btnGetWinInfoClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure lbWinInfoDrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
procedure hdWinInfoSectionResize(HeaderControl: THeaderControl;
Section: THeaderSection);
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
function EnumWindowsProc(Hw: HWnd; AMainForm: TMainForm): Boolean; stdcall;
// niniejsza funkcja jest funkcją zwrotną wywoływaną z wnętrza
// przez biblioteki User32.DLL dla każdego głównego okna w systemie
var
WinName, CName: array[0..144] of char;
WindowInfo: TWindowInfo;
begin
// domyślna wartość, nakazująca kontynuowanie enumeracji
Result := True;
GetWindowText(Hw, WinName, 144); // pobierz tytuł okna
GetClassName(Hw, CName, 144); // pobierz nazwę klasy okna
{ stwórz obiekt klasy TWindowInfo zawierający informację o oknie
i dodaj go do listy Listbox1
}
WindowInfo := TWindowInfo.Create;
with WindowInfo do
begin
SetLength(WindowName, strlen(WinName));
SetLength(WindowClass, StrLen(CName));
WindowName := StrPas(WinName);
WindowClass := StrPas(CName);
end;
MainForm.lbWinInfo.Items.AddObject('', WindowInfo);
end;
procedure TMainForm.btnGetWinInfoClick(Sender: TObject);
begin
{ Wykonaj enumerację po wszystkich oknach głównych, wykorzystując
funkcję EnumWindowsProc() w charakterze funkcji zwrotnej
}
EnumWindows(@EnumWindowsProc, 0);
end;
procedure TMainForm.FormDestroy(Sender: TObject);
var
i: integer;
begin
{ zwolnij wszystkie obiekty TWindowInfo }
for i := 0 to lbWinInfo.Items.Count - 1 do
TWindowInfo(lbWinInfo.Items.Objects[i]).Free
end;
procedure TMainForm.lbWinInfoDrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
begin
{ wyczyść obszar płótna, na którym wypisywana będzie informacja }
lbWinInfo.Canvas.FillRect(Rect);
{ wypisz informację zawartą w rekordzie TWindowInfo zawartym w liście
ListBox1 na pozycji "index". Szerokość poszczególnych kolumn określają
separatory w nagłówku HeaderControl1
}
with TWindowInfo(lbWinInfo.Items.Objects[Index]) do
begin
DrawText(lbWinInfo.Canvas.Handle, PChar(WindowName),
Length(WindowName), Rect,dt_Left or dt_VCenter);
{ dostosuj położenie i szerokość wyświetlanej informacji do rubryki
określonej przez nagłówek
}
Rect.Left := Rect.Left + hdWinInfo.Sections[0].Width;
DrawText(lbWinInfo.Canvas.Handle, PChar(WindowClass),
Length(WindowClass), Rect, dt_Left or dt_VCenter);
end;
end;
procedure TMainForm.hdWinInfoSectionResize(HeaderControl:
THeaderControl; Section: THeaderSection);
begin
lbWinInfo.Invalidate; // wyświetl ponownie zawartość listy
end;
end.
Funkcja zwrotna, wywoływana w kontekście konkretnego okna, pobiera jego tytuł oraz nazwę jego klasy i
zapisuje te informacje w obiekcie TWindowInfo, dodawanym następnie do listy wyświetlanej na formularzu.
Ponieważ postać wyświetlanej informacji musi być dostosowana do układu kolumnowego narzuconego przez
komponent THeaderControl, jest ona wypisywana w sposób specyficzny dla listy (owner drawing) w ramach
zdarzenia OnDrawItem. Zajmiemy się najpierw działaniem samej funkcji zwrotnej, następnie wyjaśnimy
szczegóły wspomnianego rysowania specyficznego.
Działanie funkcji zwrotnej
Funkcja zwrotna nosi nazwę EnumWindowsProc() i posiada dwa parametry. Pierwszy parametr jest uchwytem
okna, którego dotyczy wywołanie, drugi natomiast ma wartość 0 i nie jest do niczego wykorzystywany; wartość
ta przekazywana jest jako drugi parametr wywołania funkcji EnumWindows(). Jak każda funkcja zwrotna,
funkcja EnumWindows() stosuje standardową dla Win32 konwencję wywołania stdcall.
Na podstawie otrzymanego uchwytu okna funkcja pobiera jego tytuł oraz nazwę klasy i zapisuje te informacje w
nowo tworzonym obiekcie klasy TWindowInfo. Wskaznik tego obiektu jest następnie dodawany do tablicy
Objects listy lbWinInfo, służącej do wyświetlania informacji na formularzu głównym. Zwróć uwagę na
ważny fakt, iż destruktor listy TListBox nie zwalnia obiektów wskazywanych przez jego tablicę Objects i
użytkownik musi dokonać tego zwolnienia w sposób jawny; w naszym przypadku zwolnienie to odbywa się w
ramach zdarzenia OnDestroy formularza.
Enumeracja rozpoczyna się w momencie kliknięcia jedynego przycisku formularza głównego  obsługa tego
kliknięcia sprowadza się do wywołania funkcji EnumWindows() z odpowiednimi parametrami.
Funkcja EnumWindowsProc() zwraca zawsze wartość True, enumeracja jest więc wykonywana dla wszystkich
okien pierwszego poziomu.
Specyficzne wyświetlanie elementów listy
Jedyną informacją zawartą w liście lbWinInfo są wskazniki do obiektów TWindowInfo. Właściwość Items
nie zawiera żadnego łańcucha, zatem lista musi być wyświetlona w sposób specyficzny; ustawiamy więc jej
właściwość Style na lbOwnerDrawFixed. Odpowiedzialność za wyświetlenie konkretnego jej elementu
spoczywa odtąd na procedurze obsługującej zdarzenie OnDrawItem.
Obsługa zdarzenia OnDrawItem rozpoczyna się od wypisania pierwszego łańcucha zawierającego tytuł okna. W
miejscu, gdzie rozpoczyna się druga kolumna, przeznaczona na nazwę klasy okna, nadpisywany jest na niego
drugi łańcuch, zawierający tę nazwę. Szerokość pierwszej rubryki pobierana jest z właściwości
Sections[0].Width komponentu nagłówkowego THeaderControl.
Wywoływanie funkcji zwrotnych z bibliotek DLL
W poprzednim przykładzie organizacją iteracji i wywoływaniem funkcji zwrotnej zajmował się sam system
operacyjny. Obecnie zademonstrujemy przykład wywołania funkcji zwrotnej przez zewnętrzną bibliotekę DLL.
Przykładem takiej biblioteki jest projekt StrSrchLib.dpr, którego treść przedstawiamy na wydruku 6.10.
Wydruk 6.10. Przykład biblioteki DLL dokonującej odwołań zwrotnych
library StrSrchLib;
uses
Wintypes,
WinProcs,
SysUtils,
Dialogs;
type
{ zadeklaruj typ funkcji zwrotnej }
TFoundStrProc = procedure(StrPos: PChar); StdCall;
function SearchStr(ASrcStr, ASearchStr: PChar; AProc: TFarProc): Integer; StdCall;
{ Niniejsza funkcja szuka wystąpienia podłańcucha ASearchStr
w łańcuchu ASrcStr. W przypadku jego znalezienia wywoływana jest
funkcja zwrotna identyfikowana przez AProc ze znalezionym wystąpieniem
łańcucha jako parametrem. Poszukiwanie jest następnie kontynuowane
w celu znalezienia ewentualnych dalszych wystąpień.
}
var
FindStr: PChar;
begin
FindStr := ASrcStr;
FindStr := StrPos(FindStr, ASearchStr);
while FindStr <> nil do
begin
if AProc <> nil then
TFoundStrProc(AProc)(FindStr);
FindStr := FindStr + 1;
FindStr := StrPos(FindStr, ASearchStr);
end;
end;
exports
SearchStr;
begin
end.
Funkcja SearchStr() poszukuje wystąpień określonego wzorca w łańcuchu i dla każdego wystąpienia tego
wzorca wywołuje funkcję zwrotną określoną przez parametr AProc; jedynym parametrem wywołania tej funkcji
jest adres wystąpienia wzorca w łańcuchu.
Ponieważ adres funkcji zwrotnej przekazywany jest w postaci amorficznego wskaznika, musi on być rzutowany
na typ zgodny z jej deklaracją; typ ten deklarowany jest jako TFoundStrProc.
Kolejny projekt  CallBackDemo.dpr  jest ilustracją wykorzystania biblioteki StrSrchLib, gdyż zawiera
definicję wywoływanej przez nią funkcji zwrotnej; treść jego modułu głównego przedstawiamy na wydruku
6.11.
Wydruk 6.11. Przykładowa aplikacja zawierająca funkcję zwrotną wywoływaną z biblioteki DLL
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;
type
TMainForm = class(TForm)
btnCallDLLFunc: TButton;
edtSearchStr: TEdit;
lblSrchWrd: TLabel;
memStr: TMemo;
procedure btnCallDLLFuncClick(Sender: TObject);
end;
var
MainForm: TMainForm;
Count: Integer;
implementation
{$R *.DFM}
{ zaimportuj funkcję SearchStr z biblioteki DLL }
function SearchStr(ASrcStr, ASearchStr: PChar; AProc: TFarProc): Integer; StdCall
external
'STRSRCHLIB.DLL';
{ zdefiniuj funkcję zwrotną wywoływaną przez funkcję SearchStr }
procedure StrPosProc(AStrPsn: PChar); StdCall;
begin
inc(Count); // zwiększ licznik wystąpień
end;
procedure TMainForm.btnCallDLLFuncClick(Sender: TObject);
var
S: String;
S2: String;
begin
Count := 0; // zainicjuj licznik
{ zapisz w zmiennej S łańcuch, w którym prowadzone będzie poszukiwanie }
SetLength(S, memStr.GetTextLen);
memStr.GetTextBuf(PChar(S), memStr.GetTextLen);
{ zapisz w zmiennej S2 poszukiwany wzorzec }
S2 := edtSearchStr.Text;
{ wywołaj funkcję z biblioteki, przekazując łańcuch zródłowy
i poszukiwany wzorzec }
SearchStr(PChar(S), PChar(S2), @StrPosProc);
{ wyświetl liczbę wystąpień wzorca w łańcuchu, obliczoną przez
funkcję zwrotną
}
ShowMessage(Format('%s%s%s %d %s', ['Aańcuch "', edtSearchStr.Text, '" wystąpił',
Count, 'razy.']));
end;
end.
Przeszukiwanym łańcuchem jest tutaj zawartość komponentu TMemo, natomiast poszukiwany wzorzec pobierany
jest za pomocą kontrolki TEdit. Procedura StrPosProc(), która pełni rolę funkcji zwrotnej, zlicza
wystąpienia wzorca w przeszukiwanym łańcuchu.
Współdzielenie danych biblioteki DLL przez różne
procesy
W środowisku 16-bitowym współdzielenie danych globalnych biblioteki DLL przez kilka aplikacji było czymś
naturalnym i nieuniknionym  wszystkie aplikacje działały w ramach tej samej przestrzeni adresowej, toteż
dany fragment biblioteki widziany był przez wszystkie pod tym samym adresem. Taki stan rzeczy powodował,
że biblioteki DLL stanowiły naturalny obszar wymiany danych między aplikacjami i  niestety  również
arenę niepożądanych interferencji, co projektanci zawsze musieli brać pod uwagę.
Wyjaśnialiśmy już wcześniej zasady wykorzystywania bibliotek DLL w Win32  w warunkach, gdy każda
biblioteka DLL istnieje wyłącznie pod postacią swoich instancji w przestrzeniach adresowych poszczególnych
procesów; wobec rozłączności tych przestrzeni, tradycyjna technika rodem z Windows 3.x na nic się w tym
wypadku nie przyda.
Nie oznacza to bynajmniej, iż komunikacja dwóch aplikacji przez tę samą bibliotekę DLL jest zupełnie
niemożliwa; nadal możliwe jest komunikowanie się przez obszar danych biblioteki DLL (co może być przydatne
przy przenoszeniu aplikacji 16-bitowych do Delphi 6), realizowane jest jednak za pomocą zupełnie innych
środków  mianowicie mechanizmu plików odwzorowanych, który opisaliśmy ze szczegółami na stronach 580
 598 książki  Delphi 4. Vademecum profesjonalisty . W tym miejscu ograniczymy się tylko do jego wybranych
elementów.
Tworzenie bibliotek DLL z pamięcią dzieloną
Poniższy wydruk przedstawia plik projektu biblioteki ShareLib.dll, zawierający kod, który umożliwia
współdzielenie obszaru danych tej biblioteki. Obszar ten wskazywany jest przez zmienną o nazwie
GlobalData.
Wydruk 6.12. Biblioteka DLL umożliwiająca współdzielenie swego obszaru danych przez różne procesy
library ShareLib;
uses
ShareMem,
Windows,
SysUtils,
Classes;
const
cMMFileName: PChar = 'SharedMapData';
{$I DLLDATA.INC}
var
GlobalData : PGlobalDLLData;
MapHandle : THandle;
{ fnkcja importowana z biblioteki DLL }
procedure GetDLLData(var AGlobalData: PGlobalDLLData); StdCall;
begin
{ pobierz wskazanie na obszar danych globalnych biblioteki }
AGlobalData := GlobalData;
end;
procedure OpenSharedData;
var
Size: Integer;
begin
{ pobierz rozmiar mapowanych danych }
Size := SizeOf(TGlobalDLLData);
{ Stwórz obiekt mapujący; zwróć uwagę, iż zamiast uchwytu odwzorowywanego
pliku występuje $FFFFFFFF co oznacza, iż plik ten nie będzie widoczny na zewnątrz
pod konkretną nazwą, lecz stanowił będzie fragment pliku wymiany.
Wymaga to, by obiekt mapujący posiadał unikatową nazwę
}
MapHandle := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, Size,
cMMFileName);
if MapHandle = 0
then
RaiseLastWin32Error;
{ dokonaj mapowania w obszar pamięci i przypisz adres tego obszaru
do zmiennej GlobalData
}
GlobalData := MapViewOfFile(MapHandle, FILE_MAP_ALL_ACCESS, 0, 0, Size);
{ Zainicjuj dane globalne jakąś zawartością }
GlobalData^.S := 'ShareLib';
GlobalData^.I := 1;
if GlobalData = nil then
begin
CloseHandle(MapHandle);
RaiseLastWin32Error;
end;
end;
procedure CloseSharedData;
{ zwolnij obszar pamięci odwzorowujący zawartość pliku
i obiekt mapujący
}
begin
UnmapViewOfFile(GlobalData);
CloseHandle(MapHandle);
end;
procedure DLLEntryPoint(dwReason: DWord);
begin
case dwReason of
DLL_PROCESS_ATTACH: OpenSharedData;
DLL_PROCESS_DETACH: CloseSharedData;
end;
end;
exports
GetDLLData;
begin
{ przypisz procedurę inicjująco-kończącą }
DllProc := @DLLEntryPoint;
DLLEntryPoint(DLL_PROCESS_ATTACH);
end.
Współdzielone dane posiadają następującą strukturę, zdefiniowaną w dołączonym pliku DLLDATA.INC:
PGlobalDLLData = ^TGlobalDLLData;
TGlobalDLLData = record
S: String[50];
I: Integer;
end;
Kod biblioteki wykorzystuje mechanizm procedury inicjująco-kończącej; za jej pośrednictwem wywoływane
są dwie procedury: OpenSharedData() (przy rozpoczynaniu programu) oraz CloseSharedData() (przy
jego kończeniu).
Mechanizm plików odwzorowanych umożliwia  mówiąc ogólnie  zarezerwowanie regionu w wirtualnej
przestrzeni adresowej Win32 i związanie z nim rzeczywistego fragmentu pamięci fizycznej. Przypomina to
trochę klasyczny przydział pamięci na stercie i odwoływanie się do niej za pomocą wskaznika; mechanizm
plików odwzorowanych umożliwia jednak znacznie więcej  mianowicie odwoływanie się za pomocą
typowego wskaznika (pointer) do fragmentu (lub całości) pliku dyskowego tak, jakby stanowił on fragment
pamięci procesu. W ten właśnie sposób biblioteki DLL włączane są do przestrzeni adresowej procesu  w
postaci oryginalnej, bądz też w postaci relokowanej kopii, o czym pisaliśmy w związku z bazowym adresem
ładowania.
Tworząc odwzorowanie pliku w pamięć aplikacji, musimy najpierw związać z plikiem obiekt realizujący to
odwzorowanie; niezbędnymi informacjami do jego utworzenia są m.in. uchwyt pliku, początek i wielkość
odwzorowywanego obszaru, tryb wykorzystywania pliku oraz nazwa, pod którą obiekt ten będzie
identyfikowany w systemie.
Rozpatrzmy następujący scenariusz. Aplikacja, którą nazwiemy umownie App1, dokonuje odwzorowania pliku
dyskowego o nazwie (na przykład) MYFILE.DAT; od tej chwili może ona zapisywać i odczytywać dane do i z
pliku tak, jakby stanowił on fragment jej przestrzeni adresowej. Jeśli teraz, w czasie wykonywania aplikacji
App1 inna aplikacja  nazwijmy ją App2  dokona odwzorowania tego samego pliku, to zmiany dokonane
w pliku przez jedną aplikację będą natychmiast widoczne dla drugiej. Rozłączność przestrzeni adresowych
obydwu procesów w niczym tu nie przeszkadza  w obydwu jest obecne odwzorowanie tego samego pliku.
W opisanym scenariuszu konkretna nazwa pliku nie ma żadnego znaczenia; ważne, by obydwie aplikacje
używały tego samego pliku. Win32 API oferuje w związku z tym rozwiązanie o wiele bardziej eleganckie:
ponieważ plik ten nie pełni żadnej samoistnej roli, możliwe jest użycie zamiast niego wewnętrznych struktur
pamięci wirtualnej; zamiast uchwytu pliku, należy w tym celu podać wartość $FFFFFFFF. Elementem wiążącym
komunikujące się aplikacje będzie wówczas nie nazwa pliku (bo tej po prostu nie ma), lecz nazwa obiektu
mapującego, w naszym projekcie ukrywająca się pod stałą cMMFileName.
Notatka
Jeżeli w miejsce uchwytu pliku podano wartość $FFFFFFFF, to podanie nazwy obiektu (jako ostatniego
parametru funkcji CreateFileMapping()) jest konieczne. Nazwa ta stanowi jedyny systemowy identyfikator
obiektu odwzorowującego i jednocześnie zarezerwowanego regionu pamięci systemowej  dwa obiekty
odwzorowujące, pochodzące z różnych aplikacji, lecz posiadające tę samą nazwę, będą uważane za obiekty
realizujące to samo odwzorowanie.
Opisany przed chwilą scenariusz dzielenia danych między dwie aplikacje daje się także zastosować do dzielenia
danych między aplikację a bibliotekę DLL i, w konsekwencji  wykorzystanie globalnych danych biblioteki
jako medium dzielonego między dwie aplikacje, co  zgodnie z tytułem  jest zasadniczym tematem tego
podrozdziału.
Procedura OpenSharedData()z wydruku 6.12 tworzy odwzorowanie pliku w pamięci procesu. Pierwszym
krokiem jest stworzenie, za pomocą funkcji CreateFileMapping(), obiektu reprezentującego
odwzorowywany plik; obiekt ten jest następnie odwzorowywany w obszar pamięci operacyjnej za pomocą
funkcji MapViewOfFile(), która tym samym zwraca  wskaznik do zawartości pliku. Oczywiście dla dwóch
różnych aplikacji wartości tego wskaznika będą na ogół różne, poza tym obydwa wskazniki odnosić się będą do
różnych przestrzeni adresowych, ważne jest jednak to, że odwzorowuje się ten sam obszar pliku.
Wynik funkcji MapViewOfFile()  przypisywany zmiennej GlobalData  stanowi wskaznik do
globalnego obszaru dzielonych danych; biblioteka udostępnia go aplikacji wywołującej za pośrednictwem
funkcji GetDLLData(). W ten oto sposób dwie różne aplikacje, importujące z biblioteki DLL funkcję
GetDLLData(), mogą uzyskać wskazniki do tego samego globalnego obszaru pamięci.
Po zakończeniu procesu, procedura CloseSharedData() zrywa połączenie pomiędzy przestrzenią adresową
procesu a globalnym obszarem systemowym (UnmapVievOfFile()) oraz likwiduje obiekt odwzorowujący,
zamykając jego uchwyt (CloseHandle()).
Notatka
Niewątpliwie istotą powyższego przykładu jest sam mechanizm plików odwzorowanych, bez jakiegokolwiek
związku z konkretnymi mechanizmami charakterystycznymi dla bibliotek DLL. Fakt, iż przykład ten znalazł się
w rozdziale dotyczącym bibliotek DLL wynika z roli bibliotek w aplikacjach 16-bitowych: programista
przenoszący do 32-bitowej wersji Delphi 16-bitową aplikację traktującą jakąś bibliotekę DLL na modłę
 skrzynki kontaktowej zyskuje oto gotowe rozwiązanie, uwalniające go od gruntownego
 przeprogramowywania mechanizmów komunikacyjnych (przyp. tłum.).
Dzielenie globalnych danych biblioteki przez aplikacje
Na załączonym krążku CD-ROM znajdują się projekty dwóch aplikacji  App1.dpr i App2.dpr. Obydwie te
aplikacje uzyskują dostęp do globalnych danych biblioteki ShareLib.DLL wywołując funkcję GetDLLData()
zwracającą wskaznik do tego obszaru.
Formularz pierwszej z wymienionych aplikacji zawiera dwa pola edycyjne odpowiadające polom S oraz I
rekordu TGlobalData. Każdorazowa zmiana któregokolwiek z tych pól powoduje stosowne uaktualnienie
danych globalnych; uaktualnienie takie następuje również w wyniku kliknięcia (jedynego) przycisku formularza.
Kod zródłowy modułu głównego aplikacji App1 został przedstawiony na wydruku 6.13.
Wydruk 6.13. Kod zródłowy formularza aplikacji modyfikującej dane globalne
unit MainFrmA1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ExtCtrls, Mask;
{$I DLLDATA.INC}
type
TMainForm = class(TForm)
edtGlobDataStr: TEdit;
btnGetDllData: TButton;
meGlobDataInt: TMaskEdit;
procedure btnGetDllDataClick(Sender: TObject);
procedure edtGlobDataStrChange(Sender: TObject);
procedure meGlobDataIntChange(Sender: TObject);
procedure FormCreate(Sender: TObject);
public
GlobalData: PGlobalDLLData;
end;
var
MainForm: TMainForm;
{ zaimportuj procedurę udostępniającą dane globalne }
procedure GetDLLData(var AGlobalData: PGlobalDLLData); StdCall External 'SHARELIB.DLL';
implementation
{$R *.DFM}
procedure TMainForm.btnGetDllDataClick(Sender: TObject);
begin
{ pobierz wskaznik do danych globalnych }
GetDLLData(GlobalData);
{ uaktualnij kontrolki tak, by odzwierciedlały zawartość danych globalnych }
edtGlobDataStr.Text := GlobalData^.S;
meGlobDataInt.Text := IntToStr(GlobalData^.I);
end;
procedure TMainForm.edtGlobDataStrChange(Sender: TObject);
begin
{ uaktualnij zawartość danych globalnych }
GlobalData^.S := edtGlobDataStr.Text;
end;
procedure TMainForm.meGlobDataIntChange(Sender: TObject);
begin
{ uaktualnij zawartość danych globalnych }
if meGlobDataInt.Text = EmptyStr then
meGlobDataInt.Text := '0';
GlobalData^.I := StrToInt(meGlobDataInt.Text);
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
btnGetDllDataClick(nil);
end;
end.
Druga aplikacja odczytuje dane globalne i wyświetla je na swym formularzu. Momenty odczytu wyznaczane są
przez komponent zegarowy TTimer, zaś do wyświetlenia globalnych danych służą dwie etykiety TLabel. Gdy
zmienisz zawartość kontrolek edycyjnych na formularzu aplikacji App1, zaobserwujesz konsekwencje tych
zmian na formularzu aplikacji App2  z opóznieniem wynikającym z częstotliwości  tykania komponentu
TTimer.
Kod zródłowy formularza aplikacji App2 jest przedstawiony na wydruku 6.14.
Wydruk 6.14. Kod zródłowy formularza aplikacji odczytującej dane globalne
unit MainFrmA2;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, StdCtrls;
{$I DLLDATA.INC}
type
TMainForm = class(TForm)
lblGlobDataStr: TLabel;
tmTimer: TTimer;
lblGlobDataInt: TLabel;
procedure tmTimerTimer(Sender: TObject);
public
GlobalData: PGlobalDLLData;
end;
{ zaimportuj procedurę udostępniającą dane globalne }
procedure GetDLLData(var AGlobalData: PGlobalDLLData); StdCall External 'SHARELIB.DLL';
var
MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TMainForm.tmTimerTimer(Sender: TObject);
begin
GetDllData(GlobalData); // uzyskaj dostęp do danych
{ wyświetl zawartość danych globalnych }
lblGlobDataStr.Caption := GlobalData^.S;
lblGlobDataInt.Caption := IntToStr(GlobalData^.I);
end;
end.
Aby się przekonać o tym, iż opisana komunikacja rzeczywiście funkcjonuje, wystarczy uruchomić obydwie
aplikacje z poziomu pulpitu  ich pliki wykonywalne znajdują się na załączonym krążku CD-ROM.
Eksportowanie obiektów z bibliotek DLL
Nieco wcześniej zaprezentowaliśmy wykorzystanie formularzy definiowanych w bibliotekach DLL, obecnie
pokażemy, w jaki sposób wykorzystać zawartą w DLL definicję klasy. Opisywana tu technika ma raczej
ograniczone zastosowanie  podobny efekt osiągnąć można za pomocą pakietów czy interfejsów 
prezentujemy ją tu jednak jako jeszcze jedną możliwość wykorzystania bibliotek DLL.
Skoro biblioteki DLL są z natury niezależne od konkretnego języka programowania, to można się spodziewać, iż
eksportowanie obiektu z biblioteki DLL ograniczone będzie jedynie do elementów na swój sposób
uniwersalnych. Istotnie, istnieją ograniczenia dotyczące samej klasy, jak i jej wykorzystywania  oto
najważniejsze z nich:
" Jedynymi elementami klasy, do których ma prawo odwoływać się aplikacja wywołująca, są jej metody
wirtualne.
" Egzemplarze obiektu mogą być tworzone jedynie wewnątrz biblioteki  na zewnątrz udostępniać
można jedynie ich wskazniki lub inne identyfikujące je wielkości.
" Aplikacji wywołującej musi być znany zestaw i kolejność definicji metod wirtualnych klasy. Innymi
słowy  deklaracja klasy, którą posługuje się aplikacja wywołująca, musi być zgodna z deklaracją klasy
w bibliotece DLL co najmniej pod względem kolejności i początkowego zestawu deklarowanych metod
wirtualnych (niewykorzystane  końcowe deklaracje można pominąć).
" Niedozwolone jest definiowanie (w aplikacji wywołującej) klas pochodnych w stosunku do klasy
zdefiniowanej w bibliotece DLL.
W charakterze przykładu zdefiniowaliśmy prostą klasę TStringConvert, której możliwości sprowadzają się do
konwersji zadanego łańcucha znaków na małe lub duże litery. Deklarację tej klasy umieściliśmy w pliku
StrConvert.Inc, prezentowanym na poniższym wydruku; deklaracja ta dostępna jest dzięki temu zarówno dla
biblioteki DLL, jak i aplikacji wywołującej, zaś ewentualne jej modyfikacje dokonywane będą tylko
jednokrotnie.
Wydruk 6.15. Deklaracja klasy eksportowanej z biblioteki DLL
type
TConvertType = (ctUpper, ctLower);
TStringConvert = class(TObject)
{$IFDEF STRINGCONVERTLIB}
private
FPrepend: String;
FAppend : String;
{$ENDIF}
public
function ConvertString(AConvertType: TConvertType; AString: String): String;
virtual; stdcall; {$IFNDEF STRINGCONVERTLIB} abstract; {$ENDIF}
{$IFDEF STRINGCONVERTLIB}
constructor Create(APrepend, AAppend: String);
destructor Destroy; override;
{$ENDIF}
end;
{
Z punktu widzenia aplikacji wywołującej symbol STRINGCONVERTLIB nie jest
zdefiniowany, deklaracja klasy jest więc równoważna następującej:
TStringConvert = class(TObject)
public
function ConvertString(AConvertType: TConvertType; AString: String): String;
virtual; stdcall; abstract;
end;
}
Zwróć uwagę na deklarację jedynej metody wirtualnej:
function ConvertString(AConvertType: TConvertType; AString: String): String;
virtual; stdcall; abstract;
Metoda ta zadeklarowana jest jako wirtualna nie dlatego, by można ją przedefiniować w klasie pochodnej  tej
przecież nie wolno definiować w aplikacji wywołującej  lecz po to, by była dostępna za pośrednictwem
tablicy VMT. Jak wiadomo, częścią tablicy VMT jest lista adresów metod wirtualnych; kolejność tych adresów
na liście wynika z kolejności deklarowania poszczególnych metod. W całym łańcuchu klas pochodnych
określona metoda posiada tę samą pozycję na wspomnianej liście; aby odnalezć adres tej metody w konkretnej
klasie, należy jeszcze tylko uzyskać adres związanej z tą klasą tablicy VMT  co jest sprawą oczywistą, jeżeli
dysponuje się wskaznikiem do konkretnego obiektu tej klasy.
Zrozumiałe jest więc w tym kontekście zarówno ograniczenie się do wirtualnych metod klasy, jak i zgodności
ich deklaracji pod względem struktury tablic VMT.
Wskazówka
Struktura tablicy VMT opisana jest szczegółowo na stronach 44  46 książki  Delphi 5. Vademecum
profesjonalisty  suplement .
Istnienie symbolu kompilacji warunkowej STRINGCONVERTLIB wynika z faktu, iż niektóre elementy deklaracji
klasy powinny być widoczne jedynie w bibliotece DLL, nigdy zaś w aplikacji wywołującej  do elementów
takich należą m.in. konstruktor i destruktor. Klauzula abstract przeznaczona jest natomiast wyłącznie dla
aplikacji wywołującej  zwalniając ją z konieczności definiowania zadeklarowanej klasy (z punktu widzenia
aplikacji importowana z biblioteki DLL klasa jest klasą czysto wirtualną  pure virtual class).
Definicja klasy (w projekcie realizującym bibliotekę DLL) znajduje się w module StringConvertImp.pas,
którego treść przedstawia wydruk 6.16.
Wydruk 6.16. Definicja eksportowanej klasy
unit StringConvertImp;
{$DEFINE STRINGCONVERTLIB}
interface
uses SysUtils;
{$I StrConvert.inc}
function InitStrConvert(APrepend, AAppend: String): TStringConvert; stdcall;
implementation
constructor TStringConvert.Create(APrepend, AAppend: String);
begin
inherited Create;
FPrepend := APrepend;
FAppend := AAppend;
end;
destructor TStringConvert.Destroy;
begin
inherited Destroy;
end;
function TStringConvert.ConvertString(AConvertType: TConvertType; AString: String):
String;
begin
case AConvertType of
ctUpper: Result := Format('%s%s%s', [FPrepend, AnsiUpperCase(AString), FAppend]);
ctLower: Result := Format('%s%s%s', [FPrepend, AnsiLowerCase(AString), FAppend]);
end;
end;
function InitStrConvert(APrepend, AAppend: String): TStringConvert;
begin
Result := TStringConvert.Create(APrepend, AAppend);
end;
end.
Oprócz metod eksportowanej klasy moduł ten definiuje także funkcję InitStrConvert(), tworzącą
egzemplarz obiektu i zwracającą jego wskaznik. Przekazywane do konstruktora parametry tej funkcji
umożliwiają określenie przedrostka i przyrostka, którymi dodatkowo opatrywany będzie konwertowany łańcuch.
Funkcja ta jest jedyną funkcją eksportowaną z biblioteki, o czym łatwo się przekonać, zerknąwszy na główny
plik projektu  StringConvertLib.dpr:
Wydruk 6.17. Plik główny projektu biblioteki DLL
library StringConvertLib;
uses
ShareMem,
SysUtils,
Classes,
StringConvertImp in 'StringConvertImp.pas';
exports
InitStrConvert;
end.
Zwróć uwagę na obecność nazwy ShareMem na liście uses  parametrami eksportowanej funkcji są długie
łańcuchy, więc użycie modułu ShareMem jest konieczne.
Projekt aplikacji wywołującej nosi nazwę StrConvertTest.dpr. Jego formularz zawiera kontrolkę edycyjną i
dwa przyciski, powodujące zamianę tekstu w kontrolce edycyjnej na (odpowiednio) duże lub małe litery. Kod
formularza głównego projektu przedstawiamy na wydruku 6.18.
Wydruk 6.18. Aplikacja importująca klasę z biblioteki DLL
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
{$I strconvert.inc}
type
TMainForm = class(TForm)
btnUpper: TButton;
edtConvertStr: TEdit;
btnLower: TButton;
procedure btnUpperClick(Sender: TObject);
procedure btnLowerClick(Sender: TObject);
private
public
end;
var
MainForm: TMainForm;
function InitStrConvert(APrepend, AAppend: String): TStringConvert; stdcall;
external 'STRINGCONVERTLIB.DLL';
implementation
{$R *.DFM}
procedure TMainForm.btnUpperClick(Sender: TObject);
var
ConvStr: String;
FStrConvert: TStringConvert;
begin
FStrConvert := InitStrConvert('Duże : "', '"');
try
ConvStr := edtConvertStr.Text;
if ConvStr <> EmptyStr then
edtConvertStr.Text := FStrConvert.ConvertString(ctUpper, ConvStr);
finally
FStrConvert.Free;
end;
end;
procedure TMainForm.btnLowerClick(Sender: TObject);
var
ConvStr: String;
FStrConvert: TStringConvert;
begin
FStrConvert := InitStrConvert('Małe : "', '"');
try
ConvStr := edtConvertStr.Text;
if ConvStr <> EmptyStr then
edtConvertStr.Text := FStrConvert.ConvertString(ctLower, ConvStr);
finally
FStrConvert.Free;
end;
end;
end.
Aplikacja rozpoczyna swą pracę od utworzenia (wewnątrz biblioteki DLL) odnośnego obiektu i uzyskania jego
wskaznika. Procedury obsługujące kliknięcie poszczególnych przycisków wywołują metodę ConvertString()
wskazywanego obiektu. Zwolnienie egzemplarza obiektu odbywa się przez wywołanie jego destruktora.
Podsumowanie
Biblioteki DLL stanowią podstawowy element aplikacji dla Windows i samego systemu Win32, głównie dzięki
wielokrotnemu wykorzystaniu tego samego kodu i zasobów (reusability), dlatego też poświęciliśmy im dość
obszerny rozdział. Na początku opisaliśmy zasady tworzenia projektów generujących biblioteki DLL. Następnie
pokazaliśmy dwa sposoby łączenia biblioteki z aplikacją  domyślny i jawny. Na zakończenie
zademonstrowaliśmy dzielenie globalnych danych biblioteki przez dwie aplikacje za pomocą techniki plików
odwzorowanych, a także wykorzystywanie klasy zdefiniowanej w bibliotece DLL.


Wyszukiwarka

Podobne podstrony:
04 (131)
2006 04 Karty produktów
04 Prace przy urzadzeniach i instalacjach energetycznych v1 1
04 How The Heart Approaches What It Yearns
str 04 07 maruszewski
[W] Badania Operacyjne Zagadnienia transportowe (2009 04 19)
Plakat WEGLINIEC Odjazdy wazny od 14 04 27 do 14 06 14
MIERNICTWO I SYSTEMY POMIAROWE I0 04 2012 OiO
r07 04 ojqz7ezhsgylnmtmxg4rpafsz7zr6cfrij52jhi
04 kruchosc odpuszczania rodz2
Rozdział 04 System obsługi przerwań sprzętowych
KNR 5 04

więcej podobnych podstron