Procedury a funkce

Psaní funkcí a procedur (tzv. podprogramů) tvoří hlavní náplň programování. Dnes si popíšeme, co všechno se s nimi dá v Pascalu dělat.

Malý slovníček:
lokální proměnná = proměnná, která byla definována v dané proceduře a žije tedy pouze tam
globální proměnné = proměnné, které se vyskytují v celém programu
vedlejší efekt = procedura změní proměnnou, kterou bychom dle hlavičky neočekávali

Procedury

Definujeme-li funkci či proceduru, snažíme se udělat ji co nejmenší. Měla by řešit jen jeden jediný problém. Dále se budeme snažit veškeré informace předávat funkci či proceduře pouze pomocí parametrů. Předejdeme tak chybám. (Když funkce mění globální proměnnou pouze prostřednictvím parametrů, mnohem snáze se odhaluje chyba.)

Procedury a funkce se obvykle definují až za definicí typů. Při jejich definici tak můžeme vyžívat vlastní typy. Běžná definice procedury vypadá takto:

procedure Nazev(Parametry);
Deklarace;
begin
 Prikazy;
end;

V Pascalu je možné, aby definice a deklarace obsahovaly definice dalších procedur a funkcí. My se tomu budeme vyhýbat.

Definice funkce je podobná, stačí za názvem napsat :VyslednyTyp; Výsledný typ musí být nějaký z Pascalem předdefinovaných typů, nemáme tedy moc možností, jak vracet složitější data. Toto omezení se často obchází, funkce vytvoří dynamickou proměnnou a vrátí ukazatel na ní. Pozor, funkce nemůže vrátit odkaz na svou lokální proměnnou, ta totiž zaniká v okamžiku ukončení funkce.

Většina programátorů preferuje funkce před procedurami. Nemá-li totiž funkce parametry předávané odkazem, představuje její návratová hodnota její jediné spojení se zbytkem programu, na výstupu tak můžeme např. vracet, jestli nastala nějaká chyba. Z tohoto pohledu jsou funkce bezpečnější než procedury, budeme se snažit využívat především je. Funkce vrací nějakou hodnotu, nejde tedy o samostatný příkaz, občas nás ovšem návratová hodnota nezajímá. Když se to stane, dovoluje Pascal využít tzv. rozšířenou syntaxi, v které můžeme funkce volat stejně jako procedury, aniž bychom se ptali na jejich návratové hodnoty. (Tuto možnost využívá např. jednotka strings). Nejčastěji se volné syntaxe využívá, pokud naše funkce vrací chybový kód a my víme, že při tomhle volání se chyba stát nemůže.

Příklad:

{$X+} {Tento prikaz zapne rozsirenou syntaxi, zkuste ji vypnout pomoci {$X-}}
program Ukazka1;
function tisk(s:Openstring):integer;
{funkce zobrazi dany string s a vrati pocet vytistenych znaku, ci -1 v pripade chyby}
begin
 writeln(s);
 if 1>2 then tisk:=-1
  else tisk:=length(s);
end;

const a:string='Ahoj';     {Je pro me zahadou, proc se v Pascalu promenne inicializuji takto}
      b:string[15]='lidi';

begin
 tiskni(a);
 tiskni(b);
 readln;
end.

Parametry

Pascal rozlišuje čtyři typy parametrů:

  1. Parametry předávané hodnotou
  2. Parametry předávané odkazem
  3. Konstantní parametry
  4. Parametry bez udaného typu

Jako parametry funkcí a procedur jsou povoleny i následující typy (s kterými se jinde nesetkáme):

Parametry předávané hodnotou

Tyto parametry jsou nejjednodušší. Při volání dané procedury se nejprve vypočte hodnota daného parametru a ta se pak zkopíruje do hodnoty lokální proměnné daného názvu. Lokální proměnnou můžeme jakkoli měnit a na původní parametr to nebude mít žádný vliv. Při volání procedury lze na místo parametrů předávaných hodnotou dosadit jakýkoli výraz, který bude mít odpovídající typ. (Je možné volat různé funkce, sčítat, dosazovat konstanty a čísla apod.)

