Biblioteki DLL

1. Co to są biblioteki DLL?

W Microsoft® Windows®, dynamicznie dołączane biblioteki (Dynamic Load Library) są modułami, które zawierają funkcje i dane. Biblioteka DLL jest ładowana w czasie wykonywania procesu, wywołanego z poziomu innych modułów (.EXE lub .DLL). Po załadowaniu, biblioteka jest odwzorowywana w przestrzeń adresową wywołanego procesu.
Biblioteki DLL mogą zawierać dwa rodzaje funkcji: eksportowane i wewnętrzne. Funkcje eksportowane mogą być wywoływane z innych modułów. Natomiast funkcje wewnętrzne mogą być wywoływane tylko w bibliotece, w której zostały zdefiniowane. Pomimo, że biblioteki DLL mogą eksportować dane, to zazwyczaj, dane znajdujące się w bibliotekach są używane tylko lokalnie.
Stosowanie bibliotek DLL pozwala na modułowe tworzenie aplikacji. Wiąże się to z możliwością łatwej modernizacji funkcjonalności programów. Pomaga też w redukowaniu zapotrzebowania na pamięć, szczególnie wtedy, gdy kilka aplikacji korzysta jednocześnie z takiej samej bazy funkcji. Pomimo, że każda aplikacja tworzy własną kopię danych, to oszczędność pamięci wynika z możliwości uwspólniania kodu.
W Microsoft® Win32® API (Application Programming Interface) zaimplementowano zbiór dynamicznie łączonych bibliotek, więc procesy używające Win32 API używają połączeń dynamicznych.
(text by Arnold Adamczyk, Katedra Miernictwa Elektronicznego, Wydział ETI, Politechnika Gdańska)

2. Różnice między "unitem" a biblioteką DLL.

Główna różnica to sposób dołączania kodu do programu. Kod wykonywalny biblioteki DLL jest umieszczany tylko jeden raz w PAO. Wszystkie programy korzystajace z biblioteki DLL odwołują się do tego samego miejsca w pamięci. Gdy programy korzystają z unitów, każdy z nich ma swoją statyczną kopię, co w efekcie niepotrzebnie zajmuje PAO.

3. Jak utworzyć bibliotekę DLL w Delphi?

Robi się to tak samo jak przy tworzeniu zwykłych aplikacji.
Trzeba wybrać: File -> New -> DLL, potem Project -> Options -> VersionInfo -> Module Attributes i tam trzeba zaznaczyć DLL.
Składnia jest podobna do składni unitu. Ale dla formalności wygląda tak:

library <nazwa>; //musi być taka sama jak nazwa pliku!!!
Uses //deklaracje używanych unitów
SysUtils, Classes;

//deklaracje zmiennych, deklaracje i definicje funkcji
exports
//poniżej jest miejsce na deklaracje funkcji udostępnianych w DLL'u
//np.
//my_funct_l index 1,
//my_function_2 index 2;

begin
//część inicjalizująca
end.

Indeks to liczba porządkowa, potrzebna przy importowaniu funkcji z biblioteki do programu.
Przy deklaracji funkcji & procedur należy pamiętać o dyrektywie stdcall, która decyduje o sposobie przekazywania parametrów do wywoływanych funkcji/procedur z DLL'a. Stdcall jest standardowym sposobem przekazywania parametrów w Windozie, więc polecane jest ją stosować. Dlaczego? Chodzi m.in. o kompatybilność między kodami napisanymi w różnych językach. Przykład: Napisałem DLL'a w C++ i zaimportowałem z niego funkcje do programu napisanego w Delphi. Na początku przy deklaracjach funkcji pominąłem dyrektywę stdcall. Efekt był piorunujący: BLUE SCREEN! YEA!. Bo oczywiście C++ przekazuje parametry do funkcji od prawej do lewej, a Delphi odwrotnie (albo na odwrót - can't remember). I stąd całe zamieszanie.

Pamiętajcie zatem - przy pisaniu DLL'a najlepiej skompilować go tak, ażeby wszystkie funkcje wywoływane były z dyrektywą stdcall. Przy importowaniu funkcji nie zapomnijcie również tej dyrektywy dodać. A robi się to tak:

function My_function(...): ...; stdcall;
procedure my_proc(...);stdcall;

Żeby otrzymać wynikowego DLL trzeba tylko skompilować aktualny "projekt".

4. Jak zaimportować funkcje & procedury z DLL 'a?

Jest kilka sposobów. Główny podział to importowanie dynamiczne i statyczne. Importowane statyczne polega na pobieraniu zawsze tej samej procedury z tej samej biblioteki. Importowanie dynamiczne polega na określeniu biblioteki dopiero w trakcie wykonywania programu. Aby zaimportować dynamiczne trzeba zadeklarować użycie unitu Windows.
Okej, poniżej opisałem sposoby importowania funkcji (i procedur też - ale mnie to wkurza):

    a) Importowanie dynamiczne przez liczbę porządkową (moje ulubione :) )

Jest to dokładnie tak, jak na załączonym przykładzie. Trzeba najpierw zadeklarować identyfikator
biblioteki (tzw. handler) czyli np. var xxx: HModule. Żeby móc użyć typu Hmodule trzeba najpierw zadeklarować użycie unitu Windows. Teraz konieczna jest deklaracja danej funkcji/procedury (posłużę się jednym przykładem):

rob_cos : procedure (...); stdcall;

Następnie trzeba załadować DLL' a do PAO:

xxx:=LoadLibrary (<nazwa>);

<nazwa> to obiekt typu string.

Kolejnym krokiem jest zaimportowanie interesującej nas funkcji. Robi się to za pomocą procedury GetProcAddress(handler: Hmodule, Pchar(liczba_porządkowa) );
Np. (zakładam, że liczba porządkowa dla rob_cos to 1)

Rob cos:=GetProcAddress(xxx,Pchar(1) );

No i teraz hulaj dusza. Można już korzystać z funkcji rob_cos. Nie zapomnijcie tylko zwolnić pamięci po zakończeniu programu:

FreeLibrary(<nazwa>);

    b) Importowanie statyczne poprzez nazwę

Deklaracje funkcji w programie muszą wyglądać następująco:

function haha(...) : zwrot; external 'moj_dll' name 'nazwa_funkcji_w_dll'; stdcall;

Czyli odnosząc sie do załączonego przykładu taka deklaracja wyglądałaby następująco:

function odejmij(const a, b : TWektor) : TWektor; external 'dll_sample' name 'odejmij_wektory'; stdcall;

    c) Importowanie statyczne poprzez liczbę porządkową

Ogólnie deklaracja taka ma postać:

function rehehe(...) : typ ; external 'moj_dll' index liczba; stdcall;

I znowu odniosę się do załączonego przykładu:

function odejmij(const a, b :TWektor) : TWektor; external 'dll_sample' index 2; stdcall;

opracował:
Adam Grześko