Delphi Kompendium Roz10

background image

Logowanie | Rejestracja | Forum | Pomoc | Reklama | Szukaj

Strona główna :: Delphi :: Kompendium

Edytuj

Historia

Rozdział 10

Biblioteki DLL

Pewnie nieraz spotkałeś się z pojęciem biblioteka DLL. Być może wiesz już, czym są te biblioteki i jak ich
używać. Jeżeli jeszcze nie jesteś tego świadomy, nie przejmuj się ? objaśnieniem tego pojęcia zajmę się na
samym początku niniejszego rozdziału. Nauczysz się tworzyć i wykorzystywać stworzone przez siebie biblioteki
w programach działających w systemie Windows.

Spis treści

1 Czym jest biblioteka DLL?

2 Do czego mogą się przydać biblioteki DLL?

2.1 Zalety

2.2 Wady

3 Tworzenie bibliotek DLL

3.1 Budowa biblioteki

3.2 Rozmiar biblioteki

4 Eksportowanie procedur i funkcji

4.1 Eksportowanie przez nazwę

4.2 Eksport przez indeks

5 Ładowanie bibliotek DLL

5.1 Ładowanie statyczne

5.2 Ładowanie dynamiczne

6 Konwersje wywołania

7 Formularze w bibliotekach DLL

7.1 Tworzenie formularza

7.2 Eksportowanie formularza

8 Przekazywanie rekordów do bibliotek

8.1 Budowa pliku mp3

8.2 Odczyt tagu z pliku mp3

8.3 Demo

9 Łańcuchy w bibliotekach DLL

10 Zasoby w bibliotece DLL

10.1 Przygotowanie zasobów

10.2 Ładowanie zasobów z biblioteki DLL

11 Procedura inicjująco-kończąca

11.1 Blok begin biblioteki DLL

11.2 DLLProc

11.3 Kod biblioteki

11.4 Program wykorzystujący bibliotekę

12 Podsumowanie

W tym rozdziale:

dowiesz się, czym są biblioteki DLL;
nauczysz się tworzyć i wykorzystywać biblioteki DLL;
zaprojektujesz bibliotekę odczytującą informacje z plików mp3.

Czym jest biblioteka DLL?

DLL to skrót od słów Dynamic Link Library, czyli biblioteka dynamicznie łączona. W skrócie można powiedzieć,
że biblioteka DLL to plik o rozszerzeniu *.dll, zawierający procedury i funkcje, które mogą być następnie
wykorzystywane w programie. Jednak biblioteka DLL i plik wykonywalny programu to dwa różne pliki, które
jednak są w stanie ze sobą współpracować. Biblioteka DLL może zawierać funkcje i procedury, które
uruchamiać może jedynie aplikacja wykonywalna.

Ten rozdział będzie poświęcony tworzeniu bibliotek DLL oraz wykorzystywaniu ich w swoich programach. Nie
jest to takie trudne, gdyż budowanie bibliotek jest bardzo podobne do tworzenia zwykłych aplikacji
wykonywalnych. Istnieje jednak parę zasad, których poznanie jest wymagane do bezproblemowej współpracy
pomiędzy bibliotekami DLL a aplikacjami je wykorzystującymi.

Do czego mogą się przydać biblioteki DLL?

Pewnie zadajesz sobie w tym momencie pytanie: ?do czego właściwie są potrzebne te biblioteki DLL??.
Umieszczanie części kodu w bibliotekach DLL ma wiele zalet, lecz istnieją także wady tego rozwiązania.

Zalety

Do zalet można zaliczyć przede wszystkim podział kodu. W przypadku, gdy program podzielony jest na
biblioteki DLL, a konieczne jest poprawienie jakiegoś błędu, wystarczy wymienić tylko jeden plik DLL, a nie cały
program.
W bibliotekach DLL można przechowywać także zasoby programu: kursory, ikony, bitmapy, inne programy itp.
Z poziomu programu mamy pełny dostęp do takich zasobów. Wykorzystując biblioteki DLL, można stworzyć
program działający w wielu wersjach językowych.

Raz napisana biblioteka DLL może być użyta wiele razy przez wiele programów. Ogromną zaletą jest możliwość
obsługi bibliotek DLL przez różne języki programowania. Przykładowo biblioteka napisana w C++ Builder może
być wykorzystana zarówno w Delphi, jak i we wszystkich środowiskach programistycznych działających w
systemie Windows. Praktycznie wszystkie funkcje API zawarte są w bibliotekach DLL. Podczas dotychczasowej

Delphi

Artykuły
Kompendium
Gotowce
FAQ
.NET

Turbo Pascal

FAQ

PHP

FAQ

Java

FAQ

C/C++

Artykuły
FAQ

C#

Wprowadzenie

Assembler

FAQ

(X)HTML
CSS
JavaScript
Z pogranicza
Algorytmy

WIĘCEJ

»

Delphi
C/C++
Turbo Pascal
Assembler
PHP

Programy
Dokumentacja
Kursy
Komponenty

WIĘCEJ

»

Delphi :: Kompendium :: Rozdział 10 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_10

1 z 12

2009-03-14 15:40

background image

pracy z Delphi nieraz miałeś okazję wykorzystać funkcje z modułu Windows.pas. W rzeczywistości funkcje i
procedury z tego modułu są jedynie importowane z bibliotek DLL systemu Windows. Cały system Windows jest
oparty na bibliotekach DLL! Funkcje zawarte w jednym tylko pliku mogą być używane przez wiele programów
(właśnie z tych możliwości korzystamy teraz w Delphi).

Kolejny przykład to biblioteka umożliwiająca odtwarzanie plików mp3. Osoba projektująca taki program
zawarła jego kod w bibliotece DLL. Dzięki temu każdy programista, który chce wykorzystać w swojej aplikacji
możliwość odtwarzania plików mp3, użyje owego pliku DLL (tutaj ujawnia się kolejna zaleta ? niezależność od
języka programowania). Ów programista nie musi zatem od początku tworzyć funkcji obsługujących
odtwarzanie plików mp3 ? są one od razu dostępne.

Wady

