Tworzenie aplikacji Windows Biblioteki DLL. Aplety panelu sterowania (C++ Builder)

Podobne dokumenty
Tworzenie aplikacji Windows Biblioteki DLL. Aplety panelu sterowania (Delphi)

Wprowadzenie do biblioteki klas C++

Podstawy wykorzystania bibliotek DLL w skryptach oprogramowania InTouch

Cel: Przypisujemy przyciskom określone funkcje panel górny (Panel1)

Laboratorium 1 Temat: Przygotowanie środowiska programistycznego. Poznanie edytora. Kompilacja i uruchomienie prostych programów przykładowych.

Wskaźniki a tablice Wskaźniki i tablice są ze sobą w języku C++ ściśle związane. Aby się o tym przekonać wykonajmy cwiczenie.

znajdowały się różne instrukcje) to tak naprawdę definicja funkcji main.

Zastanawiałeś się może, dlaczego Twój współpracownik,

Wykład 8: klasy cz. 4

Wyrażenie include(sciezka_do_pliku) pozwala na załadowanie (wnętrza) pliku do skryptu php. Plik ten może zawierać wszystko, co może się znaleźć w

Wykład 5: Klasy cz. 3

Delphi podstawy programowania. Środowisko Delphi

Instrukcja laboratoryjna cz.3

Podstawy programowania skrót z wykładów:

KROK 17 i 18. Cel: Tworzymy oddzielne okno - O autorze. 1. Otwórz swój program. 2. Skompiluj i sprawdź, czy działa prawidłowo.

PROGRAM: WYSZUKANIE LICZBY MAKSYMALNEJ

Programowanie obiektowe

Tworzenie własnych komponentów

Tworzenie projektu asemblerowego dla środowiska Visual Studio 2008.

2. W oknie dialogowym Choose Toolbox Items w zakładce.net Framework Components naciskamy przycisk Browse...

Kierunek: ETI Przedmiot: Programowanie w środowisku RAD - Delphi Rok III Semestr 5. Ćwiczenie 5 Aplikacja wielo-okienkowa

WPROWADZENIE DO JĘZYKA JAVA

Java jako język programowania

Wskaźnik może wskazywać na jakąś zmienną, strukturę, tablicę a nawet funkcję. Oto podstawowe operatory niezbędne do operowania wskaźnikami:

Tak przygotowane pliki należy umieścić w głównym folderze naszego programu. Klub IKS

Przygotowanie własnej procedury... 3 Instrukcja msgbox wyświetlanie informacji w oknie... 6 Sposoby uruchamiania makra... 8

#line #endif #ifndef #pragma

Tablice (jedno i wielowymiarowe), łańcuchy znaków

ApSIC Xbench: Szybki start wydanie Mariusz Stępień

PROE wykład 2 operacje na wskaźnikach. dr inż. Jacek Naruniec

Podczas dziedziczenia obiekt klasy pochodnej może być wskazywany przez wskaźnik typu klasy bazowej.

Informatyka II. Laboratorium Aplikacja okienkowa

Laboratorium 1 - Programowanie proceduralne i obiektowe

Utworzenie pliku. Dowiesz się:

Programowanie w języku Python. Grażyna Koba

Expo Composer Garncarska Szczecin tel.: info@doittechnology.pl. Dokumentacja użytkownika

JĘZYKI PROGRAMOWANIA Z PROGRAMOWANIEM OBIEKTOWYM. Wykład 6

Programowanie na poziomie sprzętu. Programowanie w Windows API

Delphi 7 + Indy 10 Przykłady prostych aplikacji sieciowych

METODY I JĘZYKI PROGRAMOWANIA PROGRAMOWANIE STRUKTURALNE. Wykład 02

PARADYGMATY PROGRAMOWANIA Wykład 4

1. Wartość, jaką odczytuje się z obszaru przydzielonego obiektowi to: a) I - wartość b) definicja obiektu c) typ oboektu d) p - wartość

Programowanie niskopoziomowe

5.2. Pierwsze kroki z bazami danych

Rozpocznijmy ten odcinek od rozwiązania problemu postawionego w poprzednim odcinku:

1 Podstawy c++ w pigułce.

Programowanie Komputerów 2FD. Materiały pomocnicze do laboratorium

Podstawy Programowania 2

1 Podstawy c++ w pigułce.

Praktycznie całe zamieszanie dotyczące konwencji wywoływania funkcji kręci się w okół wskaźnika stosu.

Kopiowanie ustawień SolidWorks

Papyrus. Papyrus. Katedra Cybernetyki i Robotyki Politechnika Wrocławska

Wprowadzenie do systemu Delphi

Szablony klas, zastosowanie szablonów w programach

Windows 10 - Jak uruchomić system w trybie

Budowa aplikacji z graficznym interfejsem użytkownika - GUI (Graphic User Interface)

Informacje ogólne. Karol Trybulec p-programowanie.pl 1. 2 // cialo klasy. class osoba { string imie; string nazwisko; int wiek; int wzrost;

Tworzenie i korzystanie z plików JAR. Biblioteka JFreeChart

Programowanie w środowisku graficznym GUI

Cwiczenie nr 1 Pierwszy program w języku C na mikrokontroler AVR

Zmienne i struktury dynamiczne

Ćwiczenie nr 6. Programowanie mieszane

HELIOS pomoc społeczna

16) Wprowadzenie do raportowania Rave

Edytor tekstu OpenOffice Writer Podstawy

Instrukcja obsługi notowań koszykowych w M@klerPlus

Makropolecenia w PowerPoint Spis treści

Instalowanie VHOPE i plików biblioteki VHOPE

Programowanie obiektowe zastosowanie języka Java SE

Część XVII C++ Funkcje. Funkcja bezargumentowa Najprostszym przypadkiem funkcji jest jej wersja bezargumentowa. Spójrzmy na przykład.

Politechnika Gdańska Katedra Optoelektroniki i Systemów Elektronicznych

Programowanie Wizualno Obiektowe - zajęcia 2 (PWO_BCPP_2_2) Tworzenie i kompilowanie projektów Programowanie Wizualno Obiektowe Zajęcia 2, część 2

Wprowadzenie do projektu QualitySpy

MentorGraphics ModelSim

