EN | CZ
O společnostiProduktyObchodPodpora
Moravské přístroje
Hlavní stránka
O společnosti
Stažení software
Stažení dokumentů
Produkty
Control Web
Strojové vidění VisionLab
Kamery DataCam a osvětlovače DataLight
Průmyslový počítačový systém DataLab
Vědecké kamery
Speciální technika
Ceník
Aktivace produktů
Služby
Školení
Zakázková řešení
Podpora
Volba kamery a objektivu pro Strojové vidění
Control Web - Ukázkové aplikace

Hlavní stránkaControl Web - Ukázkové aplikaceControl Web Dokumentace

Programování a procedury — OCL

Programování se v nejobecnějším slova smyslu zabývá způsoby, jak počítači sdělit algoritmus, který má vykonávat. Od prvopočátků výpočetní techniky, kdy bylo nutno vkládat do paměti počítače přímo jednotlivé instrukce, které pak stroj interpretoval, udělala tato disciplína obrovský pokrok. I když i dnešní počítače v principu nejsou schopny nic více, než jen vykonávat sekvence jednoduchých instrukcí uložených v paměti, způsoby tvorby těchto sekvencí — programů — se dramaticky proměnily.

Pokud z palety přístrojů systému Control Web vyberete zobrazovač hodnoty a v inspektoru mu předepíšete, aby každou sekundu změřil a zobrazil nějakou veličinu, vytvoříte tím program. Z hlediska instrukcí procesoru je to program velmi složitý, zahrnující stovky tisíc instrukcí. Důvod je prostý — samozřejmě neexistuje instrukce pro zobrazení čísla nebo pro vykreslení ručičky měřicího přístroje. Nejsou ani instrukce pro nakreslení přímky (ano, perfekcionisté mohou namítnout, že specializované grafické procesory umí vykreslit přímku jedinou instrukcí, to ale na principu výkladu nic nemění) a dokonce ani pro výpočet funkce sinus, nutné k výpočtu souřadnic konce přímky ze známého úhlu. Procesorové instrukce dokáží jen velmi málo, například přečíst oblast paměti do vnitřního registru procesoru, sečíst dva registry nebo vyhodnotit nějakou podmínku a podle výsledku odskočit do jiné části programu.

Naštěstí vývoj programování již dávno přinesl skvělý vynález — programovací jazyk. Je to způsob zápisu algoritmů na vyšší úrovni, než jsou strojové instrukce procesoru, pomocí symbolických vyjádření. Chcete-li ve vyšším programovacím jazyce naprogramovat sčítání dvou čísel, není nutné znát adresy, na nichž jsou tato čísla uložena v operační paměti, ani není nutné vyhradit pro ně registry procesoru, prostě stačí napsat a + b. Jak je to možné, když procesor rozumí jen svým jednoduchým instrukcím? Do celého procesu je třeba zapojit jiný program — překladač. Překladač převádí zápis algoritmu z programovacího jazyka, většinou srozumitelného člověku, do tvaru strojových instrukcí, kterým rozumí počítač.

Programy tedy lze tvořit pomocí několika prostředků, lišících se úrovní abstrakce, nezávislosti na konkrétním prostředí:

  • Strojový kód představuje přímo instrukce pro procesor daného počítače. Jeho použití je velmi pracné a neefektivní, a navíc algoritmus zapsaný pomocí strojových instrukcí pracuje jen na konkrétním typu procesoru a případně i počítače. To jsou hlavní důvody, proč se strojový kód (byť i zapsaný symbolicky v jazyce nazývaném assembler) používá jen naprosto výjimečně, především při implementaci operačních systémů. Na druhé straně je strojový kód nejobecnější vyjadřovací prostředek a lze říci, že pokud něco nelze naprogramovat pomocí strojových instrukcí, pak to nelze naprogramovat vůbec.

  • Programovací jazyk je definován množinou pravidel, jimiž se zápis programu, nazývaný zdrojový text, musí řídit (gramatika jazyka) a zdrojový text je pak více či méně nezávislý na prostředí, ve kterém program běží. Překlad zdrojového textu do instrukcí srozumitelných procesoru zajišťuje jiný program — překladač. Programovacích jazyků je celá řada a samy se také liší úrovní abstrakce — od obtížně použitelných jazyků blízkých strojovému kódu (např. jazyk C) po snadněji použitelné modulární jazyky (např. Pascal, Modula-2, Oberon).

  • Samostatnou položku si zaslouží programovací jazyky, které jsou mimo samozřejmou schopnost zápisu jakýchkoliv algoritmů schopny komunikovat s jinými komponentami a mohou tak pracovat jako „lepidlo“ spojující jednotlivé komponenty. Pro rychlý vývoj aplikací je tato vlastnost velice důležitá, neboť nejefektivněji řeší odvěký problém programování — jak nepsat zbytečně stejný kus programu dvakrát. Klasické jazyky tento problém řeší zavedením knihoven — bloků kódu, který lze k programům připojovat (např. výpočet funkce sinus je typický příklad bloku knihovního kódu, který se v případě potřeby připojí k programu). Knihovny ale mají řadu omezení a až komponenty problém znovupoužitelnosti skutečně řeší. Typickými příklady takových jazyků jsou VisualBasic nebo i OCL (Object Control Language — jazyk řízení objektů) systému Control Web. I když typicky lze komponenty používat i z jiných jazyků, snadnost a přímost práce tím zpravidla utrpí.

  • Nástroje rychlého vývoje aplikací představují nejvyšší úroveň abstrakce od principů práce procesoru. Zpravidla umožňují aplikaci „poskládat“ z připravených, parametrizovatelných komponent. Právě Control Web je příkladem takového systému.

Motivace zavedení OCL do systému Control Web je tedy zřejmá: zatímco většina aplikací pracuje jako sada předpřipravených komponent, někdy je zapotřebí nástroje sahajícího za hotové komponenty, který dokáže využívat jejich vlastností (a není tedy nutno programovat vykreslování ručičky měřicího přístroje) a přitom může realizovat jakýkoliv algoritmus a doplnit tím do aplikace potřebnou funkčnost.

Procedura nebo metoda?

I když se bloky kódu (zápisy algoritmů) v systému Control Web nazývají procedury, přesto se poněkud liší od tzv. procedurálního programování popsaného v řadě klasických učebnic. Podstatný rozdíl tkví především v absenci nějakého „hlavního modulu“, který se spustí jako první a který teprve případně volá podprogramy. Proč je tomu tak? Control Web nepracuje klasickým dávkovým způsobem. Control Web je událostmi řízený systém. Neexistuje „hlavní program“, namísto toho systém reaguje na události a vyvolává bloky kódu které tyto události zpracovávají. Co si ale pod pojmem „událost“ představit? U běžných uživatelských aplikací je událost například stisk klávesy nebo pohyb myši. Control Web je ale průmyslový systém reálného času a množina událostí je rozšířená o události generované ovladači vstupně/výstupních zařízení, virtuálními přístroji nebo časovači. Při výskytu dané události je tedy zavolána odpovídající procedura.

Událost se nikdy nevyskytuje „sama o sobě“, bez vztahu k nějakému přístroji. Každý stisk klávesy, každý pohyb myši, ale i každá periodická aktivace nebo každá výjimka způsobená ovladačem se týká konkrétního virtuálního přístroje. Z tohoto důvodu jsou i procedury vždy spojeny s konkrétním virtuálním přístrojem. Takový model je velice blízký objektově-orientovanému modelu, v rámci něhož se právě bloky kódu realizující chování objektu nazývají metody objektu. Snad by tedy bylo možné nazývat procedury spíše metodami přístrojů. Avšak objektově-orientované technologie přinášejí komplikace, kterých je lépe se u nástrojů jako je Control Web vyvarovat, a tak zůstáváme u procedur. Jedna věc je ale podstatná — OCL procedury jsou vždy spojeny s nějakým virtuálním přístrojem, nikdy se nevyskytují samostatně.

Rozhraní procedury — parametry a návratová hodnota

Jak již bylo řečeno, procedury vykonávají nějaký kód — zápis algoritmu. Algoritmus zpracovává data a nebyl by k užitku, pokud by nenačetl data zvenčí a výsledky opět nevrátil. V podstatě mohou existovat dva způsoby takové výměny dat, které lze samozřejmě kombinovat:

  • Procedura může přistupovat k datovým elementům, které jsou definovány v sekcích aplikace (proměnné, kanály apod.) a číst či zapisovat jejich hodnoty. Takové datové elementy nazýváme globální. Globální datové elementy jsou přístupné z jakékoliv části aplikace, z jakéhokoliv virtuálního přístroje a z jakékoliv procedury.

  • Další způsob je definování parametrů procedury a případně její návratové hodnoty. Parametry a návratová hodnota představují soukromé rozhraní procedury a jejich použití je výhodnější. Hned vysvětlíme proč.

Problémů s přístupem na globální data je několik. Především je nutno vyhradit pro každou nastavovanou proměnnou každé procedury globální proměnnou a jejich počet tak roste. I orientace ve velkém množství globálních proměnných se výrazně ztěžuje. Hrozí problém synchronizace — jedna procedura použije globální proměnnou a během přestávky ve vykonávání kódu (způsobené instrukcemi yield, pause nebo wait — tyto instrukce jsou popsány později) jiná procedura její hodnotu přepíše.

Z těchto důvodů může mít každá procedura za svým jménem seznam parametrů — proměnných a jejich typů, např.:

procedure Calculate( Value : real; Flag : boolean );

Na rozdíl od definice globálních proměnných nemají proměnné tvořící parametry procedury inicializační hodnotu — jejich hodnota je vždy definována volajícím kódem. Pokud tedy jakákoliv procedura bude chtít, aby jí procedura Calculate něco spočítala, musí jí předat dvě hodnoty nebo i výrazy, jeden je číslo typu real, další je logická hodnota typu boolean.

Jaká jsou tedy pravidla pro přístup k proměnným, které se objeví jako parametry procedury?

  • Parametr se může jmenovat stejně, jako již definovaný globální datový element, lhostejno jakého typu. Pak je nutno si uvědomit, že použití takového identifikátoru vždy ukazuje na parametr a globální objekt je zneviditelněn, nelze jej použít. Přitom i pokud existuje např. globální pole kanálů typu real jménem c1 a procedura má definovaný parametr c1 typu boolean, pak v těle procedury lze c1 použít jen jako jednoduchou boolean proměnnou.

  • Pokud jsou definovány parametry procedury, tyto parametry jsou viditelné pouze v rámci procedury. Mimo proceduru je nelze použít.

  • V parametrech nelze uchovávat žádnou hodnotu, která má „přežít“ mezi voláními procedury. Hodnota parametrů je vždy nastavena při jejím vyvolání.

Při zápisu více parametrů stejného typu není nutno uvádět typ každého parametru zvlášť. Můžete parametry oddělit čárkami a typ uvést za dvojtečku za posledním parametrem.

procedure Check( fl1, fl2 : boolean );

Oba způsoby lze samozřejmě nezávisle kombinovat.