Jeżeli decydujemy się na podział aplikacji pomiędzy biblioteki DLL i plik wykonywalny, musimy liczyć się ze
zwiększeniem rozmiarów całej aplikacji ? pliki DLL mają zazwyczaj taki sam rozmiar, jak aplikacje
wykonywalne, więc łączny rozmiar programu będzie dość duży.

Jedna biblioteka DLL może być często odpowiedzialna za prawidłowe działanie wielu programów. Brak takiego
jednego pliku (usuniętego na przykład przy odinstalowywaniu jakiejś aplikacji) może spowodować
nieprawidłowe działanie aplikacji lub niemożność uruchomienia takiego programu.

Tworzenie bibliotek DLL

Do stworzenia własnej biblioteki DLL nie będzie nam potrzebny żaden zewnętrzny program. Wystarczy
otworzyć Repozytorium (File/New/Other) i wybrać ikonę DLL Wizard (rysunek 10.1). Wówczas w Delphi
zostanie utworzony nowy projekt, a w Edytorze kodu będzie znajdować się kod przedstawiony w listingu 10.1.

Rysunek 10.1. Okno Repozytorium z zaznaczonym elementem DLL Wizard.

Listing 10.1. Startowy kod źródłowy biblioteki DLL

library Project2;

{ Important note about DLL memory management: ShareMem must be the
first unit in your library's USES clause AND your project's (select
Project?View Source) USES clause if your DLL exports any procedures or
functions that pass strings as parameters or function results. This
applies to all strings passed to and from your DLL??even those that
are nested in records and classes. ShareMem is the interface unit to
the BORLNDMM.DLL shared memory manager, which must be deployed along
with your DLL. To avoid using BORLNDMM.DLL, pass string information
using PChar or ShortString parameters. }

uses
SysUtils,
Classes;

{$R *.res}

begin
end
.

Budowa biblioteki

W przeciwieństwie do ?zwykłych? programów pierwszy wiersz kodu źródłowego zawiera słowo kluczowe library
(zamiast standardowego program), po którym następuje nazwa biblioteki.

Charakterystyczną cechą biblioteki, która od razu rzuca się w oczy, jest dość duży komentarz. Jego treścią
zajmiemy się nieco później ? teraz możesz go zwyczajnie usunąć.

Reszta kodu źródłowego jest już standardowa i nie powinieneś mieć problemów z jej zrozumieniem. Ogólnie
można przyjąć, że budowa biblioteki jest bardzo podobna do standardowego pliku projektu (*.dpr) Delphi.

Rozmiar biblioteki

Niestety rozmiar biblioteki w żadnym stopniu nie będzie mniejszy od rozmiaru aplikacji wykonywalnej. Jest to
spowodowane użyciem takich modułów, jak SysUtils czy Classes. Ogólnie rzecz biorąc, rozmiar biblioteki jest
zależny od zasobów, które mają być do niej włączone, oraz od użytych modułów. Pewnym rozwiązaniem jest
stosowanie jedynie modułu Windows oraz funkcji API ? pozwoli to na tworzenie bibliotek DLL o rozmiarach
rzędu 13 KB. Tworzeniem aplikacji API zajmiemy się w rozdziale 12.

Eksportowanie procedur i funkcji

Nie wystarczy jedynie stworzenie procedury czy funkcji, aby mogła ona być wykorzystywana przez inne

RSS | Forum | Pastebin |

Regulamin | Pomoc | Usuń

cookies | Prawa autorskie |

Kontakt | Reklama

Copyright © 2000-2006 by Coyote Group 0.9.3-pre3

Czas generowania strony: 0.9726 sek. (zapytań SQL:

12)

Delphi :: Kompendium :: Rozdział 10 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_10

2 z 12

2009-03-14 15:40

background image

aplikacje. Konieczne jest także wyeksportowanie takiej funkcji, aby była ona ?widoczna? na zewnątrz. Do tego
służy słowo kluczowe

exports

. Wystarczy, że przed blokiem begin..end wstawisz taki kod:

exports
About,
SomeFunc;

Powyższy przypadek spowoduje wyeksportowanie dwóch procedur (funkcji): About i SomeFunc.

Eksportowanie przez nazwę

Eksportować funkcje oraz procedury można na dwa sposoby: przez nazwę oraz przez indeks. Spójrz na listing
10.2. Znajduje się tam kod źródłowy prostej biblioteki, w której następuje eksport procedury About poprzez
nazwę.
Listing 10.2. Eksport procedury About

library SimpleDLL;

uses
Windows;

procedure About; stdcall;
begin
MessageBox

(

0

,

'Hello World!'

,

'Hello'

, MB_OK + MB_ICONINFORMATION

)

;

end;

exports
About

name

'About'

;

begin
end
.

Wyeksportowanie procedury czy też funkcji poprzez nazwę polega jedynie na użyciu dyrektywy

name

. W

cudzysłowach należy umieścić nazwę eksportowanej procedury, która może być różna od rzeczywistej nazwy. A
zatem jeżeli procedura nazywa się About, nic nie stoi na przeszkodzie, aby wyeksportować ją pod nazwą MyDLL
(pod taką też nazwą ową procedurę będą ?widzieć? inne programy).

Eksport przez indeks

Istnieje inny sposób eksportu procedur lub funkcji ? przez indeks. Moim zdaniem jest to rzadziej stosowany
sposób eksportu. Wynika to z tego, że zamiast nazw używamy indeksów ? np.:

exports
About

index

1

,

SomeFunc

index

2

;

Jeśli w takim wypadku chcemy wykorzystać owe funkcje, należałoby w naszej aplikacji zapamiętać indeksy, z
jakimi są one eksportowane z biblioteki DLL. Łatwiej jest jednak zapamiętać nazwy niż cyfry, stąd
eksportowanie przez indeks jest używane dość rzadko.

Ładowanie bibliotek DLL

Ogólnie rzecz biorąc, wykorzystanie procedur i funkcji zawartych w bibliotekach DLL nie jest czymś
nadzwyczajnym. Jest to dość prosta czynność, chociaż być może to moja subiektywna opinia.