Język JAVA podstawy. wykład 1, część 2. Jacek Rumiński. Politechnika Gdańska, Inżynieria Biomedyczna

Instrukcja. importu dokumentów. z programu Fakt do programu Płatnik. oraz. przesyłania danych do ZUS. przy pomocy programu Płatnik

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

Rozdział 4 KLASY, OBIEKTY, METODY

Państwowa Wyższa Szkoła Zawodowa w Gorzowie Wlkp. Laboratorium architektury komputerów

Podstawy programowania. Ćwiczenie. Pojęcia bazowe. Języki programowania. Środowisko programowania Visual Studio

Przewodnik Szybki start

Zadanie 9. Projektowanie stron dokumentu

Wskaźniki do funkcji i metod

- Narzędzie Windows Forms. - Przykładowe aplikacje. Wyższa Metody Szkoła programowania Techniczno Ekonomiczna 1 w Świdnicy

SYSTEMY OPERACYJNE I SIECI KOMPUTEROWE

1. Pierwszy program. Kompilator ignoruje komentarze; zadaniem komentarza jest bowiem wyjaśnienie programu człowiekowi.

Nagrywamy podcasty program Audacity

Java pierwszy program w Eclipse «Grzegorz Góralski strona własna

I - Microsoft Visual Studio C++

Informatyka I : Tworzenie projektu

5.6.2 Laboratorium: Punkty przywracania

Podstawy programowania. Wykład: 9. Łańcuchy znaków. dr Artur Bartoszewski -Podstawy programowania, sem 1 - WYKŁAD

Scenariusz lekcji. Scenariusz lekcji 1 TEMAT LEKCJI: 2 CELE LEKCJI: 2.1 Wiadomości: 2.2 Umiejętności: 3 METODY NAUCZANIA: 4 ŚRODKI DYDAKTYCZNE:

PROGRAM TESTOWY LCWIN.EXE OPIS DZIAŁANIA I INSTRUKCJA UŻYTKOWNIKA

Instalacja programu Warsztat 3 w sieci

Funkcje przeciążone, konstruktory kopiujące, argumenty domyślne

Przykładowa dostępna aplikacja w Visual Studio - krok po kroku

Instrukcja obsługi aplikacji Karty Pojazdów dla Dealerów Samochodowych

Kopiowanie, przenoszenie plików i folderów

Gdy z poziomu programu Delphi otworzysz folder pierwszy program, zauważysz tylko dwa pliki [rys.1]:

Transkrypt:

Jacek Matulewski http://www.phys.uni.torun.pl/~jacek/ Tworzenie aplikacji Windows Biblioteki DLL. Aplety panelu sterowania (C++ Builder) Ćwiczenia Toruń, 13 grudnia 2002 Najnowsza wersja tego dokumentu znajduje się pod adresem http://www.phys.uni.torun.pl/~jacek/dydaktyka/rad/rad4_dll_cbuilder.pdf Źródła opisanych w tym dokumencie programów znajdują się pod adresem http://www.phys.uni.torun.pl/~jacek/dydaktyka/rad/rad4_dll.zip 1

I. Spis treści I. Spis treści... 2 II. Funkcje w bibliotekach DLL... 3 1. Tworzenie biblioteki DLL eksport funkcji... 3 2. Statyczne łączenie bibliotek DLL import funkcji... 5 3. Dynamiczne łączenie bibliotek DLL... 6 4. Powiadamianie biblioteki o załadowaniu jej lub usunięciu z pamięci... 7 III. Formy w bibliotece DLL... 9 IV. Wykorzystanie skompilowanych bibliotek... 12 1. Wykorzystanie biblioteki DLL w Visual Basic... 12 2. Uruchamianie funkcji z linii komend (RunDLL/RunDLL32)... 12 3. Wykorzystanie kodu napisanego w C++ Builderze w Delphi...13 V. Aplet panelu sterowania... 14 2

II. Funkcje w bibliotekach DLL 1. Tworzenie biblioteki DLL eksport funkcji Najprościej stworzyć bibliotekę DLL (ang. dynamic linked library) korzystając z kreatora ukrytego w menu New..., na zakładce New pod pozycją DLL Wizard (w starszych wersjach Buildera po prostu DLL). Dla naszych potrzeb nie trzeba zmieniać opcji w okienku dialogowym kreatora. Powstanie plik cpp bez skojarzonego pliku nagłówkowego, w którym zdefiniowana jest jedna funkcja DllEntryPoint 1 o której napiszę niżej. W module możemy zdefiniować funkcje i je wyeksportować. Zdefiniujmy sześć najprostszych funkcji, których jedynym działaniem jest wyświetlanie komunikatów w stylu oto jestem : void Test0(void) ShowMessage("Test0 - Funkcja znajduje się w bibliotece DLL"); void Test1(void) ShowMessage("Test1 - Funkcja znajduje się w bibliotece DLL"); void stdcall Test2(void) ShowMessage("Test2 - Funkcja znajduje się w bibliotece DLL"); void stdcall Test3(void) ShowMessage("Test3 - Wywoływanie funkcji wewnętrznej"); Funkcja_wewnetrzna(); void Funkcja_wewnetrzna(void) ShowMessage("Funkcja_wewnetrzna - nie jest wyeksportowana"); int stdcall Test4(AnsiString argument) ShowMessage("Test4 - Wywołanie funkcji wewnętrznej\nargument: \"" +argument+"\"\nfunkcja zwraca liczbe losowa"); randomize(); return rand(); //zwraca liczbe pseudolosowa 1 Jeżeli wybrana została opcja emulacji Microsoft Visual C++ funkcję DllEntryPoint zastąpi funkcja DllMain. 3

Pierwsze trzy (Test0(), Test1() i Test2()) wyglądają tak samo nie mają argumentów, ani nie zwracają wartości, ale w różny sposób je wyeksportujemy. Test3() wywołuje funkcję wewnętrzną o nazwie Funkcja_wewnetrzna(). Funkcja ta nie będzie wyeksportowana, a więc nie będzie dostępna z zewnątrz biblioteki, co nie przeszkadza w jej wywołaniu przez inne funkcje z tej samej biblioteki. Wreszcie Test4() jest przykładem wyeksportowanej funkcji przyjmującej argumenty (w naszym przypadku łańcuch) i zwracający wartość (tu będzie to liczba naturalna). Tak przygotowane funkcje musimy wyeksportować. W tym celu na początku pliku, za linią USERES("Project1.res");, dodajemy deklaracje funkcji zawierające odpowiednie modyfikatory, które podczas kompilacji biblioteki spowodują utworzenie odpowiednich wejść do biblioteki. Test0 Pierwsza funkcja zadeklarowana jest z modyfikatorem export 2, który informuje kompilator, o tym, że ma dopisać funkcję do tablicy eksportu dla danego modułu. Ten sam modyfikator można zastosować do klas lub danych zawsze po podaniu typu zmiennej, typu zwracanej przez funkcję wartości lub klasy. Odpowiedni prototyp powinien wyglądać następująco: void export Test0(void); Test1 Równoznaczne z modyfikatorem eksport jest zastosowanie słowa kluczowego declspec z argumentem dllexport. declspec(dllexport) void Test1(void); lub void declspec(dllexport) Test1(void); W tym przypadku położenie modyfikatora nie ma znaczenia. Test2 Obie funkcje będą dobrze rozpoznawane przy statycznym podłączaniu biblioteki DLL do aplikacji z wykorzystaniem plików lib, które zawierają pełne informacje o bibliotekach DLL (tj. położenie funkcji wewnątrz biblioteki, ale o tym poniżej). Jednak w przypadku podłączania dynamicznego należy użyć kolejnych modyfikatorów: extern "C" który zapobiega wikłania (ang. mangle) nazwy funkcji w programach C++ oraz stdcall, który wymusza typową dla Win32 konwencję przekazywania argumentów. Chcę podkreślić jeszcze raz, że modyfikator ten ma znaczenie jedynie w przypadku dynamicznego ładowania bibliotek; w przypadku statycznym do projektu dodawany jest plik lib, który informuje kompilator o liczbie i typie przyjmowanych przez wyeksportowane funkcje argumentów. extern "C" declspec(dllexport) void stdcall Test2(void); Test3 Kolejna deklaracja nie zawiera żadnych modyfikatorów zadeklarowana funkcja Funkcja_wewnetrzna pełni rolę przykładu funkcji wewnętrznej biblioteki (dostępnej np. dla funkcji Test3, ale niedostępnej z zewnątrz biblioteki). Funkcja Test3 jest wyeksportowana identycznie jak Test2. extern "C" declspec(dllexport) void stdcall Test3(void); void Funkcja_wewnetrzna(void); Generalną zasadą powinno być, żeby udostępniać jedynie niezbędne minimum funkcji, klas i danych. Podobnie jak w przypadku zasad projektowania obiektowego dostępne z zewnątrz powinny być tylko metody przeznaczone do wykorzystania przez użytkownika biblioteki. 2 Równoważne do export jest _export (z jednym podkreśleniem z przodu) 4

Test4 I wreszcie ostatnia funkcja pokazuje w jaki sposób deklarować funkcję przyjmujące argumenty i zwracające wartość funkcji: extern "C" declspec(dllexport) int stdcall Test4(AnsiString); Po skompilowaniu biblioteki otrzymamy plik z rozszerzeniem DLL. Ważną własnością tego pliku jest jego uniwersalność można go bowiem wykorzystać nie tylko w projektach tworzonych w tym samym środowisku firmy Borland. Jest on zgodny ze specyfikacją bibliotek DLL Windows i może być np. wykorzystany w platformach programistycznych Microsoft Visual C++ lub Visual Basic, a nawet z linii komend za pomocą polecenia RunDLL32. 2. Statyczne łączenie bibliotek DLL import funkcji Aby dołączyć bibliotekę DLL do aplikacji w sposób statyczny (biblioteka ładowana jest do pamięci zaraz po uruchomieniu aplikacji i pozostaje tam, aż do zakończenia działania programu) należy stworzyć bibliotekę importową. Służy do tego narzędzie dostarczane razem z kompilatorami Borlanda IMPLIB.EXE z katalogu BIN 3. Ponieważ moduł DLL w trakcie tworzenia projektu jest zazwyczaj dość często modyfikowany i po każdej kompilacji należy od nowa tworzyć nową bibliotekę dostępową LIB warto napisać plik wsadowy np. IMPLIB -c -f Project1 Project1.dll Tworzy on plik Project1.lib, który powinien być włączony do projektu aplikacji statycznie łączącej bibliotekę DLL. Uwaga! Plik.lib zawiera absolutne ścieżki do plików. Przy przenoszeniu katalogu projektu (także przy zmianie komputera) musi być odnowiony. Uwaga! W nowszych wersjach C++ Buildera w Project, Options..., zakładka Linker znajduje się opcja Generate import library, która powoduje automatyczne wywołanie IMPLIB przy kompilacji biblioteki DLL. Stwórzmy nowy pusty projekt i zachowajmy go np. jako Projekt2. Aby wykorzystać w nim bibliotekę Projekt1.dll należy do projektu włączyć odpowiednią bibliotekę importową Projekt1.lib (Add file to project... z menu lub paska narzędzi). Spowoduje to dodanie do źródła projektu (Project2.cpp) linii USELIB("Project1.lib");. Aby wykorzystać umieszczone w niej funkcje należy je odpowiednio zadeklarować. Deklaracja importująca funkcję z biblioteki musi być dokładnym odzwierciedleniem deklaracji eksportującej. Funkcje z prototypem zawierającym export należy importować korzystając z modyfikatora import. I analogicznie dla funkcji z declspec(dllexport) należy teraz napisać declspec(dllimport). Wszystkie modyfikatory muszą być uwzględnione (extern "C", stdcall). W efekcie w pliku nagłówkowym, przed deklaracją klasy formy powinniśmy zapisać: void import Test0(void); declspec(dllimport) void Test1(void); extern "C" declspec(dllimport) void stdcall Test2(void); extern "C" declspec(dllimport) void stdcall Test3(void); extern "C" declspec(dllimport) int stdcall Test4(AnsiString); Możemy teraz bez ograniczeń wykorzystywać funkcje z biblioteki DLL tak, jakby znajdowały się w bieżącym projekcie. Można je na przykład uruchomić naciskając przycisk Button1: 3 W istocie program ten już w tej samej wersji był dostarczany z Borland Pascal 7. 5

void fastcall TForm1::Button1Click(TObject *Sender) Test0(); Test1(); Test2(); Test3(); int wynik=test4("przykładowy łańcuch"); ShowMessage("Wynik funkcji Test4: "+(AnsiString)wynik); Uwaga! Jeżeli po prawidłowej kompilacji projektu, w którym wykorzystywane są biblioteki DLL łączone w sposób statycznym (np. Project2 z dołączonych do skryptu źródeł), w trakcie jej uruchomienia występuje błąd pierwszą czynnością powinno być sprawdzenie biblioteki importowej lib. Najlepiej usunąć ją z projektu, przygotować aktualną wersję i jeszcze raz włączyć ją do projektu. 3. Dynamiczne łączenie bibliotek DLL Choć łączenie statyczne jest proste, to jednak nie wykorzystuje zalety bibliotek DLL pozwalającej oszczędzać zajmowaną ilość pamięci komputera. W łączeniu dynamicznym biblioteka jest ładowana tylko na czas wykorzystania jej zawartości, potem może być usunięta z pamięci. Łączenie dynamiczne zajmuje nieco czasu, więc nie powinno być wykonywane przed każdym wykorzystaniem często wywoływanej funkcji, bo w znaczący sposób spowolni to działanie programu. Łączenie dynamiczne jest nieco trudniejsze w realizacji, ale za to w pełni wykorzystuje zalety DLL. Jak już wspomniałem można bibliotekę DLL załadować do pamięci i przestrzeni adresowej aplikacji w dowolnym momencie działania tej aplikacji za pomocą funkcji WinAPI LoadLibrary(). Nazwa pliku biblioteki nie musi być znana w momencie kompilacji występuje w funkcji ładującej jedynie jako łańcuch. Każda wyeksportowana funkcja, którą chcemy wykorzystać musi być indywidualnie zaimportowana, a dokładniej aplikacja musi pobrać adres funkcji w swojej przestrzeni adresowej po załadowaniu biblioteki (służy do tego funkcja WinAPI GetProcAddress()). Zbędne jest więc korzystanie z bibliotek importowych LIB. void fastcall TForm1::Button1Click(TObject *Sender) //Sciezka przeszukiwania biblioteki jest w Win32 SDK, zob. LoadLibrary HINSTANCE DLLHandle=LoadLibrary("Project1.dll"); if (DLLHandle!=NULL) ShowMessage("Wczytanie biblioteki udało się"); else ShowMessage("Wczytanie biblioteki nie powiodło się"); //Dla ulatwienia definiujemy typ funkcji bez argumentow i zwracanych //wartosci; DLLTestType - funkcja void Test#(void), wskaźniki do funkcji typedef void (*DLLTestType)(void); //Nazwa funkcji to dla C++ jej wskaznik, //bedziemy mogli z nich korzystac jak ze zwyklych funkcji DLLTestType ATest0=(DLLTestType)GetProcAddress(DLLHandle, "Test0"); DLLTestType ATest1=(DLLTestType)GetProcAddress(DLLHandle, "Test1"); DLLTestType ATest2=(DLLTestType)GetProcAddress(DLLHandle, "Test2"); DLLTestType ATest3=(DLLTestType)GetProcAddress(DLLHandle, "Test3"); //Kontrolowane uruchamianie funkcji if (ATest0==NULL) ShowMessage("Uruchomienie Test0 nie jest możliwe"); else ATest0(); if (ATest1==NULL) ShowMessage("Uruchomienie Test1 nie jest możliwe"); else ATest1(); if (ATest2==NULL) ShowMessage("Uruchomienie Test2 nie jest możliwe"); else ATest2(); if (ATest3==NULL) ShowMessage("Uruchomienie Test3 nie jest możliwe"); else ATest3(); 6

//Definicja typu dla Test4 //DLLTest4Type - funkcja int Test4(AnsiString), wskaźniki do funkcji typedef int (*DLLTest4Type)(AnsiString); DLLTest4Type ATest4=(DLLTest4Type)GetProcAddress(DLLHandle, "Test4"); if (ATest4==NULL) ShowMessage("Uruchomienie Test0 nie jest możliwe"); else int wynik=atest4("przykładowy łańcuch"); ShowMessage("Wynik funkcji Test4: "+(AnsiString)wynik); //Zwolnienie biblioteki z pamieci if (FreeLibrary(DLLHandle)) ShowMessage("Usunięcie biblioteki z pamięci udało się"); else ShowMessage("Usunięcie biblioteki z pamięci nie powiodło się"); Jak wspominałem wcześniej dynamiczne łączenie biblioteki realizowane jest przez funkcję WinAPI LoadLibrary() pobierającą nazwę biblioteki, a zwracającą, jeżeli połączenie się powiodło, uchwyt do tej biblioteki (uchwyt może być przechowywany w zwykłym THandle lub, jak jest w powyższym przykładzie, w zmiennej typu HINSTANCE). Podanie samej nazwy biblioteki bez jego ścieżki jest wygodne, gdyż umożliwia to swobodne przemieszczanie pliku biblioteki między katalogiem aplikacji lub katalogiem bieżącym w trakcie projektowania aplikacji, a katalogiem systemowym (WINDOWS\SYSTEM), katalogiem głównym Windows lub wreszcie innym katalogiem wymienionym w zmiennej środowiskowej PATH po jej zainstalowaniu. Do czterech wskaźników 4 ATest0 ATest3 pobierane są adresy funkcji Test0 Test3. Ponieważ nazwa funkcji jest dla C/C++, właśnie jej wskaźnikiem możemy posługiwać się nowymi zmiennymi tak jak nazwami funkcji tzn. wywołać funkcję Test0() z biblioteki pisząc ATest0(). Zgodnie z zapowiedzią funkcje Test0() i Test1() nie działają, jeżeli biblioteka nie jest załadowana dynamicznie (nie mają w deklaracji modyfikatora stdcall). Analogicznie uruchamiana jest ostatnia funkcja testowa różnica polega na uwzględnieniu jej argumentów i zwracanej wartości. Zwolnienie biblioteki możliwe jest dzięki funkcji WinAPI FreeLibrary(), której argumentem jest uchwyt do biblioteki. 4. Powiadamianie biblioteki o załadowaniu jej lub usunięciu z pamięci Można sobie wyobrazić bardziej skomplikowane od naszego wykorzystanie bibliotek DLL, która na przykład przy jej załadowaniu musi zainicjować zmienne globalne, tworzyć obiekty itp. Niezbędna do tego jest funkcja inicjująca, analogiczna do konstruktora obiektu oraz odpowiednik destruktora przy usuwaniu biblioteki z pamięci. Użytkownik może zawrzeć w bibliotece funkcję wewnętrzną o nazwie DllEntryPoint() (C++ Builder tworzy ją za nas), której deklaracja jest następująca: int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*) Alternatywnym sposobem, a jedynym w przypadku Delphi, jest skorzystanie ze zmiennej DLLProc, która może przechowywać adres funkcji typu void fastcall nazwa_funkcji(int reason);. Przypisanie adresu tej funkcji do DllProc spowoduje wywoływanie jej w analogicznych sytuacjach jak DllEntryPoint. Obie funkcje wywoływane są w czterech sytuacjach rozróżnianych wartościami argumentu reason: 4 Wygodnie jest, jak zrobiono w tym przykładzie, zdefiniować typy dla importowanych typów funkcji 7