Parametry předávané hodnotou musí mít udaný typ. V seznamu parametrů se uvádí jako Nazvy : PrislusnyTyp. Je možné zadat více parametrů stejného typu, jejich název oddělit čárkou a výsledný typ uvést až za poslední z nich. Potřebujeme-li parametry dalšího typu, použijeme k oddělení středník. Za poslední typ středník nepíšeme. Ukažme si vše na jednoduchém příkladě:

program Ukazka2;
type MujTyp=integer;

procedure prohod(a,b:MujTyp;c:Boolean);
var pom:Mujtyp;{a,b,pom,c jsou lokalni promenne procedury prohod}
begin
 pom:=a;
 a:=b;
 b:=pom;
end;

var e,f,g:mujtyp; {e,f jsou globalni promenne}

begin
 e:=1;
 prohod(abs(e)+15,2,true)

 f:=2;
 g:=3;
 prohod(f,g,false);
 writeln(f,g);
 readln;
end.

Manipulace s tímto typem parametrů je nejjednodušší a nejprůhlednější. Tím, že nic neměníme, snižujeme riziko chyby. Je-li to možné, využíváme tedy tento typ parametrů.

Parametry předávané odkazem

Občas potřebujeme, aby procedura či funkce pozměnila hodnotu nějaké "vnější" proměnné. V tom případě využije parametru volaného odkazem. Při volání procedury na místo parametru napíšeme proměnnou daného typu. Procedura dostane ukazatel na tuto proměnnou, s jeho pomocí pak provádí veškeré operace. Hodnota proměnné se tedy mění i v hlavním programu. Na místo parametrů volaných odkazem tedy není možné psát žádný výraz, musí tam být vždy jen proměnná. Značení je stejné jako v případě parametrů předávaných hodnotou, jenom před název parametrů, které chceme předávat odkazem, napíšeme var.

Příklad:

program Ukazka3;
type MujTyp=integer;

procedure prohod(var a,b:MujTyp;c:Boolean); {a,b jsou předávány odkazem, c hodnotou}
var pom:Mujtyp;
begin
 pom:=a;
 a:=b;
 b:=pom;
end;

var e,f,g:mujtyp;

begin
 e:=1;
 prohod(abs(e)+15,2,true); {první i druhá hodnota nejsou proměnné, program tedy nejde přeložit,
                           odstraňte tedy tento příkaz}

 f:=2;
 g:=3;
 prohod(f,g,false);
 writeln(f,g);
 readln;
end.

Parametry předávané odkazem mění i proměnné vlastního programu, proto si na ně musíme dávat pozor. Jejich použití je ale lepší, než měnit hodnotu globální proměnné přímo. Takhle totiž máme již v hlavičce procedury informaci: pozor, s tou proměnnou se může něco stát.

Parametry volané odkazem používáme jen v následujících případech:

První bod je jasný.
Druhý bod: Každá procedura má vlastní úložiště dat. Předáváme-li např. proceduře gigabytové pole pomocí hodnoty, vyhradí se v paměti místo pro tuto proceduru a pro její data (něco přes jeden gigabyte), pak se hodnoty původního pole zkopírují do lokální proměnné, teprve pak se procedura spustí.
Pokud je procedura volaná rekurzivně (např. při vyhledávání v daném poli), paměť dochází opravdu rychle. I samotné spuštění procedury trvá dlouho, protože čekáme, než se celé gigabytové pole zkopíruje.
Pokud takovýto parametr předáme odkazem, zkopíruje se pouze ukazatel na něj, ten zabírá pár bytů. Šetří to paměť a značně zvyšuje rychlost programu.

Konstantní parametry