Ładowanie bibliotek DLL może być statyczne lub dynamiczne. Te pierwsze jest raczej proste, za to ładowanie
dynamiczne może sprawić nieco problemów.

Ładowanie statyczne

Za przykład niech posłuży prosty kod źródłowy z katalogu ../listingi/10/SimpleDLL, znajdujący się na
dołączonej do książki płycie CD-ROM.

Ładowanie statyczne polega na pobraniu procedury z biblioteki DLL w momencie uruchomienia aplikacji.

Pamiętaj, że jeśli plik DLL nie istnieje lub nie zawiera żądanej procedury, program nie zostanie uruchomiony.
Utwórz nowy projekt, a następnie dodaj poniższy wiersz w sekcji interface:

procedure About; stdcall external

'SimpleDLL.dll'

name

'About'

;

Deklaracja powyższej procedury musi mieć specyficzną budowę ? od tej pory podczas wywołania procedury
About załadowana zostanie procedura z biblioteki DLL. Nie będziemy teraz wnikać w znaczenie słowa stdcall,
zajmiemy się za to słowem external, które spowoduje załadowanie biblioteki DLL. W pierwszym rzędzie należy
wpisać w cudzysłowie nazwę biblioteki DLL, a później nazwę procedury (funkcji), którą chcemy importować.

Gdybyśmy importowali procedurę przez indeks, deklaracja procedury wyglądałaby tak:

procedure About; stdcall external

'SimpleDLL.dll'

index

1

;

Zamiast słowa kluczowego name wystarczy po prostu wpisać index, a następnie cyfrę, jaką opatrzyliśmy ową
procedurę w bibliotece DLL.

Użycie procedury znajdującej się w bibliotece wygląda tak:

procedure TMainForm.

btnLoadClick

(

Sender:

TObject

)

;

begin
About;
end;

Nic nie stoi na przeszkodzie, aby zadeklarowana w programie procedura miała inną nazwę
niż ta, która jest rzeczywiście ładowana z biblioteki DLL.

Delphi :: Kompendium :: Rozdział 10 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_10

3 z 12

2009-03-14 15:40

background image

Ładowanie dynamiczne

Ładowanie dynamiczne procedury lub funkcji może być trochę trudniejsze, gdyż polega na załadowaniu
biblioteki i jej zwolnieniu w określonym momencie. Zaletą ładowania dynamicznego jest większa kontrola nad
wykorzystaną biblioteką oraz możliwość gospodarowania pamięcią. Pamięć na potrzeby biblioteki jest
przydzielana nie podczas uruchamiania programu, ale w określonym przez nas momencie.

Oto przykład załadowania procedury w sposób dynamiczny:

procedure TMainForm.

btnDynamicLoadClick

(

Sender:

TObject

)

;

var
DLL : THandle;
About :

procedure;

begin
DLL := LoadLibrary

(

'SimpleDLL.dll'

)

; // załadowanie pliku

try

@About :=

GetProcAddress

(

DLL,

'About'

)

; // pobranie wskaźnika do procedury

if @About = nil then raise Exception.

Create

(

'Nie można załadować procedury'

)

;

About; // wykonanie procedury

finally

FreeLibrary

(

DLL

)

;

end;

end;

Przyznasz, że w porównaniu z jednym wierszem kodu w wypadku ładowania statycznego istnieje jednak spora
różnica. Pierwszym zaskoczeniem może być taki sposób zadeklarowania zmiennej:

var
DLL : THandle;
About :

procedure;

Zmienna About jest typu procedure?! Taka kombinacja także jest możliwa ?About jest od tej pory wskazaniem
procedury. Kolejnym krokiem jest załadowanie biblioteki DLL za pomocą polecenia LoadLibrary:

DLL := LoadLibrary

(

'SimpleDLL.dll'

)

; // załadowanie pliku

W parametrze owej funkcji należy podać jedynie nazwę biblioteki, która ma zostać załadowana. Jeżeli ładowanie
powiedzie się, funkcja zwróci uchwyt do biblioteki DLL.

Następnie konieczne jest pobranie wskaźnika do procedury lub funkcji umieszczonej w bibliotece DLL.

@About :=

GetProcAddress

(

DLL,

'About'

)

; // pobranie wskaźnika do procedury

W pierwszym parametrze funkcji GetProcAddress należy podać uchwyt biblioteki DLL, a w drugim ? nazwę
procedury lub funkcji. Jeżeli ładowanie nie powiedzie się ? zmienna About będzie miała wartość

nil

. Przykład

ładowania biblioteki w sposób dynamiczny oraz statyczny, znajduje się w listingu 10.3.

Listing 10.3. Ładowanie biblioteki DLL w sposób statyczny oraz dynamiczny

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TMainForm =

class

(

TForm

)

btnStaticLoad: TButton;
btnDynamicLoad: TButton;

procedure btnStaticLoadClick

(

Sender:

TObject

)

;

procedure btnDynamicLoadClick

(

Sender:

TObject

)

;

private

{ Private declarations }

public

{ Public declarations }

end;

var
MainForm: TMainForm;

{ ładowanie statyczne }

procedure About; stdcall external

'SimpleDLL.dll'

name

'About'

;

implementation

{$R *.dfm}

procedure TMainForm.

btnStaticLoadClick

(

Sender:

TObject

)

;

begin
About; // wywołanie procedury
end;

procedure TMainForm.

btnDynamicLoadClick

(

Sender:

TObject

)

;

var
DLL : THandle;
About :

procedure;

begin
DLL := LoadLibrary

(

'SimpleDLL.dll'

)

; // załadowanie pliku

try

@About :=

GetProcAddress

(

DLL,

'About'

)

; // pobranie wskaźnika do procedury

if @About = nil then raise Exception.

Create

(

'Nie można załadować procedury'

)

;

About; // wykonanie procedury

Delphi :: Kompendium :: Rozdział 10 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_10

4 z 12

2009-03-14 15:40

background image

finally

FreeLibrary

(

DLL

)

;

end;

end;

end.

Konwersje wywołania

Jak dotąd mogłeś zauważyć, procedury prezentowane w tej książce opatrzone są klauzulą stdcall. Podczas
deklarowania procedury lub funkcji możesz opatrzyć ją jedną z klauzul: register, pascal, cdecl, stdcall,
safecall

, które określają sposób przekazywania parametrów do stosu.

Domyślną klauzulą jest register, która zapewnia największą efektywność poprzez wysyłanie parametrów do
stosu zgodnie z kolejnością deklaracji w procedurze ? od lewej do prawej (tak, jak pascal). Natomiast
dyrektywy cdecl, stdcall i safecall powodują wysyłanie parametrów do stosu od strony prawej do lewej. Z
kolei wszystkie klauzule z wyjątkiem cdecl powodują zwolnienie parametrów, przez procedurę wykonującą.
Dyrektywa cdecl jest użyteczna w przypadku, gdy używane funkcje, znajdujące się w bibliotece DLL, są
napisane w C lub C++.

Najbardziej uniwersalną klauzulą jest stdcall, która jest jakby połączeniem dwóch: pascal
oraz cdecl. Należy używać dyrektywy stdcall w wypadku, gdy nie jesteśmy pewni, w
jakim języku została napisana biblioteka DLL. Spowoduje to możliwie jak najbezpieczniejsze
użycie funkcji.

Formularze w bibliotekach DLL

Jak już wspomniałem wcześniej, źródłowy plik biblioteki funkcjonuje na takich samych zasadach, co plik główny
*.dpr. Możliwe jest tworzenie formularzy w projekcie biblioteki DLL. Po otwarciu nowego projektu biblioteki DLL
z menu File wybierz New/Form. Spowoduje to utworzenie w ramach projektu (biblioteki DLL) nowego
formularza. Formularza możemy używać tak samo, jak w zwykłym projekcie Delphi. To, co nas interesuje, to
wyeksportowanie go z biblioteki DLL.

Tworzenie formularza

Niech nasz przykładowy formularz w ramach biblioteki DLL będzie oknem O programie?. Okno nie musi
posiadać zbyt wiele ?bajerów? ? wystarczy parę pól informacyjnych z notką o autorze itp. Moja propozycja to
okno z rysunku 10.2.

Rysunek 10.2. Okno O programie

Eksportowanie formularza

W rzeczywistości problemem jest wyeksportowanie formularza z biblioteki DLL tak, aby program był w stanie
skorzystać z niego. Jeżeli nie modyfikowałeś kodu, główny plik *.dpr biblioteki powinien wyglądać tak, jak w
listingu 10.4.

Listing 10.4. Kod źródłowy biblioteki

library FormDLL;

uses
SysUtils,
Classes,
DLLFrm

in

'DLLFrm.pas'

{DLLForm};

{$R *.res}

begin
end
.

Delphi automatycznie włączył nazwę modułu, z formularzem do listy uses. Wcześniej mówiłem o eksporcie
formularza. W rzeczywistości nie musimy eksportować całego formularza, a jedynie funkcję, która spowoduje
jego wyświetlenie (listing 10.5).

Listing 10.5. Główny plik biblioteki po modyfikacji

library FormDLL;

uses
Forms,
DLLFrm

in

'DLLFrm.pas'

{DLLForm};

procedure ShowAboutForm; stdcall;

begin

DLLForm := TDLLForm.

Create

(

Application

)

; // utworzenie formularza

DLLForm.

ShowModal

; // wyświetlenie

DLLForm.

Free

; // zwolnienie

end;

exports
{ eksport procedury }
ShowAboutForm

name

'ShowAboutForm'

;

Delphi :: Kompendium :: Rozdział 10 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_10

5 z 12

2009-03-14 15:40

background image

begin
end
.

Do wyświetlenia formularza użyjemy procedury ShowAboutForm. Procedura ta ma na celu utworzenie formularza
(alokacja pamięci) i jego wyświetlenie. Z takim lub podobnym kodem mogłeś się już spotkać w rozdziale 4.

To właściwie wszystko! Teraz wystarczy w jakimś miejscu programu wykorzystującego ową bibliotekę
zadeklarować następującą procedurę:

procedure About; stdcall external

'FormDLL.dll'

name

'ShowAboutForm'

;

Po wywołaniu procedury About załadowany zostanie formularz z pliku DLL.

Pełny kod źródłowy tego programu możesz znaleźć na płycie CD-ROM w katalogu ../listingi/10/FormDLL.

Przekazywanie rekordów do bibliotek

Za chwilę przedstawię przykład, w którym aplikacja będzie współpracować z biblioteką DLL, przekazując całe
rekordy danych. Będzie to nieco praktyczniejsze zaprezentowanie funkcjonalności bibliotek. Mianowicie funkcja
zawarta w pliku DLL będzie odpowiadała za odczytanie z pliku mp3 tzw. tagu (ID3v1). Ci, którzy posiadają na
swoim dysku pliki mp3, wiedzą, o czym mówię. Program będzie wyglądał tak, jak na rysunku 10.3.

Rysunek 10.3. Program w działaniu

Budowa pliku mp3

Aby umożliwić odczyt informacji z pliku mp3, należy znać jego budowę. Ponieważ konstrukcja pliku mp3 jest
ogólnie dostępna, zatem wiemy, że zawartość interesującego nas tagu znajduje się w ostatnich 128 bajtach
pliku mp3.

Opis budowy pliku mp3 możesz ściągnąć m.in. ze strony

http://4programmers.net.

Naszym zadaniem jest odczytanie tych 128 końcowych bajtów, a następnie rozdzielenie ich na fragmenty ? np.
tytuł, wykonawcę itp. Rekord, którym posługiwała się będzie nasza aplikacja z plikiem DLL, wygląda tak:

type
{ rekord, który będzie eksportowany do aplikacji }
TMp3 =

packed record

ID:

String

[

3

]

; // czy Tag istnieje?

Title :

String

[

30

]

; // tytuł

Artist :

String

[

30

]

; // wykonawca

Album :

String

[

30

]

; // album

Year :

String

[

4

]

; // rok wydania

Comment :

String

[

30

]

; // komentarz

Genre :

String

[

30

]

; // typ ? np. POP, Techno, Jazz itp.

end;

PMp3 = ^TMp3;

Odczyt tagu z pliku mp3

Pobranie informacji z pliku mp3 nie powinno być problemem, jeżeli znamy jego budowę oraz jeśli potrafimy
wykorzystać dostępne w Delphi mechanizmy. O obsłudze plików była mowa w rozdziale 7. Ja posłużyłem się
mechanizmem plików amorficznych (równie dobrze mogłem wykorzystać strumienie), gdyż do prawidłowego
funkcjonowania biblioteki nie jest potrzebny żaden moduł ? dzięki temu rozmiar biblioteki jest mniejszy:

procedure LoadTag

(

const lpFileName :

PChar

; Tag : PMp3

)

;

stdcall;

var
F :

File;

Buffer :

array

[

1

..

128

]

of

char

;

begin

AssignFile

(

F,

String

(

lpFileName

))

;

try

Reset

(

F,

1

)

;

{ przesunięcie pozycji na 128. bajt od końca }

Seek

(

F,

FileSize

(

F

)

?

128

)

;

BlockRead

(

F, Buffer,

128

)

; // odczytanie zawartości bufora

{ do rekordu przypisz informacje odczytane z pliku mp3 }

with Tag^ do

begin

ID :=

Copy

(

Buffer,

1

,

3

)

;

Title :=

Copy

(

Buffer,

4

,

30

)

;

Artist :=

Copy

(

Buffer,

34

,

30

)

;

Album :=

Copy

(

Buffer,

64

,

30

)

;

Year :=

Copy

(

Buffer,

94

,

4

)

;

Comment :=

Copy

(

Buffer,

98

,

30

)

;

Genre := GenreArray

[

Ord

(

Buffer

[

128

])]

;

Delphi :: Kompendium :: Rozdział 10 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_10

6 z 12

2009-03-14 15:40

background image

end;


finally

CloseFile

(

F

)

;

end;

end;

Powyżej przedstawiłem procedurę zawartą w pliku DLL. W pierwszej kolejności następuje otwarcie pliku i
przesunięcie się w odpowiednie jego miejsce. Drugi etap to odczyt końcowych 128 bajtów pliku (

BlockRead

).

Następnie nie pozostaje nic innego, jak rozdzielić dane na informacje dotyczące tytułu, wykonawcy itp. (funkcja

Copy

). Cały kod źródłowy biblioteki prezentuje listing 10.6.

Listing 10.6. Kod źródłowy biblioteki mp3DLL.dll.

{
Copyright (c) 2003 by Adam Boduch
}
library mp3DLL;

type
{ rekord, który będzie eksportowany do aplikacji }
TMp3 =

packed record

ID:

String

[

3

]

; // czy Tag istnieje?

Title :

String

[

30

]

; // tytuł

Artist :

String

[

30

]

; // wykonawca

Album :

String

[

30

]

; // album

Year :

String

[

4

]

; // rok wydania

Comment :

String

[

30

]

; // komentarz

Genre :

String

[

30

]

; // typ ? np. POP, Techno, Jazz itp.

end;

PMp3 = ^TMp3;

const
{ oto tablica zawierająca typy utworów }
GenreArray :

array

[

0

..

79

]

of

ShortString

=

(

(

'Blues'

)

,

(

'Classic Rock'

)

,

(

'Country'

)

,

(

'Dance'

)

,

(

'Disco'

)

,

(

'Funk'

)

,

(

'Grunge'

)

,

(

'Hip?Hop'

)

,

(

'Jazz'

)

,

(

'Metal'

)

,

(

'New Age'

)

,

(

'Oldies'

)

,

(

'Other'

)

,

(

'Pop'

)

,

(

'R&B'

)

,

(

'Rap'

)

,

(

'Reggae'

)

,

(

'Rock'

)

,

(

'Techno'

)

,

(

'Industrial'

)

,

(

'Alternative'

)

,

(

'Ska'

)

,

(

'Death Metal'

)

,

(

'Pranks'

)

,

(

'Soundtrack'

)

,

(

'Euro?Techno'

)

,

(

'Ambient'

)

,

(

'Trip?Hop'

)

,

(

'Vocal'

)

,

(

'Jazz+Funk'

)

,

(

'Fusion'

)

,

(

'Trance'

)

,

(

'Classical'

)

,

(

'Instrumental'

)

,

(

'Acid'

)

,

(

'House'

)

,

(

'Game'

)

,

(

'Sound Clip'

)

,

(

'Gospel'

)

,

(

'Noise'

)

,

(

'AlternRock'

)

,

(

'Bass'

)

,

(

'Soul'

)

,

(

'Punk'

)

,

(

'Space'

)

,

(

'Meditative'

)

,

(

'Instrumental Pop'

)

,

(

'Instrumental Rock'

)

,

(

'Ethnic'

)

,

(

'Gothic'

)

,

(

'Darkwave'

)

,

(

'Techno?Industrial'

)

,

(

'Electronic'

)

,

(

'Pop?Folk'

)

,

(

'Eurodance'

)

,

(

'Dream'

)

,

(

'Southern Rock'

)

,

(

'Comedy'

)

,

(

'Cult'

)

,

(

'Gangsta'

)

,

(

'Top 40'

)

,

(

'Christian Rap'

)

,

(

'Pop/Funk'

)

,

(

'Jungle'

)

,

(

'Native American'

)

,

(

'Cabaret'

)

,

(

'New Wave'

)

,

(

'Psychadelic'

)

,

(

'Rave'

)

,

(

'Showtunes'

)

,

(

'Trailer'

)

,

(

'Lo?Fi'

)

,

(

'Tribal'

)

,

(

'Acid Punk'

)

,

(

'Acid Jazz'

)

,

(

'Polka'

)

,

(

'Retro'

)

,

(

'Musical'

)

,

(

'Rock & Roll'

)

,

(

'Hard Rock'

)

)

;

procedure LoadTag

(

const lpFileName :

PChar

; Tag : PMp3

)

;

stdcall;

var
F :

File;

Buffer :

array

[

1

..

128

]

of

char

;

begin

AssignFile

(

F,

String

(

lpFileName

))

;

try

Reset

(

F,

1

)

;

{ przesunięcie pozycji na 128 bajt od końca }

Seek

(

F,

FileSize

(

F

)

?

128

)

;

BlockRead

(

F, Buffer,

128

)

; // odczytanie zawartości bufora

{ do rekordu przypisz informacje odczytane z pliku mp3 }

with Tag^ do

begin

ID :=

Copy

(

Buffer,

1

,

3

)

;

Title :=

Copy

(

Buffer,

4

,

30

)

;

Artist :=

Copy

(

Buffer,

34

,

30

)

;

Album :=

Copy

(

Buffer,

64

,

30

)

;

Year :=

Copy

(

Buffer,

94

,

4

)

;

Comment :=

Copy

(

Buffer,

98

,

30

)

;

Genre := GenreArray

[

Ord

(

Buffer

[

128

])]

;

end;


finally

CloseFile

(

F

)

;

end;

end;

exports
LoadTag

name

'LoadTag'

;

begin
end
.

Zwróć uwagę na to, że do stworzenia tej biblioteki nie był potrzebny żaden moduł. Ostateczny rozmiar
biblioteki to 36 KB.

Demo

Skoro mamy już bibliotekę, która realizuje żądane zadanie, to napisanie programu ją wykorzystującego jest
jedynie formalnością.(listing 10.7.).

Listing 10.7. Program korzystający z biblioteki DLL

{
Copyright (c) 2003 by Adam Boduch
}

Delphi :: Kompendium :: Rozdział 10 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_10

7 z 12

2009-03-14 15:40

background image

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TMainForm =

class

(

TForm

)

GroupBox1: TGroupBox;
OpenDialog:

TOpenDialog

;

lblTitle: TLabel;
lblArtist: TLabel;
lblAlbum: TLabel;
lblYear: TLabel;
lblComments: TLabel;
lblGenre: TLabel;
edtTitle: TEdit;
edtArtist: TEdit;
edtAlbum: TEdit;
edtYear: TEdit;
edtComments: TEdit;
edtGenre: TEdit;
btnLoad: TButton;

procedure btnLoadClick

(

Sender:

TObject

)

;

private

{ Private declarations }

public

{ Public declarations }

end;

PMp3 = ^TMp3;
TMp3 =

packed record

ID:

String

[

3

]

; // czy Tag istnieje?

Title :

String

[

30

]

; // tytuł

Artist :

String

[

30

]

; // wykonawca

Album :

String

[

30

]

; // album

Year :

String

[

4

]

; // rok wydania

Comment :

String

[

30

]

; // komentarz

Genre :

String

[

30

]

; // typ ? np. POP, Techno, Jazz itp.

end;

var
MainForm : TMainForm;

implementation

{$R *.dfm}

procedure LoadTag

(

const lpFileName :

PChar

; Tag : PMp3

)

;

stdcall external

'mp3DLL.dll'

name

'LoadTag'

;

procedure TMainForm.

btnLoadClick

(

Sender:

TObject

)

;

var
Tag : TMp3;
begin

if OpenDialog.

Execute

then

begin

LoadTag

(

PChar

(

OpenDialog.

FileName

)

, @Tag

)

; // wywołaj funkcje z biblioteki DLL

if Tag.

ID

<>

'TAG'

then

Exit

; // jeżeli tag nie istnieje ? anuluj dalsze działania

with Tag do

begin

{ wartości z rekordu przypisz do komponentów }
edtTitle.

Text

:= Tag.

Title

;

edtArtist.

Text

:= Tag.

Artist

;

edtAlbum.

Text

:= Tag.

Album

;

edtYear.

Text

:= Tag.

Year

;

edtComments.

Text

:= Tag.

Comment

;

edtGenre.

Text

:= Tag.

Genre

;

end;

end;

end;

end.

Po załadowaniu procedury z biblioteki rekord Tag powinien zawierać informacje dotyczące tagu z pliku mp3.
Jeżeli plik mp3 jest pozbawiony tej informacji, to element ID rekordu Tag nie będzie zawierał wartości TAG. Na
końcu pozostaje jedynie przedstawienie wartości w komponentach TEdit.

Łańcuchy w bibliotekach DLL

Ten podpunkt jest związany z użyciem łańcuchów w bibliotekach DLL. Ma to związek z komentarzem, który
znajduje się w kodzie biblioteki zaraz po utworzeniu jej poprzez Repozytorium.

Twórcy przestrzegają w owym komentarzu przed używaniem w bibliotekach łańcuchów typu

String

. Taki typ

danych nie może znaleźć się w parametrach eksportowanych procedur; nie może też występować jako zwracany
przezeń element. Zamiast tego należy używać łańcuchów typu

PChar

lub

ShortString

.

Używanie długich łańcuchów nie jest wykluczone, lecz zarówno na liście uses w bibliotece, jak i w aplikacji musi
się znaleźć moduł ShareMem. Uwaga! Musi on się znaleźć na pierwszym miejscu spośród wszystkich modułów na
liście uses:

uses
ShareMem, Windows, Classes; // itd....

Zasoby w bibliotece DLL

Dzięki bibliotekom DLL można w dość prosty sposób stworzyć aplikację z wielojęzycznym interfejsem. Wszystko
dzięki zasobom, o których mowa była w poprzednim rozdziale. Tworzenie zasobów nie powinno już dla Ciebie

Delphi :: Kompendium :: Rozdział 10 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_10

8 z 12

2009-03-14 15:40

background image

stanowić problemu. Włączenie ich do biblioteki DLL wygląda identycznie, jak w przypadku zwykłego projektu:

{$R ZASOBY.RES}

Od tego momentu cały plik ZASOBY.RES zostanie włączony do projektu.

W poprzednim rozdziale podawałem przykład, w jaki sposób można odczytać zasoby umieszczone w aplikacji.
W przypadku bibliotek DLL ładowanie jest podobne.

Przygotowanie zasobów

W moim przykładzie umieszczę w zasobach biblioteki DLL łańcuchy tekstowe. Zasób w postaci pliku *.rc
zaprezentowany został w listingu 10.8.

Listing 10.8. Treść skryptu zasobów
STRINGTABLE
BEGIN
101, "Cześć"
102, "Hello"

