Rozdział 21
Bi ' ' '
I
ote k n
amiczne
Yn ( ^' y y , k-:...
Biblioteki d amiczne naz ane bibliotekami konsolidowan mi d namicznie
bibliotekami łączonymi dynamicznie, modułami bibliotecznymi czy po prostu '; ;
DLL-ami) to jedne z najważniejszych składników strukturalnych systemów Mi-
crosoft Windows. Większość plików skojarzonych z Windows to albo programy
(pliki wykonywalne), albo moduły bibliotek dynamicznych. Do tej pory pisali- .
śmy tylko programy Windows, teraz nadszedł czas zająć się pisaniem bibliotek
dynamicznych. Wiele zasad, które dotyczą pisania programów, odnosi się rów-
nież do pisania bibliotek, niemniej są pewne istotne różnice. ;. .. .
Podstawowe informacje o bibliotekach
Jak już wiadomo, program działający w Windows to zawarty w pliku wykony-
walnym kod tworzący jakieś okna i wykorzystujący pętlę komunikatów do od-
bierania danych wejściowych od użytkownika. Biblioteki dynamiczne są gene-
ralnie obiektami, których nie można bezpośrednio uruchamiać i które nie mogą
otrzymywać komunikatów. Są to osobne pliki, zawierające funkcje wywoływane
przez programy i inne biblioteki w celu wykonania konkretnych zadań. Bibliote-
ka dynamiczna pojawia się tylko wtedy, gdy jakiś program powoła się na zawar-
tą w niej funkcję.
Pojęcie konsolidacji dynamicznej oznacza proces, podczas którego Windows kon-
soliduje (albo mówiąc żargonowo: linkuje) wywołanie funkcji z jakiegoś mo-
duhx z konkretną funkcją zawartą w bibliotece. Konsolidacja statyczna to etap
pisania programów, podczas którego z plików obiektowych (OBJ), bibliotecznych
(LIB) i na ogół również z plików skompilowanych zasobów (RES) uzyskuje się
plik wykonywalny (EXE). Konsolidacja dynamiczna, jak wskazuje nazwa, odby-
wa się podczas działania programu.
Bibliotekami dynamicznymi są między innymi KERNEL32.DLL, USER32.DLL
i GDI32.DLL, a także różne pliki sterowników, takie jak KEYBOARD.DRV, SYS-
TEM.DRV czy MOUSE.DRV. Są to biblioteki, których używają wszystkie progra-
my Windows.
Niektóre biblioteki dynamiczne (na przykład pliki czcionek) są nazywane czysto
zasobowymi, ponieważ zawierają same dane (zazwyczaj w postaci zasobów), bez
kodu. Tak więc przeznaczeniem biblioteki dynamicznej jest zapewnianie funkcji
i zasobów wielu różnym programom. W tradycyjnym systemie operacyjnym
wszystkie procedury używane przez programy użytkowe udostępnia wyłącznie
1108 Część III: Zagadnienia zaawansowane
system. W Windows proces wywoływania jednych modułów przez inne został
uogólruony. W efekcie, pisząc bibliotekę dynamiczną, tworzymy rozszerzenie
systemu Windows. Z drugiej strony DLL-e (również te tworzące Windows) moż-
na również postrzegać jako rozszerzenie programu użytkowego.
Choć moduł biblioteki dynamicznej może mieć dowolne rozszerzenie (na przy-
kład .EXE lub .FON), standardowo używa się rozszerzenia .DLL. Windows auto-
matycznie ładuje jednak tylko biblioteki z rozszerzeniem .DLL. Jeżeli plik ma inne
rozszerzenie, program musi go ładować jawnie, czyli za pomocą funkcji LoadLi-
brary lub LoadLibraryEx.
Sensownym zastosowaniem dla bibliotek dynamicznych są raczej większe apli-
kacje. Załóżmy, że piszesz duży pakiet finansowo-księgowy, który będzie zawie-
rał wiele różnych programów. Z pewnością stwierdzisz, że mają one wiele wspól-
nych procedur. W takim wypadku można te wspólne procedury umieścić w nor-
malnej bibliotece statycznej (z rozszerzeniem .LIB) i skonsolidować z nią statycz-
nie poszczególne moduły programowe. Ale takie rozwiązanie jest marnotraw-
stwem, ponieważ każdy z programów pakietu zawiera dla identycznych proce-
dur ten sam kod (występuje duża nadmiarowość). Ponadto, jeżeli zmodyfikujesz
jedną z procedur, musisz dokonać ponownej konsolidacji wszystkich programów,
w których ona występowała. Jeżeli jednak zdecydujesz się umieścić wspólne pro-
cedury w bibliotece dynamicznej, na przykład zawartej w pliku FK.DLL, rozwią-
żesz w ten sposób oba przedstawione powyżej problemy. Procedury używane
w wielu programach trafią do jednego tylko modułu, przez co podczas jednocze-
snej pracy kilku aplikacji zmniejszy się zapotrzebowanie na miejsce dyskowe ca-
łego pakietu oraz na pamięć operacyjną. A jeżeli przyjdzie ci wprowadzić jakieś
zmiany, nie będziesz musiał ponownie kompilować poszczególnych programów.
Biblioteki dynamiczne mogą stanowić osobny produkt. Załóżmy, że napiszesz
kolekcję interesujących procedur do rysowania trójwymiarowych obiektów gra-
ficznych i umieścisz ją w pliku DLL o nazwie GDI3.DLL. Jeżeli następnie zainte-
resujesz swoją biblioteką innych programistów, zaproponuj im sprzedaż licencji,
aby mogli odpłatnie korzystać z twoich procedur w swoich programach graficz-
nych. Użytkownik, który korzystałby z kilku takich programów, będzie potrze-
bował tylko jednego pliku GDI3.DLL.
Biblioteka - temat-rzeka
Część nieporozumień wokół bibliotek dynamicznych bierze się z tego, że słowo
biblioteka" występuje w kilku różnych kontekstach. Oprócz bibliotek dynamicz-
"
nych są również biblioteki obiektowe (statyczne) i biblioteki importowe.
Biblioteka obiektowa to plik z rozszerzeniem .LIB, który zawiera kod dopisywa-
ny fizycznie do pliku z rozszerzeniem .EXE podczas procesu zwanego konsoli- i
dacją statyczną. Przykładem takiej biblioteki z pakietu Microsoft Visual C++ jest
normalna biblioteka czasu wykonywania o nazwie LIBC.LIB, którą konsoliduje
się z własnymi programami.
Biblioteki importowe to szczególna postać bibliotek obiektowych. Mają, jak obiek-
towe, rozszerzenie .LIB i są używane przez konsolidator do rozwikływania wy-
wołań funkcji w kodzie. Biblioteki importowe nie zawierają jednak kodu. Dostar-
Rozdział 21: Biblioteki dynamiczne 1109
czają jedynie konsolidatorowi danych niezbędnych do skonstruowania tablic re-
lokacji w pliku EXE dla potrzeb dynamicznego konsolidowania. Bibliotekami
importowymi dla funkcji Windows są KERNEL32.LIB, USER32.LIB i GDI32.LIB
- pliki dostarczane z kompilatorem Microsoftu. Jeżeli wywoła się z programu
funkcję Rectangle, GDI32.LIB informuje LINK, że funkcja ta jest zawarta w biblio-
tece dynamicznej GDI32.DLL. Dane te trafiają do pliku EXE, aby Windows mógł
w chwili działania programu przeprowadzić dynamiczną konsolidację z biblio-
teką GDI32.DLL.
Biblioteki obiektowe i importowe s; używane tylko w fazie pisania programu.
W fazie wykonywania używane są biblioteki dynamiczne. W chwili, kiedy wy-
konuje się program używający biblioteki dynamicznej, musi ona być obecna na
dysku. Windows musi załadować moduł DLL, zanim uruchomi program, który
go wymaga. Plik biblioteki musi znajdować się w katalogu zawierającym plik EXE,
w katalogu bieżącym, w katalogu systemowym Windows, w katalogu Windows
albo w dowolnym z katalogów wymienionych w definicji zmiennej środowisko-
wej PATH. Powyższe katalogi są przeszukiwane właśnie w tej kolejności.
Przykładowy DLL
Choć cały pomysł bibliotek dynamicznych polega na tym, że może z nich korzy-
stać wiele aplikacji, w rzeczywistości na ogół projektuje się je, przynajmniej po-
czątkowo, z myślą o jednej aplikacji - najczęściej o programie testowym.
To właśnie zaraz zrobimy. Napiszemy bibliotekę dynamiczną o nazwie EDR-
LIB.DLL (EDR to skrót od ang. easy drawing routines - proste procedury rysunko-
we). Nasza wersja EDRLIB będzie zawierać tylko jedną funkcję (EdrCenterText),
ale w przyszłości można dodać do niej więcej funkcji upraszczających rysowa-
nie. Z funkcji zawartych w EDRLIB.DLL będzie korzystać aplikacja EDRTEST.EXE.
Napisanie biblioteki wymaga nieco odmiennego podejścia niż to, które poznali-
śmy. Pierwszy raz zajmiemy się możliwością Visual C++. W środowisku tym
wyróżnia się obszary robocze i projekty. Projekt jest na ogół skojarzony z tworze-
niem aplikacji (plików EXE) lub bibliotek dynamicznych (plików DLL). Obszar
roboczy może zawierać kilka projektów. Dotychczas każdy z naszych obszarów
roboczych zawierał tylko jeden projekt. Teraz utworzymy obszar roboczy o na-
zwie EDRTEST, który będzie zawierał dwa projekty - jeden dla EDRTEST.EXE
i jeden dla EDRLIB.DLL, biblioteki dynamicznej wykorzystywanej przez EDR-
TEST.
Do dzieła. Wybierz w Visual C++ polecenie New z menu File. Zaznacz kartę
Workspaces (jeszcze jej nie zaznaczaliśmy). W polu Location wybierz katalog,
w którym chciałbyś umieścić swój obszar roboczy, a w polu Workspace Name
wpisz: EDRTEST. Naciśnij [Enter].
Spowoduje to utworzenie pustego obszaru roboczego. Developer Studio utwo-
rzy podkatalog o nazwie EDRTEST i plik obszaru roboczego EDRTEST.DSW (i pa-
rę innych plików).
Teraz w tym obszarze utworzymy projekt. Wybierz polecenie New z menu File
i zaznacz kartę Projects. Tam, gdzie do tej pory zawsze wybierałeś opcję Win32
Application, tym razem wybierz Win32 Dynamic-Link Library. Kliknij również
1110 Część III: Zagadnienia zaawansowane
przycisk opcji Add To Current Workspace. W ten sposób bieżący projekt zostanie
dodany do obszaru roboczego EDRTEST: Npisz w polu Project Name EDRLIB,
ale nie naciskaj jeszcze przycisku OK. Kiedy to uczynisz, Visual C++ zmieni pole
Location, w którym EDRLIB pojawi się jako podkatalog EDRTEST. Ale nie o to
chodzi! Usuń z pola Location podkatalog EDRLIB, aby projekt został utworzony
w katalogu EDRTEST. Naciśnij OK. Powstanie plik projektu o nazwie EDRLIB.DSP
i (jeżeli wybrałeś opcję Export Makefile na karcie Build okna dialogowego Tools
Options) plik EDRLIB.MAK.
Teraz możesz dodać do projektu kilka plików. Wybierz z menu File polecenie New
i uaktywnij kartę Files. Zaznacz C/C++ Header File i wpisz EDRLIB.H. Wpisz
plik EDRLIB.H z rysunku 21-1 (albo skopiuj go z CD-ROM-u dołączonego do tej
książki). Ponownie wybierz z menu File polecenie New i kliknij kartę Files. Tym
razem wybierz C++ Source File i wpisz EDRLIB.C. Wpisz treść pliku EDRLIB.C
przedstawionego również na rysunku 21-1.
EDRLIB.H
/*
Plik nagłówkowy EDRLIB.H
*/
i fdef cpl uspl us
4define EXPORT extern "C" declspec (dllexport)
el se
define EXPORT declspec (dllexport)
endi f
EXPORT BOOL CALLBACK EdrCenterTextA (HDC, PRECT, PCSTR) ;
EXPORT BOOL CALLBACK EdrCenterTextW (HDC, PRECT, PCWSTR) ;
ifdef UNICODE
4kdefine EdrCenterText EdrCenterTextW
el se
define EdrCenterText EdrCenterTextA
endi f
EDRLIB.C
/*
EDRLIB.C - Modul z prostą biblioteka procedur rysunkowych
(c) Charles Petzold, 1998
*/
iinclude windows.h>
include "edrlib.h"
int WINAPI DllMain (HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
(
return TRUE ;
EXPORT BOOL CALLBACK EdrCenterTextA (HDC hdc, PRECT prc, PCSTR pString)
(
Rozdział 21: Biblioteki dynamiczne 1111
int iLength ;
SIZE size ;
iLength = lstrlenA (pString) ;
GetTextExtentPoint32A (hdc, pString, iLength, &size) ;
return TextOutA (hdc, (prc->right - prc->left - size.cx) / 2,
(prc->bottom - prc->top - size.cy) / 2,
pString, iLength) ;
EXPORT BOOL CALLBACK EdrCenterTextW (HDC hdc, PRECT prc, PCWSTR pString)
(
int iLength ;
SIZE size ;
iLength = lstrlenW (pString) ;
GetTextExtentPoint32W (hdc, pString, iLength, &size) ;
return TextOutW (hdc, (prc->right - prc->left - size.cx) / 2,
(prc->bottom - prc->top - size.cy) / 2,
pString, iLength) ;
Rysunek 21-1. Biblioteka EDRLIB
Teraz możesz zbudować plik EDRLIB.DLL w konfiguracji Release lub Debug. Po
zbudowaniu katalogi RELEASE i DEBUG będą zawierały plik EDRLIB.LIB, któ-
ry jest biblioteką importową dla biblioteki dynamicznej, oraz plik EDRLIB.DLL,
właściwą bibliotekę dynamiczną.
W całej książce tworzyliśmy programy, które można kompilować dla łańcuchów
unikodowych lub nieunikodowych, w zależności od definicji identyfikatora UNI-
CODE. DLL powinien jednak zawierać obie wersje każdej funkcji mającej argu-
menty związane ze znakami i łańcuchami znaków. Tak więc EDRLIB.C zawiera
funkcje o nazwach EdrCenterTextA (wersja ANSI) i EdrCenterTextW (wersja dla zna-
ków szerokich). Funkcja EdrCenterTextA wymaga parametru PCSTR (wskaźnika
do łańcucha zwykłych znaków typu const), a EdrCenterTextW - parametru PCWSTR
(wskaźnika do łańcucha znaków szerokich typu const). Funkcja EdrCenterTextA
wywołuje jawnie lstrlenA, GetTextExtentPoint32A i TextOutA. Natomiast funkcja
EdrCenterTextW wywołuje jawnie lstrlenW, GetTextExtentPoint32W i TextOutW. W
pliku EDRLIB.H zdefiniowano EdrCenterText jako EdrCenterTextW, gdy identyfi-
kator UNICODE jest zdefiniowany, a jako EdrCenterTextA w przypadku, gdy nie
jest zdefiniowany. Tak wyglądają właśnie pliki nagłówkowe Windows.
EDRLIB.H zawiera również funkcję o nazwie DIlMain, która zastępuje w DLL-u
funkcję WinMain i służy do przeprowadzania czynności inicjacyjnych oraz zamy-
kających, o których piszę w dalszej części tego rozdziału. Dla naszych celów
wystarczy, że funkcja DllMain zwróci TRUE.
Jedyną niewyjaśnioną zagadką w powyższych dwóch plikach mogą być defini-
cje z identyfikatorem EXPORT. Funkcje biblioteki DLL używane przez aplikacje
1112 Część III: Zagadnienia zaawansowane
zewnętrzne muszą być wyeksportowane. Nie chodzi tu o żadne taryfy celne ani
przepisy handlowe, lecz o kilka słów kluczowych gwarantujących, że nazwy pli-
ków zostaną dodane do EDRLIB.LIB (aby linker mógł rozwikłać je podczas kon-
solidowania aplikacji korzystających z funkcji) i że funkcje będą widoczne z po-
ziomu EDRLIB.DLL. Słowo kluczowe EXPORT zawiera specyfikator klasy pamięci
declspec (dllexport) oraz extern "C" , jeżeli plik nagłówkowy jest kompilowany
w trybie C++. Zapobiega to zwyczajowemu "międleniu nazw" funkcji C++ przez
kompilator, a tym samym umożliwia używanie biblioteki DLL zarówno progra-
mom C, jak i C++.
Punkt wejścia i wyjścia biblioteki
Przed pierwszym użyciem biblioteki i po zakończeniu jej działania uruchamiana
jest funkcja DllMain. Pierwszy parametr DllMain to uchwyt instancyjny bibliote-
ki. Jeżeli biblioteka używa zasobów wymagających uchwytów instancyjnych (ta-
kich jak DialogBox), wówczas hlnstance powinno się zapisać w jakiejś zmiennej
globalnej. Ostatni parametr DIlMain jest zastrzeżony dla systemu.
Parametr fdwReason może mieć jedną z czterech wartości oznaczających powód
wywołania przez Windows funkcji DIlMain. Czytając poniższe omówienie, pa-
miętaj, że jeden program może być ładowany wiele razy i działać współbieżnie
w Windows. Każde załadowanie programu jest uznawane za osobny proces.
Wartość fdwReason DLL PROCESS ATTACH oznacza, że biblioteka dynamicz-
na została odwzorowana w przestrzeń adresową jakiegoś procesu. Jest to wska-
zówka dla biblioteki, aby przeprowadziła wszelkie zadania inicjacyjne niezbęd-
ne do obsługi przyszłych żądań pochodzących od procesu. Takie inicjacje mogą
obejmować rezerwacje pamięci i inne tego typu sprawy. W czasie działania pro-
cesu funkcja DIlMain jest wywoływana z parametrem DLL PROCESS ATTACH
tylko raz. Wszelkie inne procesy używające tego samego DLL-a wywołują dalsze
wywołania funkcji DIlMain z parametrem DLL PROCESS ATTACH, ale to dzie-
je się już w odniesieniu do innego procesu.
Jeżeli inicjacja zakończy się powodzeniem, funkcja DllMain powinna zwracać
wartość niezerową. Zwracanie wartości zero spowoduje, że Windows nie uru-
chomi programu.
Jeżeli fdwReason ma wartość DLL PROCESS DETACH, oznacza to, że biblioteka
nie jest już potrzebna procesowi, co daje jej możliwość posprzątania po sobie. W
32-bitowych wersjach Windows często bywa to zbędne, a zawsze stanowi dobrą
praktykę programistyczną.
Podobnie, jeżeli DIIMain jest wywoływana z parametrem fdwReason równym
DLL THREAD ATTACH, oznacza to, że skojarzony proces utworzył nowy wą-
tek. Kiedy kończy się wątek, Windows wywołuje DIIMain z parametrem fdwRe-
ason równym DLL THREAD DETACH. Warto wiedzieć, że możliwe jest otrzy-
manie wywołania DLL THREAD DETACH bez wcześniejszego wywołania
DLL THREAD TTACH. Dochodzi do tego wówczas, gdy biblioteka jest koja-
rzona z procesem już po utworzeniu wątku.
Wątek nadal istnieje, kiedy funkcja DllMain jest wywoływana z parametrem
DLL THREAD DETACH. Podczas tego procesu funkcja może nawet wysyłać
/*
Rozdział 21: Biblioteki dynamiczne 1113
wątkowi komunikaty, ale nie powinna używać funkcji PostMessage, ponieważ
wątek przed dotarciem komunikatu mógłby zniknąć.
Program testowy
Utworzymy teraz drugi projekt obszaru roboczego EDRTEST, tym razem dla pro-
gramu o nazwie EDRTEST, który będzie użytkownikiem biblioteki EDRLIB.DLL.
Mając załadowany obszar roboczy EDRTEST w Visual C++, wybierz z menu File
polecenie New. Zaznacz kartę Projects okna dialogowego New. Tym razem wy-
bierz opcję Win32 Application. Zaznacz przycisk Add To Current Workspace. Jako
nazwę projektu podaj EDRTEST. Znów musisz w polu Locations usunąć drugi
podkatalog EDRTEST. Naciśnij-OK, a w następnym oknie dialogowym wybierz
An Empty Project. Naciśnij przycisk Firush.
Wybierz ponownie z menu File polecenie New. Zaznacz kartę Files i opcję C++
Source File. Pole listy Add To Project powinno zawierać EDRTEST, a nie EDR-
LIB. Wpisz nazwę pliku EDRTEST.C, a następnie treść pliku z rysunku 21-2. Pro-
gram ten wyśrodkowuje tekst wyświetlany w obszarze roboczym za pomocą funk-
cji EdrCenterText.
EDRTEST.C
tuKitSI.C - Program używający biblioteki dynamicznej EDRLIB
(c) Charles Petzold, 1998
tinclude
ttinclude "edrlib.h"
*/
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
static TCHAR szAppName[] = TEXT ("StrProg")
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW CS UREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH)
, wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
f
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
1114 Część III: Zagadnienia zaawansowane
(cigg dalszy ze strony 1113)
szAppName, MB ICONERROR) ; `
)
hwnd = CreateWindow (szAppName, TEXT ("DLL Demonstration Program"),
WS_OVERLAPPEDWINDOW,
CWUSEDEFAULT, CW USEDEFAULT,
CW_USEDEFAULT, CW USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
f
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
)
return msg.wParam ;
)
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
(
HDC hdc ;
PAINTSTRUCT ps ;
RECT rect ;
switch (message)
(
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ; `
GetClientRect (hwnd, &rect) ;
EdrCenterText (hdc, &rect,
TEXT ("This string was displayed by a DLL")) ;
EndPaint (hwnd, &ps) ;
return 0 ; ,
case WM_DESTROY:
PostOuitMessage (0) ;
return 0 ;
1
return DefWindowProc (hwnd, messa9e, wParam, lParam> ;
)
Rysunek 21-2. Program EDRTEST
Zauważ, że plik EDRTEST.C zawiera dyrektywę włączającą plik nagłówkowy
EDRLIB.H. Znajduje się w nim definicja funkcji EdrCenterText, wywoływanej pod- `
czas obsługi komunikatu WMPAINT.
Zanim skompilujesz ten program, powinieneś dowiedzieć się kilku rzeczy. Po
pierwsze wybierz z menu Project polecenie Select Active Project. Powinieneś zo-
Rozdział 21: Biblioteki dynamiczne 1115
baczyć EDRLIB i EDRTEST. Wybierz EDRTEST. Podczas budowania tego obsza-
ru roboczego będziesz chciał w rzeczywistości budować program. Z menu Pro-
ject wybierz także polecenie Dependencies. Z listy Select Project To Modify wy-
bierz EDRTEST. Na liście Dependent On The Following Project(s) zaznacz EDR-
LIB. Oznacza to, że EDRTEST wymaga biblioteki dynamicznej EDRLIB. Podczas
każdego budowania projektu EDRTEST przed kompilacją i konsolidacją przebu-
dowywany będzie również projekt EDRLIB, okaże się to konieczne.
Wybierz z menu Project polecenie Settings. Zaznacz kartę General. Kiedy wybie-
rzesz projekt EDRLIB lub EDRTEST w lewym panelu, pokazane w prawym pane-
lu pliki Intermediate Files i Output Files powinny stanowić katalog RELEASE dla
konfiguracji Win32 Release i katalog DEBUG dla konfiguracji Win32 Debug. Jeżeli
nie są, zmień je. Dzięki temu plik EDRLIB.DLL trafi do tego samego katalogu co
EDRTEST.EXE, a więc program będzie mógł bez problemu korzystać z biblioteki.
Będąc wciąż w oknie dialogowym Project Setting i mając zaznaczony projekt
EDRTEST, kliknij kartę C/C++. W Preprocessor Definitions dodaj UNICODE do
konfiguracji Debug, tak jak w przypadku wszystkich programów z tej książki.
Teraz powinno ci się udać zbudować plik EDRTEST.EXE w obu konfiguracjach
(Debug i Release). Najpierw zostanie skompilowany i skonsolidowany projekt
EDRLIB, o ile będzie to niezbędne. Katalogi RELEASE i DEBUG będą zawierały
bibliotekę (importową) EDRLIB.LIB oraz EDRLIB.DLL. Po przeprowadzeniu kon-
solidacji EDRTEST, biblioteka importowa zostanie włączona automatycznie.
Należy pamiętać, że kod EdrCenterText nie jest włączany do pliku EDRTEST.EXE.
Plik wykonywalny zawiera jedynie proste odwołania do pliku EDRLIB.DLL oraz
funkcji EdrCenterText. Plik EDRTEST.EXE wymaga do poprawnej pracy pliku
EDRLIB.DLL.
Podczas wykonywania EDRTEST.EXE Windows przeprowadza korekty odwo-
łań do funkcji z zewnętrznych modułów bibliotecznych. Wiele z tych funkcji znaj-
duje się w normalnych bibliotekach dynamicznych Windows. Ale system widzi
również, że program wywołuje funkcję z EDRLIB.LIB, więc ładuje ten plik do
pamięci i wywołuje procedurę inicjacyjną biblioteki. Wywołanie z programu
EDRTEST funkcji EdrCenterText jest dynamicznie zestawiane z kodem funkcji z bi-
blioteki EDRLIB.
Włączenie pliku EDRLIB.H do pliku EDRTEST.C jest podobne do włączania pli-
ku WINDOWS.H. Konsolidowanie z EDRLIB.LIB przypomina konsolidowanie
z bibliotekami importowymi Windows (takimi jak USER32.LIB). Uruchomiony
program jest konsolidowany z EDRLIB.DLL tak samo jak z USER32.DLL. Gratu-
lacje! Utworzyłeś własne rozszerzenie Windows!
Zanim przejdziemy dalej, jeszcze kilka słów na temat bibliotek dynamicznych.
Po pierwsze, choć nazwałem właśnie bibliotekę DLL rozszerzeniem Windows
,
z drugiej strony można ją postrzegać jako rozszerzenie aplikacji. Wszystko, co robi
DLL, odbywa się poprzez aplikację, na przykład cała pamięć, jaką rezerwuje,
należy do aplikacji. Wszystkie okna utworzone przez DLL znajdują się we wła-
daniu aplikacji. To samo dotyczy wszystkich otwieranych plików - do nich też
prawa ma aplikacja. Z jednej biblioteki dynamicznej może korzystać jednocze-
śnie wiele aplikacji, ale Windows chroni je przed wzajemnymi interferencjami.
1116 Częć III: Zagadnienia zaawansowane
Ten sam kod biblioteki dynamicznej może współużytkować wiele procesów. Jed-
nak dane utrzymywane przez DLL są dla każdego procesu różne. Każdy ma wła-
sną przestrzeń adresów na dane używane przez DLL. Współużytkowanie pamięci
przez procesy wymaga, jak się okaże w następnym podrozdziale, dodatkowej
pracy.
Biblioteki dynamiczne a pamigć wspólna
To bardzo miło, że Windows izoluje aplikacje wykorzystujące współbieżnie tę samą
bibliotekę dynamiczną. Nie zawsze jest to jednak pożądane. Załóżmy, że chcesz
napisać DLL zawierający jakąś pamięć, która mogłaby być współużytkowana
przez kilka aplikacji albo przez wiele instancji tej samej aplikacji. Dotyczy to ko-
rzystania z pamięci wspólnej, która jest w rzeczywistości plikiem odwzorowa-
nym w pamięci.
Sprawdzimy, jak to działa, za pomocą programu o nazwie STRPROG (skrót od
ang. string program) oraz biblioteki dynamicznej o nazwie STRLIB (od ang. string
Library). STRLIB ma trzy funkcje eksportowe, z których korzysta STRPROG. Aby
urozmaicić ten przykład, jedna z funkcji STRLIB będzie używała funkcji zwrot-
nej (call-back) zdefiniowanej w STRPROG.
STRLIB to moduł biblioteki dynamicznej przechowujący i sortujący do 256 łań-
cuchów znaków. Łańcuchy są kapitalikowane i przetrzymywane w pamięci wspól-
nej STRLIB. STRPROG może za pomocą trzech funkcji STRLIB dodawać łańcuch,
usuwać łańcuch oraz uzyskiwać bieżące łańcuchy z STRLIB. Program testowy STR-
PROG ma dwie pozycje menu, które otwierają okna dialogowe służące do doda-
wania i usuwania łańcuchów. STRPROG wyświetla na swoim obszarze roboczym
wszystkie obecnie zapisane w STRLIB łańcuchy.
Oto zdefiniowana w STRLIB funkcja, która dodaje nowy łańcuch do pamięci
wspólnej biblioteki STRLIB:
EXPORT BOOL CALLBACK AddString (pStringIn)
Argument pStringln jest wskaźnikiem do łańcucha. Łańcuch jest kapitalikowany
za pomocą funkcji AddString. Jeżeli na liście łańcuchów STRLIB istnieje już iden-
tyczny łańcuch, funkcja dodaje kolejną jego kopię. Funkcja AddString zwraca war-
tość TRUE (wartość niezerową), jeżeli zakończy się powodzeniem, a FALSE (0)
w przeciwnym razie. Wartość zwrotną FALSE można otrzymać wtedy, gdy łań-
cuch miał długość 0, gdy nie udało się zarezerwować wystarczająco dużo pamię-
ci na łańcuch albo gdy zapisano już 256 łańcuchów.
Oto funkcja STRLIB usuwająca łańcuch z pamięci wspólnej STRLIB:
EXPORT BOOL CALLBACK DeleteString (pStringIn)
Znów argument pStringln jest wskaźnikiem do łańcucha. Jeżeli pasują przynaj-
mniej dwa łańcuchy, usuwany jest tylko pierwszy. Funkcja DeleteString zwraca
wartość TRUE (wartość niezerową), jeżeli zakończy się powodzeniem, a FALSE
(0) w przeciwnym razie. Zwrócenie FALSE oznacza, że długość łańcucha wyno-
siła 0 albo że nie można było odnaleźć pasującego łańcucha.
Oto funkcja STRLIB używająca funkcji zwrotnej ulokowanej w programie wywo-
łującym i wyliczającej łańcuchy zapisane dotąd w pamięci STRLIB:
Rozdział 21: Biblioteki dynamiczne 1117
EXPORT int CALLBACK GetStrings (pfnGetStrCallBack, pParam)
Funkcja zwrotna musi być zdefiniowana w programie wywołującym w następu-
jący sposób:
EXPORT BOOL CALLBACK GetStrCallBack (PSTR pString, PVOID pParam)
Na funkcję zwrotną wskazuje argument pfnGetStrCallBack funkcji GetStrings. Get-
Strings wywołuje GetStrCalIBack raz dla każdego łańcucha, aż funkcja zwrotna
zacznie zwracać FALSE (czyli 0). GetStrings zwraca liczbę łańcuchów przekaza-
nych do funkcji zwrotnej. Parametr pParam jest dalekim wskaźnikiem do danych
definiowanych przez programistę.
Oczywiście, wszystko dodatkowo komplikuje unikod, a raczej konieczność za-
pewniania w STRLIB obshxgi aplikacji zarówno unikodowych, jak i nieunikodo-
wych. Podobnie jak z EDRLIB, znajdują się tu wersje A i W wszystkich funkcji.
Wszystkie łańcuchy są wewnętrznie w STRLIB przechowywane jako łańcuchy
unikodowe. Jeżeli biblioteki STRLIB używją programu nieunikodowego (czyli wy-
wołującego funkcje AddStringA, DeleteStringA lub GetStringsA), wówczas łańcu-
chy są przekształcane na i z wersji unikodu.
Obszar roboczy skojarzony z projektami STRPROG i STRLIB nazywa się STR-
PROG. Pliki są zbudowane tak samo jak obszar EDRTEST. Na rysunku 21-3 przed-
stawiono dwa pliki niezbędne do utworzenia moduhx biblioteki dynamicznej
STRLIB.DLL.
STRLIB.H
/*
Plik nagłówkowy STRLIB.H
*/
lli fdef cpl uspl us
lldefine EXPORT extern "C" declspec (dllexport)
llel se
lldefine EXPORT declspec (dllexport)
Itendif
// Maksymalna liczba lańcuchów, jaką przechowa STRLIB, i ich dlugość
Ildefine MAX_STRINGS 256
Idefine MAX LENGTH 63
// W definicji typu funkcji zwrotnej użyto zwyklych łańcuchów
typedef BOOL (CALLBACK * GETSTRCB) (PCTSTR, PVOID) ;
// Każda funkcja ma wersję ANSI i unikodowd
EXPORT BOOL CALLBACK AddStringA (PCSTR) ;
EXPORT BOOL CALLBACK AddStringW (PCWSTR) ;
EXPORT BOOL CALLBACK DeleteStringA (PCSTR) ;
EXPORT BOOL CALLBACK DeleteStringW (PCWSTR) ;
1118 Część III, Zagadnienia zaawansowane
(ciąg dalszy ze strony 1117)
EXPORT int CALLBACK GetStringsA (GETSTRCB, PVOID) ; r
EXPORT int CALLBACK GetStringsW (GETSTRCB, PVOID) ;
ll Użyć należy wersji wlaściwej dla danego identyfikatora UNICODE
4tifdef UNICODE
tdefine AddString AddStringW
tfdefine DeleteString DeleteStringW
ttdefine GetStrings GetStringsW
ltel se
define AddString AddStringA
tdefine DeleteString DeleteStringA
ttdefine GetStrings GetStringsA
endi f
STRLIB.C
/*
STRLIB.C - Modul biblioteki dla programu STRPROG
(c) Charles Petzold, 1998
*/
include
ltinclude // dla funkcji na lańcuchach szerokich znaków '
iinclude "strlib.h"
// sekcja pamięci wspólnej (wymaga w opcjach konsolidacji ciągu
// SECTION:shared,RWS)
lpragma data_seg ("shared")
int iTotal = 0 ;
WCHAR szStrings [MAX_STRINGS][MAX_LENGTH + 1] = ( \0 ) ;
pragma data seg ( ) '
tpragma comment(linker,"/SECTION:shared,RWS") ,
int WINAPI DllMain (HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
(
return TRUE ;
1
EXPORT BOOL CALLBACK AddStringA (PCSTR pStringIn)
(
BOOL bReturn ;
int iLength ;
PWSTR pWideStr ;
// Przeksztalcenie ciagu na unikod i wywolanie AddStringW
iLength = MultiByteToWideChar (CP ACP, 0, pStringIn, -1, NULL, 0) ;
pWideStr = malloc (iLength) ;
MultiByteToWideChar (CP ACP, 0, pStringIn, -1, pWideStr, iLength) ; ,
bReturn = AddStringW (pWideStr) ;
free (pWideStr) ;
return bReturn ;
?
Rozdział 21: Biblioteki dynamiczne 1119
EXPORT BOOL CALLBACK AddStringW (PCWSTR pStringIn)
PWSTR pString ;
int i, iLength ;
if (iTotal = MAX_STRINGS - 1)
return FALSE ;
if ((iLength = wcslen (pStringIn)) = 0)
return FALSE ;
// Rezerwacja pamięci na ciąg, skopiowanie lańcucha, '
// konwersja na duże litery
pString = malloc (sizeof (WCHAR) * (1 + iLength)) ;
wcscpy (pString, pStringIn) ;
wcsupr (pString) ;
' // Alfabetyzacja ciagów =
for (i = iTotal ; i > 0 ; i )
(
if (wcscmp (pString, szStringsCi - 1]) >= 0)
break ;
wcscpy (szStrings[i], szStringsCi - 1]) ;
wcscpy (szStringsCi], pString) ;
iTotal++ ;
free (pString) ;
return TRUE ;
i
EXPORT BOOL CALLBACK DeleteStringA (PCSTR pStringIn)
s.
BOOL bReturn ;
int iLength ;
PWSTR pWideStr ;
// Przeksztalcenie łańcucha na unikod i wywolanie DeleteStringW
i,
iLength = MultiByteToWideChar (CP ACP, 0, pStringIn, -1, NULL, 0) ;
pWideStr = malloc (iLength) ;
MultiByteToWideChar (CP_ACP, 0, pStringIn, -1, pWideStr, iLength) ;
bReturn = DeleteStringW (pWideStr) ; t .
free (PWideStr) ;
F':. .
' return b.Return ;
EXPORT BOOL CALLBACK DeleteStringW (PCWSTR pStringIn)
int i, j ;
if (0 == wcslen (pStringIn>)
1120 Część III: Zagadnienia zaawansowane
(ciąg dalszy ze strony 1117)
return FALSE ;
for (i = 0 ; i < iTotal ; i++)
(
if ( wcsicmp (szStrings[i], pStringIn) == 0)
break ;
)
// Jeżeli danego ląńcucha nie ma na liście,
// powrót bez podejmowania działań
if (i == iTotal)
return FALSE ;
// W przeciwnym wypadku skorygowanie listy
for (j = i ; j < iTotal ; j++)
wcscpy (szStrings[j], szStrings[j + 1]) ;
szStrings[iTotal ][0] = \0 ,
return TRUE ;
)
EXPORT int CALLBACK GetStringsA (GETSTRCB pfnGetStrCallBack, PVOID pParam)
(
BOOL bReturn ;
int i, iLength ;
PSTR pAnsiStr ;
for (i = 0 ; i < iTotal ; i++)
(
// Przekształcenie lańcucha z unikodu
iLength = WideCharToMultiByte (CP_ACP, 0, szStrings[i], -1, NULL, 0,
NULL, NULL) ;
pAnsiStr = malloc (iLength) ;
WideCharToMultiByte (CP ACP, 0, szStrings[i], -1, pAnsiStr, iLength,
NULL, NULL) ;
// Wywołanie funkcji zwrotnej
bReturn = pfnGetStrCallBack (pAnsiStr, pParam) ;
if (bReturn == FALSE)
return i + 1 ;
free (pAnsiStr) ;
)
return iTotal ;
)
EXPORT int CALLBACK GetStringsW (GETSTRCB pfnGetStrCallBack, PVOID pParam)
BOOL bReturn ;
int i ;
for (i = 0 ; i < iTotal ; i++)
Rozdział 21: Biblioteki dynamiczne 1121
(
bReturn = pfnGetStrCallBack (szStringsCi7, pParam) ;
if (bReturn == FALSE>
return i + 1 ;
)
return iTotal ;
Rysunek 21-3. Biblioteka STRLIB
Oprócz funkcji DllMain STRLIB zawiera jeszcze tylko sześć irmych funkcji. Będą
one wyeksportowane do użytku przez inne programy. Wszystkie te funkcje są
zdefiniowane słowem kluczowym EXPORT, dzięki czemu zostaną umieszczone
na liście w bibliotece STRLIB.LIB.
Program STRPROG
' Program STRPROG, pokazany na rysunku 21-4, jest dość prosty. Jego menu za-
wiera dwie opcje: Enter i Delete, które wyświetlają okna dialogowe umożliwiają-
ce wpisanie łańcucha. STRPROG wywołuje AddString lub DeleteString. Kiedy pro-
gram musi dokonać aktualizacji obszaru roboczego, wywołuje funkcję GetStrings
i wyświetla listę łańcuchów za pomocą funkcji GetStrCallBack.
STRPROG.C
/*
STRPROG.C - Program używający biblioteki dynamicznej STRLIB
(c) Charles Petzold, 1998
*/
` ttinclude
ilinclude "strlib.h"
itinclude "resource.h"
typedef struct
(
HDC hdc :
int xText ;
int yText ;
int xStart ;
a
int yStart ;
int xIncr ;
int yIncr ;
int xMax :
int yMax ;
I t.
CBPARAM ;
1 =
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
I
TCHAR szAppName C] = TEXT ("StrProg") ;
TCHAR szString CMAXLENGTH + 17 :
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow> ...
c
1122 Część III: Zagadnienia zaawansowane
(ciąg dalszy ze strony 1121)
t
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW CS VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION> ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = szAppName ;
wndclass.lpszClassName = szAppName ; ,
if (!RegisterClass (&wndclass))
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB ICONERROR) ;
return 0 ;
1
hwnd = CreateWindow (szAppName, TEXT ("DLL Demonstration Program"),
WS_OVERLAPPEDWINDOW,
CW USEDEFAULT, CW_USEDEFAULT,
CW USEDEFAULT, CW USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
(
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
)
return msg.wParam ;
?
BOOL CALLBACK DlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
(
switch (message)
(
case WM_INITDIALOG:
SendDlgItemMessage (hDlg, IDCSTRING, EM_LIMITTEXT, MAX LENGTH, 0) ;
return TRUE ;
case WM COMMAND:
switch (wParam)
r
case IDOK:
GetDlgItemText (hDlg, IDCSTRING, szString, MAX__LENGTH) ;
EndDialog (hDlg, TRUE) ;
return TRUE ;
Rozdział 21: Biblioteki dynamiczne 1123
, case IDCANCEL:
EndDialog (hDlg, FALSE) ;
return TRUE ;
l
return FALSE ;
BOOL CALLBACK GetStrCallBack (PTSTR pString, CBPARAM * pcbp)
(
TextOut (pcbp->hdc, pcbp->xText, pcbp->yText,
pString, lstrlen (pString)) ;
if ((pcbp->yText += pcbp->yIncr) > pcbp->yMax)
,
pcbp->yText = pcbp->yStart ;
if ((pcbp->xText += pcbp->xIncr) > pcbp->xMax)
return FALSE ;
,:
return TRUE ;
)
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
,
static HINSTANCE hInst ;
static int cxChar, cyChar, cxClient, cyClient ;
static UINT iDataChangeMsg ;
CBPARAM cbparam ;
HDC hdc ;
PAINTSTRUCT ps ;
TEXTMETRIC tm ;
ś
ts
switch (message)
case WM CREATE:
hInst = ((LPCREATESTRUCT) lParam)->hInstance ;
hdc = GetDC (hwnd) ;
GetTextMetrics (hdc, &tm) ;
cxChar = (int) tm.tmAveCharWidth ;
cyChar = (int) (tm.tmHeight + tm.tmExternalLeading) ; I;
ReleaseDC (hwnd, hdc) ;
// Rejestrowanie komunikatu powiadamiającego o zmianach danych
iDataChangeMsg = RegisterWindowMessage (TEXT ("StrProgOataChange")) ; ,'i
return 0 ;
i3:
i
case WM COMMAND:
=.
switch (wParam)
case IDM_ENTER:
5
if (DialogBox (hInst, TEXT ("EnterDlg"), hwnd, &DlgProc))
!
if (AddString (szString))
PostMessage (HWND BROADCAST, iDataChangeMsg, 0, 0) ;
else
MessageBeep (0) ;
,
1124 Część III Zagadnienia zaawansowane
(cigg dalszy ze strony 1223)
1 c
break ;
case IDM_DELETE:
if (DialogBox (hInst, TEXT ("DeleteDlg"), hwnd, &DlgProc))
(
if (DeleteString (szString)) ~
PostMessage (HWNDBROADCAST, iDataChangeMsg, 0, 0) ;
else
MessageBeep (0) ;
l
break ;
)
return 0 ;
case WM_SIZE:
cxClient = (int) LOWORD (lParam) ;
cyClient = (int) HIWORD (lParam) ; !
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
l
cbparam.hdc = hdc ;
cbparam.xText = cbparam.xStart = cxChar ;
cbparam.yText = cbparam.yStart = cyChar ;
cbparam.xIncr = cxChar * MAX_LENGTH ;
cbparam.yIncr = cyChar ;
cbparam.xMax = cbparam.xIncr * (1 + cxClient / cbparam.xIncr) ;
cbparam.yMax = cyChar * (cyClient / cyChar - 1) ;
I
GetStrings ((GETSTRCB) GetStrCallBack, (PVOID) &cbparam) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WMDESTROY:
PostOuitMessage (0) ;
return 0 ;
default:
if (message == iDataChangeMsg)
InvalidateRect (hwnd, NULL, TRUE) ;
break ;
)
return DefWindowProc (hwnd, message, wParam, lParam) ;
STRPROG.RC (fragmenty)
//Microsoft Developer Studio generated resource script.
Ilinclude "resource.h"
Ilinclude "afxres.h"
/////////////////////////////////////////////////////////////////////////////
// Dialog
Rozdział 21: Biblioteki dynamiczne 1125
ENTERDLG DIALOG DISCARDABLE 20, 20, 186, 47
STYLE DSMODALFRAME WSPOPUP WS CAPTION WSSYSMENU
CAPTION "Enter"
FONT 8, "MS Sans Serif"
BEGIN
LTEXT "&Enter:",IDC_STATIC,7,7,26,9
EDITTEXT IDC_STRING,31,7,148,12,ES AUTOHSCROLL
DEFPUSHBUTTON "OK",IDOK,32,26,50,14
PUSHBUTTON "Cancel",IDCANCEL,l04,26,50,14
END
DELETEDLG DIALOG DISCARDABLE 20, 20, 186, 47
STYLE DS MODALFRAME WSPOPUP WS CAPTION WS SYSMENU
CAPTION "Delete"
FONT 8, "MS Sans Serif"
BEGIN
LTEXT "&Delete:",IDC_STATIC,7,7,26,9
EDITTEXT IDC_STRING,31,7,148,12,ES UTOHSCROLL
DEFPUSHBUTTON "OK",IDOK,32,26,50,14
PUSHBUTTON "Cancel",IDCANCEL,l04,26,50,14
END
/////////////////////////////////////////////////////////////////////////////
// Menu
STRPROG MENU DISCARDABLE
BEGIN
MENUITEM "&Enter!" IDM_ENTER
MENUITEM "&Delete!", IDM_DELETE
END
RESOURCE.H (fragmenty)
// Microsoft Developer Studio generated include file.
// Used by StrProg.rc
define IDC_STRING 1000
define IDM_ENTER 40001
define IDM_DELETE 40002
define IDC STATIC -1
Rysunek 21-4. Program STRPROG
STRPROG.C zawiera dyrektywę włączającą plik nagłówkowy STRLIB.H. W pli-
ku tym są zdefiniowane trzy funkcje z STRLIB, których będzie używał STRPROG.
Najciekawsza właściwość tego programu ujawnia się dopiero wtedy, kiedy uru-
chomi się jego kilka instancji. STRLIB zapamiętuje łańcuchy znaków i wskaźniki
do nich w pamięci wspólnej, do której mogą mieć dostęp wszystkie instancje STR-
PROG. Zobaczmy, jak się to dzieje.
1 126 Część III: Zagadnienia zaawansowane
Współużytkowanie danych przez instancje programu STRPROG
Windows tworzy mur wokół przestrzeni adresowej procesu Win32. Normalnie
dane z tej przestrzeni są prywafie, niewidoczne z innych procesów. Ale urucho-
mienie kilku instancji STRPROG dowodzi, że STRLIB wszystkim instancjom pro-
gramu pozwala na bezproblemowe współużytkowanie danych. Dodanie lub usu-
nięcie łańcucha w oknie STRPROG jest natychmiast odzwierciedlane w pozosta-
łych oknach.
Dwie zmienne STRLIB są dostępne wszystkim instancjom: tablica łańcuchów i
liczba całkowita informująca o liczbie zapisanych łańcuchów. Obie zmienne są
przechowywane w specjalnej sekcji pamięci, która została oznaczona jako wspólna:
ilpragma data_seg ("shared")
int iTotal = 0 ;
WCHAR szStrings CMAX STRINGS]CMAX LENGTH + 1] = ( _\0 ) ;
lpragma data seg ()
Pierwsza instrukcja #pragma tworzy sekcję danych, tutaj o nazwie shared. Sekcje
można nazywać zupełnie dowolrue. Wszystkie zmienne zainicjowane po instrukcji
#pragma trafiają do sekcji shared. Druga instrukcja #pragma zamyka sekcję. Bar-
dzo ważne jest odpowiednie (szczególne) zainicjowanie zmiennych; bez niego
kompilator umieści je w sekcji normalnej, niezainicjowanej, a nie w sekcji shared.
O sekcji shared należy też poinformować program konsolidujący. W oknie dialo-
gowym Project Settings zaznacz kartę Link. W polu Project Options dla projektu
STRLIB (w konfiguracjach Release i Debug) należy umieścić następujący argu-
ment konsolidatora:
/SECTION:shared,RWS
Litery RWS oznaczają, że sekcja ma atrybuty odczytu (R), zapisu (W) i współ-
użytkowania (S). Ewentualnie opcje konsolidacji można określić bezpośrednio
w źródle biblioteki dynamicznej, jak to jest zrealizowane w STRLIB.C:
ilpragma comment(linker,"/SECTION:shared,RWS")
Sekcja pamięci wspólnej umożliwia współużytkowanie zmiennych iTotal (liczba
całkowita) i szStrings (tablica łańcuchów) przez wszystkie instancje STRLIB. Po-
nieważ MAXSTRINGS równa się 256, a MAX LENGTH 63, sekcja pamięci wspól-
nej ma 32 772 bajty długości: 4 bajty zajmuje zmienna iTotal, a 128 bajtów każdy
z 256 wskaźników.
Korzystanie z sekcji pamięci wspólnej jest prawdopodobnie najprostszym sposo-
bem współużytkowania danych przez kilka aplikacji. Jeżeli obszar pamięci wspól-
nej trzeba rezerwować dynamicznie, należy się przyjrzeć używaniu obiektów
odwzorowań w pliki, udokumentowanych w temacie /Platform SDK/Windows Base
Services/Interprocess Communication/File Mapping.
Różne tematy związane z bibliotekami dynamicznymi
Wspomniałem już wcześniej, że moduł biblioteki dynamicznej nie może otrzy-
mywać komunikatów. Może on jednak wywoływać funkcje GetMessage i PeekMes-
sage. Komunikaty wyciągane przez bibliotekę z kolejki za pomocą tych funkcji
Rozdział 21: Biblioteki dynamiczne 1127
stanowią komunikaty adresowane do programu, który wywołuje funkcje biblio-
teki. Ogólnie rzecz biorąc, biblioteka działa w imieniu programu wywołującego
jej funkcje - zasada ta obowiązuje większość funkcji Windows wywoływanych
przez bibliotekę.
Biblioteka dynamiczna może ładować zasoby (na przykład ikony, łańcuchy zna-
ków i bitmapy) - albo z pliku biblioteki, albo z pliku programu wywołującego.
Funkcje służące do ładowania zasobów wymagają uchwytów instancyjnych. Je-
żeli biblioteka używa swojego własnego uchwytu instancyjnego (który otrzymu-
je podczas inicjacji), otrzymuje zasoby ze swojego własnego pliku. Aby załado-
wać zasoby z pliku EXE programu wywołującego, biblioteka potrzebuje uchwy-
tu instancyjnego tego programu.
Rejestrowanie klas okien i tworzenie okien z poziomu biblioteki jest dość cieka-
we. Uchwytu instancji wymaga zarówno struktura klasy okna, jak i funkcja Cre-
ateWindow. Choć podczas tworzenia klasy okna oraz okna można użyć uchwytu
biblioteki, komunikaty okien będą i tak przechodzić przez kolejkę komunikatów
programu wywołującego, jeżeli to biblioteka utworzy okno. Gdy trzeba utworzyć
klasę okna lub okno z poziomu biblioteki, prawdopodobnie najlepiej jest użyć
uchwytu instancyjnego programu wywołującego.
Ponieważ komunikaty dla modalnych okien dialogowych są odbierane spoza pętli
komunikatów programu, okno modalne z poziomu biblioteki można utworzyć
za pomocą funkcji DialogBox. Uchwytem instancyjnym może być uchwyt biblio-
teki, a argumentem hwndParent funkcji DialogBox może być NULL.
Dynamiczna konsolidacja bez importu
Zazwyczaj to system Windows wykonuje dynamiczną konsolidację. Robi to
w chwili załadowania programu do pamięci. Ale konsolidowanie programu z mo-
dułem bibliotecznym możliwe jest w trakcie działania programu.
Na przykład funkcję Rectangle z reguły wywołuje się tak:
Rectangle (hdc, xLeft, yTop, xRight, yBottom);
Takie wywołanie działa dlatego, że program został skonsolidowany z biblioteką
importową GDI32.LIB, która zapewniła adres funkcji Rectangle.
Ale funkcję Rectangle można wywołać okrężną drogą. Należy najpierw, używa-
jąc dyrektywy typedef, zdefiniować typ funkcji dla Rectangle:
typedef BOOL (WINAPI * PFNRECT) (HDC, int, int, int, int);
Następnie należy zdefiniować dwie zmienne:
HANDLE hLibrary;
PFNRECT pfnRectangle;
Teraz hLibrary nadaje się wartość uchwytu biblioteki, a IpfnRectangle wartość ad-
resu funkcji Rectangle:
hLibrary = L.oadLibrary (TEXT ("GDI32.DLL"))
pfnRectangle = (PFNPRECT) GetProcAddress (hLibrary, TEXT ("Rectangle"))
Funkcja LoadLibrar zwraca NULL, jeżeli nie można odnaleźć pliku biblioteki albo
jeżeli wystąpią inne trudności. Teraz można wywołać funkcję i zwolnić bibliote-
kę:
Część III: Zagadnienia zaawansowane
pfnRectangle (hdc, xLeft, yTop, xRight, yBottom);
FreeLibrary (hLibrary);
Choć omawiana technika dynamicznej konsolidacji podczas działania programu
nie ma specjalnego sensu w przypadku funkcji ftectangle, może się przydać wów-
czas, gdy nazwa biblioteki dynamicznej jest niewiadoma do momentu urucho-
mierua programu.
W powyższym kodzie użyto funkcji LoadLibrary i FreeLibrary. Windows ewiden-
cjonuje dla wszystkich modułów bibliotek liczniki odwołań. Funkcja LoadLibrary
zwiększa licznik odwołań. Licznik jest również zwiększany wtedy, gdy Windows
ładuje program używający biblioteki. FreeLibrary zmniejsza licznik odwołań. Po-
woduje ją również zakończenie instancji programu korzystającego z biblioteki.
Kiedy licznik odwołań ma wartość 0, Windows może usunąć bibliotekę z pamię-
ci, ponieważ biblioteka nie jest już potrzebna.
Biblioteki z samymi zasobami
Każda funkcja biblioteki dynamicznej, z której może korzystać program Windows
czy inna biblioteka, musi zostać wyeksportowana. Biblioteka dynamiczna może
jednak nie zawierać żadnych funkcji eksportowych. Co więc może zawierać? Otóż
zasoby.
Załóżmy, że pracujesz nad aplikacją windowsową, która wymaga kilku bitmap.
Zazwyczaj umieszcza się je w skrypcie zasobów programu i ładuje do pamięci
za pomocą funkcji LoadBitmap. Ale co zrobić, jeśli trzeba utworzyć kilka zesta-
wów map bitowych, z których każdy będzie przeznaczony dla innej rozdzielczo-
ści? Dobrze by było zapisać różne zestawy map bitowych w osobnych plikach
,
ponieważ użytkownik będzie potrzebował tylko jednego zestawu. Takimi plika-
mi są biblioteki z samymi zasobami.
Na rysunku 21-5 pokazano, jak można utworzyć bibliotekę z samymi zasobami
o nazwie BITLIB.DLL, zawierającą dziewięć bitmap. Poszczególne bitmapy wymie-
nione są w pliku BTTLIB.RC. Tam też każda z nich otrzymuje swój numer. Aby utwo-
rzyć plik BITLIB.DLL, potrzeba dziewięciu bitmap o nazwach BTTMAPI.BMP, BIT
MAP2.BMP itd. Możesz użyć bitmap z CD-ROM-u dołączonego do tej książki albo
utworzyć własne grafiki za pomocą Visual C++. Bitmapy mają przypisane iden-
tyfikatory od 1 do 9.
BITLIB.C
/*
BITLIB.C - Punkt wejścia dla biblioteki BITLIB
(c) Charles Petzold, 1998
*/
ilinclude
int WINAPI DllMain (NINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
(
return TRUE ;
łT
Rozdział 21: Biblioteki dynamiczne 1129
; BITLIB.RC (fragmenty)
//Microsoft Developer Studio generated resource script.
include "resource.h"
include "afxres.h"
/////////////////////////////////////////////////////////////////////////////
// Bitmap
1 BITMAP DISCARDABLE "bitmapl.bmp"
2 BITMAP DISCARDABLE "bitmap2.bmp"
3 BITMAP DISCARDABLE "bitmap3.bmp"
4 BITMAP DISCARDABLE "bitmap4.bmp"
i 5 BITMAP DISCARDABLE "bitmap5.bmp"
6 BITMAP DISCARDABLE "bitmap6.bmp"
7 BITMAP DISCARDABLE "bitmap7.bmp"
8 BITMAP DISCARDABLE "bitmap8.bmp"
9 BITMAP DISCARDABLE "bitmap9.bmp"
Rysunek 21-5. Biblioteka BITLIB
! Utwórz projekt BITLIB w przestrzeni roboczej SHOWBIT. W kolejnym projekcie
o nazwie SHOWBIT (jak przedtem) utwórz program SHOWBIT, pokazany na
rysunku 21-6. Nie definiuj jednak BITLIB jako obiektu zależnego SHOWBIT;
w przeciwnym razie bowiem na etapie konsolidacji będzie potrzebny plik BI-
TLIB.LIB, a ten nie jest tworzony, ponieważ BITLIB rue ma funkcji eksportowych.
Zbuduj BITLIB i SHOWBIT osobno, nadając na przemian każdemu z nich status
projektu aktywnego. =
Program SHOWBIT.C odczytuje zasoby bitmap z BITLIB i wyświetla je w obsza-
rze roboczym. Można je przełączać, naciskając dowolny klawisz.
!
SHOWBIT.C
/*
SHOWBIT.C - Wyświetlanie bitmap z biblioteki BITLIB
(c) Charles Petzold, 1998
*/
include
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
TCHAR szAppName [) = TEXT ("ShowBit") ;
I
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
i
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
.
1130 Część III: Zagadnienia zaawansowane
(ciąg dalszy ze strony 1129)
wndclass.style = CS_HREDRAW CSVREDRAW ; S
wndclass.lpfnWndProc = WndProc ;
wndc7ass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; '
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
(
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MBICONERROR) ; !
1
i
hwnd = CreateWindow (szAppName,
TEXT ("Show Bitmaps from BITLIB (Press Key)"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
if (!hwnd)
return 0 ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0)) .
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
)
return msg.wParam ;
)
void DrawBitmap (HDC hdc, int xStart, int yStart, HBITMAP hBitmap)
BITMAP bm ;
HDC hMemDC ;
POINT pt ;
hMemDC = CreateCompatibleDC (hdc) ;
SelectObject (hMemDC, hBitmap) ;
GetObject (hBitmap, sizeof (BITMAP), &bm) ;
pt.x = bm.bmWidth ;
pt.y = bm.bmHeight ; )
BitBlt (hdc, xStart, yStart, pt.x, pt.y, hMemDC, 0, 0, SRCCOPY) ; ,
DeleteDC (hMemDC) ;
)
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
Rozdział 21: Biblioteki dynamiczne 1131
I
static HINSTANCE hLibrary ;
static int iCurrent = 1 ;
HBITMAP hBitmap ;
HDC hdc ;
PAINTSTRUCT ps ;
switch (message)
(
case WM_CREATE:
if ((hLibrary = LoadLibrary (TEXT ("BITLIB.DLL"))) == NULL)
MessageBox (hwnd, TEXT ("Can't load BITLIB.DLL."),
szAppName, 0) ;
return -1 ;
return 0 ;
case WM_CHAR:
if (hLibrary)
(
iCurrent ++
InvalidateRect (hwnd, NULL, TRUE) ;
1
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
if (hLibrary)
(
hBitmap = LoadBitmap (hLibrary, MAKEINTRESOURCE (iCurrent)) ;
'Ś
if (!hBitmap)
(
iCurrent = 1 ;
hBitmap = LoadBitmap (hLibrary,
MAKEINTRESOURCE (iCurrent)) ;
1
if (hBitmap)
(
DrawBitmap (hdc, 0, 0, hBitmap) ;
Delete0bject (hBitmap) ;
)
)
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY:
if (hLibrary)
FreeLibrary (hLibrary) ;
PostQuitMessage (0) ;
return 0 ;
)
return DefWindowProc (hwnd, message, wParam, lParam) ;
)
Rysunek 21-6. Program SHOWBIT
1132 Część III: Zagadnienia zaawansowane
Podczas obsługi komunikatu WMCREATE SHOWBIT otrzymuje uchwyt do BIT
LIB.DLL:
if ((hLibrary = LoadLibrary (TEXT ("BITLIB.DLL"))) == NULL)
Jeżeli biblioteka BITLIB.DLL nie będzie się znajdowała w tym samym katalogu
co SHOWBIT.EXE, Windows rozpocznie szukania jej we wszystkich stosownych
katalogach (w jakich, informowałem w tym rozdziale). Jeżeli funkcja LoadLibrary
zwróci NULL, wówczas SHOWBTT wyświetli okno komunikatu informujące o błę-
dzie i z obshzgi komunikatu WM CREATE zwróci -1. Spowoduje to, że Create-
Window wywoła funkcję WinMain, która zwróci NULL, i program się zakończy.
SHOWBIT może pozyskać uchwyt do bitmapy wywołaniem LoadBitmap z uchwy-
tem biblioteki i numerem bitmapy jako argumentami:
hBitmap = LoadBitmap (hLibrary, MAKEINTRESOURCE (iCurrent));
Spowoduje to zwrócenie błędu, jeżeli bitmapa odpowiadająca numerowi iCur-
rent jest nieprawidłowa albo jeżeli zabrakło pamięci na załadowanie tej mapy.
Program SHOWBIT zwalnia bibliotekę podczas obsługi komunikatu WM DE-
STROY:
FreeLibrary (hLibrary);
Kiedy zakończy się instancja programu SHOWBIT, licznik odwołań biblioteki
BITLIB.DLL osiągnie wartość 0 i pamięć zajmowana przez nią zostanie zwolnio-
na. Jak widać, jest to prosta metoda zaimplementowania programu do "klipar-
tów", który mógłby wczytywać jakieś gotowe grafiki bitowe (bądź też metapliki
albo metapliki rozszerzone) do Schowka, aby korzystały z nich inne programy.
Wyszukiwarka
Podobne podstrony:
Programowniae windows petzold Petzold01
Programowniae windows petzold Petzold05
Programowniae windows petzold Petzold08
Programowniae windows petzold Petzold09
Programowniae windows petzold Petzold13
Programowniae windows petzold Petzold24
Programowniae windows petzold Petzold02
Programowniae windows petzold Petzold21
Programowniae windows petzold Petzold14
Programowniae windows petzold Petzold04
Programowniae windows petzold Petzold20
Programowniae windows petzold Petzold03
Asembler Podstawy programowania w Windows
2 Podstawy programowania Windows (2)
Visual Studio 05 Programowanie z Windows API w jezyku C vs25pw
informatyka usb praktyczne programowanie z windows api w c andrzej daniluk ebook
więcej podobnych podstron