procedure TestFlags( fl1, fl2 : boolean; fl3 : boolean );

Pokud procedura žádné parametry nemá, uvedou se na místo seznamu parametrů prázdné závorky. Prázdné závorky je nutno uvádět v definici procedury i při jejím volání — podle závorek se pozná, že se jedná o proceduru a ne o identifikátor něčeho jiného.

procedure Go(); (* definice procedury *)

Go(); (* volání procedury *)

Návratová hodnota procedury

Mimo parametrů může procedura definovat i návratovou hodnotu. V systému Control Web se lze při psaní výrazů setkat s řadou zabudovaných funkcí, např. funkce pro výpočet sinu zadaného úhlu sin apod. Použití takové funkce je snadné — stačí ve výrazu uvést jméno funkce a do závorek zadat parametr (nebo parametry). Při vyhodnocování výrazu je funkce zavolána a na jejím místě se objeví hodnota, kterou funkce vrací (v našem případě hodnota sinu zadaného úhlu).

Stejně jako zabudované funkce, i procedury mohou vracet hodnotu. Pokud nějaká procedura hodnotu vrací, je možné ji volat v rámci jakéhokoliv výrazu. Samozřejmě návratová hodnota procedury je určitého typu a tak její použití ve výrazu musí tomuto typu odpovídat, musí být typově korektní.

Proceduru, která žádnou návratovou hodnotu nevrací, samozřejmě ve výrazu volat nejde. Naopak ale proceduru vracející hodnotu je možné volat jako samostatný příkaz, její návratová hodnota bude prostě přehlédnuta.

Pokud procedura vrací hodnotu, je v její definici nutno uvést typ návratové hodnoty za pravou závorkou uzavírající seznam parametrů a dvojtečkou:

procedure CheckValue( Value : real ): boolean;

Poznámka:

Některé programovací jazyky (např. Pascal) rozlišují jménem mezi podprogramy, které návratovou hodnotu nevracejí (procedure) a které ji vracejí (function). Toto přináší nejen problémy při odkazování se (je nutno psát procedury a funkce), ale také se tím zavádí zbytečná omezení pro volání takových procedur a funkcí — není možno volat funkci jako samostatný příkaz v programu. Proto OCL mezi procedurami a funkcemi formálně nerozlišuje a jediným omezením je nemožnost volat proceduru bez návratové hodnoty v rámci výrazu.

Použití procedur s návratovou hodnotou je jednoduché. Např. procedura která otestuje zda-li je zadané číslo v intervalu 0..1 bude vypadat takto:

procedure Test( a : real ): boolean;
begin
  return (a >= 0) and (a <= 1);
end_procedure;

Poznámka:

Příkaz return bude vysvětlen později.

Proceduru Test je pak možné používat kdekoliv v logickém výraze (vrací hodnotu typu boolean):

if Test( a ) then (* splněno pokud proměnná a má hodnotu mezi 0 a 1 *)
  ...
end;
...
Test( 0.5 ); (* syntakticky správné volání, ale bez jakéhokoliv efektu - návratová hodnota přehlédnuta *)

Parametry předávané odkazem

Co se stane, pokud procedura změní hodnotu parametru? Jestliže jsou parametry vlastně proměnné, lze do nich uvnitř těla procedury i zapisovat (přiřazovat jim novou hodnotu). OCL proto zavádí dvě volací konvence, které odlišují dva druhy chování při přiřazení do parametrů uvnitř procedury:

  • Parametry předávané hodnotou se prostě kopírují do proměnných, které tvoří parametry procedury. Protože kód procedury pracuje s kopií parametru, může do nich zapisovat, aniž by se to po návratu z procedury projevilo. Protože procedura pracuje s lokální kopií, je možné při volání procedury předat jako parametr i libovolný výraz daného typu. Výraz je vyhodnocen před vlastním zavoláním procedury a výsledná hodnota je uložena do proměnné parametru.

  • Parametry předávané odkazem pracují jinak. Uvnitř procedury nevznikne nová proměnná s hodnotou parametru, ale kód procedury pracuje přímo s proměnnou předanou jako parametr. To znamená že jakékoliv změny hodnoty takového parametru se provádí ve skutečnosti přímo v datovém elementu, který byl na místo parametru uveden při volání. Z toho plyne i několik omezení — do parametru předávaného odkazem nelze předat konstantu (procedura by do ní nemohla zapsat hodnotu) ani výraz, protože výraz nereprezentuje jediný datový element.

Parametry předávané hodnotou tedy „zmizí“ v těle procedury a jejich případné změny se navenek neprojeví. Z hlediska procedury se tedy jedná o parametry pouze vstupní. Změny v parametrech předávaných odkazem se po ukončení procedury objeví v předaných datových elementech, jedná se tedy o parametry vstupní i výstupní.

Způsob předávání parametrů je definován při deklaraci procedury. Prosté uvedení jména parametru a jeho typu definuje parametr předávaný hodnotou. Klíčové slovo var před parametrem značí parametr předávaný odkazem. Například v deklaraci procedury:

procedure Calculate( var Value : real; Flag : boolean );

je parametr Value předán odkazem, parametr Flag hodnotou. Na místo prvního parametru není tedy možné předávat konstantu ani výraz, pouze jednoduchý datový element. Příklady správného a chybného volání:

Calculate( 2.5, true ); (* chyba, konstantní literál předáván do var parametru *)
Calculate( 2 * Count, flag1 & flag2 ); (* chyba, výraz předáván do var parametru *)
Calculate( Count, ~flag1 ); (* správně, pokud datový element Count není konstanta a je číselného typu *)

Upozornění:

Pokud je v proceduře použit některý zpožďující příkaz pause, yield nebo wait (tyto příkazy jsou vysvětleny dále), jsou z procedury při volání vráceny parametry s hodnotami v době kdy procedura narazila na zpožďující instrukci, nikoliv na konci procedury.

Poznámka:

V předchozích verzích systému Control Web možnost předávání parametrů odkazem neexistovala — procedury pracovaly vždy s lokální kopií parametrů. Přesto bylo možné změněné hodnoty parametrů přenést zpět do datových elementů předaných jako parametry. Tato vlastnost ale nebyla definována v rámci definice hlavičky procedury, ale závisela čistě na kódu, který proceduru volal. Mějme například proceduru s jedním parametrem:

procedure Search( s : string );

Pokud tedy bude tato procedura volána příkazem:

Search( Str );

proměnná Str zůstane nezměněna bez ohledu na jakékoliv změny parametru s uvnitř procedury Search. Pokud volající kód chce aby se případné změny v parametru s promítly i do proměnné Str, musí při volání použít znak & před jménem parametru:

Search( &Str );

Samozřejmě nelze znakem & uvést konstantu nebo výraz:

Search( &'ahoj' ); (* chyba, odkaz před konstantou *)
Search( &( s1 + s2 )); (* chyba, odkaz před výrazem *)

Tato možnost předávání parametrů odkazem zůstala zachována i v současné verzi systému Control Web z důvodu zachování kompatibility s aplikacemi pro předchozí verze systému. Nicméně se nedoporučuje jí dále využívat. Základní nevýhoda tohoto způsobu spočívá v nutnosti uvádět znak & v zápisu každého volání, což vedlo k častým chybám. Rovněž je tento způsob méně efektivní než použití volání odkazem.

Tento způsob zcela selhává při pokusu předat tímto způsobem parametry do procedur vzdálených objektů. V takovém případě je nezbytné použít var parametry, jinak nebudou změny parametru provedené v proceduře ve vzdálených objektech promítnuty zpět do volajícího kódu.

Ve stávající verzi systému Control Web byly v řadě případů hlavičky nativních procedur vracejících data zpět do aplikace modifikovány tak, aby parametry byly předávány odkazem. Uživatelé tak nemusí při volání psát před parametry znak &, čímž se značně omezují problémy způsobené jeho opomenutím. To se ale může stát zdrojem drobných komplikací při spouštění aplikací vyvinutých v předchozích verzích systému Control Web. Pokud aplikace neměla o nějaký parametr zájem, bylo na místo logické proměnné možné prostě uvést např. konstantu false. Před konstantou samozřejmě nebyl znak & a kód pracoval správně. Ve stávající verzi systému Control Web je ale nutné deklarovat logickou proměnnou a tuto předat na místo var parametru, i když aplikace o tuto hodnotu nemá zájem.

GetWinState( false, &MaxFlag ); (* v předchozích verzích funguje, nyní je hlášena chyba *)
GetWinState( MinFlag, MaxFlag ); (* nutná proměnná MinFlag pro novou konvenci předávání parametrů, i když o ni aplikace nemá zájem *)

V předchozím příkladu je patrné, že ve stávající verzi systému není nutno psát znak & a přesto budou proměnné předány z procedury zpět. Pokud je přesto znak & uveden u var parametru, nemá to na běh aplikace vliv a znak & je ignorován.

Pole jako parametry procedur

Protože při předávání parametrů odkazem není předávaná proměnná kopírována do lokální proměnné, ale pracuje se pouze s odkazem na tuto proměnnou, je možné předat celé pole jako var parametr. Pokud má být na místo parametru předáno celé pole, parametr je uvozen klíčovými slovy array of. Např. procedura hledající největší prvek pole může mít hlavičku:

procedure FindMax( a : array of real ): real;

Do takové procedury tedy není možné předat jediný datový element, ale je nutno jako parametr předat celé pole. Protože pole mohou být v systému Control Web různě velká a mohou také začínat libovolným indexem, je možné použít dvě funkce pro zjištění indexů polí loindex a hiindex. Obě funkce vyžadují jako parametr identifikátor pole (nikoliv pouze prvek pole!) a vracejí nejnižší a nejvyšší index daného pole. S pomocí těchto funkcí může kód v proceduře bezpečně manipulovat se všemi elementy v poli. Výše zmíněná procedura FindMax by tedy mohla být implementována například takto:

procedure FindMax( a : array of real ): real;
var
  i : longint;
  max : real;
begin
  max = a[ loindex( a ) ];
  for i = loindex( a ) + 1 to hiindex( a ) do
    if a[ i ] > max then
      max = a[ i ];
    end;
  end;
  return max;
end_procedure;

Upozornění:

Parametry typu array of jsou automaticky předávány odkazem, i když v jejich definici není klíčové slovo var. Předat celé pole hodnotou (kopírovat pole do lokální proměnné) není možné. Jakékoliv změny v hodnotách prvků pole se tedy provádí přímo v předaném poli.

Signatura procedury

V řadě případů je nutné procedury identifikovat, vzájemně rozlišit. K tomu slouží tzv. signatura (podpis) procedury. Například není možné v rámci jednoho přístroje deklarovat dvě stejné procedury, protože pak by nebylo možno rozhodnout, kterou proceduru volat.

