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í:
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, doplnit do aplikace potřebnou funkčnost.
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 podle daných událostí. 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á aktivace po uplynutí časového intervalu 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ě.
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:
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 — jsou vysvětleny 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?
Co se stane, pokud procedura změní hodnotu parametru? Jestliže jsou parametry vlastně proměnné, lze do nich i zapisovat. Vlastní kód procedury ale nemůže ovlivnit, jestli ten, kdo ji volá, přečte změněnou hodnotu zpět. To závisí čistě na kódu, který proceduru volá. Pokud tedy bude výše zmíněná procedura Calculate volána
InstrumentName.Calculate( a, fl );
objeví se uvnitř těla procedury v parametru value hodnota elementu a a parametru flag bude hodnota fl. Kód procedury Calculate může měnit hodnoty value i flag, avšak hodnoty a a fl nebudou změněny. Pokud chcete, aby se např. hodnota value promítla po volání Calculate zpět do datového elementu a (může to být proměnná i kanál), je nutno použít při volání znak odkazu &.
InstrumentName.Calculate( &a, fl );
Je nutno si uvědomit, že jako parametry při volání lze použít libovolný výraz, který se v rámci volání vyčíslí a do proměnné parametru se zapíše výsledek. Znak odkazu ale lze umístit jen před výraz obsahující pouze jeden datový element — samozřejmě nelze vracet zpět hodnotu do výrazu.
InstrumentName.Calculate( &x, (x > y) or (x > z) ); (* v pořádku *) InstrumentName.Calculate( &(x + y), fl ); (* chyba, výraz nelze předat odkazem *)Pozor! Pokud je v proceduře použita některá zpožďující instrukce pause, yield nebo wait, 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.
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 i v zápisu 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(); ... InstrumentName.Go()
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. 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.
Pozor! 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 a typy parametrů. Vlastní jména parametrů již signaturu neovlivňují. Proto procedury
procedure SetColor( color : cardinal ); procedure SetColor( c1 : cardinal );
mají shodnou signaturu.
Pokud je v algoritmické smyčce (nejenom while, loop apod. ale i ve smyčce vzniklé např. voláním goto) vykonávané v rámci jednoho kroku čtení kanálů, algoritmus bude přerušen, jádro přečte požadované kanály a vyčte frontu událostí. Pokud ale žádné kanály čteny nejsou, algoritmus bude stále setrvávat ve smyčce, potenciálně velmi dlouhou dobu. Během této doby se nedostanou ke slovu jiné přístroje (což je v pořádku, program dělá to, co mu autor předepsal) ale hlavně systém nevyčítá události a aplikace nereaguje na zásahy uživatele.
Aby k těmto jevům nedochácelo, kód každé procedury je vždy po určitém počtu instrukcí přerušen, aby systém mohl vyčíst frontu událostí a reagovat např. na požadavek zastavení aplikace. Počet instrukcí je standardně nastaven na 32768.
Pokud toto přerušení nastane v proceduře OnActivate() aktivované časovačem nebo výjimkou, na jejím běhu se to nijak neprojeví. Po vyčtení fronty kód pokračuje v rámci stejného časového kroku. Pokud je ale procedura volána nějakou jinou procedurou, po vykonání limitního počtu instrukcí se volání vrátí do volající procedury, aniž by kód doběhl do konce. Volaná procedura sice pokračuje v běhu v následujícím systémovém časovém kroku (podobně jako po instrukci yield), ale sémantika algoritmu je změněna.
Tento jev může u záměrně dlouhých smyček a obecně dlouhých algoritmů působit potíže. Proto je v sekci settings parametr procedure_instruction_limit umožňující nastavit limitní počet instrukcí.
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á určující počet průchodů 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é budou mít 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 hodnotu výchozí. 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.
Pozor! 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.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".
Zápis procedur musí odpovídat následující LL(1) 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 , Value ] { ; Identifier = TYPE , Value } 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 | 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 | send Identifier | sound EXPRESSION | stop | Identifier : [ STATEMENT ] | DataElement = EXPRESSION | move ArrayDataElement , ArrayDataElement, EXPRESSION | self . MethodName ( PARAM_LIST ) | ObjectName . MethodName ( PARAM_LIST ) | (* empty statement *) (* this rule is valid only in block between 'loop' and 'end' *) STATEMENT -> exit | continue PARAM_LIST = [ EXPRESSION | & DataElement ] { , EXPRESSION | & DataElement }
Každá procedura začíná deklarací hlavičky
procedure JménoProcedury( seznam_parametrů );
Za hlavičkou následují deklarace bloků oddělené znakem ; (středník). Celkem existuje pět typů bloků:
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_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_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.
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;
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é, inicializační_hodnota;
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:
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:
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;
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 *)
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 *)
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 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 řídidí 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. Výraz udávající krok musí být vždy konstantní, vyčíslitelný během překladu. To znamená, že nesmí obsahovat žádné proměnné nebo kanály.
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;
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;
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 *)
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 *)
Použití této instrukce je ekvivalentní doběhnutí procedury na konec. Další aktivace procedury způsobí její rozběhnutí opět od počátku.
Za klíčovým slovem send následuje identifikátor označující jméno nějakého jiného přístroje v aplikaci. Uvedení tohoto příkazu zajistí aktivování uvedeného přístroje 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ů. V programu je k aktivaci více přístrojů různých jmen nutno použít více příkazů send. 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.
Pozor! 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; send 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.
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;
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 ř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.
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.
Pozor! 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 uchovat v proměnné typu integer.Příklad:
(* numerické 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 (* boolean výrazy *) result = value <= ( a + 1 ); ExitCondition = true; (* řetězcové výrazy *) Message = concat( 'Error: ', ErrorString ); Title = 'Demonstrační panel';
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 ve tvaru
InstrumentName.Go();
Chcete-li vyvolat proceduru, musíte před její jméno uvést jméno přístroje, který tuto proceduru implementuje. Již dříve bylo řečeno, že procedury nemohou být implementovány mimo nějaký přístroj. 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();
V mnoha případech se kód jedné procedury obrací na kód jiné procedury téhož přístroje. I v takovém případě musí před jméno volané procedury uvést 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. 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 nové 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 svému přístroji *) self.SetValue( a ); (* pracuje stejně jako předchozí volání, ale funguje i po přejmenování přístroje *) ... end_procedure; end_meter;
Při volání procedur je nutné vzít v úvahu několik skutečností:
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ž když poslední komentářová závorka.
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í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:
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 vždy, když switch zapíše na svůj výstup novou hodnotu *) 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.
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:
V 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
Podrobně se o časování můžete dočíst v kapitole Podrobně o real-time a datově řízených aplikacích v 1.`dílu a v kapitolách Běh datově řízené aplikace a Časování aplikací reálného času ve 2.`dílu.
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í č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 tvaruprocedure 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ů – ByTimer i ByInstrument.
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 (podrobně o výrazech a funkcích se dočtete v kapitole Datové elementy a výrazy. 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 : cardinal ); 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í:
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í:
Množství a signatury událostních procedur jsou dány jednotlivými přístroji. Přesto existuje skupina událostních procedur shodná v rámci celého systému. Jak již bylo řečeno, neznamená to, že všechny přístroje budou tyto procedury vyvolávat, to závisí na charakteru přístroje.
Proč je nutné tyto procedury popisovat zvlášť? Aby se snadněji předcházelo chybám zaviněným přehlédnutími, mají tyto procedury dvě výjimečné vlastnosti:
Standardní událostní procedury jsou:
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í:
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í.
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é.
Pozor! 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 aplikacevar a = real, 1; end_var; ... instrument meter m1; expression = ch1 / a; var a = real, 0; 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, 0; 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é.
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; channel a = real, 1, drv, input; end_channel; 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 není viditelný *) procedure Go(); var a = array[ 0..1 ] of boolean, false; begin ... if a[ 0 ] then (* a použito jako lokální pole proměnných typu boolean *) ... end_procedure; end_string_display; end_instrument;
Všimněte si, že na lokální úrovní (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.
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 Web a 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 deklarace 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
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: