Moduły

Adam Boduch

Moduł (ang. unit) jest plikiem tekstowym zawierającym polecenia przetwarzane przez kompilator. Inaczej mówiąc, jest to fragment kodu źródłowego.

1 Tworzenie nowego modułu
2 Budowa modułu
     2.1 Nazwa
     2.2 Sekcja Interface
     2.3 Sekcja Implementation
3 Włączanie modułu
4 Funkcje wbudowane
5 Sekcje Initialization oraz Finalization
6 Dyrektywa forward

Zastosowanie modułów pozwala na podział kodu na osobne pliki. Każdemu formularzowi odpowiada jeden moduł, ale z kolei moduł nie musi być zawsze formularzem. Jakie są zalety programowania strukturalnego (modularnego)? Otóż programista ma możliwość umieszczenia części kodu (np. paru procedur) w osobnych modułach. Wyobraźmy sobie, że tworzymy dość spory program i cały kod źródłowy jest zawarty w jednym pliku, podzielony na procedury. W takim pliku trudno się będzie później odnaleźć ? przede wszystkim ze względu na jego długość. Koncepcja programowania modularnego polega na podzieleniu takiego programu na kilka plików i włączaniu ich po kolei do głównego pliku źródłowego.

Dzięki temu możemy w jednym pliku, np. interfaces.pas, zawrzeć jedynie procedury związane z tworzeniem interfejsu użytkownika, a z kolei w pliku db.pas umieścić procedury związane z obsługą bazy danych.

Tworzenie nowego modułu

  1. Z menu File wybierz New/Other.
  2. Następnie zaznacz Delphi Projects i wybierz Console Application.
  3. Z menu File wybierz polecenie New/Unit ? Delphi for Win32. Spowoduje to stworzenie w edytorze kodu nowej zakładki (nowego modułu).
  4. Z menu File wybierz polecenie Save As ? plik zapisz pod dowolną nazwą.

Uwaga! Te instrukcje odpowiadają tworzeniu modułu w wersji Delphi 2005.

Budowa modułu

Zaraz po utworzeniu nowego modułu Delphi generuje potrzebne instrukcje, które są niezbędnym elementem tego modułu.

unit MainUnit;

interface

implementation

end.

Nazwa

Pierwszy wiersz zawiera instrukcję Unit, która określa nazwę modułu. Nie należy jej zmieniać ? Delphi generuje tę nazwę automatycznie w momencie zapisywania pliku na dysku. Podczas próby zmiany nazwy modułu wystąpi błąd: [Error] MainUnit.pas(1): Unit identifier 'MainUnitx' does not match file name.

Dzieje się tak, gdyż nazwa modułu jest jednocześnie nazwą pliku *.dcu i *.pas w projekcie. Przykładowo, jeżeli moduł nosi nazwę MainUnit, to Delphi zapisze plik źródłowy pod nazwą MainUnit.pas oraz plik skompilowany pod nazwą MainUnit.dcu.

Sekcja Interface

Jest to tzw. część publiczna modułu. Tutaj należy umieszczać deklaracje procedur i funkcji, które mają być dostępne ?na zewnątrz?, dla innych modułów. W tym momencie należy rozróżnić pewne pojęcia, takie jak deklaracja oraz definicja.

Deklaracja jest udostępnieniem jedynie nagłówka funkcji lub procedury. Dzięki temu procedury i funkcje są dostępne dla innych modułów. Np. deklaracja procedury ProcMain może wyglądać następująco: procedure ProcMain(Param1, Param2 : Integer);

Definicja to cały kod procedury lub funkcji zawarty w sekcji Implementation.

Oczywiście można pominąć deklarację i od razu w sekcji implementation wpisać całe ciało procedury czy funkcji, ale trzeba liczyć się z tym, że nie będzie ona dostępna poza modułem.

Sekcja Implementation

Sekcja Implementation stanowi część prywatną modułu. Kod w niej zawarty w (procedury, funkcje, zmienne, tablice czy stałe) nie jest dostępny dla innych modułów ? z punktu widzenia kompilatora dane zawarte w tej sekcji nie istnieją dla innych modułów.

W sekcji Implementation należy także umieszczać definicję procedur i funkcji. Przykład prawidłowego modułu (deklaracji i definicji) to:

unit MainUnit;

interface

  procedure ProcMain(Param1, Param2 : Integer);

implementation

procedure ProcMain(Param1, Param2 : Integer);
begin

end;

end.

Trzeba pamiętać o tym, że również moduł musi być zakończony instrukcją end. (kropka na końcu!).

Włączanie modułu

Podczas poprzedniego ćwiczenia, którego celem było utworzenie nowego modułu, można było się dowiedzieć, że język Delphi zwolnił programistę z obowiązku włączenia tego modułu do głównego pliku *.dpr.

Włączenie modułu do programu (lub do innych modułów) realizowane jest za pomocą dyrektywy Uses.

uses
  MainUnit in 'MainUnit.pas';

Od tej pory wszystkie deklaracje z modułu MainUnit będą dostępne także w obrębie programu głównego. Taka wygenerowana przez Delphi konstrukcja nie jest jedyną poprawną konstrukcją. Wystarczy napisać tylko:

uses
  MainUnit;

Aby włączyć do programu kilka modułów, należy je wypisać jeden po drugim, oddzielając ich nazwy przecinkami.

Funkcje wbudowane

Od momentu włączenia modułu do aplikacji można korzystać z funkcji zawartych w owym module ? to jasne. Jednak w swoich programach możesz korzystać z funkcji typu Writeln, Readln czy Inc. Są to tzw. funkcje wbudowane ? ich deklaracja nie jest umieszczona w żadnym module, więc są dostępne w każdym miejscu programu ? stanowią po prostu element języka Delphi [#]_.

Różnica między zwykłymi funkcjami a tymi zaimplementowanymi dodatkowo w Delphi jest wyraźnie widoczna na przykładzie funkcji MessageBox. Żeby użyć tej funkcji (tak aby była dostępna dla kompilatora), trzeba było dołączyć do programu moduł Windows.

Sekcje Initialization oraz Finalization

Dwie wspomniane sekcje ? Initialization oraz Finalization ? są opcjonalnymi sekcjami modułu.

Umieszczenie kodu bezpośrednio za sekcją Initialization powoduje wykonanie go zaraz po uruchomieniu programu, natomiast kod znajdujący się za sekcją Finalization zostanie wykonany po zakończeniu pracy z modułem.

unit MainUnit;

interface

uses Windows; // włączamy moduł Windows

  procedure ProcMain(Param1, Param2 : Integer);

implementation

procedure ProcMain(Param1, Param2 : Integer);
begin

end;

initialization
  MessageBox(0, 'Rozpoczynamy pracę z modułem...', 'OK', MB_OK);

finalization
  MessageBox(0, 'Kończymy pracę z modułem...', 'OK', MB_OK);


end.

Po uruchomieniu programu zostanie wyświetlone okno dialogowe. Tak samo stanie się po zamknięciu naszej aplikacji.

Najczęściej sekcje Initialization oraz Finalization są używane do przypisywania lub zwalniania danych (pamięci), przydzielanych w ramach konkretnego modułu.

Jeżeli w programie korzysta się z sekcji Finalization, to konieczne staje się umieszczenie także sekcji Initialization. W przeciwnym przypadku próba uruchomienia programu zakończy się błędem: [Error] MainUnit.pas(17): Declaration expected but 'FINALIZATION' found.

Sekcja Initialization może nie zawierać żadnego kodu ? istotna jest tylko jej obecność w programie.

Jeżeli chodzi o sekcję Finalization, to jest ona opcjonalna. Jeżeli np. w sekcji Finalization należy umieścić jakiś kod zwalniający pamięć, to nie będzie to konieczne w środowisku takim jak .NET, które samodzielnie dba o zwalnianie pamięci.

Dyrektywa forward

Po zadeklarowaniu nagłówka procedury czy funkcji należy także umieścić jej definicję w sekcji Implementation, aby była dostępna na zewnątrz modułu ? to już wiemy. Trochę skłamałem wcześniej, wskazując, że deklarowanie tych samych parametrów funkcji zarówno w definicji, jak i w deklaracji jest konieczne, tak jak to pokazano poniżej:

...
interface
  function Main(const : String) : Integer;
implementation
...
function Main(const : String) : Integer;
begin

end;
...

Można pominąć cały nagłówek funkcji w sekcji implementation, skracając zapis do następującej postaci:

...
interface
  function Main(const : String) : Integer;
implementation
...
function Main;
begin

end;
...

Warto zwrócić uwagę, że w sekcji implementation nie jest konieczne nawet wpisanie typu zwracanego przez funkcję Main ? wszystko to jest określone w definicji w sekcji Interface.

Dyrektywa forward wiąże się z deklarowaniem procedur bądź funkcji jedynie w sekcji implementation albo w ogóle w pliku głównym programu (*.dpr). Wiadomo, że kompilator odczytuje kod od góry do dołu, wiec taki zapis będzie dla niego niepoprawny:

program Example;

{$APPTYPE CONSOLE}

procedure Main;
begin
{ wywołanie procedury, która jest nie widoczna, gdyż jej
  deklaracja znajduje się poniżej }
  DoIt('Test');
end;

procedure DoIt(var S : String);
begin

end;

begin

end.

Wczujmy się na chwilę w sposób pracy kompilatora. Analizując kod od góry do dołu, napotyka on na nieznaną instrukcję ? DoIt ? w ciele procedury Main. Owa instrukcja jest dla niego nierozpoznawalna, więc może przyjąć, że jest to jakaś funkcja. W tym momencie sprawdza kod powyżej i nie na potyka na procedurę ani funkcję o nazwie DoIt, a zatem zgłasza błąd. Czasem można spotkać się z takim problemem, jeśli zadeklarowanie procedury zaraz na początku jest niemożliwe, gdyż oddziałuje ona z kolei na inną procedurę, której kod znajduje się poniżej. W takim przypadku można zastosować dyrektywę forward i napisać tak:

program Example;

{$APPTYPE CONSOLE}

uses Windows;

procedure DoIt(const S : String); forward;

procedure Main;
begin
{ wywołanie procedury, która jest nie dostępna, gdyż jej
  deklaracja znajduje się poniżej }
  DoIt('Test');
end;

procedure DoIt;
begin
  Writeln(S);
end;

begin
  Main; // wywołanie głównej procedury
end.

Powyższy kod zostanie skompilowany bez problemu, gdyż na samej górze zadeklarowaliśmy jedynie nagłówek procedury oraz opatrzyliśmy ją dyrektywą forward. Dalszy kod owej procedury można zapisać w dalszej części programu.

Zwróćmy uwagę, że jedynie nagłówek zawiera listę parametrów procedury. Ciało procedury zawiera tylko nazwę. Jest to przydatny zapis, gdyż niejeden raz projektant jest zmuszony do zmiany listy parametrów. Wówczas trzeba by zmienić te parametry w deklaracji procedury oraz w definicji, a dzięki dyrektywie forward parametry są zapisane jedynie w definicji, stąd ich zmiana jest szybka i bezproblemowa. _____ .. [#] Tutaj małe niedopowiedzenie. Otóz wspomniane funkcje są w rzeczywistości umieszczone w module [[Delphi/Moduły/System]], który jest automatycznie włączany do programu w trakcie kompilacji. Nie ma więc konieczności dołączania go "ręcznie" do listy [[Delphi/Uses]].

Zobacz też:

FileCtrl

  • 2009-01-16 19:56
  • 2 komentarzy
  • 1661 odsłon

ShellApi

  • 2010-10-31 18:27
  • 3 komentarzy
  • 4231 odsłon

Windows

  • 2010-10-31 18:27
  • 3 komentarzy
  • 3098 odsłon

4 komentarzy

[1] To NIE prawda - pisze wyraźnie w System w komencie, że jest to funkcja wbudowana w kompilator i nie jest w System.. A wskazuje na System po Ctrl+klik bo tak zostało ustalone.

Jak dodaje nowy moduł, o nazwie suma w którym jest jedna funkcja:

function Suma(A, B: Integer): Integer;
begin
Result := A + B;
end;

To mi wywala "File not Found Suma.dcu".
Czemu *.dcu skoro to mialo byc pas?

Tak, racje, juz dodalem :)

Hmm... W rozdziale "Funkcje wbudowane" jest odnośnik do nieistniejącego przypisu :O