Nejjednodušší způsob, jak definovat signaturu procedury, je prostě prohlásit za signaturu její jméno. Pak by nebylo možné deklarovat dvě procedury stejného jména. Avšak v případě, že potřebujete dvě procedury, které dělají totéž a liší se jen jinými parametry, je škoda muset vymýšlet jiné jméno. Proto jsou v rámci systému Control Web signatury metod tvořeny nejen jménem procedury, ale i jejími parametry a návratovou hodnotou. Proto můžete například definovat dvě procedury:

procedure SetColor( color : cardinal );
procedure SetColor( red, green, blue : shortcard );

Ačkoliv se obě procedury stejně jmenují, každá má jiné parametry a tak není problém je při volání rozlišit. Pokud zavoláte SetColor s jedním parametrem, zavolá se první procedura, pokud se třemi parametry, zavolá se druhá procedura. Pokus o zavolání SetColor s jiným počtem argumentů povede k chybě při překladu.

Upozornění:

V rámci systému Control Web se provádí automatická konverze číselných typů. V praxi to znamená, že můžete např. do proměnné typu integer přiřadit proměnnou typu real (samozřejmě s nebezpečím ztráty informace, podrobněji popsaným v podkapitole o výrazech) a že do procedury s parametrem typu real můžete předat proměnnou typu cardinal.

Automatická konverze číselných typů je velmi pohodlná, ale má jeden následek, který musíte mít na paměti. Znamená, že signatury procedur se stejným počtem byť rozličných číselných parametrů jsou zaměnitelné a nelze je tedy současně definovat u jednoho přístroje.

procedure SetColor( color : integer );
procedure SetColor( color : real ); (* Chyba "procedura redefinována", číselné typy jsou zaměnitelné. *)

Jako všechny identifikátory v systému Control Web jsou i jména procedur citlivá na velikost písmen. To znamená, že

procedure SetColor( color : cardinal );
procedure setColor( color : cardinal ); (* jméno začíná malým písmenem, jedná se o jinou proceduru *)

jsou jiné procedury, lišící se názvem.

Mějte na paměti, že signaturu tvoří jen jméno procedury, typy parametrů a typ návratové hodnoty. Vlastní jména parametrů již signaturu neovlivňují. Proto procedury

procedure SetColor( color : cardinal );
procedure SetColor( c1 : cardinal );

mají shodnou signaturu.

Lokální data procedury

Už víte, že pokud má procedura parametry, lze k těmto parametrům přistupovat jako ke zvláštnímu druhu proměnných. Zvláštnost spočívá v tom, že pokud se parametry jmenují stejně jako globální datové elementy, pak se používají přednostně a globální datové elementy jsou zastíněny, zneviditelněny. Další zvláštností je, že proměnné parametrů jsou viditelné jen zevnitř procedury.

Pro mnoho algoritmů jsou tyto vlastnosti užitečné. Například neexistuje důvod, proč by proměnná čítající průchody cyklem měla být viditelná i v jiných procedurách. Navíc pokud se v rámci cyklu vykonávání procedury pozastaví, jiný přístroj může hodnotu proměnné změnit a algoritmus nebude pracovat správně. Určitě by bylo výhodné pro takovou proměnnou vyhradit místo uvnitř procedury, aby na ni jiné přístroje neviděly a aby nezabírala globálně viditelný identifikátor, využitelný i jinak. Proto je v rámci definice procedur možné deklarovat lokální proměnné a konstanty.

Deklarace lokálních proměnných a konstant se musí v zápisu procedury objevit před vlastním kódem procedury (podrobně o způsobu zápisu pojednává následující podkapitola). Lokální proměnné a konstanty můžete používat zcela stejně, jako parametry procedury s výjimkou, že netvoří rozhraní procedury a jejich hodnoty nelze mimo proceduru (na rozdíl od parametrů) ani nastavovat, ani číst. Stejně jako v případě parametrů, lokální proměnné a konstanty stejného jména jako má nějaký globální datový element tento element zneviditelní, zastíní.

Od parametrů se lokální proměnné podstatně liší svou hodnotou na počátku vykonávání procedury. Zatímco hodnoty parametrů nastavuje ten, kdo proceduru volá, lokální proměnné jsou inicializovány implicitně nebo mají svou inicializační hodnotu uvedenou v deklaraci.

V řadě případů je ale výhodné, pokud se hodnota lokální proměnné mezi jednotlivými voláními procedury nezapomíná. Například pokud chcete v lokální proměnné uchovávat nějakou hodnotu po celou dobu běhu aplikace, bylo by špatné, kdyby se při každém zavolání procedury tato hodnota nastavila na výchozí hodnotu. Proto lze lokální proměnné deklarovat pomocí dvou klíčových slov — var a static. Zatímco proměnné deklarované za slovem var budou vždy inicializovány na počátku procedury, proměnné deklarované za slovem static budou statické, inicializační hodnota se jim nastaví jen při startu aplikace a poté již stále budou uchovávat naposledy přiřazenou hodnotu.

Upozornění:

Pokud chcete, aby se hodnota lokální proměnné uchovávala i mezi jednotlivými voláními procedury, musíte ji deklarovat za klíčovým slovem static, nikoliv var.

Zápis kódu procedur

Zápis procedur musí odpovídat následující gramatice. Terminální symboly jsou psány malými písmeny, nonterminální symboly velkými písmeny:

PROCEDURE   -> procedure ( PARAMETERS );
               { DECLARATION }
               begin 
                 BLOCK
               end_procedure ;

PARAMETERS  -> [ name { , name } : TYPE ] { ; name { , name } : TYPE }

DECLARATION ->   label  LABEL_LIST ;
               | const  CONST_LIST ;
               | var    VAR_LIST ;
               | static VAR_LIST ;

LABEL_LIST  -> [ Identifier ] { , Identifier }

CONST_LIST  -> [ Identifier = Value ] { ; Identifier = Value }

VAR_LIST    -> [ Identifier : TYPE ] { ; Identifier : TYPE }

TYPE        ->   boolean
               | shortreal
               | real
               | shortint
               | integer
               | longint
               | shortcard
               | cardinal
               | longcard
               | string

BLOCK       -> [ STATEMENT ] { ; STATEMENT }

STATEMENT   ->   if EXPRESSION then
                   BLOCK
                 { elsif EXPRESSION then
                   BLOCK }
                 [ else
                   BLOCK ]
                 end
               | switch EXPRESSION of
                 { case CONST_EXPRESSION [ , CONST_EXPRESSION ] ;
                   BLOCK }
                 [ else
                   BLOCK ]
                 end
               | loop
                   BLOCK
                 end
               | while EXPRESSION do
                   BLOCK
                 end
               | repeat
                   BLOCK
                 until EXPRESSION
               | for SimpleNumericVariable = EXPRESSION to EXPRESSION [ by CONST_EXPRESSION ] do
                   BLOCK
                 end
               | goto Identifier
               | pause EXPRESSION
               | yield (* alias for pause 0 *)
               | wait EXPRESSION
               | commit
               | send Identifier { , Identifier }
               | sound EXPRESSION
               | return [ EXPRESSION ]
               | Identifier : [ STATEMENT ]
               | DataElement = EXPRESSION
               | move ArrayDataElement , ArrayDataElement, EXPRESSION
               | MethodName ( PARAM_LIST )
               | self . MethodName ( PARAM_LIST )
               | ObjectName . MethodName ( PARAM_LIST )
               | DataElement -> MethodName ( PARAM_LIST )
               | (* empty statement *)

(* this rule is valid only in block between 'loop' and 'end' *)
STATEMENT   ->   exit
               | continue

PARAM_LIST   = [ PARAMETER ] { , PARAMETER }
PARAMETER    = EXPRESSION | DataElement | Array

Každá procedura začíná deklarací hlavičky

procedure JménoProcedury( seznam_parametrů );
procedure JménoProcedury( seznam_parametrů ): typ_návratové_hodnoty;

Za hlavičkou následují deklarace bloků oddělené znakem ; (středník). Celkem existuje pět typů bloků:

  • Blok label deklaruje návěští použitá v proceduře.

  • Blok const deklaruje lokální konstanty.

  • Blok var deklaruje lokální inicializované proměnné.

  • Blok static deklaruje lokální statické proměnné.

  • Blok begin uvozuje zápis kódu procedury.

Bloky deklarací návěští label, konstant const, inicializovaných proměnných var a statických proměnných static mohou být uvedeny v libovolném pořadí a mohou se libovolně opakovat, případně nemusí být uvedeny vůbec. Je ale nutno pamatovat na to, že každý symbol musí být nejprve deklarován a pak teprve použit. Například zápis

var
  a : real {init_value = init_a}; (* chyba, symbol init_a není znám *)
const
  init_a = 1;

je špatný. Konstantu použitou k inicializaci proměnné je nutno definovat dříve.

const
  init_a = 1;
var
  a : real {init_value = init_a};

Blok definující vlastní kód procedury begin musí být uveden vždy, musí být jen jeden a musí být uveden vždy jako poslední. Tento blok je ukončen vždy klíčovým slovem end_procedure, které také uzavírá celou deklaraci procedury.

Návěští

Jsou-li v rámci programu použity skokové instrukce goto, je nutno nejprve deklarovat všechny identifikátory použité jako návěští za klíčovým slovem label. Jednotlivé identifikátory jsou odděleny čárkami a celá deklarace návěští je ukončena středníkem. V programu se cíl skoku označí uvedením návěští následovaného dvojtečkou před příkaz, který se má po příkazu skoku vykonat.

Příklad:

procedure Test();
label
  Error;
begin
  if a < b then
    goto Error;
  end;
  ...
Error:
  sound "ALARM.WAV";
  ...
end_procedure;

Deklarace lokálních konstant a proměnných

Jak již bylo uvedeno dříve, konstanty se deklarují za klíčovým slovem const, inicializované proměnné za klíčovým slovem var a statické proměnné za klíčovým slovem static. Zápis lokálních konstant a proměnných je syntakticky identický se zápisem globálních konstant a proměnných.

Konstanty se zapisují ve tvaru:

const
  název_konstanty = hodnota_konstanty;

Typ konstanty není uveden, odvozuje se automaticky z její hodnoty.

Proměnné se zapisují ve tvaru:

var
  název_proměnné : typ_proměnné { atributy };

Tělo procedury

Tělo procedury uvozené klíčovým slovem begin obsahuje jednotlivé příkazy oddělené znakem ; (středník). Tělo procedury je ukončeno klíčovým slovem end_procedure a konec těla procedury je současně i koncem deklarace celé procedury. Příkazy lze rozdělit na dva druhy:

  • Řídicí příkazy, které určují vykonávání jiných příkazů, jako např. podmínky nebo cykly a vyvolávají jiné procedury.

  • Výkonné příkazy, které ovlivňují stavy proměnných, aktivují jiné přístroje apod.

Příkaz if

Příkaz if slouží k podmíněnému vykonávání určitých částí programu. Za klíčovým slovem if musí být uveden logický výraz a klíčové slovo then. Následující blok příkazů je vykonáván jen pokud vyhodnocení logického výrazu vrátí hodnotu true. Blok příkazů může být ukončen třemi způsoby:

  • Klíčovým slovem end. V případě nesplnění podmínky program pokračuje příkazem za tímto klíčovým slovem.

  • Klíčovým slovem else. Za else následuje blok příkazů, který bude vykonán pouze v případě nesplnění podmínky. Tento blok příkazů musí být ukončen klíčovým slovem end.

  • Klíčovým slovem elsif. Za tímto slovem následuje podmínka a klíčové slovo then a blok příkazů stejně jako za if.

Tato kombinace umožňuje realizovat výběr jediné varianty v závislosti na různých podmínkách. Platí tedy, že je vykonán blok příkazů za první splněnou podmínkou následující za if nebo elsif a případné zbylé podmínky za elsif nejsou ani testovány. Není-li splněna žádná podmínka, je vykonán blok příkazů za klíčovým slovem else, pokud je přítomno. Není-li na konci podmíněného příkazu else, program pokračuje za klíčovým slovem end.

Příklad:

if i < 10 then
  i = i + 1;
end;

if i < 0 then
  di = 1;
  i = 0;
else
  di = -1;
  i = 100;
end;

if ErrorCode = 0 then
  ErrorMsg = 'Chyba čtení kanálu';
elsif ErrorCode = 1 then
  ErrorMsg = 'Chyba zápisu kanálu';
elsif ErrorCode = 2 then
  ErrorMsg = 'Chyba komunikace';
else
  ErrorMsg = 'Neznámá chyba';
end;

Poznámka:

Podmíněný příkaz if je zobecněním podmíněného výrazu realizovaného zabudovanou funkcí iif. Pokud za then a else následují jediný příkaz přiřazující výrazu do stejné proměnné, lze například podmíněný příkaz:

if a < 100 then
  a = a + 1;
else
  a = 0;
end;

nahradit jediným podmíněným výrazem:

a = iif( a < 100, a + 1, 0 );

Příkaz switch

Příkaz switch rozděluje vykonávání kódu na základě hodnoty jediného výrazu. Pokud tedy je po řadě testována hodnota jediného výrazu, jako např. hodnota proměnné ErrorCode v předchozím příkladě, použití příkazu switch je jednodušší a jeho vykonání je mnohem rychlejší než série příkazů if elsif.

switch ErrorCode of
case 0;
  ErrorMsg = 'Chyba čtení kanálu';
case 1;
  ErrorMsg = 'Chyba zápisu kanálu';
case 2;
  ErrorMsg = 'Chyba komunikace';
else
  ErrorMsg = 'Neznámá chyba';
end;

Stejně jako u příkazu if je else větev příkazu switch nepovinná. Pokud testovaný výraz nenabude žádné z hodnot vyjmenovaných za klíčovými slovy case a větev else chybí, žádná větev příkazu switch se neprovede.

Za klíčovým slovem case není nutné uvádět jedinou hodnotu, ale je možné vyjmenovat více hodnot oddělených čárkami, např.:

switch InputValue of
case 0, 1, 2, 3;
  ...
case 10, 100, 1000;
  ...
end;

Upozornění:

Hodnoty za klíčovými slovy case musí být konstantní (vyčíslitelné v době překladu). Pokud je nutno výraz porovnat s obecnými výrazy, musí se použít testování příkazem if elsif.

Příkaz loop

Příkaz loop realizuje obecné smyčky. Nemá-li být tato smyčka nekonečná, musí být ukončena příkazem exit. Klíčové slovo exit je platné pouze uvnitř cyklu loop. Za klíčovým slovem loop následuje blok příkazů ukončený klíčovým slovem end, který bude cyklicky vykonáván.

Příkaz loop je vhodné použít, pokud podmínka testující opuštění smyčky je vyhodnocována uvnitř bloku příkazů nebo pokud existuje více míst opuštění smyčky. Má-li být ukončovací podmínka na počátku nebo na konci cyklu, je vhodnější použít smyčky while nebo repeat until. Pokud se má cyklus opakovat pro předem známý počet průchodů, je výhodnější použít cyklus for.

Pokud je v sobě zanořených více smyček loop, příkaz exit způsobí opuštění vždy nejvnitřnější smyčky. Uvnitř smyčky loop je možné použít také příkaz continue. Tento příkaz způsobí přenesení řízení na počátek smyčky, tedy přeskočení zbytku příkazů ve smyčce. V případě zanoření smyček se příkaz continue vztahuje na nejvnitřnější smyčku.

Příklad:

loop
  a = b + c;
  if a > 123 then
    exit;
  end;

  i = i + 1;
  if i > 100 then
    exit;
  end;

  if i < 20 then
    continue;
  end;

  j = i;
end; (* loop *)

Příkaz while

Tento příkaz je speciálním případem smyčky. Za klíčovým slovem while následuje logický výraz a klíčové slovo do. Pokud je výsledkem vyhodnocení logického výrazu hodnota true, je vykonáván následující blok příkazů až po klíčové slovo end. Je dobré si uvědomit, že pokud v těle příkazů není příkaz pause nebo wait, neprobíhá v rámci dalších průchodů smyčkou komunikace s vnějšími zařízeními.

Pokud podmínka není ani napoprvé splněna, příkazy uvnitř smyčky nebudou vykonány ani jednou.

Příklad:

i = 0;
while i < 10 do
  a = a + c;
  i = i + 1;
end; (* while *)

Příkaz repeat until

Klíčové slovo repeat uvozuje začátek bloku příkazů. Blok příkazů je ukončen klíčovým slovem until, za nímž následuje logický výraz. Po provedení bloku příkazů program otestuje podmínku za until a pokud její výsledek je false, začne se opět vykonávat blok příkazů.

I pokud má logický výraz již na počátku hodnotu true, blok příkazů se provede alespoň jednou.

Příklad:

repeat
  a = a + b;
  i = i + 1;
until i >= 10;

Příkaz for

Příkaz cyklu for uvozuje počítaný cyklus. Za klíčovým slovem for následuje zápis číselné proměnné, znak = (rovnítko) a číselný výraz. Poté následuje klíčové slovo to a další číselný výraz následovaný klíčovým slovem do a blokem příkazů těla cyklu ukončeným klíčovým slovem end. Číselná proměnná bude při vstupu do cyklu nastavena na hodnotu danou výrazem za rovnítkem. Poté bude hodnota této proměnné porovnána s hodnotou výrazu za klíčovým slovem to. Pokud bude menší nebo rovna této hodnotě, provede se tělo cyklu až po odpovídající end, řídicí proměnná bude zvětšena o jedničku a porovnání se bude opakovat. Pokud je řídicí proměnná již po přiřazení inicializační hodnoty větší než výraz za to, tělo cyklu se neprovede ani jednou.

Příklad:

for i = 1 to 10 do
  a[ i ] := i * i;
end;

for i = 3 to 2 do
  (* tělo cyklu se neprovede ani jednou *)
end;

Ne vždy je požadováno, aby se řídicí proměnná zvětšovala o jedničku. Někdy je nutné krok zvětšit nebo i řídicí proměnnou zmenšovat. Za tímto účelem je možno zápis cyklu for doplnit o krok řídicí proměnné. Krok se zapisuje za klíčové slovo by, před klíčové slovo do.

Příklad:

for Alpha = 0 to 360 by 10 do
  x = sin( Alpha / 180 * Pi );
end;

for x = 10 to 1 by -1 do
  y = x * 10;
end;

Upozornění:

Výraz udávající krok za klíčovým slovem by musí být vždy konstantní (vyčíslitelný v době překladu). To znamená, že nesmí obsahovat žádné proměnné nebo kanály.

Příkaz yield

Jak již bylo řečeno, celé tělo procedury je vykonáno v jediném časovém kroku, tedy se stejnou hodnotou na vstupně/výstupních kanálech. Někdy je ale nutno časový krok ukončit a umožnit systému změřit nové hodnoty vstupně/výstupních kanálů. Příkaz yield ukončí vykonávání procedury v aktuálním časovém kroku a ponechá systému čas na obsluhu všech ostatních přístrojů, které mají být v probíhajícím časovém kroku aktivovány. Vykonávání procedury bude pokračovat v nejbližším dalším systémovém časovém kroku, tedy nikoliv v dalším časovém kroku přístroje, který danou proceduru implementuje.

Příklad:

while Level < 12.5 do (* čeká na dosažení hladiny *)
  yield; (* ukončení časového kroku a umožnění komunikace pro změření nové hodnoty *)
end; (* while *)

Control1 = true; (* uzavření klapky *)
pause 5; (* čekání na vyprázdnění čerpadla *)
Control2 = false; (* vypnutí čerpadla *)

Příkaz pause

Tento příkaz slouží k pozastavení činnosti programu na určitou dobu. Za klíčovým slovem pause následuje numerický výraz udávající počet sekund zdržení programu. Narazí-li procedura na příkaz pause, pozastaví vykonávání na zadanou dobu a poté pokračuje dalším příkazem. Příkaz pause 0; sice nepozastaví program, ale poskytne systému čas k případné obsluze dalších přístrojů (stejnou funkci plní příkaz yield).

Příklad:

while Level < 12.5 do (* čeká na dosažení hladiny *)
  pause 0; (* možno nahradit i příkazem yield *)
end;

Control1 = true; (* uzavření klapky *)
pause 5; (* čekání na vyprázdnění čerpadla *)
Control2 = false; (* vypnutí čerpadla *)

Příkaz wait

Tento příkaz pozastaví běh programu do splnění zadané podmínky. Za klíčovým slovem wait následuje logický výraz. Je-li hodnota výrazu true, program pokračuje následujícím příkazem v tomtéž časovém kroku. Pouze je-li hodnota výrazu false, program poskytne systému čas k případné obsluze dalších přístrojů a opět testuje podmínku. Mezitím samozřejmě dojde k případnému přečtení kanálů. Příklad z popisu příkazu pause by tedy bylo možno zapsat efektivněji:

Příklad:

wait Level >= 12.5; (* čekání na dosažení hladiny *)

Control1 = true; (* uzavření klapky *)
pause 5; (* čekání na vyprázdnění čerpadla *)
Control2 = false; (* vypnutí čerpadla *)

Příkaz commit

Příkaz commit slouží k vyvolání komunikace (zápisu a čtení kanálů) bez ztráty časového kroku. Uvedení příkazu commit má podobný efekt jako např. podmíněný příkaz obsahující kanál v podmínce — kanály označené pro čtení jsou přečteny a kanály se zapsanou hodnotou jsou zapsány do ovladače.

Podobný efekt jako commit má např. příkaz yield nebo pause, ovšem tyto příkazy ukončí provádění časového kroku a způsobí pokračování algoritmu v následujícím kroku. Příkaz commit pouze vyvolá komunikaci a algoritmus pokračuje v rámci téhož časového kroku.

Podrobně je funkce příkazu commit popsána v podkapitole Použití kanálů v procedurách.

Příkaz return

Příkaz return má dvě podoby v závislosti na tom, jestli daná procedura vrací návratovou hodnotu nebo ne. Pokud procedura návratovou hodnotu nevrací, způsobí příkaz return ukončení běhu procedury a návrat do kódu, který danou proceduru zavolal. Případná další aktivace procedury způsobí její rozběhnutí opět od počátku.