DLL_PROCESS_ATTACH / DLL_PROCESS_DETACH gdy biblioteka jest włączona/wyłączona z wirtualnej przestrzeni adresowej procesu 5. Zdarza się to przy uruchomieniu/zamknięciu aplikacji (przy włączeniu jeszcze przed pokazaniem formy), jeżeli biblioteka jest ładowana statycznie i przy każdym wywołaniu LoadLibrary()/FreeLibrary() w przypadku ładowania dynamicznego. DLL_THREAD_ATTACH / DLL_THREAD_DETACH analogicznie jak wyżej, ale po utworzeniu przez proces wątku. Przy ładowaniu dynamicznym wartość (0 lub 1) zwracana przez DllEntryPoint dla argumentu DLL_PROCESS_ATTACH decyduje o powodzeniu funkcji LoadLibrary(). W przypadku statycznym wartość 0 powoduje wystąpienie wyjątku int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*) switch (reason) case DLL_PROCESS_ATTACH: ShowMessage("DllEntryPoint DLL_PROCESS_ATACH"); case DLL_PROCESS_DETACH: //Usunac dla biblioteki ladowanej dynam. ShowMessage("DllEntryPoint DLL_PROCESS_DETACH"); case DLL_THREAD_ATTACH: ShowMessage("DllEntryPoint DLL_THREAD_ATTACH"); case DLL_THREAD_DETACH: ShowMessage("DllEntryPoint DLL_THREAD_DETACH"); return 1; Po zmodyfikowaniu biblioteki (Project1) możemy sprawdzić kiedy inicjowana jest biblioteka przy statycznym (Project2) i dynamicznym jej dołączaniu (Project3). Warto zauważyć, że po zmianie biblioteki, właściwa aplikacja nie musi być nawet przekompilowywana. Uwaga! Przy ładowaniu statycznym biblioteki w starszych wersjach C++ Builder próba wyświetlenia komunikatu przy DLL_PROCESS_DETACH kończy się błędem. 5 W Win32 każdy proces ma osobną wirtualną przestrzeń adresową o wielkości 4GB, w odróżnieniu od wspólnej przestrzeni adresowej Win16. 8

III. Formy w bibliotece DLL. Korzystając z TDiskInfoPanel i przykładu IV.3 w części WinAPI napiszemy i umieścimy w bibliotece DLL formę pokazującą informacje o dyskach zainstalowanych w komputerze. Skopiujmy pliki DiskInfo.h oraz DiskInfo.cpp zawierające definicję i deklarację klasy TDiskInfoPanel. Stwórzmy bibliotekę DLL (New..., DLL) i dołączmy do niej te pliki (#include "DiskInfo.cpp"). Wygodne będzie przechowywanie globalnego (w obrębie pliku biblioteki) wskaźnika do formy, zadeklarujmy więc zmienną typu wskaźnika do TForm: TForm* DIPForm=NULL; Ponadto potrzebujemy dwóch funkcji tworzącej formę i usuwającej ją z pamięci. Pierwsza funkcja DIP_CreateForm(bool modal) tworzy formę bez właściciela: DIPForm=new TForm((TComponent*)NULL); i zapisuje wskaźnik do niej do zmiennej globalnej DIPForm. Następnie wykonywany jest kod, który jest kopią poleceń z Unit1.cpp znajdującego się w przywołanym wyżej przykładzie WinAPI.IV.3 (zastąpiono jedynie Form1 przez DIPForm). I wreszcie na koniec pokazywana jest forma w sposób zależny od argumentu całej funkcji (modalnie tzn. uniemożliwiając dostęp do aplikacji zanim nie zostanie zamknięte wywołane okno lub jako zwyczajne okno): if (modal) DIPForm->ShowModal(); else DIPForm->Show(); Druga funkcja DIP_DestroyForm() najpierw zamyka formę, a następnie usuwa ją z pamięci (delete DIPForm;). Istotne jest też przypisanie na powrót wskaźnikowi wartości NULL. Uwaga! Prototypy obu form zostały napisane tak, żeby możliwe było ich uruchomienie przy dynamicznym załadowaniu biblioteki. Równocześnie pozwala to na używanie biblioteki z tymi funkcjami w innych aplikacjach, a także uruchamianie ich bezpośrednio z linii komend (zob. poniższe paragrafy). Oto kompletny kod biblioteki DLL: #include <vcl\vcl.h> #pragma hdrstop USERES("Project1.res"); int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*) return 1; #include "DiskInfo.cpp" TForm* DIPForm=NULL; //Prototyp funkcji exportowych extern "C" declspec(dllexport) void stdcall DIP_CreateForm(bool); extern "C" declspec(dllexport) void stdcall DIP_DestroyForm(); 9

//implementacja funkcji exportowych void stdcall DIP_CreateForm(bool modal) if (DIPForm!=NULL) if (modal) DIPForm->ShowModal(); else DIPForm->Show(); return; DIPForm=new TForm((TComponent*)NULL); DIPForm->Caption="Informacja o dyskach"; DIPForm->Position=poScreenCenter; const int drvletterno='z'-'a'+1; TDiskInfoPanel* DiskInfoPanel[drvletterno]; //tablica wskaznikow int polozenie_ostatniego=0; const int margines=10; int n=0; //numer kolejny odpowiadajacy literze int drvno=0; //ilosc istniejacych fizycznie dyskow for(char litera='a';litera<='z';litera++) DiskInfoPanel[n]=new TDiskInfoPanel(DIPForm); DiskInfoPanel[n]->Left=margines; DiskInfoPanel[n]->DriveLetter=litera; if (DiskInfoPanel[n]->Values.disk_accesible) DiskInfoPanel[n]->Parent=DIPForm; DiskInfoPanel[n]->Top=margines+drvno* (DiskInfoPanel[n]->Height+1.5*margines); polozenie_ostatniego=diskinfopanel[n]->top+ DiskInfoPanel[n]->Height; DIPForm->ClientWidth=DiskInfoPanel[n]->Width; drvno++; else delete DiskInfoPanel[n]; DiskInfoPanel[n]=NULL; DIPForm->ClientHeight=polozenie_ostatniego; if (modal) DIPForm->ShowModal(); else DIPForm->Show(); ; void stdcall DIP_DestroyForm() if (DIPForm!=NULL) DIPForm->Close(); delete DIPForm; DIPForm=NULL; Uwaga! Proszę zwrócić uwagę, że zamknięcie okna nie powoduje usunięcia go z pamięci. Po zamknięciu okna na i zniknięciu jego reprezentacji graficznej na ekranie (albo przez użytkownika, albo metodą DIPForm- 10

>Close()) obiekt formy istnieje i wskaźnik jest różny od NULL. Dlatego na samym początku funkcji DIP_CreateForm umieszczono warunek: if (DIPForm!=NULL) if (modal) DIPForm->ShowModal(); else DIPForm->Show(); return; chroniący przed ponownym tworzeniem obiektu. Jeżeli forma istnieje jest po prostu pokazywana. Inaczej powstałaby nowa forma, której wskaźnik zostałby zapisany do DIPForm, a do starej stracilibyśmy dostęp. Teraz zajmiemy się aplikacją, która posłuży do przetestowania biblioteki (menu New Application). Zapiszmy ją do tego samego katalogu co bibliotekę DLL jako Project2 i Unit2. Następnie programem IMPLIB stwórzmy bibliotekę importową i dołączmy ją do projektu. Z przyciskami zwiążemy funkcje wyeksportowane w bibliotece: void fastcall TForm1::Button1Click(TObject *Sender) DIP_CreateForm(CheckBox1->Checked); void fastcall TForm1::Button2Click(TObject *Sender) DIP_DestroyForm(); 11

IV. Wykorzystanie skompilowanych bibliotek Stworzona w poprzednim paragrafie biblioteka DLL posłuży teraz jako przykład przenaszalności skompilowanej biblioteki na inne platformy programistyczne. Należy pamiętać o jej skopiowaniu do katalogu projektów lub, czego raczej nie polecam, do katalogu Windows. 1. Wykorzystanie biblioteki DLL w Visual Basic Istotną zaletą skompilowanej biblioteki DLL jest możliwość wykorzystania jej w dowolnym innym kompilatorze. Warunkiem jest wykorzystanie modyfikatorów umożliwiających ładowanie dynamiczne wyeksportowanych funkcji przez funkcję LoadLibrary(). Dla przykładu wywołajmy funkcję z naszej biblioteki DIP_CreateForm() z aplikacji napisanej w Visual Basicu. Informacje o wykorzystywaniu bibliotek DLL w Visual Basicu 6.0 można znaleźć w MSDN 6 Na początku musimy zadeklarować importowane funkcje w sekcji General, Declarations: Private Declare Sub DIP_CreateForm Lib "Project1" (ByVal modal As Boolean) Private Declare Sub DIP_DestroyForm Lib "Project1" () Uwaga! W Visual Basicu, w przeciwieństwie do C++ istnieje rozróżnienie między funkcjami i procedurami/subroutinami. Wobec tego, ponieważ nasze funkcje nie zwracają wartości, muszą być zadeklarowane słowem kluczowym Sub, a nie Function. Zapomnienie o tym będzie powodowało zgłaszanie błędów i wadliwe działanie aplikacji. Teraz możemy wywołać je w procedurach związanych z naciśnięciem klawiszy (zawartość formy powinna być identyczna jak w poprzednim paragrafie): Private Sub Command1_Click() DIP_CreateForm Check1.Value End Sub Private Sub Command2_Click() DIP_DestroyForm End Sub 2. Uruchamianie funkcji z linii komend (RunDLL/RunDLL32) W katalogu systemowym Windows (WINDOWS\SYSTEM32) znajduje się program RunDLL32.exe umożliwiający uruchamianie programów umieszczonych w bibliotekach DLL. Ponieważ wykorzystuje on funkcję WinAPI LoadLibrary() funkcje muszą pozwalać na ładowanie dynamiczne, tzn. powinny być zadeklarowane z odpowiednimi modyfikatorami np.: extern "C" declspec(dllexport) void stdcall DIP_CreateForm(bool); Składnia tego programu jest następująca: RUNDLL.EXE <dllname>,<entrypoint> <optional arguments> 6 Odpowiedni paragraf: MSDN Home > MSDN Library > Visual Basic 6.0 > Component Tools Guide (Pro, Enterprise only) > Accessing DLLs and the Windows API > Using a DLL Procedure in Your Application 12