END

Po skompilowaniu tego pliku w katalogu z programem powinien znajdować się plik TEKST.RES.

Kod biblioteki jest krótki:

library ResDLL;

{$R TEKST.res}

begin
end
.

W kodzie znajduje się jedynie dyrektywa powodująca włączenie zasobów do programu.

Ładowanie zasobów z biblioteki DLL

W poprzednim rozdziale zaprezentowałem Ci, drogi Czytelniku, funkcję LoadString, która umożliwiała pobranie
tekstu z zasobów. Użycie tej procedury w tym momencie jest bardzo podobne, tyle że pierwszy parametr musi
być wskazaniem biblioteki DLL. Oto kod:

procedure TMainForm.

FormCreate

(

Sender:

TObject

)

;

var
DLL : THandle;
Buffer :

array

[

0

..

255

]

of

char

; // bufor przechowujący tekst

begin
DLL := LoadLibrary

(

'ResDLL.dll'

)

;

try

{ załadowanie tekstu }
LoadString

(

DLL,

101

, Buffer,

SizeOf

(

Buffer

))

;

lblLabel1.

Caption

:= Buffer;

LoadString

(

DLL,

102

, Buffer,

SizeOf

(

Buffer

))

;

lblLabel2.

Caption

:= Buffer;

finally

FreeLibrary

(

DLL

)

;

end;

end;

Funkcja LoadLibrary zwraca uchwyt do załadowanej biblioteki (THandle). Ten uchwyt jest następnie
wykorzystywany podczas ładowania tekstu.

Na dołączonej do książki płycie CD-ROM znajdują się inne przykłady wykorzystania zasobów w bibliotekach DLL.
W katalogu ../listingi/10/ResDLL umieszczony jest przykład prezentujący ładowanie tekstu oraz bitmap ?
wszystkie te procesy działają w wątku.

Kolejny przykład (../listingi/10/MusicDLL) należałoby umieścić w trzech działach niniejszej książki. Prezentuje
on możliwość odtwarzania dźwięków przy wykorzystaniu mechanizmów WinAPI (będzie o tym mowa w
kolejnym rozdziale). Wszystkie funkcje są jednak umieszczone w bibliotece DLL.

Wreszcie ostatni przykład z katalogu ../listingi/10/DLL & Image prezentuje współdziałanie biblioteki oraz
aplikacji w zakresie operowania grafiką.

Procedura inicjująco-kończąca

Korzystając z modułu, możemy w nim umieścić sekcje initialization oraz finalization, w których kod
będzie wykonywany w momencie przyłączenia modułu do pamięci lub jego odłączenia (zaprzestania
korzystania). W kodzie biblioteki DLL nie możemy stosować tych sekcji, ale dysponujemy czymś w rodzaju ich
odpowiedników ? instrukcją inicjująco-kończącą.

Blok begin biblioteki DLL

Dotąd podczas omawiania bibliotek DLL nie umieszczałem instrukcji w bloku begin i end. Kod znajdujący się w
tym bloku zostanie uruchomiony na samym początku ?w czasie, gdy biblioteka będzie ładowana do pamięci.

library DLL;

uses
Windows;

{ jakieś procedury }

begin
MessageBox

(

0

,

'Witaj! Właśnie korzystasz z mojej biblioteki!'

,

':?)'

, MB_OK

)

;

end.

Po załadowaniu biblioteki za pomocą LoadLibrary najpierw na ekranie zostanie wyświetlony tekst w okienku.

Delphi :: Kompendium :: Rozdział 10 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_10

9 z 12

2009-03-14 15:40

background image

DLLProc

Poprzedni przykład pokazywał, jak przechwytywać moment ładowania biblioteki do pamięci, lecz co w
przypadku jej zwalniania? W bloku begin..end do ukrytej zmiennej DLLProc możemy przypisać procedurę
inicjująco-kończącą, dzięki której będziemy mogli przechwycić zakończenie korzystania z biblioteki (tabela
10.1).

Nazwa

Opis

DLL_PROCESS_ATTACH

Załadowanie biblioteki przez program ją wykorzystujący

DLL_PROCESS_DETACH

Zwalnianie biblioteki przez program ją wykorzystujący

DLL_THREAD_ATTACH

Proces korzystający z danej biblioteki uruchomił wątek

DLL_THREAD_DETACH

Proces korzystający z danej biblioteki zakończył wątek

Aby przechwycić zwalnianie biblioteki lub wątku, należy utworzyć pewną procedurę ? nazwijmy ją DLL_Proc.

procedure DLL_Proc

(

Reason :

Integer

)

;

begin

end;

Procedura musi mieć ściśle określoną budowę ? w tym wypadku istotny jest parametr Reason. Oto, jak powinna
wyglądać całość:

library DLLPro;

procedure DLL_Proc

(

Reason :

Integer

)

;

begin

end;

begin
DLLProc := @DLL_Proc;
end.

Teraz, stosując instrukcję case w procedurze DLL_Proc, możesz wpisać kod, który będzie wykonywany zależnie
od zdarzenia.

Uważaj, aby nie zapisać projektu biblioteki DLL pod nazwą DLLProc, czyli taką samą, jak
nazwa zmiennej.
Zauważ, że w bloku begin do przypisania wartości do DLLProc użyłem znaku @. Wszystko
dlatego, ze DLLProc jest zmienną typu

Pointer

.

Kod biblioteki

Załóżmy, że chcemy, aby biblioteka przechwytywała moment załadowania jej do pamięci, jej zwalniania, a
także moment uruchamiania wątku przez program wykorzystujący ów plik DLL. Kod biblioteki mógłby wyglądać
tak, jak na listingu 10.9.

Listing 10.9. Kod biblioteki wykorzystującej procedurę inicjująco-kończącą

library DLLPro;

uses Windows;

procedure DLL_Proc

(

Reason :

Integer

)

;

begin

case Reason of

DLL_PROCESS_DETACH: MessageBox

(

0

,

'Zwalnianie biblioteki...'

,

''

, MB_OK

)

;

DLL_THREAD_ATTACH: MessageBox

(

0

,

'Tworzenie wątku...'

,

''

, MB_OK

)

;

DLL_THREAD_DETACH: MessageBox

(

0

,

'Zamknięcie wątku...'

,

''

, MB_OK

)

;

end;

end;

begin
DLLProc := @DLL_Proc;
MessageBox

(

0

,

'Ładowanie biblioteki...'

,

''

, MB_OK

)

;

end.

Od tego momentu, w zależności od zaistniałego zdarzenia, wyświetlony zostanie komunikat informacyjny.
Zwróć uwagę, że w procedurze DLL_Proc nie użyłem DLL_PROCESS_ATTACH. Ta stała znajduje się w Delphi
jedynie z powodów zachowania kompatybilności z poprzednimi wersjami. W rzeczywistości nie jest
wykorzystywana, a kod, który ma zostać wykonany na starcie (po załadowaniu biblioteki), powinien zostać
umieszczony w bloku begin..end.

Program wykorzystujący bibliotekę

Wykorzystanie biblioteki, która korzysta z procedury inicjująco-kończącej, nie jest niczym nadzwyczajnym. Nie
potrzeba żadnych specjalnych instrukcji ? załadowanie i zwolnienie biblioteki może wyglądać np. tak:

var
DLL : THandle;

procedure TMainForm.

FormCreate

(

Sender:

TObject

)

;

begin
DLL := LoadLibrary

(

'DLLPro.dll'

)

; // załaduj bibliotekę

end;

procedure TMainForm.

Button1Click

(

Sender:

TObject

)

;

begin

Delphi :: Kompendium :: Rozdział 10 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_10

10 z 12

2009-03-14 15:40

background image

« Multimedia

Spis treści

WinAPI »

FreeLibrary

(

DLL

)

; // zwolnij zasoby

Application.

Terminate

;

end;

Dobrze, ale co z wątkami? Na płycie CD-ROM umieściłem bardziej rozbudowany przykład działania procedury
inicjująco-kończącej. Znajduje się on w katalogu ../listingi/10/Entry Proc. Program wykorzystujący bibliotekę
DLL może uruchomić wątek, co spowoduje przechwycenie tego faktu przez naszą bibliotekę. Wysyła ona do
naszej aplikacji odpowiedni komunikat (przy okazji możemy sobie utrwalić wiedzę dotyczącą komunikatów).

Wykorzystanie procedury inicjująco-kończącej jest dobrym sposobem na tworzenie bibliotek DLL typu
shareware . W kodzie begin można umieścić kod (komunikat informacyjny), który może informować
programistę o tym, że korzysta z niepełnej funkcjonalności biblioteki i należy za nią zapłacić.

Podsumowanie

Jeżeli poznasz już zasady projektowania bibliotek DLL, okażę się, że nie jest to takie trudne. Tworzenie bibliotek
DLL jest kolejnym krokiem do projektowania bardziej zaawansowanych programów, w których podział okazuje
się bardziej ?ekonomiczny?.

Załączniki:

Listingi_10.zip

(416.63 kB)

Więcej informacji

Delphi 2005. Kompendium
programisty

Adam Boduch

Format: B5, stron: 1048
oprawa twarda
Zawiera CD-ROM

©

Helion 2003. Autor:

Adam Boduch

. Zabrania się rozpowszechniania tego tekstu bez zgody autora.

Kategoria

:

Kompendium

Ostatnia modyfikacja

07-02-2006 12:09

Ostatni autor

Adam Boduch

Ilość wyświetleń

20701

Wersja

1

RampleR

dnia 13-04-2007 17:33

A przepraszam błąd naprawiłem chodziło o USES gdzie nie dałem Forms

RampleR

dnia 13-04-2007 17:10

super artykuł jedynie mam jeden problem. w wyrażeniu : DLLForm := TDLLForm.Create(Application); //
utworzenie formularza pisze mi że jest błąd : [Error] Projectdll1.dpr(11): Undeclared identifier:
'Application' co mam zrobić??

StinG

dnia 13-09-2006 00:00

Mimo wszystko super artykuł. Ide pisać kolejnego DLL'a

Mam tylko jedno pytanie jak w DLL'u zapisać ikony (domyślam się, że przez plik *.RES ale jak)?

Morgoth_

dnia 21-06-2006 14:54

migajek

dnia 06-04-2006 19:26

cppbuilder: tak jest taka mozliwosc

CPPBuilder

dnia 14-02-2006 12:32

Czy jest możliwość ładowania biblioteki DLL do programu przez OpenDialog?

CPPBuilder

dnia 14-02-2006 12:24

Czy jest możliwość ładowania biblioteki DLL do programu przez OpenDialog?

Wolverine

dnia 07-02-2006 20:14

Taki maly blad tu jest:

Domyślną klauzulą jest register, która zapewnia największą efektywność poprzez wysyłanie
parametrów do stosu zgodnie z kolejnością deklaracji w procedurze ? od lewej do prawej (tak, jak
pascal).

Register jak sama nazwa mowi przekazuje to co sie da w rejestrach i dlatego jest to najefektywniejsze
(dlatego jest domyslne w delphi, choc wielkiej roznicy to nie zrobi).

Delphi :: Kompendium :: Rozdział 10 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_10

11 z 12

2009-03-14 15:40

background image

Dodaj komentarz

Delphi :: Kompendium :: Rozdział 10 - 4programmers.net

http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_10

12 z 12

2009-03-14 15:40


Wyszukiwarka

Podobne podstrony:
Delphi Kompendium Roz8
Delphi Kompendium Roz6
Delphi Kompendium Roz12
Delphi Kompendium Roz5
Delphi Kompendium Roz14
Delphi 7 Kompendium programisty
Delphi 7 Kompendium programisty del7ko 2
Delphi 7 Kompendium programisty
Delphi Kompendium programisty 2
Delphi Kompendium Roz5
Delphi 7 Kompendium programisty
Delphi 7 Kompendium programisty 2
Delphi Kompendium programisty
Delphi Kompendium Roz6
Delphi Kompendium Roz12
Delphi Kompendium programisty 2
Delphi 7 Kompendium programisty del7ko
Delphi Kompendium programisty delpbb

więcej podobnych podstron