procedure Divide( a, b : real; var c : real );
begin
  if b = 0 then
    c = 0;
    return;
  end;
  c = a / b;
end_procedure;

Pokud ale procedura vrací nějakou hodnotu (může být volána v rámci výrazu), pak za klíčovým slovem return následuje výraz, jehož hodnota je vrácena jako návratová hodnota procedury. I v tomto případě příkaz return způsobí ukončení běhu procedury a návrat do kódu, který danou proceduru zavolal.

procedure Divide( a, b : real; var c : real ): boolean;
begin
  if b = 0 then
    return false;
  end;
  c = a / b;
  return true;
end_procedure;

Příkaz send

Za klíčovým slovem send následuje seznam identifikátorů oddělených čárkami, označující jména přístrojů v aplikaci (seznam samozřejmě může obsahovat pouze jediný přístroj). Uvedení tohoto příkazu zajistí aktivování uvedených přístrojů ihned po skončení právě aktuálního časového kroku. Pokud chcete aktivovat stejný přístroj, jež implementuje danou proceduru, můžete vypsat jeho jméno nebo použít zástupného označení self. Příkaz send self; tedy znamená „aktivuj sám sebe v nejbližším dalším systémovém časovém kroku“. Toto aktivování je zcela plnohodnotné (provádí se před ním komunikace se vstupně/výstupními zařízeními a aktualizace hodnot kanálů) a prakticky se rovná vložení zvláštního časového kroku pro všechny přístroje uvedené za příkazy send.

Příkaz send má v některých jiných přístrojích ekvivalent v podobě slova receivers následovaného seznamem přístrojů. Pokud má více přístrojů stejné jméno a toto jméno je uvedeno v příkaze send, jsou aktivovány všechny přístroje tohoto jména.

Poznámka:

Tento příkaz je velice mocný a může ušetřit velké množství výpočetního výkonu. Pokud je např. nějakými proměnnými řízena pouze viditelnost panelů, nemusí být tyto panely vůbec časovány, ale mohou být uvědomeny příkazem send jen při jejich změně.

Příklad:

Panel1Visible = (KeyInput = F1);
Panel2Visible = (KeyInput = F2);
send Panel1, Panel2;

Obdobný postup je samozřejmě možno použít i u přístrojů disponujících příkazem receivers, jako např. multi_switch nebo panel s aktivními obdélníky.

Příkaz sound

Tento příkaz zajišťuje přehrávání zvukových souborů formátu WAV. Za klíčovým slovem sound je řetězcový výraz vyjadřující jméno souboru obsahujícího navzorkovaný zvuk. Funkčnost tohoto příkazu je samozřejmě podmíněna přítomností podporované zvukové karty v počítači a správnou instalací jejích ovladačů v prostředí Windows. Není-li zvuková podpora správně instalována, příkaz bude ignorován.

Příklad:

if Alarm = 1 then
  sound 'hori.wav';
elsif Alarm = 2 then
  sound 'vybuch.wav';
else
  sound 'klid.wav';
end;

Příkaz move

Instrukce move slouží ke vzájemnému přiřazení polí nebo jejich částí. Použití move je vždy rychlejší než přiřazení jednotlivých prvků polí například s použitím programové smyčky. Velmi výrazného urychlení je dosaženo zejména při přenosu pole kanálů do pole proměnných. Při přiřazení prvků pole do proměnných během cyklu je program nucen vždy požádat jádro systému o změření každého prvku kanálu zvlášť, neboť obecně nová hodnota změřeného prvku pole kanálů může ovlivnit index následujícího prvku pole.

Za klíčovým slovem move následuje zápis prvního prvku pole, který má být čten (zdroj), za čárkou je uveden zápis prvního prvku pole, do něhož má být hodnota zapsána (cíl) a za další čárkou je výraz pro počet prvků, které mají být přesunuty.

Příklad:

move sa[ i ], da[ j ], count;

Tento příkaz přesune celkem count prvků pole sa počínaje prvkem s indexem i do pole da od prvku s indexem j.

Příkaz přiřazení

Kdekoliv v zápisu algoritmu se může vyskytovat přiřazovací příkaz. Má obecnou podobu datový_element = výraz. Datový element může být proměnná, prvek pole proměnných, výstupní nebo obousměrný kanál nebo pole výstupních nebo obousměrných kanálů. Vpravo od rovnítka je libovolný výraz, který však musí typově odpovídat datovému elementu vlevo od rovnítka. Datovému elementu typu boolean musí odpovídat výraz typu boolean. Datovému elementu typu string musí odpovídat výraz typu string. Pokud je datový element číselný (typu shortint, integer, longint, shortcard, cardinal, longcard, shortreal, real), je možné do něj přiřadit jakýkoliv číselný výraz.

Upozornění:

Pokud typ datového elementu není schopen pojmout výsledek výrazu, dojde ke ztrátě informace. Při převodu na celá čísla je odřezána desetinná část a při převodu velkých čísel se chyba týká nejvyšších platných míst. Např. číslo nelze v plném rozsahu uchovat v proměnné typu integer.

Příklad:

(* číselné výrazy *)
i = i + 1;
x[ 12 ] = ( k[ j ] + k[ j + 1 ] ) / ( atan2( theta, ksi ) * 2 );
a[ i ] = fi * cos( beta ) / PiBy2;
b[ i + 1 ] = fi * sin( alfa ) / PiBy2

(* logické výrazy *)
result = value <= ( a + 1 );
ExitCondition = true;

(* řetězcové výrazy *)
Message = 'Error: ' + ErrorString;
Title = 'Demonstrační panel';

Poznámka:

V myslích uživatelů bývá často příkaz přiřazení spojen pouze s přiřazováním číselných hodnot a v případě např. logických hodnot bývá omezen pouze na prosté přiřazení true nebo false. Často se tak lze setkat s konstrukcemi typu:

if a = b then
  result = true;
else
  result = false;
end;

Tato konstrukce sice funguje správně, ale téhož efektu lze snadněji a rychleji dosáhnou jediným přiřazením:

result = a = b;

Výskyt dvou znaků = za sebou může být poněkud matoucí, ale jejich záznam je zcela přesně definován. První = má význam přiřazení, druhé = pak logického porovnání.

Volání procedur

Předešlé příklady se soustředily převážně na to, jak procedury zapsat, jak řídit jejich běh, jak pracovat s lokálními daty a jak do a z procedur předávat parametry. Poslední, co potřebujete znát, je jak procedury vyvolávat. Jedná se vlastně o uzavřený problém, protože procedury se volají zase z jiných procedur a zápis tohoto volání tedy současně patří k popisu vnitřních částí procedury.

Pokud bylo volání procedury v předchozím textu zapsáno, bylo např. ve tvaru

Go();

Toto volání vyvolá proceduru Go stejného přístroje nebo sekce, který ji volá. Pokud daný přístroj nebo sekce proceduru Go nemá, nastane při překladu chyba. Chcete-li vyvolat proceduru jiného přístroje nebo sekce, musíte před její jméno uvést jméno přístroje (sekce), který tuto proceduru implementuje. Již dříve bylo řečeno, že procedury nemohou být implementovány mimo nějaký přístroj nebo sekci.

InstrumentName.Go();

Proceduru je také možné volat pomocí ukazatelů na přístroj.

InstrumentPointer->Go();

Pokud ukazatel neukazuje na žádný přístroj nebo na přístroj, který nemá proceduru Go, dojde k chybě za běhu aplikace a aplikace bude zastavena.

Pokud voláte proceduru přístroje, který je v jiném modulu, bude před jménem přístroje ještě jméno modulu

ModuleName.InstrumentName.Go();

Poznámka:

V předchozích verzích systému Control Web bylo nutno vždy před jméno metody uvádět jméno přístroje. I v případech, kdy kód jedné procedury volá jinou proceduru téhož přístroje, muselo být před jménem volané procedury uvedeno jméno přístroje. To ale není moc pohodlné a navíc to může činit problémy, pokud přístroj přejmenujete nebo překopírujete proceduru do jiného přístroje — pak je obtížné rozhodnout, jestli dané volání má být přesměrováno na jiný přístroj nebo ponecháno. Proto bylo do procedur zavedeno klíčové slovo self. Toto klíčové slovo vždy v každé proceduře zastupuje přístroj, v němž je procedura implementována.

Příklad:

meter m1;
  ...
  procedure Go();
    var a = real, 0;
  begin
    ...
    m1.SetValue( a ); (* zavolá proceduru SetValue sám sobě *)
    self.SetValue( a ); (* pracuje stejně jako předchozí volání, ale funguje i po přejmenování přístroje *)
    ...
  end_procedure;
  ...
end_meter;

Klíčové slovo self je ale v systému Control Web nepovinné a prosté uvedení jména procedury pracuje shodně.

Při volání procedur je nutné vzít v úvahu několik skutečností:

  • Na rozdíl od aktivace přístrojů příkazem send, což prakticky představuje vložení výjimečného časového kroku, následujícího bezprostředně za aktuálním časovým krokem a zahrnujícího komunikaci se vstupně/výstupními zařízeními, volání procedury se vždy děje bezprostředně ve stávajícím časovém kroku a tedy vyhodnocují se vždy právě aktuální hodnoty kanálů. Zavolání procedury nějakému přístroji nezpůsobí přečtení nové hodnoty vyhodnocovaného kanálu ze vstupně/výstupního zařízení, pokud není přečtení vyvoláno nějakým jiným přístrojem vyhodnocovaným v rámci téhož časového kroku.

  • Volání procedury je blokující operace. To znamená, že vykonávání kódu se přenese z volající procedury do volané procedury a volající procedura bude pokračovat následující instrukcí za zápisem volání až po ukončení volané procedury a návratu z volání.

  • Pokud je vykonávání procedury pozastaveno instrukcí pause, yield nebo wait, pak se procedura vrátí z volání do volající procedury a volající procedura pokračuje v běhu bez doběhnutí kódu volané procedury do konce. Přitom takto volaná procedura pokračuje v nejbližším systémovém časovém kroku (po yield), po uplynutí stanovené doby (po pause) nebo po splnění podmínky (po wait) samostatně ve vykonávání svého kódu stejně, jako by byla aktivována např. příkazem send, tedy včetně měření kanálů apod.

  • Procedury není možno volat rekurzivně. Procedura tedy nemůže vyvolat ještě jednou sama sebe a to ani přes jiné procedury! Je nutno si uvědomit, že řetězec volání může být velice složitý a k rekurzi může dojít po celé řadě dalších vnořených volání.

Komentáře

Kdekoliv v zápisu procedur je možno používat komentáře. Komentář je uvozen komentářovou závorkou (* a ukončen opačnou závorkou *). Cokoliv je uvedeno v těchto závorkách je při překladu přeskočeno. Komentářů je tak možno využívat nejen k poznámkám o významu procedur nebo jednotlivých příkazů, ale i k vyřazení částí kódu.