W naszym przypadku musimy napisać: RunDLL32.exe Project1.dll,DIP_CreateForm 0 3. Wykorzystanie kodu napisanego w C++ Builderze w Delphi Przeniesienie kodu (funkcji, obiektów) napisanego w C++ Builderze do Delphi nie jest możliwe wprost, tak jak jest możliwe w odwrotną stronę do Delphi nie jest dołączany kompilator C++. Jeżeli chcemy tego dokonać musimy skorzystać z bibliotek DLL. Podobnie jak w Visual Basicu w Pascalu istnieje rozróżnienie na funkcje i procedury. Ponieważ nasze funkcje nie zwracają wartości w tej nomenklaturze są procedurami. Stwórzmy projekt Delphi. Stwórzmy dodatkowy moduł (unit) tzw. moduł importowy, który pozwoli łączyć plik DLL statycznie identycznie jak inne skompilowane moduły w sekcji uses. unit Project1_ImportUnit; interface uses StdCtrls; procedure DIP_CreateForm(modal :Boolean); StdCall; procedure DIP_DestroyForm; StdCall; implementation procedure DIP_CreateForm; external 'Project1.dll'; procedure DIP_DestroyForm; external 'Project1.dll'; end. W sekcji interface zadeklarowane są funkcje ze słowem kluczowym StdCall, bo w taki sposób zadeklarowaliśmy przekazywanie argumentów w bibliotece DLL. Ale zamiast implementacji wskazujemy słowem kluczowym external na bibliotekę DLL, w której znajduje się jej kod. W zasadniczym kodzie aplikacji (jej forma może być taka sama jak w przykładzie C++ Buildera lub VB) możemy dodać moduł Project1_ImportUnit (tak nazwałem moduł importowy) do sekcji uses. Dodawanie statyczne bibliotek DLL w Delphi jest nieco łatwiejsze niż w C++ Builderze. Teraz można korzystać z funkcji (w tym kontekście procedur) DIP_CreateForm i DIP_DestroyForm. Zwiążmy je z przyciskami: procedure TForm1.Button1Click(Sender: TObject); begin DIP_CreateForm(CheckBox1.Checked); end; procedure TForm1.Button2Click(Sender: TObject); begin DIP_DestroyForm; end; 13

V. Aplet panelu sterowania Mając gotową bibliotekę DLL zawierającą klasę formy lub, jak jest w naszym przypadku, tworzącą dynamicznie formę możemy w kilku krokach uzyskać aplet panelu sterowania. Aplet taki jest w istocie biblioteką DLL zawierającą wyeksportowaną funkcję zwrotną o nazwie CPlApplet() i zmienionym rozszerzeniem nazwy pliku na.cpl. To jest jedyna funkcja za pomocą której Panel sterowania kontaktuje się z biblioteką. Zastępuje ona standardowe wejście do biblioteki funkcję DllEntryPoint(), można ją usunąć, ale oczywiście nie ma takiej konieczności. Funkcja zwrotna CPlApplet() zadeklarowana jest w WinAPI jako LONG APIENTRY CPlApplet(HWND hwndcpl, UINT umsg, LONG lparam1, LONG lparam2); Tłumacząc to na standardowy język C++ można deklarację przepisać jako 7 long stdcall CPlApplet(THandle hwndcpl, unsigned umsg, long lparam1, long lparam2); W C++ Builderze, w przeciwieństwie do Delphi, takie tłumaczenie nie jest wcale konieczne. Zdefiniowane są w nim już od najstarszej wersji wszystkie typy i stałe w identycznym kształcie jak w MS Win32 SDK. Należy pamiętać, że CPlApplet(), w przeciwieństwie do DllEntryPoint() musi być wyeksportowana, tj. jej rzeczywista deklaracja powinna być następująca: extern "C" declspec(dllexport) long stdcall CPlApplet(HWND hwndcpl, unsigned umsg, long lparam1, long lparam2); W opisie funkcji posłużę się przykładami kodu, w którym zdefiniuję jeden aplet, wywołujący znane nam z poprzednich paragrafów okienko prezentujące informacje o dyskach w postaci pasków postępu z odpowiednimi opisami. Wykorzystamy napisane wcześniej funkcje DIP_CreateForm(bool) i DIP_DestroyForm(). W cyklu życia apletu funkcja CPlApplet jest wywoływana wielokrotnie z różnymi argumentami w celu wymiany informacji między apletem a systemem. Typ i nazwa argumentów ma kojarzyć się z komunikatami Windows. Ich rodzaj zależy od żądanych od apletu informacji. Jako pierwszy wysyłany jest komunikat CPL_INIT, który jest czymś w rodzaju pytania od Panelu sterowania: Czy jesteś apletem?. Zwrócona wartość powinna być true, jeżeli chcemy, aby aplet został umieszczony wśród innych apletów Panelu sterowania i żeby wysyłane były kolejne komunikaty. extern "C" declspec(dllexport) long stdcall CPlApplet(HWND hwndcpl, unsigned umsg, long lparam1, long lparam2) switch (umsg) case CPL_INIT: return true; //Potwierdzamy, ze "jesteśmy" apletem default: return 0; 7 Modyfikator APIENTRY jest tożsamy z modyfikatorem WINAPI w kontekście bibliotek DLL, a ten ostatni dla środowiska 32-bitowego jest równoznaczny z stdcall, a więc oznacza standardowy sposób wywoływania funkcji i przekazywania do niej argumentów (ang. standard calling convention). W takim przypadku kompilator nie dodaje znaków podkreślenia przed nazwą funkcji, zachowuje wielkość liter, argumenty muszą być w zadeklarowanej ilości i bezwzględnie identycznego typu. 14