Tyto parametry jsou zvláštním případem parametrů předávaných hodnotou. V průběhu procedury (či funkce) se nesmí měnit. Pokud víme, že naše procedura skutečně daný parametr nemění (a ani nikdy měnit nebude), je lepší nadefinovat ho takto. Překladač nás pak upozorní, pokud se o změnu omylem pokusíme. Konstantní parametry by se měly zpracovávat rychleji než ty obyčejné, ale Pascal s nimi v paměti dělá psí kusy. To je důvod, proč se jim programátoři vyhýbají. V moderních programovacích jazycích hrají konstantní parametry neocenitelnou úlohu. Budeme se je tedy snažit používat, kdykoli to budeme s jistotou vědět, že jsou namístě.
Definice je jednoduchá - před název parametru napíšeme klíčové slovo const.
(procedure NemenimL(const L:integer;var A,B:real;C:boolean);)

Parametry bez udaného typu

Tyto parametry musí být volané odkazem. V průběhu funkce či procedury do nich nejde nic přiřazovat. Pracuje se s nimi pomocí přetypování, nejčastěji na bitové úrovni. Jsou poměrně nebezpečné, nebudeme je používat.
procedure Nebezpecna(var A);

Direktivy

Nejprve se musíme podívat na to, jak překladač překládá program a jak jsou procedury a funkce reprezentovány v paměti. Paměť je dělená na malé segmenty, pohybujeme-li se pouze v rámci jednoho z nich, je veškerá manipulace s pamětí poměrně jednoduchá a rychlá. Horší to je, pokud si s jedním segmentem nevystačíme. V Pascalu platí, že program sídlí v jednom segmentu a každá unita má vlastní segment. (Přístup k objektům definovaných v unitám je tedy pomalejší než přístup k objektům samotného programu.)

Při překladu Pascal řádek po řádku prochází kód a dělá si značky, co už je definované a o čem tedy ví, jak to používat. Volá-li program nějakou proceduru, vypočte hodnoty parametrů, které zkopíruje do lokálních proměnných, uloží do paměti, na jakém místě kódu se program právě nachází a skočí v paměti na místo, kde sídlí procedura (tím ji předá řízení). Procedura se po svém skončení podívá, kde skončil hlavní program a skočí na příslušné místo kódu. Pokud je tento skok v rámci jednoho segmentu, jedná se o tzv. blízké volání, jinak jde o volání vzdálené.

Forward

Problém nastane, pokud máme dvě funkce (fa, fb) takové, že jedna volá druhou a druhá první.
Překladač k tomu, aby danou funkci mohl správně použít, potřebuje znát jen její hlavičku (při volání funkce je totiž zapotřebí jenom zkopírovat parametry do lokálních proměnných dané funkce, problémy nastávají, pouze pokud nevím, jakých typů dané parametr jsou (5 může být integer i reálné číslo) a jak je tedy reprezentován v paměti.) Direktiva forward slouží právě k tomu, že překladači říká, hele, tady máš funkci (proceduru), která bude vypadat tak a tak (překladač ji tedy může volat), ale definovat ji budu až později.
Příklad:

program ukazka4;
{To je jen jednoduchá ukázka, nevíte někdo, co dělá?}
function a(i:integer):integer;forward; {chci v ni pouzivat funkci b, nemuzu sem tedy dat cele telo}

function b(i:integer):integer;
begin
 if i>0 then b:=i*a(i-1)
 else b:=-1;
end;

function a(i:integer):integer; {Dle pravidel uz staci napsat function a; lepsi je ale hlavicku opsat}
begin
 if i>0 then a:=i*b(i-2)
 else a:=1;
end;

begin
 writeln(a(4),a(3));
 readln;
end.

Virtual

Tato direktiva lze použít pouze u metod tříd. Objekty se vyznačují polymorfismem. To znamená, že za proměnnou třídy rodiče lze dosazovat i proměnnou libovolného potomka (či potomka potomka atd.) Zavoláme-li pak metodu příslušného objektu, má program na výběr dvě možnosti: Buď zavolá metodu rodiče, nebo příslušnou metodu potomka. Z příkladu to je patrné:

program polymorfismus;
type PRodic=^TRodic;
     TRodic=object
             procedure NecoDelej;
            end;
     PSyn=^TSyn;
     TSyn=object(TRodic)
              procedure NecoDelej;
          end;