Komentáře v systému Control Web mohou být vnořené. Hloubka zanoření je uchovávána a komentář skončí až s poslední uzavírací komentářovou závorkou.

Příklad:

i = 0;
(* komentář hloubky 1
(* hloubka komentářů je 2 *)
   stále ještě platí první komentářová závorka *)
i = i + 1;

Komentářové závorky lze samozřejmě používat kdekoliv v textovém zápisu aplikace systému Control Web. Vzhledem k duálnímu vývojovému prostředí ale není možné komentáře v grafickém módu zachovat, neboť textová podoba aplikace zcela zaniká. Procedury jsou v tomto ohledu výjimkou a komentáře zachovávají i při přechodech mezi textovým a grafickým módem.

Vždy (ne)pravdivé výrazy v podmínkách

U řady příkazů jejich syntax předepisuje logický výraz, který ovlivňuje chování příkazu (např. příkaz while vyžaduje logický výraz, který určuje zda-li bude smyčka vykonávána nebo ne). Omezení rozsahu datových typů může zásadním způsobem ovlivnit splnitelnost (či nesplnitelnost) podmínkových logických výrazů. Uvažme následující příklad:

var
  c : cardinal;
begin
  c = 10;
  while c >= 0 do
    ...
    c = c - 1;
  end;
...

Smyčka while v tomto příkladu bude nekonečná, protože datový element typu cardinal nemůže obsahovat číslo menší než 0. Podobně podmínka:

var
  i : shortint;
begin
  if i > 200 then
...

nebude nikdy splněna, protože největší číslo reprezentované v datovém elementu typu shortint je 127 a nikdy nemůže být větší než 200. Podobných příkladů výrazů, které jsou trvale pravdivé či nepravdivé je celá řada i v dalších příkazech (repeat until, for to do, wait, ...).

Překladač je v některých případech (např. pokud je porovnáván datový element s konstantou) detekovat, že výraz je vždy pravdivý či nepravdivý a během překladu vypíše varování (nikoliv však chybu překladu). V řadě případů ale není možné během překladu neměnnost podmínky určit, protože plyne z logiky aplikace. Je proto nezbytné na tuto možnost pamatovat při ladění aplikace a ještě lépe už při jejím návrhu.

Tip:

Překladač provádí při spouštění aplikace řadu optimalizací, které při běžném překlápění či zkušebním překladu provádět nemůže. Jednou z těchto optimalizací je vyčíslení konstantních podvýrazů. Pokud tedy je v aplikaci vždy (ne)splnitelná podmínka skládající se z datového elementu porovnávaného s konstantním výrazem, varování o trvalém (ne)splnění podmínky se objeví pouze při spouštění aplikace, nikoliv při pouhém překlápění.

Událostní a uživatelské procedury

Víte, že každou proceduru je možné volat z jiné procedury. Někdy je ale nutné, aby se alespoň nějaká procedura zavolala jiným mechanizmem, jinak by systém volání nikdy „neobživnul“, nikdo by nezačal s prvním zavoláním.

Takový mechanizmus samozřejmě existuje a byl nastíněn již v úvodu této kapitoly. Control Web nemá žádný „hlavní program“, namísto toho aktivuje kousky kódu na základě akcí uživatele nebo systémových událostí. I procedury zapadají do tohoto kontextu a mnohé procedury jsou vyvolávány při splnění určitých podmínek — samozřejmě pokud tyto procedury naprogramujete.

Všechny procedury, které naprogramujete v rámci každého přístroje, lze rozdělit na dvě skupiny:

  • Událostní procedury. Pokud jsou tyto procedury naprogramovány, jsou po výskytu dané události vyvolávány přímo systémem, bez přispění jiné procedury.

  • Uživatelské procedury. O těchto procedurách žádný kód, který je součástí systému Control Web, nic neví a ani je nebude volat. Pokud mají být aktivovány, musí být volány z jiných procedur, ať už uživatelských nebo událostních.

Aby systém Control Web, přesněji konkrétní přístroj tohoto systému, aktivoval (zavolal) událostní proceduru, je nutné, aby ji poznal. K tomu, aby událostní procedura byla virtuálním přístrojem poznána, musí mít signaturu odpovídající dané událostní proceduře konkrétního přístroje (o signaturách procedur bylo psáno výše). Musí tedy mít jméno (pozor na velikost písmen) a parametry odpovídající definici událostní procedury. Pak je přístrojem rozpoznána a volána při vzniku odpovídající události.

Příklad:

switch sw1;
  ...
  procedure OnOutput( state : boolean );
  begin (* tato procedura bude vyvolána při každé výstupní akci přístroje switch *)
    ...
  end_procedure;
  ...
end_switch;

Signatury událostních procedur a podmínky jejich vyvolání jsou popsány u jednotlivých přístrojů. Pokud otevřete nad přístrojem inspektor a kliknete na záložku Procedury, objeví se seznam všech událostních procedur daného přístroje. Navíc máte možnost doplnit libovolné uživatelské procedury, ale jak již bylo řečeno, o jejich volání se musíte postarat sami.

Existuje skupina událostních procedur, které jsou pro všechny přístroje společné. Neznamená to, že všechny přístroje budou vyvolávat všechny procedury z této skupiny. Např. neviditelné přístroje nebudou vyvolávat procedury pro obsluhu myši nebo klávesnice. Pokud ale má daná procedura pro daný přístroj smysl, bude ji přístroj volat. Podrobněji budou tyto základní událostní procedury popsány po popisu klíčové událostní procedury OnActivate.

Procedura OnActivate()

OnActivate je výjimečná událostní procedura. První důvod její výjimečnosti spočívá v tom, že je to jediná procedura, volaná vždy při aktivaci přístroje. Prakticky všechny přístroje provádějí svou činnost v rámci své aktivace. Pokud například u přístroje meter definujete výraz, který bude vyčíslen a výsledek přiřazen do nějakého datového elementu, toto vyčíslení, přiřazení a taktéž případné překreslení přístroje meter s novou hodnotou se děje právě v rámci aktivace. V rámci své aktivace přístroj alarm testuje překročení mezních hodnot a přístroj archiver zapisuje data, přístroj draw animuje svou kresbu apod.

Mimo svou aktivaci pracují jen přístroje, které nějak interagují s okolím. Například přístroj control zapíše do datového elementu hodnotu po uživatelském zásahu, aniž by vůbec byl aktivován. Taktéž přístroj httpd, pracující jako WWW server, odešle data po požadavku klienta bez čekání na aktivaci.

Přístroj může být aktivován několika různými způsoby. Příčiny aktivace se velmi liší v aplikacích reálného času a v datově řízených aplikacích.

V aplikacích reálného času může být přístroj aktivován těmito podněty:

  • Periodické časování hodnotou nebo časovačem.

  • Výjimka způsobená jiným přístrojem (aktivace přístrojem).

  • Výjimka způsobená ovladačem (aktivace ovladačem).

datově řízených aplikacích je periodické časování povoleno jen některým přístrojům, jejichž činnost je závislá na přesném dodržování časového intervalu, např. přístroji archiver nebo pid_regulator. Aktivace výjimkami od přístrojů i ovladačů zůstává zachována. Přibývá ale nový podnět

  • Výjimka způsobená změnou dat.

Jestliže v přístroji deklarujete proceduru odpovídající signatuře:

procedure OnActivate();

bude tato procedura vyvolána při každé aktivaci, přesněji před každou aktivací přístroje. Procedura OnActivate má tedy možnost testovat a modifikovat datové elementy, ovlivňující následnou činnost přístroje.

Druhý důvod výjimečnosti procedury OnActivate je skutečnost, že tato procedura může odpovídat několika signaturám, i když se jedná o jedinou proceduru. Důvod této výjimky je prostý. V některých situacích může být užitečné znát důvod aktivace procedury. Pak můžete napsat proceduru OnActivate ve tvaru

procedure OnActivate( ByTimer, ByInstrument, ByDriver, ByData : boolean );

V této podobě jsou proceduře OnActivate předány čtyři logické parametry určující důvod aktivace. Parametr ByData samozřejmě může být true jen v datově řízených aplikacích. Vlastní kód procedury OnActivate pak může příčinu aktivace testovat a nějakou činnost provádět například jen při periodické aktivaci nebo jen při vyvolání výjimkou z ovladače.

V určitých situacích může dojít k souběhu několika důvodů aktivace. Například nějaký přístroj aktivuje jiný přístroj a v rámci téhož systémového časového kroku nastane i čas pro periodické časování přístroje. Pak je přístroj aktivován jen jednou, logická hodnota true je nastavena do dvou parametrů — ByTimerByInstrument.

Systém Control Web ale dokáže přístroji při aktivaci poskytnout ještě mnohem více informací, než jen příčinu aktivace. Není ale možné předávat veškeré údaje jako samostatné parametry. Proto jsou tyto informace předávány v podobě tzv. bitové masky. Bitová maska představuje jedno číslo, jehož jednotlivé bity (dvojkové číslice, nabývající hodnoty buď 0 nebo 1) nesou logické hodnoty podobně jako logické parametry. Pro zjištění, zda patřičný bit daného čísla nastaven lze použít funkci bitget. Pokud tedy například potřebujete nějakou akci v proceduře OnActivate vykonávat jen při aktivaci přístroje z ovladače, můžete proceduru OnActivate zapsat dvěma způsoby:

procedure OnActivate( ByTimer, ByInstrument, ByDriver, ByData : boolean );
begin
  if ByDriver then
    (* tento kód se vykoná jen při aktivaci od ovladače *)
  end;
end_procedure;

procedure OnActivate( ActivateMode : longcard );
begin
  if bitget( ActivateMode, 2 ) = 1 then
    (* tento kód se taktéž vykoná jen při aktivaci od ovladače *)
  end;
end_procedure;

Ačkoliv oba způsoby zápisu budou pracovat stejně, druhý způsob je přeci jen poněkud komplikovanější, než první způsob. Proč jej tedy zavádět? Protože testováním dalších bitů v parametru ActivateMode můžete zjistit více informací než jen důvod aktivace. Význam jednotlivých bitů je následující:

  • bit 0 — přístroj je aktivován časovačem

  • bit 1 — přístroj je aktivován jiným přístrojem

  • bit 2 — přístroj je aktivován ovladačem

  • bit 3 — rezervováno, vždy 0

  • bit 4 — přístroj je aktivován změnou dat (pouze v datově řízených aplikacích)

  • bit 5 — přístroj je v časovém skluzu

  • bit 6 — přístroj je nazván startup a je právě aktivován jako první přístroj aplikace

  • bit 7 — přístroj je nazván terminate a je právě aktivován jako poslední přístroj aplikace

  • bit 8 — aplikace je v časovém skluzu (tedy ne nutně tento přístroj, ale jakýkoliv přístroj v aplikaci)

  • bit 9 — aplikace končí, běží přístroj terminate

  • bit 10 — přístroj je aktivován z jiného modulu

  • bit 11 až 31 — rezervováno pro budoucí využití

Použijete-li tedy v proceduře OnActivate jediný parametr ActivateMode, můžete například zjistit, kdy je aplikace v časovém skluzu nebo kdy se právě startuje.

Shrnutí:

  • Procedura OnActivate je jediná procedura volaná v rámci aktivace přístroje.

  • Volání OnActivate nastane vždy před vlastní aktivací přístroje, takže lze měnit datové elementy zpracovávané v rámci vlastní aktivace.

  • Procedura OnActivate může mít tři signatury (typy parametrů), vždy to ale je jediná procedura.

  • V rámci jednoho přístroje nelze definovat více procedur OnActivate, byť s jinými parametry.

Časování procedur

Pokud není stanoveno jinak, celý algoritmus procedury (od počátku do konce) proběhne vždy v jednom časovém kroku, tedy se shodnými naměřenými hodnotami na kanálech. To je nutné si uvědomit zvláště při programování cyklů. Je však možné použít některou ze zpožďujících instrukcí pause, yield nebo wait. Tyto instrukce způsobí pozastavení procedury na určitou dobu, případně do splnění podmínky. V následujícím systémovém časovém kroku (nikoliv tedy časovém kroku přístroje, který proceduru implementuje!) je otestována podmínka běhu, a je-li splněna, procedura pokračuje od místa za podmínkou, ne opět od počátku. V tomto případě už jsou hodnoty čtených kanálů znovu změřeny.

Více podrobností o souvislostech mezi voláním a časováním procedur a zpožďujícími instrukcemi se dočtete v podkapitole Rekurzivní volání a zpožďující instrukce.

Upozornění:

Algoritmem procedury lze velmi snadno realizovat nekonečnou (nebo alespoň velmi dlouhou) smyčku. Je nutné si uvědomit, že takové smyčka blokuje přechod k dalšímu časovému kroku a celá aplikace tak čeká na její dokončení.

Použití kanálů v procedurách

Pokud nějaký přístroj vyhodnocuje v rámci svého časového kroku nějaký výraz (např. meter) a tento výraz obsahuje kanály, pak jádro systému zajistí změření těchto kanálů před vyhodnocením výrazu (samozřejmě pokud je to možné během času definovaného parametrem kanálu timeout). Podobná situace nastane pokud jsou kanály použity v proceduře OnActivate. Mějme definovány proměnné a1, a2 a a3 a kanály c1, c2 a c3. Pak následující část kódu:

  a1 = c1;
  a2 = c2;
  a3 = c3;

bude vykonána až poté, co systém změří hodnoty kanálů c1, c2 a c3.

Poznámka:

Co se stane, pokud je v rámci jediného časového kroku použit jeden kanál vícekrát? Jádro jej v jediném časovém kroku samozřejmě změří jen jednou. Při opakovaném použití v rámci téhož časového kroku je již kanál považován za změřený a není měřen opakovaně. Opakované změření téhož kanálu je možné si „vynutit“ zpožďující instrukcí pause nebo yield. Tyto instrukce ale způsobí pokračování procedury až v rámci dalšího systémového časového kroku. Pravidlo, že žádný kanál není v jednom časovém kroku měřen vícekrát, je tedy dodrženo.

Co se ale stane, pokud bude kód vypadat následovně:

  if c1 > 0 then
    a2 = c2;
  else
    a3 = c3;
  end; 

V zápisu procedury se sice vyskytují všechny tři kanály, ale konstrukce algoritmu vylučuje, že by byly potřebné všechny tři. Podle hodnoty kanálu c1 bude dále zapotřebí kanál c2 nebo c3, ale nikdy oba současně. V tomto případě to vypadá jako nepodstatné diskuse, protože změření jediného kanálu navíc aplikaci nemůže citelně zdržet. Ovšem pokud se v jednotlivých větvích budou vyskytovat ne jednotlivé kanály, ale tisíce či desetitisíce kanálů, může být násobná doba komunikace vážný problém.

Z těchto důvodů je vykonávání procedur optimalizováno následujícím způsobem: Pokud je nalezeno větvení v programu, pro jehož rozhodnutí je zapotřebí přečíst hodnotu nějakého kanálu, algoritmus určující které kanály bude zapotřebí přečíst se zastaví a dosud označené kanály se přečtou. To umožní rozhodnutí podmínky určující která část procedury se bude dále vykonávat. Vyhledávání potřebných kanálů se dále rozběhne jen ve vybrané větvi procedury.

Někdy je užitečné uprostřed algoritmu vyvolat komunikaci se zařízeními, kdy jsou dosud označené kanály přečteny a zapsané kanály odeslány do zařízení. Jak jsme již popsali, prostý výskyt nějakého podmíněného příkazu (např. if nebo while) toto zaručí. Stejně tak zpožďující instrukce (yield, pause nebo wait) způsobí komunikaci, ale za cenu přerušení algoritmu a pokračování až v následující systémovém časovém kroku. Pokud je ale zapotřebí např. zapsat více hodnot sekvenčně po sobě a přitom neztratit časový krok, je možné použít příkazu commit. Výskyt tohoto příkazu v programu nemá vliv na vykonávání algoritmu, pouze způsobí nucenou komunikaci se zařízením v rámci jediného časového kroku.

  cmd = 'init';
  commit; (* řetězec 'init' se zapíše do kanálu *)
  cmd = 'start';
  commit; (* řetězec 'start' se zapíše do kanálu *)
  ...

Pokud bychom nepoužili příkazu commit, dva zápisy po sobě by způsobily přepsání řetězce.

Upozornění:

Mezi příkazy yield a commit je zásadní rozdíl, který budeme demonstrovat na následujících příkladech. Mějme část kódu:

  a1 = c1;
  commit;
  a2 = c1;
  commit;

Příkaz commit způsobí přečtení kanálu c1. Přiřazení za commit požaduje opět čtení c1, ale protože algoritmus pokračuje v témže časovém kroku, systém již nebude kanál c1 číst bez ohledu na další příkaz commit, neboť tento kanál již byl přečten.

  a1 = c1;
  yield;
  a2 = c1;
  yield;

V tomto případě bude kanál c1 skutečně přečten 2×, protože algoritmus se rozpadne na více časových kroků.

Tato omezení neplatí pro zápis, který se provádí tolikrát, kolikrát se vyskytuje v algoritmu.

Rekurzivní volání a zpožďující instrukce

Již dříve bylo zdůrazněno, že procedury není možno volat rekurzívně, to znamená, že pokud je některá procedura rozpracována a ještě nedoběhla do konce, nelze ji znovu explicitně vyvolat. Za normálních okolností systém zaručuje, že ke slovu se nedostane jiná procedura, pokud právě běžící procedura nedoběhne. Přesto existuje několik možností, jak může dojít k rekurzivnímu volání, např. procedura volaná z jiné procedury vyvolá opět volající proceduru. Volající procedura však čeká na dokončení volání jiné procedury a je tedy rozpracována. Takové volání bude odmítnuto a do okna zpráv se vypíše varování. Je při tom důležité si uvědomit, že rekurze nemusí být tak prostá, volaná procedura zavolá jinou proceduru, která může zavolat opět jinou proceduru a až na další úrovni může být opět zavolána volající procedura. I taková situace je nepřípustná.

Situace s rekurzivním voláním se ještě podstatně zkomplikuje, pokud se v procedurách použijí zpožďovací instrukce pause, yield nebo wait. Taková instrukce pozastaví vykonávání procedury a vrátí vykonávání opět do volající procedury. Přitom volaná procedura zůstane v rozpracovaném stavu a nemůže být opět zavolána, dokud nedoběhne do konce.

Jedinou výjimkou je implicitně volaná procedura OnActivate, která v takovém případě pokračuje v testování podmínky běhu a případně pokračuje od místa přerušení dále. Pokud tedy procedura OnActivate čeká na nějaké zpožďovací instrukci a nějaký ovladač aktivuje daný přístroj, procedura OnActivate tuto aktivaci „spotřebuje“ na testování podmínky dalšího běhu, nikoliv na rozběhnutí od počátku a případné testování důvodu aktivace! Pokud tedy je v proceduře OnActivate použita nějaká zpožďující instrukce a procedura se zároveň rozhoduje podle podmínky aktivace, pak podle časových poměrů může dojít k vynechání ošetření aktivace.

Zvláště časté problémy mohou vznikat při použití zpožďovacích instrukcí v událostních procedurách. Pokud se vykonávání událostní procedury pozastaví a daná událost nastane znovu, opět dojde k rekurzivnímu volání.

Aby se výše popsaným problémům s rekurzivním voláním předešlo, je do OCL zavedena možnost omezení použití zpožďovacích instrukcí:

  • Zpožďovací instrukce pause, yield a wait lze použít výhradně v proceduře OnActivate. V žádných jiných událostních nebo uživatelských procedurách tyto instrukce nejsou povoleny.

  • Proceduru OnActivate nelze volat explicitně. Může být volána jen implicitně v rámci aktivace přístroje.

Protože v řadě případů může být toto omezení velmi nepříjemné, můžete na sebe převzít zodpovědnost za nevolání procedur rekurzivně a zrušit toto omezení nastavením parametru independent_procedure_execution v sekci settings na true. Tento parametr lze také nastavit v záložce Datové inspektory — Systém v integrovaném vývojovém prostředí. V takovém případě můžete používat zpožďující instrukce a volat proceduru OnActivate bez omezení.

Lokální data přístroje

Výhodnost použití lokálních proměnných a konstant v rámci procedury již byla zmíněna dříve. Jedna z klíčových výhod je v tom, že lokální data jsou viditelná jen zevnitř procedury, „nepletou“ se mezi globální, společné proměnné, viditelné ze všech přístrojů.

Pokud ale jedinému přístroji nadefinujete více procedur, které nějakým způsobem spolupracují, mechanizmus lokality proměnných je zneviditelní i mezi procedurami téhož přístroje. Problém je, že procedury jednoho přístroje tak nemohou pracovat se společnými proměnnými, samozřejmě mimo vyhrazení určitých globálních proměnných. Do jisté míry lze tento nedostatek nahradit uschováním potřebných informací v lokálních proměnných zvolené procedury a do ostatních procedur je předávat jako parametry. Mimo nepohodlnost tento postup selže, pokud k takovým proměnným potřebují přistupovat i událostní procedury, jejichž seznam parametrů (signatura) je předem dán.

Proto stejně jako v rámci těla procedury lze v blocích const, var a static deklarovat lokální proměnné a konstanty, lze tyto bloky použít i na vyšší úrovni, v rámci přístroje. Na konstanty a proměnné deklarované v rámci přístroje pak vidí všechny procedury daného přístroje, ale z venku nejsou viditelné.

Na rozdíl od těla procedury, kde blok definující konstanty a proměnné je automaticky ukončen začátkem jiného bloku, v přístroji musí bloky const, var a static končit odpovídajícími klíčovými slovy end_const, end_var a end_static.

