Moravian instruments, Inc., source: http://www.controlweb.eu/art?id=999&lang=409, printed: 03.05.2025 18:28:23
Main page▹Control Web Examples▹Control web Documentation | 22.5.2019 |
---|
In a general sense, programming deals with ways how to specify algorithms, which will be executed by a computer. Programming has made great progress since the origination of computer science, when it was necessary to put individual instructions into the computer memory, which were than interpreted by the machine. Although present-day computers are not, in principle, able to do more than just execute sequences of simple instructions stored in the memory, the ways of creating these sequences — programs — have dramatically changed. If you drag the meter virtual instrument from the instrument palette, drop it to the application workspace and modify its properties to measure and display each second some value, you create a program. From the processor instructions point of view, it is a very complex program, including hundreds of thousands of instructions. The reason is simple — there is not an instruction for displaying of a number or for drawing of the meter's pointer. Neither are there instructions for drawing a line (yes, perfectionists can object that specialized graphical processors can draw a line with a single instruction, but it does not make any difference to the principle of our explanation) nor for evaluation of sine function necessary for calculation of co-ordinates of the line end from a known angle. Processor instructions can do very little, for instance, they can read the memory area into the processor's internal register, add two registers or evaluate a condition and jump to another part of the program according to the logical value of the result. Fortunately the development of programming brought an important invention — high-level programming language. It is a way of writing algorithms on a higher level than machine instructions, by means of symbolic representations. If you want to program the addition of two numbers in a high-level programming language, it is not necessary to know the addresses at which these numbers are stored in the memory, nor is it necessary to assign processor registers to them, simply it is enough to write a + b. How is it possible when the processor understands only its simple instructions? Another program has to be involved in the process — compiler. The compiler converts the high-level language notation, which is usually intelligible to man, into the form of machine instructions intelligible to the computer. Programs can therefore be made by means of several tools, different in the level of abstraction and independence of a specific platform.
The motivation for implementing OCL into the Control Web is clear: while most applications work as a set of prepared components, sometimes a tool is needed which reaches behind the functionality of present components. OCL can use all instruments (and thus it is not necessary to program the drawing of a measuring instrument indicator) and at the same time it can realize any algorithm and add the required functionality to the application. Procedure or Method?Despite the fact that code blocks (algorithm notations) in the Control Web are called procedures, they are a little different from what is called procedural programming described in many classic textbooks. A considerable difference consists especially in the absence of a main program, which is started first and possibly calls subroutines. Why is it so? The Control Web does not work in a traditional batch manner. Control Web is an event-driven system. There is no main program; instead the system reacts to events and calls code blocks according to given events. But what should we imagine under the term event? For common user applications an event is, for example, pressing the key or movement by mouse. However, the Control Web is a real-time industrial system and the set of events is expanded by events generated by the input/output device drivers, virtual instruments or the timer. Thus, the corresponding procedure is called when the given event occurs. An event never occurs by itself, without any relation to an instrument. Every key pressing, mouse movement and also every activation after a time interval or every exception caused by the driver relates to a specific virtual instrument. For this reason, procedures are always connected with a specific virtual instrument too. Such a model is very close to the object-oriented model, within which code blocks realizing object behavior are called object methods. Therefore, procedures could perhaps rather be called instrument methods. However, object-oriented technologies cause complications which should be avoided in tools such as the Control Web, and thus we stick to procedures. One matter is crucial — OCL procedures are always linked with some virtual instrument, they never exist independently.. Procedure interface — parameters and return valueAs was said, procedures execute some code — algorithm notation. The algorithm processes data and it would not be of any use unless it read data from outside and returned the results. There can exist two manners of such data exchange which of course can be combined:
There are several problems with access to global data. First of all it would be necessary to define a global variable for each data element to be handled by procedure code, and thus the number of variables grows. Also, orientation in a big number of global variables is considerably more difficult. There is also a risk of the problem of synchronization — one procedure will use a global variable and during a break in execution of the code (caused by the instructions yield, pause or wait — these instructions are described later) another procedure overwrites its value. For these reasons, each procedure may have the list of parameters — variables and their types, for instance: procedure Calculate( Value : real; Flag : boolean ); Unlike the definition of global variables, variables representing parameters of the procedure do not have an initializing value — their value is always defined by the calling code. If any procedure wants the procedure Calculate to calculate something, it must provide two values (or expression, which will be evaluated upon call), one is the number of the type real, the other is the logical value of the type boolean. Therefore, what are the rules for access to variables which appear as procedure parameters?
When writing more parameters of the same type you need not define each parameter type individually. You can separate parameters by commas and define the type after the colon following the last parameter. procedure Check( fl1, fl2 : boolean ); Of course, both ways can be independently combined. procedure TestFlProcedure return valueags( fl1, fl2 : boolean; fl3 : boolean ); If the procedure has no parameters, empty parentheses are placed in the place of the list of parameters. Empty parentheses must be used both in the definition of the procedure also in procedure call notation — parentheses identifies this is a procedure and not data element identifier of something else. procedure Go(); (* procedure definition *) ... Go(); (* procedure call *) Procedure return valueIn addition to parameters, the procedure may also contain return value. It is possible to meet a series of built-in functions, e.g. function sin for calculation of the sine of the respective angle etc., when writing expressions in the Control Web. The use of such a function is easy — it is enough to write its name in the expression and to define the parameter (or parameters) in parentheses after the name. When evaluating the expression, the function is called and the value returned by the function (in our case the value of the sine of the respective angle) is used in its place. Similarly as built-in functions, procedures may return values. If any procedure returns a value, it is possible to call it within any expression. Of course, the return value of the procedure is of a certain type and its use in the expression must corresponds to this type. It is of course not possible to call the procedure, which does not return any value, in the expression. On the contrary, the procedure returning the value can be called as an independent statement, its return value will be omitted. If the procedure returns the value, it is necessary to define return type behind the right parenthesis completing the list of parameters and the colon: procedure CheckValue( Value : real ): boolean; Remark: Some programming languages (e.g. Pascal) distinguish between subroutines which do not return value and which do return value by name (procedures and functions). This brings problems when referring (it is necessary to write procedures and functions) which results in introduction of redundant restrictions for calling of such procedures and functions — it is not possible to call a function as an independent statement in the program. Therefore, OCL does not distinguish between procedures and functions and the only restriction is impossibility of calling the procedure without a return value within the expression. The use of procedures with return value is simple. For example, the procedure which tests whether the particular number is in the interval 0..1 will look like this: procedure Test( a : real ): boolean; begin return (a >= 0) and (a <= 1); end_procedure; Remark: The return statement will be explained later. It is possible to use the procedure Test any time in the logical expression (it returns the value of the type Boolean): if Test( a ) then (* fulfilled if the variable a has the value between 0 and 1 *) ... end; ... Test( 0.5 ); (* syntactically correct call but without any effect – the return value will be omitted *) Parameters passed by referenceWhat will happen if a procedure changes parameter value? If parameters are variables, it is possible to write into them in the body of the procedure (to assign them a new value). Therefore, OCL introduces two calling conventions, which distinguish between two kinds of behavior during assignment into parameters inside the procedure:
Parameters passed by the value disappear in the body of the procedure and their possible changes will not be reflected outside. In term of the procedure their represent only input parameters. Changes in parameters passed by the reference will appear after completion in the passed data elements, so their represent input and output parameters. The manner of parameter passing is defined during the declaration of the procedure. Simple notation of the parameter name and its type define parameter passed by value. The keyword var before the parameter name means a parameter passed by the reference. For example, in the declaration of the procedure: procedure Calculate( var Value : real; Flag : boolean ); the parameter Value is passed by the reference, the parameter Flag by the value. It is not possible to pass a constant or expression in the place of the first parameter, a simple data element is required. Examples of correct and incorrect calling are: Calculate( 2.5, true ); (* error, constant literal passed as var parameter *) Calculate( 2 * a, flag1 & flag2 ); (* error, expression passed as var parameter *) Calculate( a, ~flag1 ); (* correct, if the passed data element a is not a constant and is of numeric type *) Warning: If a delay statement pause, yield or wait (these statements are explained below) is used in the procedure, the procedure returns from call with reference parameter values at the time when the procedure executes the delay instruction, not at the end of the procedure. Remark: The possibility to pass parameters by reference was not available in the Control Web 2000 and previous versions — procedures always worked with local copies of parameters. Despite this fact, it was possible to transfer changed parameter values back to the data elements passed as parameters. However, this feature was not defined within the definition of the procedure header, but it depended on the code which called the procedure. Let's consider a procedure with one parameter: the procedure Search( s : string ); If this procedure is called by the command: Search( Str ); the variable Str remains unchanged irrespective of any changes in the parameter s inside the procedure Search. If the calling code wants to reflect possible changes in the parameter s in the variable Str, it is necessary to use the character & before the parameter name during the calling: Search( &Str ); Of course, it is not possible to introduce a constant or expression after the & character: Search( &'hello' ); (* error, reference before the constant *) Search( &( s1 + s2 )); (* error, reference before the expression *) This possibility of the transfer of parameters by reference was also maintained in the current version of the Control Web to keep newer versions backward compatible. Nevertheless, it is not recommended to use it. The basic disadvantage of this manner is based on the necessity to use the character & in the notation of each calling, which often resulted in errors. It is also less effective than the use of calling by reference. This way entirely fails in the case of an attempt to pass parameters to procedures of remote objects. In this case, it is necessary to use var parameters, otherwise changes in the parameters performed in the procedure in remote objects will not be reflected back in the calling code. In many cases headers of native procedures returning data back into the application were modified so that parameters are transferred by reference in the current version of the Control Web. Users no longer need to write the character & before such parameters, which eliminates number of problems caused by & omission. But this may become a source of minor complications during the port of applications developed in previous versions of Control Web. It was possible to define constant (for example false for logical parameters) instead of the variable, if the application was not interested in particular output parameter. There was not the & character before the constant and the code worked correctly. It is necessary to declare the variable in the current version of the Control Web and pass it in the place of the var parameter, even if the application is not interested in it. GetWinState( false, &MaxFlag ); (* no error reported in Control Web 2000 *) GetWinState( MinFlag, MaxFlag ); (* the variable MinFlag is necessary for var parameter even if the application is not interested in it *) The previous example demonstrates that it is not necessary to write the & character in the current version of the system and, despite this fact, the variable will be transferred back from the procedure. If, despite this fact, the character & is written before the var parameter, it does not influence the application and the character & is ignored. Passing arrays to proceduresBecause the passed variable is not copied into the local variable during the reference passing of parameters, but only the reference to this valuable is used, it is possible to transfer the whole array as the var parameter. If the whole array is to be passed, the parameter is introduced with keywords array of. For example, the procedure searching for the maximum element of the array may have the header: procedure FindMax( a : array of real ): real; It is not possible to pass scalar data element into such a procedure, but it is necessary to pass the whole array. Because arrays in the Control Web may have arbitrary size and may start by any index, two functions returning minimal and maximal array indexes loindex and hiindex were introduced. Both functions require the array identifier (not one element of the array!) as a parameter and return the lowest and the highest index of the respective array. The code in the procedure may handle all elements in the field with the use of these functions. The above-mentioned procedure FindMax could be implemented, for example, as follows: 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; Warning: Parameters of type array of are automatically passed by reference, even if there is no var keyword in their definition. It is not possible to pass the whole array by value (to copy the array into the local variable). Any changes in values of array elements are performed directly in the passed field. Procedure signatureIt is necessary to identify procedures (to mutually distinguish them) in many cases. Procedure signature is used for this purpose. For example, it is not possible to declare two identical procedures within one instrument because then it would be impossible to decide which procedure should be called. The simplest way how to define procedure signature would be declaring its name as its signature. Then it would be impossible to declare two procedures of the same name. However, if you need two procedures, which do the same thing, and only have different parameters there is no need to create a new name. That is why procedure signatures are created not only by the procedure name but also by its parameters within the Control Web. Thus, you can define, for example, two procedures: procedure SetColor( color : cardinal ); procedure SetColor( red, green, blue : shortcard ); Although both procedures have the same name, they have different parameters and thus it is not a problem to distinguish them during calling. If you call SetColor with one parameter, the first procedure will be called; if you call the procedure with three parameters, the second one will be called. An attempt to call SetColor with different number of arguments or with arguments of different type will cause a compilation error. Warning: Automatic conversion of numeric types is performed in Control Web. In practice, it means that, we can for example assign variable of type real to the variable of type integer (of course with the risk of losing information, for details see the sub-chapter about expressions) and it is possible to pass the variable of the type cardinal into the procedure with the parameter of the type real. Automatic conversion of numerical types is very comfortable, but it has one consequence, which you must bear in mind. It means that signatures of procedures with the same number of numerical parameters (no matter whether they differ) are exchangeable and thus they cannot be defined in one instrument. procedure SetColor( color : integer ); procedure SetColor( color : real ); (* Error, "redefined procedure", numeric types are replaceable. *) Procedure names are like all other identifiers in the Control Web case sensitive. This means that procedure SetColor( color : cardinal ); procedure setColor( color : cardinal ); (* name begins with a small letter, it concerns another procedure *) are different procedures with different names. Note that the signature consists only of the procedure name, parameter types and the type of return value. The own names of the parameter do not influence the signature. Therefore, the procedures procedure SetColor( color : cardinal ); procedure SetColor( c1 : cardinal ); have an identical signature. Procedure local dataYou already know that if a procedure has parameters, these parameters can be referred to as a special kind of variables. The specialty is that if the parameters have the same names as global data elements, they have priority in use and global data elements are made invisible. Another specialty is that parameter variables are visible only from inside the procedure. These properties are useful for many algorithms. For instance, there is no reason why a variable used as loop counter should be visible also in other procedures. Moreover, if execution of the procedure pauses within the loop, some other instrument can change the variable value and the algorithm will not work correctly. It would definitely be advantageous to reserve some space inside the procedure for such a variable, so that other instruments do not see it and so that it does not occupy a globally visible identifier, which can also be used otherwise. Therefore, it is possible to declare local variables and constants within procedures. Definition of local variables and constants must appear in the procedure notation before the proper procedure code (the following sub-chapter deals with the manner of notation in detail). Local variables and constants can be used in the same way as procedure parameters, with the exception that they do not create the procedure interface and their values cannot be, unlike parameters, set or read outside the procedure. As in the case of parameters, a global data element of the same name as local data element becomes invisible. Local variables considerably differ from parameters by their value at the beginning of procedure execution. Whereas parameter values are assigned by whoever is calling the procedure, local variables will have their initialization value stated in the declaration. However, in many cases it is an advantage if the value of the local variable between individual callings is not forgotten. For example, if you wish to store a value for the whole period of the application's running in the local variable, it would be wrong if this value was set on the initial value upon each calling of the procedure. Therefore, it is possible to declare the local variable by means of two keywords — var and static. Whereas the variable declared after the word var will always be initialized at the beginning of the procedure, the variable declared after the word static will be static, the initializing value will be set during the starting of the application and then the last assigned value will be preserved. Warning: If you want the value of the local variable to be preserved also between individual callings of procedures, you must declare it after the keyword static, not var. Notation of procedure codeProcedure notation must correspond to the following grammar. Terminal symbols are written in small letters, non-terminal symbols in capital letters: 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 Each procedure starts by the declaration of the header procedure ProcedureName( list_of_parameters ); procedure ProcedureName( list_of_parameters ): return_value_type; The heading is followed by declarations of blocks separated by the character ; (semicolon). In total there are five types of blocks:
Blocks of declaration of labels label, constant const, initialized variables var and static variables static may be defined in any sequence order and may arbitrarily repeat or they need not be mentioned at all. But it is necessary to bear in mind that each symbol must first be declared and then used. For example, the notation var a : real {init_value = init_a}; (* error, symbol init_a is not known *) const init_a = 1; is incorrect. The constant used for initialization of the variable must be defined earlier. const init_a = 1; var a : real {init_value = init_a}; The block defining the own code of the procedure begin must be always present, must be the only one and must be the last one. This block always terminates with the keyword end_procedure; which also closes the whole procedure notation. LabelsIf the goto instructions are used within the program, it is necessary to declare all identifiers used as labels after the keyword label first of all. Individual identifiers are separated by commas and the whole label declaration ends with a semicolon. The destination of the jump is marked by using the label followed by a colon before the statement, which is to be executed after the goto statement. Example: procedure Test(); label Error; begin if a < b then goto Error; end; ... Error: sound "ALARM.WAV"; ... end_procedure; Declaration of local constants and variablesAs mentioned above, constants are declared after the keyword const, initialized variables after the keyword var and static variables after the keyword static. Notation of local constants and variables is syntactically identical with the notation of global constants and variables. Constants are declared in the following form: const constant_name = value; Constant type is not defined, it is derived automatically from its value. Variables are defined in the form: var variable_name : type { attributes }; Procedure bodyThe body of the procedure introduced by the keyword begin contains individual statements separated by the character ; (semicolon). The body terminates with keyword end_procedure and the end of the procedure body is at the same time the end of the declaration of the whole procedure. Statements can be divided into two types:
Statement ifThe if statement serves for conditional performing of certain parts of the program. After the keyword if there must be a logical expression and the keyword then. The following block of statements is executed only if evaluation of the logical expression returns the value true. The block of statements ends in three ways:
This combination allows selection of a single variant in dependence on various conditions. The block of statements following the first fulfilled if or elsif condition is executed and possible following elsif conditions are not even tested. If no condition is fulfilled, the block of statements after the keyword else is executed (if present). If there is no else section, the program continues execution after the keyword end. Example: 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 = 'Error in reading of the channel'; elsif ErrorCode = 1 then ErrorMsg = 'Error in writing of the channel'; elsif ErrorCode = 2 then ErrorMsg = 'Communication error'; else ErrorMsg = 'Unknown error'; end; Remark: The conditional expression if is a generalization of conditional expression containing built-in function iif. If there are single assignments to the same data element after then and else, it is possible to replace conditional statement: if a < 100 then a = a + 1; else a = 0; end; with conditional expression: a = iif( a < 100, a + 1, 0 ); Statement switchThe switch statement divides the execution of the code on the basis of the value an expression. If the value of one expression is tested, like for instance the value of the variable ErrorCode in the previous example, the use of the switch statement is easier and its execution is faster then a series of commands if elsif. switch ErrorCode of case 0; ErrorMsg = 'Error in reading of the channel; case 1; ErrorMsg = 'Error in writing of the channel'; case 2; ErrorMsg = 'Communication error'; else ErrorMsg = 'Unknown error'; end; Similarly to the if statement, the else block is not mandatory. If the tested expression does not result in any of the values listed after the keywords case and the branch else is missing, no branch of the command switch will be performed. It is not necessary to write only one value after the keyword case, it is possible to list more values separated by commas, e.g. switch InputValue of case 0, 1, 2, 3; ... case 10, 100, 1000; ... end; Warning: Values after the keywords case must be constant (the are calculated at compile time). If it is necessary to compare the expression with general expressions, it is necessary to use the if elsif statement. Statement loopThe loop statement implements general loops. If you do not want this loop to be endless, you must finish it by the exit statement. The keyword exit is valid only inside the cycle loop (between loop and end keywords). There is a block of statements after the keyword loop ending with the keyword end, which will be cyclically executed. The loop statement can be used if the loop exiting condition should be evaluated inside the block of statements or if there are more places for leaving the loop. If the ending condition should be evaluated at the beginning or the end of the cycle, it is better to use while or repeat until loops. If the cycle is to be repeated for a certain number of loops known in advance, it is more convenient to use the for loop. If there is a loop located inside another loop, the exit statement will cause the inner loop is being left. It is also possible to use the continue statement inside the loop. This statement causes transferring of control at the beginning of the loop, that is jumping over the rest of the statements in the loop. In the case of more loops, the command continue relates to the inner loop. Example: 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 *) Remark: Keep in mid that if there are no pause, yield or wait statements within loop loop, next pass through the loop (including evaluation of the exit conditions) will not cause communication with I/O devices and channel values will not change. Statement whileThis statement is a special case of loop. There is the logical expression after the keyword while followed by the keyword do. If the logical expression is evaluated to true, the following block of statements is executed up to the keyword end. If the condition is not fulfilled even for the first time, the statements inside the loop will not be executed at all. Example: i = 0; while i < 10 do a = a + c; i = i + 1; end; (* while *) Statement repeat untilThe keyword repeat introduces the beginning of the block of statements. This block ends with the keyword until followed by the logical expression. After execution of the block of statements the program tests the condition after until and if the result is false it will start to execute the block of statements again. If at the beginning the logical expression has the value true the block of commands will be executed at least once. Example: i = 0; repeat a = a + b; i = i + 1; until i >= 10; Statement forThe for statement introduces the counted cycle. There is numeric variable after the keyword for, the assign symbol = and the numeric expression. Then, there is the keyword to and the next numeric expression followed by the keyword do and the block of statements of the body of the loop ending with the keyword end. When entering the cycle, the numeric variable will be set to the value determined by the expression following the equal sign. Then the value of this variable will be compared with the value of the expression following the keyword to. If the value is smaller or equal to this value, the body of the cycle will be executed up to the corresponding end keyword, the control variable will be increased by one and the comparison will be repeated. If the initializing value of the control variable is greater than the expression after to, the body of the cycle will not be executed. Example: for i = 1 to 10 do a[ i ] := i * i; end; for i = 3 to 2 do (* body of the cycle will not be executed *) end; It is not always required that the control variable should increase by 1. Sometimes it is necessary to increase the step or decrease the control variable. For this purpose, it is possible to define the step of the control variable after the keyword by before the keyword do. Example: 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; Warning: The expression defining the step after keyword by must always be constant. This means that it must not contain any variables or channels. Statement yieldAs has already been said, the whole procedure body is executed in a single time step, that is, with the same value of input/output channels. However, sometimes it is necessary to end the time step and enable the system to measure new values of input/output channels. The command yield will finish executing the procedure in the current time step and provide time for the system to activate all other instruments which are to be activated in the running time step. Executing the procedure will continue in the nearest system time step. This does not mean the next activation of the instrument, which implemented this procedure, but the nearest system tick. Example: while Level < 12.5 do (* waits for the achievement of the level *) yield; (* termination of the time step and making possible communication for changing the new value *) end; (* while *) Control1 = true; (* closing the valve *) pause 5; (* waiting for emptying the pump *) Control2 = false; (* switching off of the pump *) Statement pauseThis statement serves for stopping of the activity of the program for a certain period. There is a numeric expression stating the number of seconds of interruption of the program after the pause keyword. If the execution approaches the pause statement, it will stop the execution for the defined period and then continue by the next statement. The statement pause 0 does not stop the program, but it provides the system with time for possible servicing of other instruments (the same function is performed by the statement yield). Example: while Level < 12.5 do (* waiting for reaching the water level *) pause 0; (* can be substituted with yield *) end; Control1 = true; (* closing the valve *) pause 5; (* waiting for emptying of the pump *) Control2 = false; (* switching off of the pump *) Statement waitThis statement will interrupt the running of the program until the fulfillment of the condition (boolean expression) following the keyword wait. If the value of the expression is true the program continues with the next statement in the same time step. If the value of the expression is false the program provides the system with time for possible servicing of other instruments and performing th I/O operations and tests the condition again. An example of description of the yield statement would be possible to write more efficiently: Example: wait Level >= 12.5; (* waiting for the achievement of the level *) Control1 = true; (* closing the valve *) pause 5; (* waiting for emptying of the pump *) Control2 = false; (* switching off of the pump *) Statement commitThe commit statement serves for initiation of communication (writing and reading of channels) without the loss of the time step. The use of the commit statement has a similar effect to, e.g. the conditional command containing the channel in the condition — channels indicated for reading are read and channels with a written value are written into the driver. A similar effect to commit have for instance yield or pause statements, however, these statements will end the execution of the time step and will cause continuation of the algorithm in the next step. The commit statement only initiates communications and the algorithm continues within the same time step. Details of the function of the command commit are described in the sub-chapter Use of channels in procedures. Statement returnThe return statement has two forms depending on whether the respective procedure returns some value or not. If the procedure does not return value, the command return will cause termination of the execution of the procedure and return to the code, which called the respective procedure. Another call to the procedure will start it again from the beginning. procedure Divide( a, b : real; var c : real ); begin if b = 0 then c = 0; return; end; c = a / b; end_procedure; However, if the procedure returns some value (it may be called within the expression), then there is an expression whose value is passed as a return value of the procedure after the return keyword. procedure Divide( a, b : real; var c : real ): boolean; begin if b = 0 then return false; end; c = a / b; return true; end_procedure; Statement sendThere is a list of identifiers separated by commas indicating the names of instruments in the application (of course, the list can be short and contain only one instrument) after the keyword send. This command will ensure activation of the specified instrument(s) immediately after finishing the actual time step. If you want to activate the same instrument as the one implementing the given procedure, you can write its name or use the substitute identification self. The statement send self means activate itself in the nearest next system time step. This activation will perform all actions (communication with input/output devices and actualization of channel values is executed before it) and it practically equals to inserting another time step for all instruments listed in the send statement. There is an equivalent of the send statement in some other instruments in the form of the property receivers followed by the list of instruments. If more instruments have the same name and this name is mentioned in the send statement, all instruments with this name are activated. Remark: This statement is very powerful and can save a big amount of calculation performance. If, for example, some variables control only the visibility of panels, it is not necessary to time these panels to periodically check variable state, but they can be notified by the send statement only during their change. Example: Panel1Visible = (KeyInput = F1); Panel2Visible = (KeyInput = F2); send Panel1, Panel2; A similar procedure can also be used for instruments using the command receivers, e.g.: multi_switch or panel with active rectangles etc. Statement soundThis statement ensures the playing of sound files of WAV format. There is a string expression which represents the name of the file containing the sampled sound after the keyword sound. Of course, the functionality of this statement is conditioned on the presence of a supported sound card in the computer and the correct installation of their drivers in the Windows environment. If sound support is not correctly installed, the statement will be ignored. Example: switch Alarm of case 1; sound 'fire.wav'; case 2; sound 'explosion.wav'; else sound 'quiet.wav'; end; Statement moveThe move statement serves for mutual assignment of arrays or their parts. The use of move is always faster than assigning individual array elements e.g. using the program loop. Considerable speeding up is reached especially when transferring the channel array into the variable array. When assigning array elements into variables using the program loop, the program is always made to ask the system core to measure each element of the channel separately, because generally a new value of the measured element of the channel array can influence the index of the subsequent element of the array. Statement syntax begins with the keyword move, followed by the first element of the array, which is to be read (source). The first element of the destination array (target) is separated by comma. Another coma separates an expression for the number of elements, which are to be moved. Example: move source_array[ i ], destination_array[ j ], count; This command moves in total count elements of the array source_array starting from the element with the index i to the array destination_array from the element with the index j. Assign statementThe assignment statement can occur anywhere in algorithm notation. It has the form data_element = expression. The target data element must be writable (it cannot be e.g. constant). There is an arbitrary expression on the right side of the equal sign, whose type must correspond to the data element type on the left from the equal sign. A boolean expression must correspond to boolean data element. A string expression must correspond to string data element. If the data element is numeric (the type shortint, integer, longint, shortcard, cardinal, longcard, shortreal, real), it is possible to assign result of any numeric expression to it. Warning: If the type of data element is not able to take the result of the expression, the information can be lost. The decimal part of the number is cut away when converting to integers; big numbers can be clamped at the highest significant places. Example: (* numeric expressions *) 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 (* logical expressions *) result = value <= ( a + 1 ); ExitCondition = true; (* string expressions *) Message = 'Error: ' + ErrorString; Title = 'Demonstration panel'; Remark: Users often use the assign statement only with numeric data elements and expressions, they hesitate to use it for example with logical values (with exception of simple assignment of true or false). It is often possible to meet constructions of the type: if a = b then result = true; else result = false; end; This notation works correctly, but the same effect can be achieved easily and quickly by one assignment: result = a = b; The occurrence of two characters = one by one can be misleading, but their meaning is quite exactly defined. The first = has the meaning of the assignment, the second = is logical comparison. Calling of proceduresPrevious examples were concentrated on how to write procedures, how to control their execution, how to work with local data and how to pass parameters into and from procedures. The final thing you need to know is how to call procedures. It is actually a closed issue, because procedures are called from other procedures, and therefore notation of this calling simultaneously belongs to description of the procedure's internal parts. If procedure calling was written in the previous text, it was in the form Go(); This calls the procedure Go() of the same instrument or section which calls it. If the respective instrument or section does not have the Go() procedure, an error in compiling will occur. If you want to call a procedure of another instrument or section, you have to write the name of the instrument (section) implementing this procedure before procedure name. It has already been said that procedures cannot be implemented outside any instrument or section. InstrumentName.Go(); The procedure can be also called using instrument pointers. InstrumentPointer->Go(); If the pointer doesn't point to an existing instrument, or points to an instrument that doesn't have the Go() procedure, a critical error will occur and the execution of the application will be halted. If you are calling a procedure of an instrument which is in another module, the module name must also be before the instrument name ModuleName.InstrumentName.Go(); Remark: It was always necessary to write the instrument name before the procedure name in Control Web 2000 and previous versions. Even in cases when the code of one procedure calls another procedure of the same instrument, it was necessary to write the instrument name before the called procedure name. However, this is not very comfortable and it can also cause problems if you rename the instrument or if you copy the procedure into another instrument — then it is difficult to decide if the given calling should be redirected to another instrument or left. Therefore, it was necessary to introduce the keyword self. This keyword in each procedure always substitutes the instrument in which the procedure is implemented. Example: meter m1; ... procedure Go(); var a = real, 0; begin ... m1.SetValue( a ); (* calls the procedure SetValue for itself *) self.SetValue( a ); (* works in the same manner as previous calling, but also functions after renaming of the instrument *) ... end_procedure; ... end_meter; The keyword self is no longer necessary and simple indication of the name of the procedure works identically. Several facts must be taken into account when calling procedures:
CommentsComments can be used anywhere in procedure notation. The comment is introduced by a comment parenthesis (* and finished by the opposite parenthesis *). Whatever is stated in these brackets is jumped over during compilation. Thus, comments can be used not only for explaining the meaning of procedures or individual statements, but also for eliminating parts of code. Comments can be nested in Control Web. The depth of nesting is evaluated and the comment ends with the last commentary parenthesis. Example: i = 0; (* comment with the depth 1 (* depth of comments is 2 *) the first commentary bracket is still valid *) i = i + 1; Of course, comment parentheses can be used wherever in the text notation of the Control Web application. With regard to the dual development environment, however, comments cannot be preserved in graphical mode, for the application source text totally lapses. Procedures are an exception in this respect and store comments also when changing over between text mode and graphical mode. Always (un)true expressions in conditionsSyntax of many statements requires the presence of a logical expression, which influences the behavior of the statement (e.g. the while statement requires a logical expression, which states whether the loop is performed or not). Restriction of the range of data types, about which we spoke in previous Chapter, may principally influence the fulfillment (or non fulfillment) of conditional logical expressions. Consider the following example: var c : cardinal; begin c = 10; while c >= 0 do ... c = c - 1; end; ... The while loop in this example will be infinite, because the data element of the type cardinal cannot contain a number smaller than 0. Similarly, the condition: var a : shortint; begin if a > 200 then ... will never be fulfilled because the maximum number in the data element of type shortint is 127 and can never be higher than 200. There are many examples of expressions, which are permanently true or false also in other examples repeat until, for to do, wait, ...). The compiler in some cases (e.g. if the data element is compared with the constant) detects that the expression is always true or false and writes a warning (not error in compiling) during compilation. But it is not possible to decide the unchangeability of the condition during compiling in many cases, because it results from the logic of the application. Therefore, it is necessary to remember this possibility during debugging of the application or, better, during its design. Hint: The compiler performs many optimizations at application startup only; optimizations cannot be performed during togging of IDE mode or test compilations. One form of optimization is calculation of constant sub-expressions. If there are (non-)fulfilling conditions consisting of the data element compared with the constant expression in the application, a warning about permanent (non-)fulfillment of the condition appears only at application startup, not during IDE mode togging. Event and user ProceduresAs you already know, each procedure can be called from another procedure. However, sometimes it is necessary to call at least some procedures by another mechanism, otherwise the calling system would never relive, no one would start with the first calling. Of course, such a mechanism exists and was mentioned in the introductory part of this chapter. The Control Web does not have any main program, instead it activates pieces of the code on the basis of actions of user or system events. Procedures are also classified into this context and many procedures are called when certain conditions are fulfilled — of course, if you program these procedures. All procedures implemented by you within each instrument can be subdivided into two groups:
The specific instrument of the Control Web can activate (call) an event procedure only if it recognizes it. Event procedure must have a signature corresponding to event signature defined by particular instrument to be called on event occurrence (procedure signatures were described above). It thus has to have a name (case-sensitive!) and parameter types corresponding to the event procedure definition. Then it is recognized by the instrument and called upon the occurrence of the corresponding event. Example: switch sw1; ... procedure OnOutput( state : boolean ); begin (* this procedure will be called on each input action of the instrument switch *) ... end_procedure; ... end_switch; Signatures of event procedures and conditions of their calling are described with the respective instrument documentation. If you open the inspector above the instrument and click on the Procedures tab, the list of all event procedures of the respective instrument will appear. Moreover, you have possibility to complete any user procedures, but as was already said, you yourself must take care of their calling. There is a group of event procedures which are common to all instruments. It does not mean that all instruments will call all procedures from this group. However, if the given procedure has sense for the given instrument, the instrument will call it. These basic event procedures will be described in more detail when describing the key event procedure OnActivate. OnActivate() event procedureOnActivate is an exceptional event procedure. The first reason is that it is the only procedure always called when the instrument is activated. Practically all instruments carry out their activities within their activation. If you for example define an expression for the instrument meter, which will be calculated and the result will be assigned to some data element, this calculation, assignment and possible redrawing of the meter instrument with the new value is carried out within the activation. Within its activation the alarm instrument tests the exceeding of limit values and the archiver instrument records data, the instrument draw animates its drawing, etc. Only instruments, which react to e.g. user actions or communication events, perform some activity outside activation intervals. For example the control instrument writes the value into the data element after user action without being activated. Similarly the httpd instrument, working as a WWW server, sends data upon the client's request, without waiting for activation. Instruments can be activated in several ways. Activation differs considerably in real-time applications and in data driven applications. In real-time applications the instrument can be activated by these impulses:
In data driven applications periodic timing is permitted only for some instruments, whose activity depends on exact keeping of the time interval, e.g. for the instrument archiver or pid_regulator. Activation by exceptions from both instruments and drivers is maintained. However, a new impulse is added:
If you declare a procedure with the following signature: procedure OnActivate(); this procedure will be called during each activation, more exactly before each activation of the instrument. The procedure OnActivate has the possibility to test and modify data elements influencing instrument operation. The second reason for the exceptionally of procedure OnActivate is the fact that this procedure can correspond to several signatures, even if only one procedure is in question. The reason for this exception is simple. In some situations it can be useful to know the reason for procedure activation. Then you can write the procedure OnActivate in the form: procedure OnActivate( ByTimer, ByInstrument, ByDriver, ByData : boolean ); In this form the procedure OnActivate receives four logical parameters stating the reason for activation. The parameter ByData can be true only in data driven applications. The code of the procedure OnActivate can then test the cause of activation and perform an operation only e.g. during periodical activation or when called by exception from the driver etc. Coincidence of several reasons for activation can occur in certain situations. For example, an instrument activates another instrument and within the same time step there will also be time for the instrument's periodical timing. Then the instrument is activated only once, the logical value true is set in two parameters — ByTimer and ByInstrument. However, Control Web can provide much more information to the instrument than just the reason for activation during activation. Still, it is not possible to pass all data as independent parameters. That is why this information is passed in the form of the so-called bitmask. The bitmask represents one number whose individual bits (binary digits, having either the value 0 or 1) bear logical values, the same as logical parameters. To find out whether the corresponding bit of the respective number is set, it is possible to use the function bitget. If, for example, you need to execute some action in the procedure OnActivate only during the activation of the instrument from the driver may you write the procedure OnActivate in two manners: procedure OnActivate( ByTimer, ByInstrument, ByDriver, ByData : boolean ); begin if ByDriver then (* this code is executed only during the activation from the driver *) end; end_procedure; procedure OnActivate( ActivateMode : longcard ); begin if bitget( ActivateMode, 2 ) = 1 then (* this code is also executed only during the activation from the driver *) end; end_procedure; Although both ways of notation will work in the same manner, the second way is a little more complicated than the first one. So why the third form of OnActivate exists? Because you can find more information by testing of other bits in the parameter ActivateMode than the reason for activation. The meaning of individual bits is as follows:
If you use the only parameter ActivateMode in the OnActivate procedure, you may find out, for example, when the application is in time slip or when it has just started. Summary:
Timing of proceduresUnless stated otherwise, the whole procedure algorithm (from the beginning to the end) is executed in one time step, that is, with identical measured values on the channels. This should be realized especially when programming loops. It is possible to use some of the delay instructions pause, yield or wait. These instructions will cause interruption of the procedure for a certain time, or until a condition is fulfilled. The condition is tested in the following system time step (and not in the time step of the instrument implementing the procedure!) and if it is fulfilled, the procedure continues from the statement following the condition, and not again from the start. In this case, the values of read channels are measured again. More details about relations between calling and timing of procedures and delay instructions can be found in the sub-chapter Recursive calls and delay statements. Warning: It is possible to implement an infinite (or at least a very long) loop very simply. It is necessary to consider that such a loop blocks passing to the next step and the whole application waits for its completion. Use of channels in proceduresIf an instrument evaluates some expression within its time step (e.g. meter) and this expression contains channels, then the Control Web core will ensure measurement of these channels before the evaluation of the expression (of course, if it is possible during the time defined by the channel parameter timeout). A similar situation will occur if channels are used in the procedure OnActivate. Let's have defined variables a1, a2 and a3 and the channels c1, c2 and c3. Then the following part of the code: a1 = c1; a2 = c2; a3 = c3; will be executed after the system reads values of channels c1, c2 and c3. Remark: What will happen if one channel is used more times within one time step? The core will read it only once in each time step. In the case of repeated use within the same time step, the channel is considered measured and is not read again. Repeated measurement of the same channel will enforce the delay statement pause or yield. These instructions will cause continuation of the procedure within another system time step. The rule that no channel is measured more than once in one time step is kept. What will happen if the code looks as follows: if c1 > 0 then a2 = c2; else a3 = c3; end; There are all three channel used in the code notation, but the construction of the algorithm rules out all three channels being necessary at once. It will be necessary to use the channel c2 or c3 according to the value of the channel c1, but not all three at the same time. In this case it appears to be an unimportant discussion because the measurement of one channel may not considerably cause delay of the application. However, if there are not individual channels, but thousands or tens of thousands of channels in individual branches, unnecessary communications may represent a serious problem. For these reasons, the execution of procedures is optimized in the following manner: If branching in the program is found and it is necessary to read some channel(s) for making a decision, the algorithm specifying which channels are necessary to be read will stop and the currently marked channels are read. This will enable evaluation of the conditional expression specifying which part of the procedure will be executed. Searching for the necessary channels will continue only in the selected branch of the procedure. It is sometimes useful to enforce communication with peripheral devices (reading of marked channels and sending of written channels) within an algorithm. As already described, the simple occurrence of some conditional command (e.g. if or while) will ensure it. At the same time, the delay instruction (yield, pause or wait) will cause communication, but with the interruption of the algorithm and continuation in the next system time step. However, if it is necessary, e.g. to write more values in a sequence order and, at the same time, not to lose the time step, it is possible to use the statement commit. The occurrence of this statement in the program does not influence execution of the algorithm, it will only cause forced communication with the device within one time step. cmd = 'init'; commit; (* the string 'init' will be recorded in the channel *) cmd = 'start'; commit; (* the string 'start' will be recorded in the channel *) ... Two consecutive writes would cause overwriting of the string if we did not use the commit statement. Warning: There is a principal difference between yield and commit statements, demonstrated in the following examples. Let's consider part of the code a1 = c1; commit; a2 = c1; commit; The commit statement will cause reading of the channel c1. The assignment after commit again requires reading c1, but because the algorithm continues in the same time step, the system will not read the channel c1 irrespective of the next commit, because this channel has already been read. a1 = c1; yield; a2 = c1; yield; In this case, the channel c1 will be really read 2× because the algorithm will be divided into more time steps. This restriction is not valid for writing, which is performed the same number of times as the number of occurrences of the algorithm. Recursive calls and delay statementsIt has already been emphasized that procedures cannot be called recursively, meaning that if a procedure is unfinished and has not yet run to the end, it cannot be explicitly recalled. Under normal circumstances the system guarantees that no other procedure can be started until the currently running procedure is accomplished. Still, there exist several possibilities how recursive calling can occur, for instance, a procedure called from another procedure calls again the calling procedure. However, the calling procedure is waiting for the finishing of calling of another procedure and is thus unfinished. Such calling will be rejected and a warning will be written into Log Window. It is important to realize that the recursion does not have to be so simple, the called procedure will call another procedure, which can call again another procedure and the calling procedure can be called again as late as on another level. This situation is also not permitted. The situation with recursive calling will be made much more complicated if the delay statements pause, yield or wait are used in procedures. Such instructions will interrupt execution of the procedure and will bring execution back into the calling procedure. The called procedure will remain in unfinished status and cannot be called again until it runs to the end. The only exception is the implicitly called procedure OnActivate which in this case continues in testing of the condition for running and possibly continues from the place of interruption. If the procedure OnActivate waits for some delay statement and some driver activates the respective instrument, the procedure OnActivate consumes this activation for testing of the condition of the further run, not for starting from the beginning and possible testing of the reason for activation! If some delay statement is used in the procedure OnActivate and, at the same time, the procedure decides according to the condition of the activation, then according to time relations there can be omission of treating of the activation. Frequent problems can originate especially when using delay statements in event procedures. If execution of an event procedure is interrupted and the given event occurs again, recursive calling will happen again. To avoid the above-mentioned problems with recursive calling, OCL has a possibility to restrict the use of delay instructions.
Because this restriction can be very unpleasant in many cases, you may take responsibility for not calling procedures recursively and cancel this restriction by setting the parameter independent_procedure_execution in the settings section to true. It is possible to set this parameter in the tab Data Inspectors — System in the integrated development environment. You can use delay instructions and call the procedure OnActivate without restriction in this case. Instrument local dataWe have already mentioned the advantage of using local variables and constants within the procedure. One of the key advantages is that local data are visible only from inside the procedure, they do not mess among global data elements visible from all instruments. However, if you define more procedures, which somehow cooperate with the instrument, the mechanism of variables’ locality will make them invisible even among procedures of the same instrument. The problem is that procedures within one instrument need to share variables invisible to other instruments. This can be to a certain extent compensated for by storing necessary information in local variables of the selected procedure and passing it to other procedures as parameters. This process, besides being cumbersome, will fail if event procedures, whose parameter list (signature) is determined in advance, also need to access such variables. Therefore it is possible to declare instrument local variables and constants in blocks const, var and static as within the body of the procedure. Thus, all constants and variables declared within the instrument can be seen by all procedures of the given instrument, but they are invisible from outside. Unlike the body of the procedure, where the block defining constants and variables is automatically terminated by the beginning of the other block, the instrument blocks const, var and static must end in the respective keywords end_const, end_var and end_static. Local data elements declared within the instrument can be used also in notation of instrument expressions. However, if these are used in an expression or procedure, the block where these elements are declared must precede their use. Variables and constants declared in this way are invisible from other instruments. Warning: Whereas within the body of the procedure the compiler monitors the sequence order of blocks and the block var cannot be located after the block of commands introduced by the keyword begin, it is not so in the case of the body of the instrument. Thus, if there exist instrument local variables with a name identical to the global data element, then the order of declaration matters. For example, the application 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; will work well at first startup, although during compilation of expression after the expression keyword the local variable a does not exist. Therefore, the global variable a initialized to 1 will be used for division in the expression. However, after automatic generation of the body of the meter instrument (e.g. after switching the development environment from graphic mode to text mode), the block of variables will be generated first meter m1; var a : real; end_var; expression = ch1 / a; end_meter; and during next start the expression after the expression keyword will be compiled with another (local) variable. Moreover, this variable is initialized to the default value 0 and it will cause the application runtime fatal error during division. Therefore, if you write blocks of variables and constants in the text mode, write them first (the graphical editor always generates these blocks first). What then is the process when searching for a data element of a certain name? The first step depends on the fact whether the data element is used in the expression inside the body of the instrument (e.g. expression = a + b for the meter instrument) or inside the instrument procedure. The next two steps are the same.
Take into consideration that only names of data elements are evaluated during compilation and their attributes (kind and type) are ascertained according to names. Even if a global and local data element are absolutely different in their kind and type, they must always be used in compliance with the lowest declaration. Consider the following example: ... 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 used as global channel of the type real *) end_meter; string_display s1; var a : string; end_var; expression = a; (* a used as a local variable of the type string, global channel a is not available *) procedure Go(); var a : array [0..1] of boolean; begin ... if a[ 0 ] then (* a is the local array of variables of the type boolean *) ... end; ... end_procedure; end_string_display; end_instrument; Note that on the local level (of both the instrument and the procedure) only constants or variables can be declared. Channels, scheduled elements and other complex data element kinds can be declared on the global level only. Like with local variables in the procedure, variables declared within the block var will always be initialized at the beginning of activation of the instrument, variables from the block static will be initialized only on application startup and then their content will be changed only by assignments irrespective of the activation (timing) of the instrument. If the value of the instrument variable changes in a strange way to a certain value at application runtime, make sure it is located in the block static and not in the block var. Native proceduresThere are many features of individual instruments, which can be represented as properties only with difficulty or inefficiently, even if they are expressed e.g. as an expression. These features of individual instruments are therefore made accessible as so-called native procedures. Remark: In older versions of Control Web and Control Panel, which did not allow definition of procedures within any instrument, OCL was implemented only in the form of the instrument program and native procedures were the only type of procedures in the system. Both definition and implementation of native procedures is exclusively an issue of appropriate instrument class and has absolutely no influence on its binary compatibility with the rest of the system. Thus, instruments’ native procedures can be added any time, without compromising the backward compatibility. Only when an application program calls a procedure, which is not available (e.g. in an attempt to compile a more recent program using older versions of instrument DLLs etc.), the compilation error is announced and the program must be modified. However, the convention allows only to add new native procedures and preserve names, types of arguments (signature), as well as semantics of the already existing native procedures. Keeping to this convention will ensure upward compatibility of all applications, meaning that the more recent version of the Control Web is able to compile all older application programs. The typical example of using native procedures is panel visibility control. Each panel has the possibility to define the expression controlling its visibility. visibility = condition_of_visibility; However, should the panel permanently evaluate the visibility condition with periodical timing or after changing a data element stated in the expression, it is an absolutely inappropriate waste of time. That is why such panels are usually not timed but only activated by another instrument which changes the visibility condition. However, showing and hiding panels can be solved in a very smart manner by means of OCL native procedures. For instance it suffices to call native procedures to switch the visibility of two panels: panel1.Hide(); panel2.Show(); Many properties are only available through native procedures. For example, a simple call panel1.MoveTo( 100, 200 ); will move the instrument panel1 to coordinates 100, 200 of display pixels. From the calling code point of view, the native procedures behave in the same manner as any other procedures, only they are not declared anywhere in the application source code. Each instrument only implements a proper set of native procedures. The list of native procedures, together with their description, can be found within the instrument palette described in Chapter Integrated development environment. Therefore, there are three categories of procedures within the Control Web, which differ in some properties:
Kinds of procedures and their calling SummaryOCL is a powerful tool helping wherever it is necessary to describe more complex behavior of the application. The basic OCL features are:
|