procedure TRodic.Necodelej;
begin
 writeln('Ja jsem rodic');
end;

procedure TSyn.NecoDelej;
begin
 writeln('Ja jsem syn');
end;

var a:PRodic;
    b:PSyn;
begin
 b:=new(psyn);
 a:=b; {Potomka lze kdykoli priradit do promenne typu rodic, to same plati o ukazatelich}
 a^.necodelej;
 dispose(b);
 readln;
end.

Pascalu jsme nesdělili, že naše metoda je zvláštní. Myslí si tedy, že má proměnnou typu rodič a zavolá tedy metodu rodiče (a na obrazovku se vypíše Ja jsem rodic).

Direktiva virtual říká, že program se má při každém volání podívat, jaký objekt je na daném místě ve skutečnosti, a podle toho vybrat správnou metodu.
Mohli bychom tedy zkusit náš program přepsat:

program polymorfismus2;
type PRodic=^TRodic;
     TRodic=object
             procedure NecoDelej;virtual;
            end;
     PSyn=^TSyn;
     TSyn=object(TRodic)
              procedure NecoDelej;virtual;
          end;

procedure TRodic.Necodelej;
begin
 writeln('Ja jsem rodic');
end;

procedure TSyn.NecoDelej;
begin
 writeln('Ja jsem syn');
end;

var a:PRodic;
    b:PSyn;
begin
 b:=new(psyn);
 a:=b; {Potomka lze kdykoli priradit do promenne typu rodic, to same plati o ukazatelich}
 a^.necodelej;
 dispose(b);
 readln;
end.

Pokusíme-li se nyní program spustit, nic se nestane. To proto, že objekt si svou identitu uvědomí teprve v okamžiku volání konstruktoru (i když ten třeba zdánlivě nic nedělá). A my jsme žádný konstruktor nezavolali. Proto píšeme konstruktor u všech objektů a proto máme všechny objekty dynamicky alokované:

program polymorfismus2;
type PRodic=^TRodic;
     TRodic=object
             constructor Init;
             procedure NecoDelej;virtual;
            end;
     PSyn=^TSyn;
     TSyn=object(TRodic)
              constructor Init;
              procedure NecoDelej;virtual;
          end;

     PDcera=^TDcera;
     TDcera=object(TRodic)
               constructor Init;
               procedure NecoDelej;virtual;
            end;

constructor TRodic.Init;
{Ac zdanlive nic nedelam, pomaham uvedomit si dane promenne, ze je skutecne typu TRodic
a zadneho jineho}
begin
end;

procedure TRodic.Necodelej;
begin
 writeln('Ja jsem rodic');
end;

constructor TSyn.Init;
begin
end;

procedure TSyn.NecoDelej;
begin
 writeln('Ja jsem syn');
end;

constructor TDcera.Init;
begin
end;
procedure TDcera.NecoDelej;
begin
 writeln('Ja jsem dcera');
end;

var a:PRodic;
    b:PSyn;
begin
 b:=new(psyn,init);
 a:=b;
 a^.necodelej;
 dispose(b);
 readln;
end.

Nyní konečně náš program očekávaným způsobem změnil své chování a na obrazovku vypise Ja jsem syn. Slovo virtual nikdy nejde napsat za konstruktor. Prostě proto, že virtuální metody jdou používat až po zavolání konstruktoru.

U destruktoru je situace trošku jiná. Každá třída by měla mít destruktor. Může se totiž stát, že časem někdo vytvoří potomka naší třídy, který bude alokovat ohromné množství paměti. (Nebo třeba vypíše něco tajného na obrazovku.) Bylo by tedy žádoucí, aby tento potomek po sobě uklidil. Nejjednodušší způsob, jak toho docílit, je mít v naší třídě vždy virtuální destruktor. Budeme se tedy držet hesla, že každá třída má virtuální destruktor, a budeme tento destruktor volat v okamžiku skončení práce s daným objektem. (Bez ohledu na to, zda něco dělá, nebo ne.)

Náš (konečně objektově napsaný) příklad tedy vypadá takto:

program polymorfismus_final;
type PRodic=^TRodic;
     TRodic=object
             constructor Init;
             procedure NecoDelej;virtual;
             destructor Done;virtual;
            end;
     PSyn=^TSyn;
     TSyn=object(TRodic)
              constructor Init;
              procedure NecoDelej;virtual;
              destructor Done;virtual;
          end;

constructor TRodic.Init;
{Ac zdanlive nic nedelam, pomaham uvedomit si dane promenne, ze je skutecne typu TRodic
a zadneho jineho}
begin
end;

procedure TRodic.Necodelej;
begin
 writeln('Ja jsem rodic');
end;

destructor TRodic.Done;
{Sice nic nedělám, ale jelikož jsem virtuální, hodím se, pokud bude
destruktor potomka něco dělat}
begin
end;

constructor TSyn.Init;
begin
end;

procedure TSyn.NecoDelej;
begin
 writeln('Ja jsem syn');
end;

destructor TSyn.Done;
begin
 writeln('End of a son');
end;

var a:PRodic;
    b:PSyn;
begin
 b:=new(psyn,init); {Zaciname pracovat s objektem, volame konstruktor}
 a:=b;
 a^.necodelej;
 dispose(b,done); {Praci jsme ukoncili, na case je destrukce objektu}
 readln;
end.

Ukažme si praktické výhody polymorfismu a virtuálních metod: vytvoříme abstraktní datový typ TVypsatelny s virtuální metodou vypis. Tento objekt tedy může reprezentovat jakákoli data, u kterých má smysl snažit se je vypsat na obrazovku. Nyní si udělejme dvě dědičné třídy : TReal a TString, kde první bude obsahovat reálné číslo a druhá řetězec. Vyrobme pole objektů typu TVypsatelny, můžeme do něj strkat reálná čísla i stringy a zavoláme-li metodu vypiš, provede se správná procedura. Jelikož nevíme, co by měla metoda Vypis dělat u zcela abstraktního typu, nadefinujeme ji jako abstraktní (tj. zavoláme-li ji, dojde k chybě Volání abstraktní metody). Bohužel Pascal nemá slovo abstract, budeme si ho tedy muset nějak nahradit.

Ukázka (tady vidíte, že přehlednější je psát metody hned za definici příslušného objektu):

program ukazka2;
uses Crt;
const MaxDelka=100;

type
     PVypsatelny=^TVypsatelny;{Vsechny objekty volame dynamicky, ke kazdemu tedy patri ukazatel}
     TVypsatelny=object
                       constructor Init;
                       procedure Vypis;virtual;
                       destructor Done;virtual;{destruktor by vzdy mel byt virtualni, vite proc?}
                 end;
     PReal=^TReal;
     TReal=object(TVypsatelny)
            R:real;
            constructor Init(var s:real);
            procedure Vypis;virtual;
           end;
     PString=^Tstring;
     TString=object(TVypsatelny)
            s:string;
            constructor Init(var r:string);
            procedure Vypis;virtual;
           end;
     PPole=^Tpole;
     TPole=object
            delka:0..maxdelka;
            data:array[1..maxdelka] of PVypsatelny;
            constructor Init;
            procedure Add;
            procedure Tisk;
            destructor Done;virtual;
           end;

constructor TVypsatelny.Init;
begin
end;

procedure TVypsatelny.Vypis;{Abstraktni metoda}
begin
 RunError(211); {Tahle metoda by se nikdy nemela zavolat; obchazeni toho, ze slovo abstract v Pascalu chybi
                Chyba 211 znamena volani abstraktni metody.}
end;

destructor TVypsatelny.Done;
begin
 write('Nicim objekt');writeln;
end;

constructor TReal.Init(var s:real);
begin
 r:=s;
end;

procedure TReal.Vypis;
begin
 writeln('Toto je realne cislo s hodnotou: ', R);
end;

constructor TString.Init(var r:string);
begin
 s:=r;
end;

procedure TString.Vypis;
begin
 writeln('Tento objekt obsahuje nasledujici retezec:',S);
