ANSI Standard PL/B Language and Visual PL/B
CALL / ROUTINE
NOTE TO OUR READERS
This web resource is written for our own use. But we feel strongly that the PL/B language should be shared with the software community. So feel free to use this at will!
BUT PLEASE...
if you find errors or omissions or have a better way to do something. TELL US! Dialog helps us all. Send e-mail to:
support@mmcctech.com
CALL / CALLS
ROUTINE LROUTINE and PROCEDURE
LOADMOD
SPECIAL NOTE
Before even starting this section it is important to explain a confusing bit of terminology. In the PL/B community the term LOADMOD is frequently used to describe a separately compiled external sub-program. In truth, LOADMOD is a PL/B verb used to load an external program into memory without executing the program. You must pay attention to the context when you hear or see "LOADMOD". For this discussion, the term will only be used to decribe the PL/B Verb case.
SUBROUTINES
Subroutines are a staple of the programming art. They are implemented in one form or another in all languages. The concept is to write a general purpose routine to handle a specific function and then to use that routine from many places in the program. For example, one routine could be written to find the day of the week for any date. That routine could then be used from many places.
Subroutines are commonly implemented for use in a CALL/RETURN manner. The subroutine can take many forms and be stored in many ways. When it is needed, a CALL instruction is written in the main code and that transfers control to the subroutine. The subroutine does it's thing then issues a RETURN to go back to the instruction following the CALL in the main routine.
In most languages, CALL/RETURN structures are implemented with a "stack". Stacks are common elements of all operating systems and languages. The concept is that data can be PUSHED onto a stack. Data is later retrieved by POPPING the top entry off the stack. It's a last in, first out scenario. Many items can be pushed onto the stack. Popping an item out gets the most recent. For CALL/RETURN, the address of the instruction following the CALL is pushed onto the stack. A RETURN instruction pops out the top entry of the stack and transfers control to that element. Events can be "nested" by multiple CALLS without returns. Each RETURN goes back to the routine that called it.
In-line vs External subroutines
There are two common methods for writing subroutines. The first, and most common, is the "in-line" subroutine. In this form, the subroutine code appears in the source code for the program and is compiled as part of the overall program.
"External" subroutines are separately compiled programs which are not a direct part of the main program. External routines are called in much the same way as an in-line routine but the similarity ends there. The in-ilne routine is simply a code snippit; the external is a full program with it's own start, body, and ending.
There are two benefits of in-line routines. First, the routine is slightly more efficient because it is part of a single program. Second, there may be less programming needed because the in-line routine has access to all of the variables and other components of the main program.
The disadvantage of an in-line routine is that it is less likely to become a re-usable component which would be used in many programs.
The solution to that is to write the in-line routine and store it as a separate code module which is included in a main program with the INCLUDE statement. As an example, MMCC has a standard date conversion module that was wirtten as an INCLUDE unit. Each program that needs date handling will INCLUDE this module. The programmer can then CALL any of the standard sub-routines within that INCLUDE unit.
The disadvandate to an INCLUDE unit is that when it is changed; the programmer must remember to re-compile every program that includes the module.
External routines have the advantage of being independent of the programs which call them. In the date routine example above, when one changes the date logic then every program which includes that module must be recompiled to get the change. With an external, only the external program itself must be recompiled. Immediately, every program which calls the external will get the change the next time it runs.
INCLUDE routines are nice Because of their simplicity. If the routine is unlikely to be changed, and if the routine is small, then including the code in the mainline is good. If, on the otherhand, the routine will likely be changed, or if the routine is large, it may be better implemented as an external program.
PL/B Subroutine techniques
Simple Subroutines
PL/B provides a number of techniques for implementing subroutines. The simplest is just a
CALL to an execution label. Control is transferred to that label in the program where processing continues. When (or if) a
RETURN statement is encountered, control is returned to the statement following the CALL.
There is no defined communication of data between this type of CALL and the sub-routine being called. Both routines are part of the same program and both have access to the same memory space. The sub-routine may modify data in memory which the calling routine may later use. For example:
WORKING_DATE DIM 8
DAY_OF_WEEK DIM 10
MOVE "20010501" to WORKING_DATE
CALL GET_DAY_OF_WEEK
DISPLAY DAY_OF_WEEK
rest of program.....
GET_DAY_OF_WEEK
figure out the day of the week
from the WORKING_DATE. Put the
results in DAY_OF_WEEK
RETURN
There are two forms of the simple CALL instruction: CALL and CALLS.
Both forms work exactly the same way. The only difference is how the subroutine is named:
CALL USING
A second calling method provides for the communication of data elements between two routines. The same CALL and CALLS instructions are used, but additional information is communicated by including the
USING phrase:
CALL GET_AGE USING MEMBER_BIRTHDATE, MEMBER_AGE
CALL GET_AGE USING MEMBER_JOINDATE, YEARS_IN_CLUB
In this case, the called subroutine does not specifically know or care where the data to be used is stored. Instead it will be given a pointer to the variables named in the USING phrase. Those variables can be used in any way that the subroutine desires.
In the example, the first using variable could be a date from which to calculate elapsed years (the age). The second variable could be calculated by the subroutine and then used by the calling routine on return.
The benefits of the USING construction can be substantial. In a simple CALL, both routines use the same, pre-defined memory space. To accomplish the same objectives of the example using simple calls would require something like this:
MOVE MEMBER_BIRTHDATE to WORKING_DATE
CALL GET_AGE
MOVE WORKING_AGE to MEMBER_AGE
MOVE MEMBER_JOINDATE to WORKING_DATE
CALL GET_AGE
MOVE WORKING_AGE to YEARS_IN_CLUB
WHEN IS "USING" USED?
The key to the USING phrase is in the way the subroutine is defined. For simple CALL routines, all that is needed for the subroutine is an execution label, some code, and a RETURN:
GET_AGE calculation code
more code, etc.
RETURN
Subroutines which are to be called with USING, must be specifically defined using either ROUTINE, LROUTINE or PRODECURE. For example:
GET_AGE LROUTINE INPUT_DATE, OUTPUT_AGE
calculation code
more code, etc.
RETURN
Subroutines of this type may be coded as part of the main-line program or they may be separately compiled routines which are outside of the calling program.
ROUTINE, LROUTINE and PROCEDURE
These three formats can be used
almost interchangebly. The exceptions are
- A PROCEDURE is written with a USING clause and a (L)ROUTINE is not:
date_routine PROCEDURE USING INPUT_DATE, OUTPUT_AGE
date_routine ROUTINE INPUT_DATE, OUTPUT_AGE
- (L)ROUTINE arguments (parameters) can be either pointers or defined variables.
PROCEDURE arguments must be defined variables.
- PROCEDURES may be called with literals in the calling string. (L)ROUTINES may not:
CALL DATE_ROUTINE USING "20011214", OUTPUT_AGE
- PROCEDURES and ROUTINES may be external routines.
LROUTINES are "local" and appear within the main program unit.
DEFINED VARIABLES vs POINTERS
Defined variables are the traditional method of creating variables in a program. You write something like:
MEMBER_BIRTHDATE DIM 8
The compiler allocates memory space for the variable and defines it according to your definition.
A pointer, on the other hand does not carry any definition information nor does it allocate memory. Instead it simply tells the compiler that the variable is a DIM or a FORM type and that the actual memory location and size will be provided at run time.
The objective of a pointer is to abstract the actual data variable from the definition of the variable. What that means is that you can, for example, define a batch of variables then refer to them in a subroutine by reference only.
A POINTER variable is a regular DIM or FORM and is defined almost like any other variable. The only difference is that where a normal variable has the number of bytes following the DIM/FORM or has an initial value, the POINTER has a carat (^).
When calling a PROCEDURE or (L)ROUTINE, the CALL is written using the specific defined variable names. The PROCEDURE or (L)ROUTINE, on the other hand, is written using different variable names and those variables are defined as POINTERs. At the time of the call, the POINTERS are given the address of the actual data and the routine works on the actual data. For example:
MEMBER_BIRTHDATE DIM 8
MEMBER_AGE FORM 3
JOINED_DATE DIM 8
YEARS_IN_CLUB FORM 3
CALL GET_AGE USING MEMBER_BIRTHDATE, MEMBER_AGE
CALL GET_AGE USING JOINED_DATE, YEARS_IN_CLUB
.... more program code....
WORKING_DATE DIM ^
CALCULATED_AGE FORM ^
GET_AGE ROUTINE USING WORKING_DATE, CALCULATED_AGE
... calculate age from date...
RETURN
PASSING ARGUMENTS
When using POINTERS, all processing is performed on the specific variables in memory. There is only a single copy of the variable and all code points to the same location. When a CALL is performed using a pointer, the subroutine is given the memory address of the variables listed in the CALL and it works on those specific items.
PROCEDURES or (L)ROUTINES can also be called using
defined variables. In this case there are individual memory locations for each of the variables. The unique element is that PL/B will internally MOVE the data in the sending variable to the corresponding receiving variable. On return the contents of the receiving variables are (optionally) passed back to the sending variable.
The effect of this is that the program works much as it would if you did a simple call and moved the variables yourself. For example:
MOVE MEMBER_BIRTHDATE to WORKING_DATE
CALL GET_AGE
MOVE WORKING_AGE to MEMBER_AGE
THE RULES:
-
With a PROCEDURE which uses defined variables, rather than pointers, the calling arguments are internally moved to the passed arguments. On return the passed arguments are internally moved back to the calling arguments.
- With a (L)ROUTINE which uses defined variables, rather than pointers, the calling arguments are internally moved to the passed arguments. On return the passed arguments are NOT moved back to the calling arguments.
- With pointer variables, there is only ONE variable in memory and that variable is used by both the calling program and the called routine.
- If literals are used as arguments they are not passed back on return.
- Literals cannot be used with pointer variables.
EXTERNAL ROUTINES
PL/B allows subroutines to be
local, part of the mainline program, or
external, separately compiled and free stadning routines.
Local routines have the advantage of more simplicity and slight preformance advantages. Since the subroutine is part of the mainline program it requires little special linkages or support.
External routines are entirely separate programs. When the "external" is called, it will be loaded into memory and executed much like a local routine. Unless you specifically unload the external, it will remain in memory and be available if called again.
External routines are a bit more complex because they require all of the general surrounding code that any program would require. You have to define the working variables, provide for startup and shutdown code, and other code.
The main advantage of an external program is that a single external can service any number of mainline programs. If the external is changed and recompiled, the all programs which call it benefit from the changes but those programs do not have to be recompiled.
Externals can be called using pointers, literals or defined variables just the same as an internally coded subroutine would be called.
WRITING AN EXTERNAL
An external is written much as any other program would be written. The unique element is that it is not executed like other programs. Instead, it is CALLED from another program and it is loaded on-the-fly.
Normal programs are executed and processing starts with the first executable statement in the program. Externals may have multiple entry points. When they are called and loaded, the CALL specifies the entry point to be used.
Externals are required to have at least one entry point. They can have more than one. An entry point is simply a PROCEDURE or a ROUTINE statement with an optional USING phrase. For example a single external could contain these subroutines:
GET_AGE PROCEDURE USING INPUT_DATE, OUTPUT_AGE
.... processing code ....
RETURN
GET_DAY_OF_WEEK PROCEDURE USING INPUT_DATE, OUTPUT_DAY_NAME
.... processing code ....
RETURN
Note that externals may NOT include defined COMMON storage. You can use LABELED COMMON.
CALLING AN EXTERNAL
Externals are called just as a local ROUTINE with one exception: The external program's entry point must be named. The external is coded as:
"{programname};{entrypoint}"
External programs may be explicitely defined within the mainline program and called using a variable name much as a CALLS would be used. Or the call may be coded explicitely using a literal. For example, the following two calls are effectively the same:
GET_AGE EXTERNAL "DATESUB;GET_AGE"
CALL GET_AGE USING MEMBER_BIRTHDATE, MEMBER_AGE
CALL "DATESUB;GET_AGE" USING MEMBER_BIRTHDATE, MEMBER_AGE
OTHER NOTES
-
PUBLIC Label table: This is used for runtime control. Although it's mentioned in the Sunbelt manuals as though it is significant to the programmer, it's not something that the you are aware of and you cannot to control it.
-
LOADMOD (load a module) is an instruction for pre-loading an external module into memory without actually calling any routine in the module. It is not necessary to do this because the external module will be automatically loaded if necessary when it is called.
The only instance when LOADMOD is necessary is when the external module defines GLOBAL memory that is used in the mainline before any function in the module is called. You can define a GLOBAL variable in the mainline with the "%%" clause which reserves the name but does not set the size. A module can then be loaded which sets the size.
-
Non-literal arguments passed to an external subroutine may be defined in the subroutine as either actual data items or pointers when using ROUTINE. Literal parameters passed to an external subroutine must be defined in the subroutine as actual data items when using ROUTINE.
Memory Usage
In February 2002 a question was posted to the Sunbelt web board concerning mamory usage by externals. The response from Sunbelt was as follows:
"An external module uses about 2K more in memory than simply including the file in the original program. Other than that there is very little difference.
"A benefit that has not been mentioned is dynamic loading of the external. If you do not use the LOADMOD statement, but simply CALL or CALLS a function in the external, then the external is not loaded until needed. The benefit is that you may not actually need all of the externals referenced in your program for any given execution. Some may be there for special cases that don't arise every time. So if the external is not needed for a given run then it is never loaded. If there are a lot of externals, then possibly many would never be used."
v1.10