Lokálních datových elementů deklarovaných v rámci přístroje lze používat i při zápisu výrazů přístroje. Jsou-li ale použity ve výrazu nebo v proceduře, blok kde jsou tyto elementy deklarovány, musí předcházet jejich použití. Z jiných přístrojů jsou ale takto deklarované proměnné a konstanty neviditelné.

Upozornění:

Zatímco v rámci těla procedury překladač hlídá pořadí bloků a blok var nelze umístit za blok příkazů uvozený klíčovým slovem begin, v případě těla přístroje tomu tak není. Pokud tedy existuje lokální proměnná přístroje se jménem shodným s globálním datovým elementem, záleží na pořadí deklarací.

Například aplikace:

data

  var
    a : real { init_value = 1 };
  end_var;

end_data;
...
instrument

  meter m1;
    expression = ch1 / a;
    var
      a : real;
    end_var;
  end_meter;

end_instrument;

bude pracovat při prvním spuštění dobře, neboť v době překladu výrazy za slovem expression ještě lokální proměnná a neexistuje a ve výrazu se proto pro dělení použije globální proměnná a inicializovaná na 1. Ale po automatickém vygenerování těla přístroje meter (např. po překlopení vývojového prostředí z grafického módu do textového módu) se blok proměnných vždy zapíše jako první

meter m1;
  var
    a : real;
  end_var;
  expression = ch1 / a;
end_meter;

a při spuštění se již výraz za expression přeloží s úplně jinou proměnnou, tentokrát lokální. Navíc tato proměnná je inicializována na hodnotu 0 a při dělení způsobí chybu za běhu aplikace. Pokud tedy zapisujete bloky proměnných a konstant v textovém módu, zapisujte je jako první (grafický editor generuje tyto bloky jako první vždy).

Jaký je tedy postup při hledání datového elementu určitého jména? První krok závisí na tom, jestli datový element použit ve výrazu uvnitř těla přístroje (např. expression = a + b; u přístroje meter) nebo ještě uvnitř procedury přístroje. Další dva kroky jsou již stejné.

  1. Nejprve se prověří, jestli existuje lokální proměnná nebo parametr procedury daného jména. Parametry a lokální proměnné procedury sdílejí jeden prostor jmen a nelze tedy pojmenovat parametr a lokální proměnnou procedury stejně. Toto pravidlo se samozřejmě uplatní jen pro datové elementy použité uvnitř procedur.

  2. Pokud datový element není nalezen uvnitř procedury, zjišťuje se, jestli není deklarován uvnitř přístroje jako lokální proměnná nebo konstanta přístroje.

  3. Až když není datový element nalezen v seznamu lokálních dat přístroje, je hledán mezi globálními datovými elementy.

  4. Pokud datový element není nalezen ani na globální úrovni, systém nahlásí chybu překladu.

Mějte na paměti, že při překladu jsou vyhodnocována pouze jména datových elementů a jejich atributy (druh a typ) jsou zjišťovány až podle jmen. I pokud se globální a lokální datový element naprosto liší svým druhem i typem, musí se vždy používat ve shodě s nejnižší deklarací. Uvažte následující příklad:

...

driver
  drv = vsource.dll, 'VSOURCE.DMF', 'VSOURCE.PAR';
end_driver;

data

  channel { driver = drv; init_value = 0 };
    a : real { direction = input; driver_index = 1 };
  end_channel;

end_data;

instrument

  meter m1;
    expression = a; (* a použit jako globální kanál typu real *)
  end_meter;

  string_display s1;
    var
      a : string;
    end_var;
    expression = a; (* a použita jako lokální proměnná typu string, globální kanál a není dostupný *)

    procedure Go();
    var
      a : array [0..1] of boolean;
    begin
      ...
      if a[ 0 ] then (* a je lokální pole proměnných typu boolean *)
        ...
      end;
      ...
    end_procedure;

  end_string_display;

end_instrument;

Všimněte si, že na lokální úrovni (ať již přístroje nebo procedury) lze deklarovat jen konstanty nebo proměnné, nikoliv vstupně/výstupní kanály. Kanály mohou být deklarovány pouze na globální úrovni.

Podobně jako u lokálních proměnných v proceduře, proměnné deklarované v rámci bloku var budou vždy na počátku aktivace přístroje inicializovány, proměnné z bloku static budou inicializovány jen při startu aplikace a poté se jejich obsah bude měnit zápisy, bez ohledu na aktivace (časování) přístroje. Pokud je tedy hodnota přístrojové proměnné za běhu aplikace „záhadně“ nastavována na určitou hodnotu, přesvědčte se, zda-li je umístěna v bloku static a ne v bloku var.

Nativní procedury

Existuje řada vlastností jednotlivých přístrojů, jen obtížně nebo velmi neefektivně reprezentovatelných v podobě vlastností, byť vyjádřených např. jako výraz. Tyto vlastnosti jednotlivých přístrojů proto bývají zpřístupněny jako tzv. nativní procedury.

Poznámka:

Ve starších verzích systémů Control Panel, které ještě neumožňovaly neomezenou definici procedur spojených s libovolným přístrojem a OCL byl implementován jen v podobě přístroje program, byly nativní procedury jediným typem procedur v systému a byly nazývány OCL metodami.

Definice i implementace nativních procedur je čistě záležitostí patřičné třídy přístroje a naprosto neovlivňuje jeho binární kompatibilitu se zbytkem systému. Kdykoliv je tedy možné rozšiřovat funkčnost přístrojů bez ohrožení systému. Pouze pokud je překládán aplikační program volající nativní proceduru, která není k dispozici (např. při pokusu přeložit novější program za použití starších versí DLL přístrojů, při zrušení nebo změně definice nativní procedury apod.), je ohlášena chyba překladu a program je nutno upravit.

Konvence však dovoluje pouze přidávat další nativní procedury a zachovávat názvy, typy argumentů (signaturu) i funkčnost u již existujících nativních procedur. Při dodržení této konvence je zajištěna vzestupná kompatibilita všech aplikací, to znamená, že novější verze systému Control Web dokáže přeložit všechny starší aplikační programy.

Typickým příkladem použití nativních procedur je ovládání viditelnosti panelu. Každý panel má možnost definování výrazu ovládajícího jeho viditelnost

visibility = podmínka_viditelnosti;

Má-li ale panel neustále vyhodnocovat podmínku viditelnosti při periodickém časování nebo po změně některého datového elementu ve výrazu uvedeného, je to zcela nepatřičné plýtvání výkonem. Proto takové panely zpravidla nebývají časovány, ale pouze aktivovány jiným přístrojem, který podmínku viditelnosti mění. Pomocí nativních procedur OCL ale jde zobrazování i skrývání panelů řešit velmi elegantně. Stačí zavolat

panel1.Hide();
panel2.Show();

Mnohé vlastnosti jsou dostupné pouze prostřednictvím nativních procedur. Například jednoduché volání

panel1.MoveTo( 100, 200 );

přesune přístroj panel1 na souřadnice 100, 200 obrazových bodů.

Nativní procedury se z hlediska volajícího kódu chovají úplně stejně, jako jakékoliv jiné procedury, pouze ve zdrojovém kódu aplikace nejsou nikde deklarovány. Každý přístroj implementuje vlastní sadu nativních procedur. Seznam nativních procedur spolu s jejich popisem můžete v rámci palety přístrojů, popsané v kapitole Integrované vývojové prostředí.

V rámci systému Control Web tedy existují tři kategorie procedur, lišících se některými vlastnostmi:

kategorie procedur implementace uživatelem volání z procedur volání systémem
událostní procedury ano ano ano
uživatelské procedury ano ano ne
nativní procedury ne ano ne

Druhy procedur a jejich volání

Shrnutí

OCL je mocný nástroj pomáhající všude tam, kde je zapotřebí popsat složitější chování aplikace. Základní rysy OCL jsou:

  • OCL je obecný programovací jazyk umožňující zapsat libovolný algoritmus.

  • Veškerý kód OCL je soustředěn do procedur. Procedury se nikdy nemohou vyskytovat samostatně, ale vždy jsou spojeny s nějakým přístrojem.

  • Neexistuje žádná „hlavní procedura“. Control Web je událostmi řízený systém a taktéž procedury jsou primárně vyvolávány jako reakce na výskyt nějaké události.

  • Možnost deklarace procedur není omezena jen na událostní procedury, v aplikaci může být definováno libovolné množství uživatelských procedur, avšak jiné procedury v aplikaci zodpovídají za jejich vyvolávání.

  • Přístroje často disponují nativními procedurami, které nelze deklarovat, ale lze je volat.

  • V rámci procedur lze deklarovat lokální, odjinud neviditelné datové elementy. Lokální datové elementy zastíní (zneviditelní) globální datové elementy stejného jména (pokud existují).

  • Procedury mohou mít vlastní parametry, pracující podobně jako lokální data, ale jejich hodnoty nastavuje volající kód.

  • Parametry mohou být předávány dvěma způsoby — hodnotou a odkazem. Parametr předaný hodnotou je vyčíslen a okopírován do proměnné parametru, jeho změny se tedy mimo proceduru neprojeví. Veškeré změny v parametru předaného odkazem se projeví přímo v daném datovém elementu. Odkazem tedy nelze předávat konstanty a výrazy, pouze jednoduché datové elementy.

  • Do procedury lze odkazem předat celé pole. Funkce loindex a hiindex dovolují zjistit dolní a horní indexy předaného pole.

  • Procedura může vracet hodnotu. Pak je ji možno volat jako funkci v rámci výrazu ať již v těle jiné procedury nebo libovolného jiného výrazu.

  • V rámci přístroje lze deklarovat lokální datové elementy. Tyto elementy pak lze použít při zápisu výrazů v přístroji i v procedurách daného přístroje. Záleží však na pořadí deklarací — lokální datové elementy musí být deklarovány před jejich použitím kdekoliv v přístroji. Lokální datové elementy jsou z jiných přístrojů neviditelné.

  • Při rozhodování, který datový element stejného jména se má použít, má vždy přednost ten nejlokálnější, tedy proměnná v proceduře má přednost před proměnnou v přístroji a tato má přednost před globální proměnnou.

  • Na lokální úrovni nelze deklarovat jiné druhy datových elementů než konstanty a proměnné.

  • Procedura je identifikována svojí signaturou — jménem, typy a pořadím parametrů a typem návratové hodnoty. Všechny číselné typy parametrů jsou kompatibilní (zaměnitelné) a z hlediska signatury splývají.

  • V rámci jednoho přístroje nelze deklarovat dvě procedury se stejnými signaturami. Kvůli zamezení problémům spojených s rozlišováním signatur, nelze deklarovat procedury jmenující se stejně jako základní událostní procedury a lišící se pouze parametry, byť mají jinou signaturu.

  • Aby byla událostní procedura po výskytu události vyvolána, musí její signatura odpovídat definované signatuře pro danou událostní proceduru.

 
 | O společnosti | Produkty | Podpora | Stažení software | Stažení dokumentů | 
Moravské přístroje, a.s., Masarykova 1148, Zlín-Malenovice, 76302