end;

constructor TPole.Init;
var I:1..maxdelka;
begin
 for I:=1 to maxdelka do
  data[i]:=nil;
 delka:=0;
end;

procedure TPole.Add;
var c:char;
    r:real;
    s:string;
    konec:boolean;
begin
 writeln('Stisknete r, pokud chcete zadat realne cislo');
 writeln('Stisknete s, pro zadavani retezce');
 writeln('Stisknete q, pokud chcete skoncit');
 konec:=false;

 repeat
 c:=UpCase(Readkey);
 case c of
  'R':begin
       write('Cislo:');
       readln(R);
       konec:=true;
      end;
  'S':begin
       write('Retezec:');
       readln(S);
       konec:=true;
      end;
  'Q':exit;{Ukoncime proceduru}
  end;{Spravne bychom meli osetrit chyby}
 until konec;

 delka:=delka+1;
 if (c='R') then data[delka]:=new(PReal,Init(R));
 if (c='S') then data[delka]:=new(PString,Init(S));
 writeln;
end;

procedure TPole.Tisk;
var i:1..maxdelka;
begin
 for i:=1 to delka do
  data[i]^.vypis;
end;

destructor TPole.Done;
var i:0..maxdelka;
begin
 for i:=1 to delka do
  Dispose(data[i],done);{Pro kazdou polozku pole jsme alokovali pamet, musime ji uvolnit,
                         jelikož je destruktor virtualni, zavola se vzdy destructor vlozeneho
                         objektu a ne destructor tridy TVypsatelne, teda pokud by
                         dany objekt mel nejaky destruktor}
end;

var i:1..maxdelka;
    c:PPole;

begin
 c:=new(PPole,Init);
 for i:=1 to 10 do
  c^.add;
 c^.tisk;
 c^.done; {Tahle radka patri nakonec, ale takhle je to videt}

 readln;
end.

Nyní není problém do našeho programu dopsat další třídu, která půjde vypsat a skladovat v našem poli. Náš program se tedy stal vysoce univerzální. (Vyzkoušejte si i, co se stane, pokud slovo virtual z definice funkce Vypis odstraníte. A podívejte se do Object-Inspektoru) Schválně si připište třídu TList (potomka TVypsatelne) - dynamický seznam integerů. Už víte, proč je důležité, aby destruktor byl virtuální?

Far a near

Dalšími direktivami jsou far a near.

Funkce a procedury v rámci hlavního programu jsou volány blízce, stejně tak jsou volány funkce a procedury uvnitř části implementation. Na rozdíl od toho funkce v sekci interface musí být vždy volány vzdáleně, jinak by nemohly být použity v jiné části programu než uvnitř dané unity.
V některých případech ale potřebujeme implicitní chování překladače změnit. (Např. do proměnné procedurálního typu jde přiřadit pouze funkce, která je volána vzdáleně.)

Napíšeme-li za název funkce direktivu far, donutíme překladač, aby danou proceduru volal vzdáleně. Naopak direktiva near říká překladači, že se má pokusit o blízké volání.

Krátká ukázka:

{$X+}
program Ukazka5;
type tfunc=function(i,j:integer):integer;

function nasob(i,j:integer):integer;far;
begin
 nasob:=i*j;
end;

function scitej(i,j:integer):integer;near;
begin
 scitej:=i+j;
end;

function vypis(iod,jod,ido,jdo:integer;f:tfunc):integer;
{Vypise tabulku pro danou operaci f}
var i,j:integer;
begin
 writeln;
 write('    ');
 for j:=jod to jdo do
  write(j:4);
 writeln;writeln;
 for i:=iod to ido do
  begin
   write(i:4);
   for j:=jod to jdo do
    write(f(i,j):4);
   writeln;
  end;
 vypis:=0;
end;

begin
 vypis(1,1,10,10,nasob); {se scitej to nepujde, funkcionalni promenne musi byt volany vzdalene}
end.

Export

Tato direktiva lze využít jen v rámci DLL knihovny. Říká, že danou funkci budou využívat i jiné programy. (Což mimo jiné např. znamená, že má být volána vzdáleně.) Na konci DLL knihovny pak musí být v sekci exports uveden seznam všech exportovaných funkcí, nejčastěji i s indexy (To je číslo typu word, sloužící k tomu, aby daná funkce byla rychleji nalezena při prohledávání), či direktivou name, která říká bacha, danou funkci budeme v knihovně hledat podle jména. Poslední možnost je direktiva resident, ta říká, že tato funkce je tak důležitá (bude využívána tak často), že má zůstat v paměti od prvního nahrání knihovny až do konce práce s programem.

Ilustrace:

library funkce;
function nasob(x,y:integer):integer;export;
begin
 nasob:=x*y;
end;

function secti(x,y:integer):integer;export;
begin
 secti:=x+y;
end;

function odecti(x,y:integer):integer;export;
begin
 odecti:=x-y;
end;

exports {Bacha, oddělovacím znaménkem je čárka}
 nasob,
 secti index 1 name 'scitej' resident,
 odecti index 2;

begin
{Spustitelná část knihovny}
end.

External

Ještě jednou se podíváme na to, co přesně dělá překladač. Nejprve soubor přeloží, k tomu potřebuje mít funkce pouze deklarované, aby věděl, jak je volat. Nemusí vůbec vědět, co a jak tyto funkce dělají. Funkce mohou být definovány v úplně jiném souboru. Po skončení překladu se spustí linker. To je program, který hledá a páruje vhodné funkce, aby vznikl spustitelný soubor. Linkeru je jedno, v jakých jazycích byly dané soubory napsány. Můžeme tedy různé části kódu psát v odlišných jazycích. A pak to nějak dát dohromady. Podobně jako direktiva forward i direktiva external slouží k tomu, aby překladači oznámila, jak daná funkce vypadá a jaké má parametry. Navíc říká, že daná funkce je definována v nějakém jiném souboru. (To může být DLL knihovna, či .obj soubor.) O správné spárování se postará linker.
Máme funkci MIN napsanou v čistém assembleru (C, C++ či jiném programovacím jazyce), ta je přeložená do souboru funkce.obj.

V Pascalu pak můžeme napsat
function MIN(x,y:integer);external; {$L funkce.obj}.
a naši funkci používat. Musíme hlídat, abychom předávali správné parametry.
Např. typ Real byl v Pascalu 6-bytový, všude jinde je 8-bytový. Takže procedura z C++, která očekává číslo typu float, dostane nějaký úplný nesmysl, když ji Pascal zavolá a předá jí svůj real. Pascal má proto i typy single a double, které mají standardní velikost 4 resp. 8 bytů.

V dnešní době se mnohem víc používají DLL knihovny. Ty jdou ovšem použít pouze v Protected či Windows módu (Nabídka Compiler/Target). DLL knihovny jsou volány jenom tehdy, když se skutečně potřebují, nezabírají tak zbytečně místo v paměti a jejich funkce jsou vždy volány vzdáleně. Tuto skutečnost musíme překladači oznámit. Předpokládejme, že v našem adresáři máme DLL knihovnu funkce.dll.
Pak stačí napsat(jsme-li ve Windows módu):
function scitej(x,y:integer):integer;far;external 'funkce';
DLL knihovny jsou indexovány, každá funkce má svůj index (číslo od 0 do 65000), chceme-li vybírat podle tohoto indexu, napíšeme:
function haha(x,y:integer):integer;far;external 'funkce' index 1;
Naše jméno se pak nemusí shodovat s názvem funkce v DLL knihovně. Funkce je také možné vybírat podle skutečných jmen v knihovně:
function scitej(x,y:integer):integer;far;external 'funkce' name 'odecti'; (Podívejte se na minulý příklad) Podpora DLL knihoven v Pascalu trošku pokulhává, často nepůjdou použít.

Assembler

Občas je zapotřebí něco napsat v assembleru a občas je tak napsaná celá funkce, která nemá žádné parametry, které by využívala.
Nejlepší je nadefinovat takovouto funkci jako assemblerovskou. To se provede připsáním slova assembler; za hlavičku funkce. Samotné tělo pak místo begin začíná slovem asm
Narazí-li překladač na takovou funkci, bude se snažit o maximální rychlost - nebude kopírovat lokální proměnné, nebude umisťovat některé věci na zásobník atd. (Nemůžeme tedy používat parametry.)
Assembler funkce by měly být krátké (max 3 strany kódu). Pokud má funkce vracet nějakou hodnotu, předá ji v registru AX.

Ilustrace:

program Ukazka6;
var x,y:integer;

function secti:integer;assembler;
asm
 mov ax,x
 mov bx,y
 add ax,bx
end;

begin
 readln(x,y);
 writeln(secti);
 readln;
end.

Inline

Strojově orientované programování v assembleru se od normálního značně liší. Dalším logickým krokem směrem k počítači je přímé psaní kódu procesoru.
Příkaz inline a inline funkce jdou ještě dál než asm a assemblerovské funkce. Procesory se stále vyvíjí, a překladač nezná všechny instrukce. Chceme-li použít instrukci neznámou překladači nebo chceme-li, aby v našem kódu byla nějaká posloupnost bytů, použijeme příkaz inline.

Použití je jednoduché: inline(data / data / data / ... data); Data jsou konstanty nebo identifikátory proměnných.
Jsou-li data konstanta v rozsahu 0-255, vloží překladač do kódu jeden byte, jinak vloží jeden word (2byty). Chceme-li toto chování změnit, použijeme < (vždy generuj jen jeden byte), či > (generuj jeden word). Název proměnné se přeloží jako její adresa, můžeme napsat i NazevPromenne + konstanta či NazevPromenne - konstanta. Určíme tak offset braný od adresy proměnné.

Na kódy jednotlivých assemblerovských instrukcí se můžete podívat do sysmana. Dokumentaci k novějším procesorům získejte od výrobce.

Příklad:

inline($FC); {instrukce CLD - nuluj vlajku směru}

Pokud takový kód potřebujeme spouštět častěji, napíšeme ho jako inline proceduru.
Inline funkce má jen jediný příkaz inline hned za svou hlavičkou.
Překladač pak každý výskyt takové procedury nahradí naší sekvencí bytů. (Je to ještě rychlejší než assembler.) Inline funkce vrací svou návratovou hodnotu v AX.
Pascal inline proceduru nepřeloží správně, má-li nějaké parametry.

Příklad(podívejte se do sysmana):

program ukazka7;
var x:integer;
function zvys:integer;
inline(
      $A1/x/ {mov AX,x - (x je šestnáctibitové, neboť je to integer)}
      $40    {inc AX - a v AX je pak návratová hodnota}
      );

begin
 readln(x);
 writeln(zvys);
 readln;
end.

Platí, že inline funkce jsou velice rychlé a velice nebezpečné. Používáme je jen tehdy, je-li to nezbytné. Navíc v C++ znamená inline funkce něco jiného a později by se nám to pletlo.

Interrupt

Některé funkce chceme použít k obsluze jistých přerušení (jsou přerušení, která měnit nemůžeme). To je hodně důležité např. u rezidentních programů (tj. u těch, které i po skončení zůstávají v paměti.) Tyto funkce nadefinujeme jako tzv. interrupt funkce, připsáním direktivy interrupt za jejich název. Interrupt funkce buď nemá žádné parametry nebo má dvanáct parametrů typu word, ty odpovídají registrům v tomto pořadí: Flags, CS, IP, AX, BX, CX, DX, SI, DI, DS, ES, BP. Interrupt funkce by měly být zatraceně rychlé, většinou se píší v assembleru. Tyto funkce představují vážný zásah do struktury operačního systému, pokud nebudeme programovat hardwarově orientovaný program, nebudeme je potřebovat.
Ukázka:

procedure ObsluhaInt33h(Flags, CS, IP, AX, BX, CX, DX, SI, DI, DS, ES, BP:word);interrupt; {Neco na obsluhu mysi}