Kolejny komunikat to CPL_GETCOUNT, który jest pytaniem o ilość apletów przechowywanych w bibliotece. Odpowiedzią powinna być zwrócona przez wartość funkcji równa ilości apletów, które chcemy udostępnić. Kolejne komunikaty przekazywane są tyle razy, ile zadeklarowaliśmy apletów. case CPL_GETCOUNT: return 1; //ilosc okien dialogowych W następnych komunikatach CPL_INQUIRE i CPL_NEWINQUIRE Panel sterowania żąda informacji o ikonie, tytule i podpowiedzi (hint) apletu. W obu przypadkach do CPlApplet() przesyłane są wskaźniki do struktur umożliwiających zwrot odpowiednich informacji. Wygodniej jest wykorzystać CPL_NEWINQUIRE, ponieważ przekazywana do niego struktura jest wygodniejsza w obsłudze i nie wymaga dodawania plików zasobów. Na przekór lenistwu skorzystamy z trudniejszego w obsłudze CPL_INQUIRE. Parametry przesyłane z tą wiadomością są następujące: UNIT uappnum=(uint)lparam1; CPLINFO* lpcpli=(lpcplinfo)lparam2; Pierwszy to numer okna dialogowego / apletu komunikat jest wysyłany raz dla każdego zadeklarowanego apletu (numerowanych od 0). Drugi to wskaźnik do struktury CPLINFO, która zawiera trzy składniki int: idicon, idname, idinfo oraz jeden typu long ldata. Pierwsze trzy są numerami identyfikującymi elementy zasobów (zawartych w dołączonym do projektu pliku.res). Po kolei są to numery ikony apletu, tytułu i treści podpowiedzi. Ostatnia dana ldata to liczba którą przechowuje Panel sterowania i przekazuje w argumentach razem z komunikatami CPL_DBLCLK i CPL_STOP. Pomaga to czasami w programowaniu pozwalając na przechowanie informacji ponieważ biblioteka nie jest cały czas załadowana, jej zmienne lokalne mogą być usunięte. Stworzenie pliku zasobów nie jest zbyt dobrze wspierane przez Borland C++ Builder/Delphi. Obcecny w IDE Image Editor pozwala na umieszczanie w plikach.res ikon, bitmap i kursorów, ale nie wspiera umieszczania tam łańcuchów. A Windows pozwala na o wiele więcej (klawisze skrótów, okna dialogowe, kod HTML, menu i informacje o wersji aplikacji). Aby umieścić pozostałe informacje należy się posłużyć narzędziami linii komend BRCC32.EXE (Borland Resource Compiler) lub wygodnymi narzędziami konkurencyjnego Visual C++. Wybierzmy ponownie trudniejszą drogę i stwórzmy plik zasobów zawierających odpowiednie ikony i łańcuchy. Należy stworzyć plik o rozszerzeniu.rc (np. DiskInfo.rc), w którym wskażemy na pliki zawierające ikony i podamy łańcuchy z odpowiednimi identyfikującymi je numerami: STRINGTABLE //tytul apletu 1, "Informacje o dyskach" //opis apletu (okienko podpowiedzi) 2, "Informacje o wielkości, ilości wolnego miejsca, typu FAT dysków lokalnych, CD-ROMów i dysków sieciowych" //ikona jest wczytywana z pliku Project1.ico 101 ICON Project1.ico a następne skompilować poleceniem brcc32.exe DiskInfo.rc. Powstanie plik DiskInfo.res, którego format jest zgodny ze standardem plików z zasobami. Uwaga! Linia zaczynająca się od 2, "Informacje o i następna powinna znajdować się w jednej linii. 15

Mając plik zasobów res możemy włączyć go do projektu (Projet, Add to project...) i zareagować na komunikat CPL_INQUIRE: case CPL_INQUIRE: lpcpli=(lpcplinfo)lparam2; //wskaznik do CPLINFO lpcpli->idicon=101; lpcpli->idname=1; lpcpli->idinfo=2; lpcpli->ldata=0; return 0; Ignorujemy pierwszy parametr przekazujący numer apletu, ponieważ w naszej bibliotece jest tylko jeden aplet (jeden zadeklarowaliśmy przy GET_COUNT) i ten komunikat będzie przekazany do biblioteki tylko jeden raz. Musimy wcześniej zadeklarować wskaźnik CPLINFO* lpcpli=null; (nie można tego zrobić w obrębie instrukcji switch). Przypisanie do idicon, idname i idinfo wartości zdefiniowanej w stałej CPL_DYNAMIC_RES spowoduje wywoływanie CPL_NEWINQUIRE przy każdym wyświetleniu ikony. Jest to sposób umożliwiający zmianę ikony w zależności od stanu komputera. Niestety, jak twierdzi MS Win32 SDK, znacznie zwalnia to działanie Panelu sterowania 8. W praktyce korzysta się zazwyczaj z tego komunikatu zamiast CPL_INQUIRE, ponieważ nie wymaga on pliku zasobów (pozwala na podawanie tekstu tytułu i podpowiedzi w postaci zwykłego łańcucha i ikony poprzez uchwyt). Oba komunikaty powinny powodować zwracanie przez CPlApplet() wartość 0, co informuje Panel sterowania o powodzeniu. Po wysłaniu tych komunikatów funkcja CPlApplet() jest wywoływana dopiero wtedy, gdy użytkownik usiłuje otworzyć odpowiedni aplet klikając dwukrotnie na jego ikonę w Panelu sterowania. Wysyłany jest wówczas komunikat CPL_DBLCLK. Reakcja na niego powinna się wiązać z wywołaniem okienka dialogowego. My po prostu wywołamy zdefiniowaną w poprzednich paragrafach funkcję DIP_CreateForm(). case CPL_DBLCLK: DIP_CreateForm(true); //okno musi byc modalne return 0; Podobnie jak w poprzednim wywołaniu CPlApplet, jeżeli wszystko się powiodło informujemy o tym panel sterowania zwracając zero. Forma musi być wywołana modalnie inaczej po jej pokazaniu zostanie zaraz zamknięta nic nie zatrzymuje wysyłanie kolejnych komunikatów i usunięcia biblioteki z pamięci 9. I wreszcie, po zamknięciu formy przez użytkownika zostaną wysłane dwa komunikaty CPL_STOP i CPL_EXIT. Jeden z nich powinien dla porządku usunąć formę z pamięci (zamknięcie okna nie powoduje usunięcia jej obiektu, ale i tak usunięty zostałby przy odłączaniu biblioteki). case CPL_EXIT: DIP_DestroyForm(); return 0; Teraz trzeba skompilować projekt, a plik wynikowy Project1.dll przemianować na przykład na Project1.cpl. Ten plik należy skopiować do katalogu systemowego Windows (C:\WINDOWS\SYSTEM lub C:\WIN2000\SYSTEM32 w zależności od wersji systemu). Jeżeli nie popełniliśmy żadnego błędu w oknie Panelu sterowania pojawi się nowa ikona, której kliknięcie pokaże informacje o dyskach. 8 Przy mocy dzisiejszych komputerów nie ma to chyba większego znaczenia. 9 Jeżeli nie tworzymy formy dynamicznie, a mamy klasę formy w bibliotece powinna mieć własność Visible ustawioną na false. Inaczej próba wywołania ShowModal() skończy się błędem. 16