DocumentationEndless PL/I for OpenVMSUser Manual  ·  ESS-PFVUM-001

Chapter 11

Using PL/I in the Common Language Environment

The Endless PL/I V3.9 for OpenVMS VAX and Endless PL/I V5.0 for OpenVMS Alpha compilers are part of the OpenVMS common language environment. This environment defines certain calling procedures and guidelines that allow you to call routines written in different languages or prewritten system routines from PL/I. You can call any of the following routine types from PL/I:

  • Routines written in other OpenVMS languages

  • OpenVMS Run-Time Library routines

  • OpenVMS system services

  • OpenVMS utility routines

Note

The definitions of routine, procedure, and function used throughout this chapter are somewhat different from standard PL/I terminology. These definitions are consistent with the OpenVMS system routines documentation and apply only throughout this chapter.

A routine is a closed, ordered set of instructions that performs one or more specific tasks. Every routine has an entry point (the routine name), and optionally an argument list. Procedures and functions are specific types of routines: a procedure is a routine that does not return a value, whereas a function is a routine that returns a value by assigning that value to the function's identifier.

System routines are prewritten OpenVMS routines that perform common tasks, such as finding the square root of a number or allocating virtual memory. You can call any system routine from your program, provided that PL/I supports the data structures required to call the routine. The system routines used most often are OpenVMS Run-Time Library routines, utility routines, and system services. System routines, which are discussed later in this chapter, are documented in detail in the VMS Run-Time Library Routines Volume, the OpenVMS Utility Routines Manual, and the OpenVMS System Services Reference Manual.

11.1. OpenVMS Calling Standard

The OpenVMS Calling Standard describes the concepts used by OpenVMS languages for invoking routines and passing data between them. It also describes the differences between the OpenVMS VAX and OpenVMS Alpha parameter passing mechanisms. The OpenVMS Calling Standard specifies the following attributes:

  • Register usage

  • Stack usage

  • Function value return

  • Argument list

The following sections discuss these attributes in more detail. The OpenVMS Calling Standard also defines such attributes as the calling sequence, the argument data types and descriptor formats, condition handling, and stack unwinding. These attributes are discussed in detail in the Introduction to VMS System Routines.

11.1.1. Register and Stack Usage

The OpenVMS Calling Standard defines registers and their uses, as listed in Table 11.1, “VAX Register Usage” and Table 11.2, “AXP Register Usage”.

Table 11.1. VAX Register Usage
RegisterUse
PCProgram counter
SPStack pointer
FPCurrent stack frame pointer
APArgument pointer (when necessary)
R1Environment value (when necessary)
R0, R1Function value return registers

Table 11.2. AXP Register Usage
RegisterUse
PCProgram counter
SPStack pointer
FPFrame pointer for current procedure
R25Argument information register

R16 to R21,

F16 to F21

Argument list registers
R0Function value return register

By definition, any called routine can use registers R2 through R11 for computation, and the AP register as a temporary register.

Figure 11.1. The Call Stack (OpenVMS VAX)
The Call Stack (OpenVMS VAX)

In the OpenVMS Calling Standard, a stack is defined as a LIFO (last-in/first-out) temporary storage area that the system allocates for every user process. The system keeps information about each routine call in the current image on the call stack. Then, each time you call a routine, the system creates a structure on this call stack, known as the call frame. The call frame for each active procedure contains the following:

  • A pointer to the call frame of the previous routine call. This pointer corresponds to the frame pointer (FP).

  • The argument pointer (AP) of the previous routine call.

  • The storage address of the point at which the routine should return; that is, the address of the instruction following the call to the current routine. This address is called the program counter (PC).

  • The contents of other general registers. Based on a mask specified in the control information, the system restores the saved contents of these registers to the calling routine when control returns to it.

Figure 11.1, “The Call Stack (OpenVMS VAX)” illustrates the call stack and several call frames. Procedure A calls procedure B, which calls procedure C.

When a routine completes execution, the system uses the frame pointer in the call frame of the current routine to locate the frame of the previous routine. The system then removes the call frame of the current routine from the stack.

11.1.2. Return of the Function Value

A function is a routine that returns a single value to the calling routine. The function value is the value returned to the caller. According to the calling standard, a function value may be returned as either an actual value or a condition value that indicates success or failure.

11.1.3. The Argument List

The OpenVMS Calling Standard also defines a data structure called the argument list. You use an argument list to pass information to a routine and receive results.

On OpenVMS Alpha systems, an argument list is formed using registers R16 to R21 or F16 to F21 and a collection of quadwords in memory (depending on the number and type of the arguments.

On VAX systems, an argument list is a collection of longwords in memory that represents a routine parameter list and possibly includes information for the return of a function value, if a function returns a value that is not suitable for return in R0 or R0/R1. Figure 11.2, “Structure of an OpenVMS VAX Argument List” shows the structure of a typical OpenVMS VAX argument list.

Figure 11.2. Structure of an OpenVMS VAX Argument List
Structure of an OpenVMS VAX Argument List

The first longword must be present; this longword stores the number of arguments (the argument count: n) as an unsigned integer value in the low byte of the longword. The remaining 24 bits of the first longword are reserved for use by VSI and should be zero. The longwords labeled arg1 through argn are the actual parameters, which can be any of the following:

  • An argument passed by reference. When an argument is passed by reference, the address of the argument is present in the argument list.

  • An argument passed by descriptor. When an argument is passed by descriptor, the address of a data structure (called a descriptor) describing the argument is present in the argument list.

  • An argument passed by value. When an argument is passed by immediate value, the actual value of the argument is present in the argument list.

The argument list contains the parameters that are passed to the routine. Depending on the passing mechanisms for these parameters, the forms of the arguments contained in the argument list vary. For example, if you pass three arguments, the first by value, the second by reference, and the third by descriptor, the argument list contains the value of the first argument, the address of the second, and the address of the descriptor of the third. Figure 11.3, “Example of an OpenVMS VAX Argument List” shows this argument list.

Figure 11.3. Example of an OpenVMS VAX Argument List
Example of an OpenVMS VAX Argument List

11.2. Parameter-Passing Mechanisms

To pass data between routines that are not written in the same OpenVMS language, you must specify how you want that data to be represented and interpreted. You do this by specifying a parameter-passing mechanism. The three general parameter-passing mechanisms and their keywords and abbreviations in PL/I are as follows:

  • By reference-REFERENCE (REF)

  • By descriptor-DESCRIPTOR (DESC)

  • By value-VALUE (VAL)

The parameter-passing mechanisms are intended for use only in calling non-PL/I routines. External routines written in PL/I should be called with the default mechanisms.

The following sections describe the arguments in terms of PL/I data types, dummy arguments created (if any), parameter-passing mechanism conventions, and attributes to define the manner in which parameters are to be passed. Remember that when PL/I creates a dummy argument, modifications, if any, that the called procedure makes to the dummy argument are not accessible to the caller.

11.2.1. Passing Parameters by Reference

When you pass a parameter by reference, the PL/I compiler passes the address at which the actual parameter value is stored. In other words, your routine has access to the parameter's storage address. Therefore, when you manipulate and change the value of this parameter, the changed value overwrites the previous value of the parameter. Thus, when you pass a parameter by reference, any changes that you make to the value of the parameter in your routine are reflected in the calling routine as well, provided a dummy argument is not created.

You can pass an argument of any data type by reference. This mechanism is the default used by PL/I for all arguments except character strings or arrays with nonconstant extents, and unaligned bit strings. Note that bit-string variables must have the ALIGNED attribute to be passed by reference. In general, the parameter descriptor for an argument to be passed by reference need specify only the data type of the parameter.

For example, the Read Event Flags (SYS$READEF) system service requires its first argument to be passed by value and its second argument to be passed by reference. This PL/I procedure can be declared as follows:

DECLARE SYS$READEF ENTRY (FIXED BINARY(31) VALUE,
                          BIT(32) ALIGNED);

Note

Most OpenVMS routines have declarations in PLI$STARLET; it is therefore not normally necessary to declare them yourself.

When this procedure is invoked, the second argument must be a variable declared as BIT(32) ALIGNED. PL/I passes the argument by reference. The following PL/I code example with Figure 11.4, “Argument Passing by Reference” illustrates argument passing by reference.

DECLARE FLAGS BIT(32) ALIGNED;
DECLARE SYS$READEF ENTRY (
        FIXED BINARY(31) VALUE,
        BIT(32) ALIGNED;
     ⋮
CALL SYS$READEF(4, FLAGS);
Figure 11.4. Argument Passing by Reference
Passing a Pointer Value as an Argument (VAX)

The data types in the parameter descriptors of all output arguments must match the data types of the written arguments.

11.2.1.1. Using the ANY Attribute

When you write a parameter descriptor for a non-PL/I procedure, you can specify the ANY attribute without the VALUE attribute to describe an argument that is to be passed by reference. The argument can be of any addressable data type known to PL/I. For example, the SYS$READEF service can be specified as follows:

DECLARE SYS$READEF ENTRY (FIXED BINARY(31) VALUE, ANY);

The second parameter descriptor in the ENTRY attribute indicates that the second argument is to be passed by reference to the procedure SYS$READEF and that it can have any data type. When you specify ANY for an argument to be passed by reference, you cannot specify data type attributes. Note that if you specify the VALUE attribute in conjunction with the ANY attribute, PL/I passes the argument by value.

The ANY attribute is especially useful when you must specify a data structure as an argument. You need not declare the structure within the parameter descriptor, only the ANY attribute.

11.2.1.2. Dummy Arguments for Arguments Passed by Reference

When PL/I passes an argument by reference, it places either the address of the actual argument or the address of a dummy argument in the argument list of the called procedure. PL/I creates a dummy argument in the following cases:

  • When the written argument is a constant or an expression

  • When the written argument is enclosed in parentheses

  • When the data type of the written argument does not match the data type or precision specified in the parameter descriptor

In the last case listed above, PL/I issues an informational or warning message and, for scalar arguments, creates a dummy argument of the data type of the parameter. It places the address of this dummy argument in the argument list. If the argument is an aggregate, PL/I issues an error message; it does not create a dummy argument for an array or for a structure.

In creating a dummy argument, PL/I performs the following conversions:

Data Type of

Written Argument

Data Type of

Dummy Argument

BIT (unaligned)BIT ALIGNED

FIXED BINARY (p,0) or

FIXED DECIMAL (p,0)

FIXED BINARY (31)
CHARACTER VARYINGCHARACTER NONVARYING

In all other cases, the data type of the dummy argument is the same as the data type of the written argument.

11.2.1.3. Using Pointer Values for Arguments Passed by Reference

When an argument is passed by reference, PL/I places the address of the actual argument in the argument list. This address can be interpreted as a pointer value. In fact, you can explicitly specify a pointer value as an argument for data to be passed by reference. For example:

DECLARE SYS$READEF (ANY VALUE, POINTER VALUE),
        FLAGS BIT(32) ALIGNED;
CALL SYS$READEF (4, ADDR(FLAGS));

At this procedure invocation, PL/I places the pointer value returned by the ADDR built-in function directly in the argument list.

Figure 11.5, “Passing a Pointer Value as an Argument (VAX)” illustrates the argument list for the following example. Note that the actual argument list in this example corresponds to the argument list shown previously in Figure 11.4, “Argument Passing by Reference”. Note also that Figure 11.5, “Passing a Pointer Value as an Argument (VAX)” applies to the VAX platform only. Please see OpenVMS Calling Standard for equivalent information for the OpenVMS Alpha platform.

DECLARE FLAGS BIT(32) ALIGNED;
DECLARE SYS$READEF (ANY VALUE, POINTER VALUE),
     ⋮
CALL SYS$READEF(4,ADDR(FLAGS));
Figure 11.5. Passing a Pointer Value as an Argument (VAX)
Passing a Pointer Value as an Argument (VAX)

11.2.1.4. Passing Arrays to a FORTRAN Routine by Reference

In FORTRAN, arrays must always be passed by reference; the array's extents are, by custom, passed as separate arguments. The REFERENCE attribute provides a convenient way to express an array parameter for FORTRAN. For example:

FTNARRAY: PROCEDURE(X);
DECLARE SUM ENTRY ((*) FLOAT REFERENCE, FIXED BINARY(31))
                    RETURNS (FLOAT);

DECLARE (S, X(*)) FLOAT;

S = SUM(X, DIM(X));

SUM is a FORTRAN routine that sums the elements of a one-dimensional array of floating-point numbers. Its second parameter is the number of elements in the array.

11.2.2. Passing Parameters by Descriptor

When you pass a parameter by descriptor, the PL/I compiler passes the address of a descriptor to the called routine. A descriptor is a data structure that contains the address of a parameter, along with other information such as the parameter's data type and size.

For some structure parameters, PL/I passes an abbreviated form of descriptor that contains only essential position and extent information. In these cases, the address of the abbreviated descriptor is placed in the argument list for the called routine. The use of an abbreviated descriptor is transparent to you.

PL/I normally passes parameters by descriptor in the following cases:

  • When a parameter descriptor specifies a character string with an asterisk length or an array with asterisk extents

  • When a parameter descriptor specifies an unaligned bit string or an array or structure consisting entirely of unaligned bit strings

  • When a parameter descriptor specifies a structure containing any strings or arrays with asterisk extents

For example, PL/I passes the arguments associated with the following parameter descriptors by descriptor:

DECLARE UNSTRING ENTRY (CHARACTER(*)),
        TESTBITS ENTRY (BIT(3)),
        MODEST ENTRY (1,
                        2 CHARACTER(*),
                        2,
                          3 BIT(3),
                          3 BIT(3));

Figure 11.6, “Argument Passing by Descriptor” illustrates a character-string descriptor and shows how a character-string argument is passed by descriptor. This example illustrates the type of character-string descriptor used by system services; this descriptor does not contain additional information required by other classes of descriptors.

DECLARE NAME CHARACTER(5);
       STATIC INITIAL ('ORION');
DECLARE SYS$SETPRN ENTRY
       (CHARACTER(*));
     ⋮
CALL SYS$SETPRN(NAME);
Figure 11.6. Argument Passing by Descriptor
Argument Passing by Descriptor

11.2.2.1. Passing Character Strings

When you declare a non-PL/I routine that requires a character-string descriptor for an argument, specify the parameter descriptor as CHARACTER(*). For example, the Set Process Name system service (SYS$SETPRN) requires the address of a character-string descriptor as an argument. You can declare this service as follows:

DECLARE SYS$SETPRN ENTRY (CHARACTER(*) OPTIONAL);

When a parameter is declared as CHARACTER(*), its written argument can be one of the following:

  • A character-string constant or expression

  • A fixed-length character-string variable

  • A varying character-string variable or a variable declared CHARACTER(*) VARYING

For any of these arguments, PL/I constructs a character-string descriptor and places its address in the procedure's argument list.

11.2.2.2. Passing Varying Character Strings

If you specify a varying character-string argument for a parameter declared as (CHARACTER(*), the PL/I compilers issue a warning message, construct a fixed-length character-string dummy argument, and create a character-string descriptor for the dummy argument.

For an input argument, as in the example of the SYS$SETPRN service, the dummy argument is acceptable. To suppress the warning message during compilation, enclose the argument in parentheses. For example, if NAME is a variable declared with the CHARACTER VARYING attributes, you can specify it as an argument to the SYS$SETPRN system service like this:

CALL SYS$SETPRN ((NAME));

The parentheses around the argument NAME force PL/I to create a dummy argument. The compiler does not issue a warning about a nonmatching parameter and argument.

For a non-PL/I routine that returns a character string to a variable, however, you cannot use a varying character string for an argument. If the actual output argument is declared as VARYING and the parameter descriptor specifies CHARACTER(*), PL/I creates a dummy argument and the actual argument is not modified. Thus, for output character strings passed by character-string descriptor, you must choose one of the following:

  • Specify a fixed-length character-string variable to receive the string and a fixed-point binary variable to receive the length of the string.

  • Construct an actual character-string descriptor and pass the name of the character-string descriptor as an argument. This technique is described in Section 11.2.2.5, “Passing an Actual Descriptor”.

In either case, you must include in your program the statements necessary to determine the length of the string returned. For example, the SYS$ASCTIM system service returns a character-string time value to a character-string descriptor and returns the length of the string to a fixed-point binary variable. These two arguments can be declared as follows:

DECLARE TIME CHARACTER(63),
        TIME_LENGTH FIXED BINARY(15);

After the call to this procedure, the following statement might output the equivalence name returned:

PUT LIST ('Time is ',SUBSTR(TIME,1,TIME_LENGTH));

The PUT statement uses the SUBSTR built-in function to obtain the length of the string returned in the variable TIME_LENGTH by SYS$ASCTIM.

11.2.2.3. Using ANY CHARACTER(*)

You can use the ANY CHARACTER(*) declaration to declare parameters for routines that can handle both fixed- or varying-length strings for a single parameter. The declaration allows either VARYING or NONVARYING strings to be passed without the creation of a dummy argument. Essentially, all OpenVMS system routines, with the exception of the system services and Librarian Utility routines, can accept string parameters of this type.

Note that routines written in other languages do not allow strings to be passed using this method by default. The routine being called must explicitly use LIB$ANALYZE_SDESC (or an equivalent routine) for this declaration to work correctly.

You can use the ANY CHARACTER(*) attribute as shown in the following example:

%INCLUDE $STSDEF;
DECLARE FIXED_STRING CHAR(22),
        VARYING_STRING CHAR(80) VARYING;

DECLARE LIB$DATE_TIME ENTRY(
            ANY CHARACTER(*))
            RETURNS (FIXED BINARY(31));

STS$VALUE = LIB$DATE_TIME(FIXED_STRING);
STS$VALUE = LIB$DATE_TIME(VARYING_STRING);

In both of these cases, the string contains the output value, since no dummy argument is required.

11.2.2.4. Using ANY DESCRIPTOR

The ANY and DESCRIPTOR attributes can be used together for routines that can process any valid data type descriptor. Routines of this type are few; however, LIB$CVT_DX_DX and the routine DTR$COMMAND in the DEC DATATRIEVE layered product callable interface allow any valid data type descriptor. For these routines, declarations should be as follows:

DECLARE LIB$CVT_DX_DX ENTRY(
                ANY DESCRIPTOR,
                ANY DESCRIPTOR,
                FIXED BINARY(15) OPTIONAL TRUNCATE)
              RETURNS(FIXED BINARY(31));

DECLARE DTR$COMMAND ENTRY(
                1 LIKE DTR_ACCESS_BLOCK,
                ANY CHARACTER(*),
                ANY DESCRIPTOR LIST TRUNCATE);

11.2.2.5. Passing an Actual Descriptor

To pass an actual descriptor as an argument, you must take the following steps; the keyed numbers correspond to the callout numbers in Example 11.1, “Writing a Character-String Descriptor”.

❶

In the parameter descriptor for the called procedure, declare a structure in the format of a descriptor for the argument that is to be passed by descriptor, specify ANY in the parameter descriptor, or use the REFERENCE built-in function to override the parameter declaration at the point of the call.

❷

Declare a structure variable in your program whose members and attributes correspond to the structure declared in the parameter descriptor for the argument.

❸

Assign values to the members of the structure variable providing the required information. For a character-string descriptor, you must provide the length of the string and a pointer to the variable containing its value.

❹

Pass the name of the structure variable as an argument in the procedure invocation.

The Set Process Name system service (SYS$SETPRN) shown in Example 11.1, “Writing a Character-String Descriptor” requires a text name string to be passed by descriptor. The structure variable NAME_DESC is a character-string descriptor; its members describe the length and location of the character-string variable NEWNAME. The value of NEWNAME is the actual argument passed to the procedure. Note that the call in this example is equivalent to the example shown in Figure 11.6, “Argument Passing by Descriptor” of passing an argument by descriptor.

Example 11.1. Writing a Character-String Descriptor
DECLARE SYS$SETPRN ENTRY (CHARACTER(*));❶

DECLARE 1 NAME_DESC,
          2 NAME_LENGTH FIXED BINARY (31),❷
          2 NAME_ADDRESS POINTER;

DECLARE NEWNAME CHARACTER (5) STATIC INITIAL ('ORION');
NAME_DESC.NAME_LENGTH = LENGTH(NEWNAME);❸
NAME_DESC.NAME_ADDRESS = ADDR(NEWNAME);❸

CALL SYS$SETPRN(REF(NAME_DESC));❹

Note that this example can be simplified if SYS$SETPRN is declared as follows:

DECLARE SYS$SETPRN ENTRY (ANY);

All other variables would be the same as in Example 11.1, “Writing a Character-String Descriptor”, although the use of the REFERENCE built-in function could be omitted.

In most cases, the first method is preferable because it allows the declarations in PLI$STARLET to be used consistently; the only cases in which special handling is required is for those calls that require it.

11.2.3. Passing Parameters by Value

When you pass a parameter by value, the PL/I compilers pass a copy of the parameter's value to the routine instead of passing its address. Because only a copy of the parameter's value is passed, the routine does not have access to the storage location of the parameter. Therefore, when you pass a parameter by value, any changes that you make to the parameter value in the called routine do not affect the value of that parameter in the calling routine.

For an argument to be passed by value, you must use the VALUE attribute in a parameter description. The following declaration of the external entry VHF illustrates a declaration for an external routine that receives its parameter by value.

DECLARE VHF ENTRY (FIXED BINARY(31) VALUE);

You can also define PL/I procedures that receive arguments by value. To do this, you must specify the VALUE attribute in the declaration of the parameter. For example, the corresponding definition of the routine VHF would be as follows:

VHF PROCEDURE (LENGTH);
⋮
DECLARE LENGTH FIXED BINARY(31) VALUE;

The following code example and Figure 11.7, “Argument Passing by Immediate Value” illustrate argument passing by value.

DECLARE VHF ENTRY(
        FIXED BINARY(31) ANY VALUE);
    ⋮
CALL VHF(4);
Figure 11.7. Argument Passing by Immediate Value
Argument Passing by Immediate Value

Arguments that can be passed by value are limited to the following data types, which can be expressed in 32 bits.

  • FIXED BINARY(m), where m is less than or equal to 31

  • FLOAT BINARY(n), where n is less than or equal to 24

  • BIT(p) or BIT(p) ALIGNED, where p is less than or equal to 32

  • ENTRY

  • OFFSET

  • POINTER

No other data types can be passed by value. Note that when ENTRY VALUE is specified in a parameter descriptor, only the entry points of external routines may be passed by value. A complete entry value for an internal routine requires two longwords, one for the parent frame and one for the 32-bit entry-point address.

When you specify the VALUE attribute in a parameter descriptor, you can specify the ANY attribute instead of declaring any data type attributes. For example, the declaration of VHF can appear as follows:

DECLARE VHF ENTRY (ANY VALUE);

At the time of the procedure's invocation, PL/I converts the written argument as needed to create a longword dummy argument.

11.2.3.1. Dummy Arguments for Arguments Passed by Value

For arguments to be passed by value, PL/I always creates a dummy argument directly in the argument list for the called procedure. If the parameter descriptor is specified with the ANY and VALUE attributes, dummy arguments are created with the following data types:

Data Type of

Written Argument

Data Type of

Dummy Argument

FIXED BINARY, (p,0) or

FIXED DECIMAL (p,0)

FIXED BINARY (31)
BIT or BIT ALIGNEDBIT (32) ALIGNED
ENTRYENTRY
OFFSETOFFSET
POINTERPOINTER

If a parameter descriptor is specified as VALUE with a particular data type (as opposed to being specified as ANY), a dummy argument of that data type is always created, and the written argument is assigned to the dummy. The written argument must be valid for conversion to the data type specified in the corresponding parameter descriptor.

11.2.4. Special Parameter Attributes

PL/I provides the LIST, OPTIONAL, and TRUNCATE parameter attributes.

  • The LIST attribute indicates that the specified list can be invoked with a list of arguments for the last parameter.

  • The OPTIONAL attribute indicates that optional arguments will not be specified in the entry invocation.

  • The TRUNCATE attribute indicates that the argument list may be truncated at a specified position.

Each of these attributes is described in the following sections.

11.2.4.1. LIST Attribute

Although most system routines and procedures require a specific number of arguments, some routines accept an unspecified number of optional arguments. To declare these routines in a PL/I program so that you can invoke them with differing numbers of arguments, you must declare the last parameter with the LIST attribute. The last parameter descriptor given in the ENTRY attribute is used for extra arguments.

The Formatted ASCII Output system service (SYS$FAO) is an example of a procedure that has a variable-length argument list. It can be declared as follows:

DECLARE SYS$FAO ENTRY (CHAR(*), FIXED BINARY(15) OPTIONAL,
                    CHAR(*), ANY VALUE LIST TRUNCATE);

This parameter descriptor specifies only four arguments. When SYS$FAO is invoked with more than four arguments, PL/I uses the parameter descriptor of the last parameter (ANY VALUE) to pass all the additional arguments. If any argument that will be specified is not to be passed by value, you must specify a parameter descriptor for the argument in the declaration.

Note

The LIST attribute is valid only for parameter descriptors.

11.2.4.2. OPTIONAL Attribute

Some PL/I and non-PL/I routines with fixed-length argument lists accept optional arguments and provide a default action if no value is specified for the optional argument. When an optional argument is not specified, its corresponding argument list longword is filled with a value of zero passed by immediate value.

In PL/I , you can omit the specification of an optional argument in a written argument list as long as you enter the correct number of commas to ensure that the argument list will have the correct number of longwords. You can indicate that you are not specifying an optional argument in either of the following ways:

  • Omit the argument from the argument list.

  • If the argument is to be passed by immediate value, specify a zero for the written argument.

In the case of trailing arguments all of which have the OPTIONAL attribute, you do not need to specify the commas, although you might choose to do so to add clarity for people who read your source code. But if you do not specify commas, the compiler will automatically create null parameters for trailing formal parameters that have the OPTIONAL attribute, ensuring that any requirement for a minimum length argument vector is met, for instance in the case of VMS system services.

For example, an argument list that has three optional arguments can be written as follows:

(,,)

The called procedure must detect and interpret the optional parameters in the argument list. The following example illustrates optional arguments omitted from an argument list:

DECLARE SYS$ASCTIM ENTRY (
                       FIXED BINARY(15) OPTIONAL,
                       CHARACTER(*),
                       ANY OPTIONAL,
                       BIT(1) ALIGNED VALUE OPTIONAL),
        TIME_STRING CHARACTER(24);
⋮
CALL SYS$ASCTIM(,TIME_STRING,,);
⋮
CALL SYS$ASCTIM(,TIME_STRING);

This call to the service SYS$ASCTIM specifies only the second argument; the argument list contains four arguments. When you specify a null argument, as in either of the cases above, PL/I always places a zero in the argument list passed to the called procedure.

11.2.4.3. TRUNCATE Attribute

Some routines may allow an argument list to be truncated in a specified location. For example:

DECLARE LIB$GET_INPUT ENTRY (ANY CHARACTER(*),
                          ANY CHARACTER(*) OPTIONAL TRUNCATE,
                          FIXED BINARY(15) OPTIONAL TRUNCATE);

This declaration of the Get Line from SYS$INPUT routine (LIB$GET_INPUT) specifies that the argument list may vary in length. In fact, LIB$GET_INPUT has three parameters, but the last two parameters are optional.

When you invoke such a routine omitting trailing arguments, you do not have to account for all the arguments in the procedure invocation argument list. For example, the LIB$GET_INPUT routine can be invoked with trailing arguments omitted:

CALL LIB$GET_INPUT(GET_STRING);

The variable GET_STRING is specified for the first argument. The other two arguments are not specified. Unlike the case with the OPTIONAL attribute, the compiler will not create blank entries in the argument vector passed to the called program.

11.2.5. Summary of Rules for Passing Parameters

You can specify the passing mechanism-reference, value, or descriptor-for a parameter in three ways:

  • Use an attribute.

  • Use a built-in function.

  • Use a default.

The following rules and examples explain these three alternatives.

  • Specify the REFERENCE, VALUE, or DESCRIPTOR attribute explicitly when you declare the parameter. For example:

    CHAR(*) REFERENCE
    (*) FIXED BINARY REFERENCE
    FIXED BIN(31) VALUE
    BIT(32) ALIGNED VALUE
    FLOAT BIN(23) VALUE
    ENTRY (FIXED BIN) RETURNS(FIXED BIN) VALUE
    FIXED BINARY DESCRIPTOR
    FIXED DECIMAL(10,2) DESCRIPTOR
    CHAR(10) DESCRIPTOR
  • Use the REFERENCE, VALUE, or DESCRIPTOR built-in function at the call, overriding whatever passing mechanism would otherwise be used. For example:

    CALL E (REF(10));
    CALL FOO (REF(E));
    CALL I (VALUE(10));
    CALL FOO (VALUE(E));
    CALL E (DESC(10));
    CALL FOO (DESC(E));

    The built-in function completely overrides the parameter declaration and consequently no type conversion is performed. The built-in function evaluates a numeric constant as FIXED BINARY(31,0) rather than FIXED DECIMAL. For example, REF(10) passes a longword containing the binary value 10 by reference.

  • Use the default passing mechanism for the data type of the parameter. By default, parameters are passed by reference except for unaligned bit strings and parameters containing asterisk (*) extents, which are passed by descriptor. (Value is never the default passing mechanism.) For example, parameters of the following types are all passed by reference:

    CHAR(10) VARYING
    BIT(22) ALIGNED
    (10) FIXED DEC
    (20,-1:7) FLOAT
    CHAR
    1,2 (10) FIXED, 2 (8) FLOAT

    Parameters of the following types are passed by descriptor, by default, because they contain asterisk (*) extents or an unaligned bit string:

    CHAR(*)
    BIT(10) UNALIGNED
    (*) FIXED BIN
    1,2(*) FIXED DEC, 2(10) FLOAT

Restrictions

For passing by reference, a parameter must be addressable. Thus, entry constants, file constants, and unaligned bit strings cannot be passed by reference.

For passing by value, a parameter cannot occupy more than 32 bits and must be one of the following types:

  • FIXED BINARY(m), where m is less than or equal to 31

  • FLOAT BINARY(n), where n is less than or equal to 24

  • BIT(p) or BIT(p) ALIGNED, where p is less than or equal to 32

  • ENTRY

  • OFFSET

  • POINTER

11.3. OpenVMS Run-Time Library Routines

The OpenVMS Run-Time Library is a library of prewritten, commonly used routines that perform a wide variety of functions. These routines are grouped according to the types of tasks they perform, and each group has a prefix that identifies those routines as members of a particular OpenVMS Run-Time Library facility. Table 11.3, “Run-Time Library Facilities” lists all the language-independent run-time library facility prefixes and the types of tasks each facility performs.

Table 11.3. Run-Time Library Facilities
Facility PrefixTypes of Tasks Performed
DTK$DECtalk routines that are used to control Digital's DECtalk device
LIB$Library routines that obtain records from devices, manipulate strings, convert data types for I/O, allocate resources, obtain system information, signal exceptions, establish condition handlers, enable detection of hardware exceptions, and process cross-reference data
MTH$Mathematics routines that perform arithmetic, algebraic, and trigonometric calculations
OTS$General purpose routines that perform tasks such as data type conversions as part of a compiler's generated code
SMG$Screen management routines that are used in designing, composing, and keeping track of complex images on a video screen
STR$String manipulation routines that perform such tasks as searching for substrings, concatenating strings, and prefixing and appending strings

The file SYS$LIBRARY:PLI$STARLET.TLB defines the entry points for these routines.

11.4. OpenVMS System Service Routines

System services are prewritten system routines that perform a variety of tasks, such as controlling processes, communicating among processes, and coordinating I/O.

Unlike the OpenVMS Run-Time Library routines, which are divided into groups by facility, all system services share the same facility prefix (SYS$). These system services are logically divided into groups that perform similar tasks. Table 11.4, “System Services” describes these groups.

Table 11.4. System Services
GroupTypes of Tasks Performed
ASTAllows processes to control the handling of ASTs
Change ModeChanges the access mode of particular routines
Condition HandlingDesignates condition handlers for special purposes
Event FlagClears, sets, reads, and waits for event flags, and associates with event flag clusters
InformationReturns information about the system, queues, jobs, processes, locks, and devices
Input/OutputPerforms I/O directly, without going through VAX RMS
Lock ManagementEnables processes to coordinate access to shareable system resources
Logical NamesProvides methods of accessing and maintaining pairs of character string logical names and equivalence names
Memory ManagementIncreases or decreases available virtual memory, controls paging and swapping, and creates and accesses shareable files of code or data
Process ControlCreates, deletes, and controls execution of processes
SecurityEnhances the security of OpenVMS systems
Time and TimingSchedules events, and obtains and formats binary time values

The file SYS$LIBRARY:PLI$STARLET.TLB defines the entry points for these routines.

11.5. OpenVMS Utility Routines

Utility routines are prewritten system routines that you can use to perform a variety of tasks such as sorting and library maintenance. Table 11.5, “OpenVMS Utilities” lists some of the commonly used OpenVMS utilities.

Table 11.5. OpenVMS Utilities
Utility PrefixTypes of Tasks Performed
ACLEDIT$Callable interface to the Access Control List Editor
CLI$Command line parsing
LBR$Library manipulation
PSM$Print Symbiont modification
SMB$Job controller and symbiont process interface
SOR$Sort/Merge procedures
TPU$Callable interface to the DEC Text Processing Utility

The file SYS$LIBRARY:PLI$STARLET.TLB defines all of the entry points for these routines.

11.5.1. OpenVMS SORT/MERGE Routines

The OpenVMS SORT Utility (SORT) provides a range of sorting capabilities and options. You can use the SORT program in two ways:

  • At the DCL command level, you can invoke the DCL command SORT. By specifying input and output files and sorting options, you can perform sorting functions interactively from the terminal.

  • In a PL/I program, you can call SORT routines.

The SORT routines can be used in either of two sequences:

  • You can sort all of the records in an entire file. This process is similar to calling SORT from the DCL command level to sort a file.

  • You can process the records in a file, pass them to SORT one at a time, and request SORT to merge and sort the records and then to return them one at a time. This process allows you to perform some intermediate operation on each record before initiating the actual sorting of the records.

Examples of calling SORT routines are included in Section 11.8, “Examples of Calling System Routines” of this chapter.

11.6. Calling Routines

The basic steps for calling routines are the same whether you are calling a routine written in PL/I, a routine written in some other OpenVMS language, a system service, a utility routine, or an OpenVMS Run-Time Library routine. The following sections outline the procedures for calling non-PL/I routines.

11.6.1. Determining the Type of Call

Before you call an external routine, you must first determine whether the call should be a procedure call or a function call. You should call a routine as a procedure if it does not return a value. You should call a routine as a function if it returns any type of value.

11.6.2. Declaring an External Routine and Its Arguments

To call a routine written in another OpenVMS language, or to call a system routine, you need to declare the routine as an external procedure or function and to declare the data types and passing mechanisms of its arguments. Note that arguments can be either required or optional.

You should include the following information in an external routine declaration:

  • The name of the routine

  • The data types of all the routine parameters

  • The passing mechanisms for all the routine parameters, provided that the routine is not written in PL/I

11.6.3. Calling the External Routine

Once you have declared a routine, you can call it. To call a procedure, you use the CALL statement. To call a function, you can use a function invocation either in an assignment statement or as an argument in another routine call. In either case, you must specify the name of the routine being called and all parameters required for that routine. Make sure the data types and passing mechanisms for the actual parameters you are passing coincide with those you declared earlier, and with those declared in the routine.

If you do not want to specify a value for a required parameter, you can pass a null argument by inserting a comma (,) as a placeholder in the argument list. If you use any passing mechanism other than the default, you must specify the passing mechanism in the CALL statement or the function call.

At this point, the routine being called receives control, executes, and then returns control to the calling routine at the next statement after the CALL statement or function call.

11.6.4. Calling System Routines

The basic steps for calling system routines are the same as those for calling any external routine. However, when calling system routines, you need to provide some additional information that is discussed in the following sections.

11.6.4.1. Declaring System Routines

The default PL/I text library PLI$STARLET.TLB contains declarations for all the system routines as external entries. The text module names are most often the same as the routine entry points. Thus, to include the declaration of a system service you are going to use, you specify a %INCLUDE statement as in this example:

%INCLUDE SYS$TRNLNM;

The PL/I compilers, by default, locate the module SYS$TRNLNM in PLI$STARLET.TLB during compilation.

In PLI$STARLET.TLB, any system service that has a wait for event flag version is found in the same module as the version without the wait. Thus, to get the definition of any wait (“W”) form, you need to use the base (nonwait) form of the service in the %INCLUDE statement. For example, to get the definition for SYS$GETJPIW, you should use the following:

%INCLUDE SYS$GETJPI;

Global symbol definitions for the entry vectors of all system services are located in the default system object module library, STARLET.OLB, in SYS$LIBRARY. When you link a PL/I program, the linker searches this library by default.

11.6.4.2. System Routine Arguments

All the system routine arguments are described in terms of the following information:

  • OpenVMS usage

  • Data type

  • Type of access allowed

  • Passing mechanism

OpenVMS usages are data structures that are layered on the standard OpenVMS data types. For example, the OpenVMS usage mask_longword signifies an unsigned longword integer that is used as a bit mask, and the OpenVMS usage floating_point represents any OpenVMS floating-point data type. Table 11.6, “PL/I Implementation of OpenVMS Usages” lists all the OpenVMS usages and the PL/I statements you need to implement them. (The callout numbers in Table 11.6, “PL/I Implementation of OpenVMS Usages” are keyed to the list following the table.)

Table 11.6. PL/I Implementation of OpenVMS Usages
OpenVMS Data TypeDeclaration
access_bit_names
1 ACCESS_BIT_NAMES(32),
  2 LENGTH FIXED BINARY(15),
  2 DTYPE FIXED BINARY(7) 
    INITIAL((32)DSC$K_DTYPE_T),
  2 CLASS FIXED BINARY(7)
    INITIAL((32)DSC$K_CLASS_S), 
  2 CHAR_PTR POINTER;  ❻
 The length of the LENGTH field in each element of the array should correspond to the length of a string of characters pointed to by the CHAR_PTR field. You can use the constants DST$K_CLASS_S and DST$K_DTYPE_T by including the module $DSCDEF from PLI$STARLET or by declaring them GLOBALREF FIXED BINARY(31) VALUE.
access_mode

FIXED BINARY(7)

(The constants for this type- PSL$C_KERNEL, PSL$C_EXEC, PSL$C_SUPER, PSL$C_USER-are declared in module $PSLDEF in PLI$STARLET.) ❶

addressPOINTER
address_range(2) POINTER ❻
arg_list
1 ARG_LIST BASED,
  2 ARGCOUNT FIXED BINARY(31),
  2 ARGUMENT (X REFER (ARGCOUNT)) 
    POINTER;  ❻
 If the arguments are passed by value, it may be appropriate to change the type of the ARGUMENT field of the structure. Alternatively, you can use the POSINT, INT, or UNSPEC built-in functions/ pseudovariables to access the data. X should be an expression with a value in the range 0-255 at the time the structure is allocated.
ast_procedurePROCEDURE or ENTRY ❷
booleanBIT ALIGNED ❶
byte_signedFIXED BINARY(7)
byte_unsignedFIXED BINARY(7) ❸
channelFIXED BINARY(15)
char_stringCHARACTER(n) ❹
complex_number
(2) FLOAT BINARY(n) or 
1 CPLX,
  2 REAL FLOAT BIN(n), 
  2 IMAG FLOAT BIN(n); 
(See floating_point for values of n.)
cond_valueSee module STS$VALUE in PLI$STARLET ❻
contextFIXED BINARY(31)
date_timeBIT(64) ALIGNED ❺
device_nameCHARACTER(n) ❹
ef_cluster_nameCHARACTER(n) ❹
ef_numberFIXED BINARY(31)
exit_handler_block
1 EXIT_HANDLER_BLOCK BASED,
  2 FORWARD_LINK POINTER,
  2 HANDLER POINTER,
2 ARGCOUNT FIXED BINARY(31),
  2 ARGUMENT (n REFER 
    (ARGCOUNT)) POINTER;  ❻
 Replace n with an expression that will yield a value between 0 and 255 at the time the structure is allocated. 
fabSee module $FABDEF in PLI$STARLET ❻
file_protectionBIT(16) ALIGNED ❶
floating_point

FLOAT BINARY(n)

The values for n are as follows:

1 <= n <= 24 - F floating or S floating (AXP)

25 <= n <= 53 - D floating or T floating (AXP)

25 <= n <= 53 - G floating (with /G_FLOAT)

54 <= n <= 113 - H floating (VAX)

function_codeBIT(32) ALIGNED
identifierPOINTER
io_status_block
Because there are different formats for I/O status blocks for various system services, different definitions will be appropriate for different uses. Some of the common formats are shown here.  ❻ 

1 IOSB_SYS$GETSYI,
  2 STATUS FIXED BINARY(31),
  2 RESERVED FIXED BINARY(31); 

1 IOSB_TTDRIVER_A,
  2 STATUS FIXED BINARY(15), 
  2 BYTE_COUNT FIXED BINARY(15),
  2 MBZ FIXED BINARY(31) INITIAL(0);
 
1 IOSB_TTDRIVER_B,
  2 STATUS FIXED BINARY(15),
  2 TRANSMIT_SPEED FIXED 
    BINARY(7), 
  2 RECEIVE_SPEED FIXED BINARY(7), 
  2 CR_FILL FIXED BINARY(7),
  2 LF_FILL FIXED BINARY(7),
  2 PARITY_FLAGS FIXED BINARY(7),
  2 MBZ FIXED BINARY(7) INITIAL(0);
item_list_2
1 ITEM_LIST_2,
  2 ITEM(SIZE), 
    3 COMPONENT_LENGTH FIXED
      BINARY(15), 
    3 ITEM_CODE FIXED BINARY(15), 
    3 COMPONENT_ADDRESS
      POINTER, 
  2 TERMINATOR FIXED BINARY(31) 
    INITIAL(0);  ❻ 

Replace SIZE with the number of items you want.
item_list_3
1 ITEM_LIST_3,
  2 ITEM(SIZE), 
    3 BUFFER_LENGTH FIXED
      BINARY(15), 
    3 ITEM_CODE FIXED BINARY(15), 
    3 BUFFER_ADDRESS POINTER, 
    3 RETURN_LENGTH POINTER, 
  2 TERMINATOR FIXED BINARY(31) 
    INITIAL(0);  ❻ 

Replace SIZE with the number of items you want.
item_list_pair
1 ITEM_LIST_PAIR,
  2 ITEM(SIZE), 
    3 ITEM_CODE FIXED BINARY(31),
    3 ITEM UNION, 
      4 INTEGER FIXED BINARY(31),
      4 REAL FLOAT BINARY(24),
  2 TERMINATOR FIXED BINARY(31) 
    INITIAL(0);  ❻ 

Replace SIZE with the number of items you want.
item_quota_list
1 ITEM_QUOTA_LIST,
  2 QUOTA(SIZE), 
    3 NAME FIXED BINARY(7),
    3 VALUE FIXED BINARY(31),
  2 TERMINATOR FIXED BINARY(7) 
    INITIAL(PQL$_LISTEND);  ❻

Replace SIZE with the number of quota entries that you want to use. The constant PQL$_LISTEND can be used by including the module $PQLDEF from PLI$STARLET or by declaring it GLOBALREF FIXED BINARY(31) VALUE.
lock_idFIXED BINARY(31)
lock_status_block
1 LOCK_STATUS_BLOCK,
  2 STATUS_CODE FIXED BINARY(15),
  2 RESERVED FIXED BINARY(15), 
  2 LOCK_ID FIXED BINARY(31);  ❻
lock_value_blockThe declaration of an item of this structure will depend on the use of the structure, because the OpenVMS system does not interpret the value. ❻
logical_nameCHARACTER(n) ❹
longword_signedFIXED BINARY(31)
longword_unsignedFIXED BINARY(31) ❸
mask_byteBIT(8) ALIGNED
mask_longwordBIT(32) ALIGNED
mask_quadwordBIT(64) ALIGNED
mask_wordBIT(16) ALIGNED
null_argOmit the corresponding parameter in the call. For example, FOO(A,,B) would omit the second parameter.
octaword_signedBIT(128) ALIGNED ❺
octaword_unsignedBIT(128) ALIGNED ❸ ❺
page_protectionFIXED BINARY(31) (The constants for this type are declared in module $PRTDEF in PLI$STARLET.)
procedurePROCEDURE or ENTRY ❷
process_idFIXED BINARY(31)
process_nameCHARACTER(n) ❹
quadword_signedBIT(64) ALIGNED ❺
quadword_unsignedBIT(64) ALIGNED ❸ ❺
rights_holder
1 RIGHTS_HOLDER,
  2 RIGHTS_ID FIXED BINARY(31),
  2 ACCESS_RIGHTS BIT(32) 
    ALIGNED;  ❻
rights_idFIXED BINARY(31)
rabSee module $RABDEF in PLI$STARLET ❻
section_idBIT(64) ALIGNED
section_nameCHARACTER(n) ❹
system_access_idBIT(64) ALIGNED
time_nameCHARACTER(n) ❹
uicFIXED BINARY(31)
user_argANY
varying_argANY with OPTIONS(VARIABLE) on the routine declaration
vector_byte_signed(n) FIXED BINARY(7) ❼
vector_byte_unsigned(n) FIXED BINARY(7) ❸ ❼
vector_longword_signed(n) FIXED BINARY(31) ❼
vector_longword_unsigned(n) FIXED BINARY(31) ❸ ❼
vector_quadword_signed(n) BIT(64) ALIGNED ❺ ❼
vector_quadword_unsigned(n) BIT(64) ALIGNED ❸ ❺ ❼
vector_word_signed(n) FIXED BINARY(15) ❼
vector_word_unsigned(n) FIXED BINARY(15) ❸ ❼
word_signedFIXED BINARY(15)
word_unsignedFIXED BINARY(15) ❸

  1. System routines are often written so the parameter passed occupies more storage than the object requires. For example, some system services have parameters that return a single bit value in a longword. These variables must be declared BIT(32) ALIGNED (not BIT(1) ALIGNED) so adjacent storage is not overwritten by return values or used incorrectly as input. (Longword Boolean parameters should always be declared BIT(32) ALIGNED.)

  2. AST procedures and those passed as parameters of type ENTRY VALUE or ANY VALUE must be external procedures. This applies to all system routines which take procedure parameters, unless explicitly stated otherwise.

  3. This is actually an unsigned integer. This declaration is interpreted as a signed number; use the POSINT function to determine the actual value, if necessary.

  4. System services require CHARACTER string representation for parameters. Most other system routines allow either CHARACTER or CHARACTER VARYING. For parameter declarations, n should be an asterisk. Note that all system services, RTL routines and utility routines are declared in PLI$STARLET.

  5. PL/I does not support FIXED BINARY numbers with precisions greater than 32. To use larger values, declare variables to be BIT variables of the appropriate size and use the POSINT and SUBSTR bits as necessary to access the values, or declare the item as a structure. The RTL routines LIB$ADDX and LIB$SUBX, which are declared in PLI$STARLET, may be useful if you need to perform arithmetic on these types.

  6. Routines declared in PLI$STARLET often use ANY so that you can declare the data structure in the most convenient way for your application. ANY may be necessary in some cases because PL/I does not allow parameter declarations for some data types used by the OpenVMS system.

  7. For parameter declarations, the bounds must be constant for arrays passed by reference, or the REFERENCE attribute must be used. For arrays passed by descriptor, asterisks should be used for the array extent instead. (OpenVMS system routines almost always take arrays by reference.)

Note

All system services and many system constants and data structures are declared in PLI$STARLET.TLB.

If a system routine argument is optional, it will be indicated in the format section of the routine description in one of two ways:

  • [,optional-argument]

  • ,[optional-argument]

If the comma appears outside the brackets, you must either pass a zero by value or use a comma in the argument list as a placeholder to indicate the place of the omitted argument. If this is the last argument in the list, you do not need to include the comma as a placeholder, as the PL/I compilers will pass a zero by value for the argument, fulfilling any minimum argument vector length requirements, for instance in the case of VMS system services. Otherwise, you can use a comma in the argument list as a placeholder, and the PL/I compilers will pass a zero by value for the argument.

To determine the PL/I parameter descriptors in the declaration of a given system routine, you can display or print the text module for that routine. For example:

$ LIBRARY/EXTRACT=SYS$TRNLNM/OUTPUT=LP:TRNLNM -
_$ SYS$LIBRARY:PLI$STARLET/TEXT

This LIBRARY command prints the contents of the text module SYS$TRNLNM from the library SYS$LIBRARY:PLI$STARLET. The file is printed on the device LP; the listing file is named TRNLNM.TXT.

11.6.4.3. Symbol Definitions

Many system routines depend on values that are defined in separate symbol definition files. OpenVMS Run-Time Library routines require you to include symbol definitions when you are calling a Screen Management facility routine or a routine that is a jacket to a system service. A jacket routine provides a simpler interface to the corresponding system service. For example, the routine LIB$SYS_ASCTIM is a jacket routine for the $ASCTIM system service.

If you are calling a system service, you must include the module $SSDEF to check status. Many system services require other symbol definitions as well. To determine whether you need to include other symbol definitions for the system service you want to use, refer to the documentation for that particular system service. If the documentation states that values are defined in a macro, you must include those symbol definitions in your program.

For example, the description for the flags parameter in the SYS$MGBLSC (Map Global Section) system service states that “Symbolic names for the flag bits are defined by the $SECDEF macro.” Therefore, when you call SYS$MGBLSC you must include the definitions provided in the $SECDEF module.

A definition module is included as follows:

%INCLUDE $SSDEF;

You can declare the names of global symbols using the GLOBALREF and VALUE attributes. Then, you can use the names to represent values in an argument list to invoke a system service. For symbolic names that are not defined as OpenVMS global symbols, PL/I provides text modules in the default INCLUDE library PLI$STARLET.

The names of the text modules, and the names and values of the symbols defined in each, are the same as the MACRO definitions in the system macro library, STARLET.MLB.

11.7. Condition Values

Many system routines return a condition value that indicates success or failure; this value can be either returned or signaled. If a condition value is returned, then you must check the returned value to determine whether the call to the system routine was successful. If a condition value is signaled, then the condition value is signaled to your program instead of being returned as a function value.

Thus, to obtain a condition value from any system routine, you can declare the routine as a function. For example:

DECLARE SYS$SETEF ENTRY (FIXED BINARY(31) VALUE)
                  RETURNS (FIXED BINARY(31));

This declaration of the SYS$SETEF service allows you to invoke the routine as a function and to obtain a condition value. To provide a unique means of identifying every return condition in the system, fields within the longword value are defined as shown in Figure 11.8, “Condition Value Fields”.

Figure 11.8. Condition Value Fields
Condition Value Fields

control bits (31-28)

Define special actions to be taken. At present, only bit 28 is used; when set, it inhibits the printing of the message associated with the return status value at image exit. Bits 29 through 31 are reserved for future use by VSI.

facility number (27-16)

Is a unique value assigned to the system component or facility that is returning the status value. Within this field, bit 27 has a special significance. If bit 27 is clear, the facility is registered with VSI: the remaining value in the facility number field is a number assigned by VSI. If bit 27 is set, the number can indicate a customer-defined facility.

message number (15-3)

Gives an identification number that specifically describes the return status or condition. Within this field, bit 15 has a special significance. If bit 15 is set, then this message number is unique to the facility that is issuing the message. If bit 15 is clear, then this message has been issued by more than one system facility.

severity (2-0)

Specifies a numeric value indicating the severity of the return status. The possible values in these three bits, and their meanings, are as follows:

ValueMeaning
0Warning
1Success
2Error
3Informational
4Severe Error
5-7Reserved

Note that odd values indicate success (an information condition is considered a successful status) and that even values indicate failures (a warning is considered an unsuccessful status).

For testing condition values in a PL/I program, you can choose to test only whether a procedure completed successfully, or you can test for specific return status values. For either case, you can use the variables declared in the text module $STSDEF. This module is in the default PL/I text library PLI$STARLET.TLB. The module $STSDEF contains the following declarations:

DECLARE STS$VALUE FIXED BINARY(31),    /* status value              */
       1 STS$FIELDS BASED (ADDR(STS$VALUE)),
         2 STS$SEVERITY,               /* low-order 3 bits          */
           3 STS$SUCCESS BIT(1),       /* low-order bit             */
           3 STS$REST BIT(2),          /* bits 1 through 2          */
         2 STS$MSG,                    /* bits 2 through 15         */
           3 STS$MSG_NO BIT(12),       /* numeric value             */
           3 STS$FAC_SP BIT(1),        /* if set, facility-specific */
         2 STS$FAC,                    /* bits 16 through 27        */
           3 STS$FAC_NO BIT(11),       /* facility number           */
           3 STS$CUST_DEF BIT(1),      /* 0 = registered facility   */
         2 STS$CONTROL,
           3 STS$INHIB_MSG BIT(1),     /* 1 = do not print          */
           3 STS$RESERVED BIT(3),      /* 32 bits                   */
         2 STS$FILLER CHARACTER(0);    /* for byte alignment        */

To obtain these declarations, specify a %INCLUDE statement in a PL/I program as follows:

%INCLUDE $STSDEF;

The compiler will automatically locate this module in PLI$STARLET.

To test a condition value for success or failure, you need only test the structure member STS$SUCCESS declared in the structure STS$FIELDS. If this bit is true, then the condition value is a successful value. For example:

%INCLUDE SYS$SETPRN;

%INCLUDE $STSDEF;

STS$VALUE = SYS$SETPRN('Student');
IF ^STS$SUCCESS THEN GOTO BAD_NAME;

The statements at the label BAD_NAME can test the value of the variable STS$VALUE and take some action based on its value.

11.7.1. Testing for Specific Condition Values

You can also test for specific condition values. Each numeric condition value defined by the system has a symbolic name associated with it. The names of these values are defined as system global symbols, and their values can be accessed by referring to their symbolic names.

The global symbol names for OpenVMS condition values have the following format:

facility$_code
facility

Is an abbreviation or acronym for the system facility that defined the global symbol.

code

Is a mnemonic for the specific condition value.

Some examples of facility codes used in global symbol names follow.

Facility CodeUsed by
PLIPL/I run-time procedures; these status codes are listed in Appendix A.
SSSystem services; these status codes are listed in the OpenVMS System Services Reference Manual.
DTKDTK$ Run-Time Library facility.
LIBLIB$ Run-Time Library facility.
MTHMTH$ Run-Time Library facility.
OTSOTS$ Run-Time Library facility.
SMGSMG$ Run-Time Library facility.
STRSTR$ Run-Time Library facility.
RMSFile system procedures; these status codes are listed in the OpenVMS Record Management Services Reference Manual.
SORSORT procedures; these status codes are listed in the VMS Sort/Merge Utility Manual.

Definitions for the global symbol names for these facilities are located in the default system object module libraries, and thus are automatically located when you link a PL/I program that references them.

When you write a PL/I program that calls system procedures and you want to test for specific return status values using the symbol names, you must do the following:

  1. Determine, from the documentation of the routine, the return status values that can be returned, and choose the values for which you want to provide specific tests.

  2. Include the appropriate module containing the definition from PLI$STARLET or declare the symbolic name for each value of interest as FIXED BINARY(31) and give the variable the GLOBALREF and VALUE attributes. (Note that the first method can be optimized more effectively by the compiler in some cases because the actual values are available at compile time.)

For example, the documentation of the SYS$SETPRN service indicates that it may return the status codes SS$_DUPLNAM (if the name specified as an argument duplicates a name that already exists) and SS$_IVLOGNAM (if the name is invalid; for example, if it contains more than 15 characters). These symbol names can be defined and used as in the following example.

%INCLUDE SYS$SETPRN;

%INCLUDE $SSDEF;              /* includes SS$_DUPLNAM and SS$_IVLOGNAM */

%INCLUDE $STSDEF;
⋮
STS$VALUE = SYS$SETPRN(NAME);
IF STS$VALUE - SS$_DUPLNAM THEN
   GET LIST(NAME) OPTIONS
                  (PROMPT('Name in use. Reenter: '));
IF STS$VALUE = SS$_IVLOGNAM
   THEN GET LIST(NAME) OPTIONS
                   (PROMPT('Invalid string. Reenter: '));

The next example illustrates the invocation of the Set Event Flag (SYS$SETEF) system service, followed by tests for ❶ success or failure and ❷ the successful status code SS$_WASSET.

%INCLUDE SYS$SETEF;

%INCLUDE $SSDEF;

%INCLUDE $STSDEF;

STS$VALUE = SYS$SETEF(4);

IF ^STS$SUCCESS THEN RETURN (STS$VALUE);❶
IF STS$VALUE = SS$_WASSET THEN DO;❷

In this example, the symbolic name SS$_WASSET is included from PLI$STARLET. The value associated with this condition value is a successful value; it indicates that the flag specified in the routine invocation was previously set.

The routine invocation returns the condition value in the variable STS$VALUE. The IF statement checks the variable STS$SUCCESS for success or failure. If the service returned a failure condition, the routine returns with the value of STS$VALUE in the RETURN statement. If the service returned with a successful status, the routine continues with an IF statement that checks whether or not the flag was previously set. If so, the DO statement specified in the THEN clause activates the DO-group.

11.7.2. Setting and Displaying Fields Within a Status Value

You can use the structure STS$FIELDS to set or display fields within a status value. For example, to define application-specific message numbers using the format used by VMS, you can specify a facility-wide message number, set the STS$CUST_DEF field to '1'B, assign unique numbers to messages, and define severities for the messages.

Because the fields within this structure are defined as bit strings, and because it is usually more convenient to express facility or message numbers as integers, you must use the UNSPEC built-in function to convert integer values to the appropriate bit-string representation. The following example shows how to define a value for STS$VALUE in which the customer-defined facility number is 55 and the unique message number is 14:

DECLARE I FIXED BINARY(31);

I = 55;
STS$FAC_NO = UNSPEC(I);
STS$CUST_DEF = '1'B;

I = 14;
STS$MSG_NO = UNSPEC(I);
STS$FAC_SP = '1'B;

The intermediate variable I is used to perform the conversions, because the UNSPEC built-in function does not accept constants for arguments.

To set a value for a severity, you must also use the STRING built-in function so that you can set the field to a single value (note that there are two bit-string variables defined within the field SEVERITY). For example:

I = 4;
STRING(STS$SEVERITY) = UNSPEC(I);

Here, the severity field of STS$VALUE is set to 4.

You can use a similar technique to display fields within a status value. For example, to display the entire facility number (including STS$CUST_DEF), you could write the following:

UNSPEC(I) = STRING(STS$FAC);
PUT SKIP LIST('Facility',I);

Here, the UNSPEC pseudovariable assigns an integer value to I that represents the bit-string value of the STS$FAC field. You can use the same technique to output the STS$SEVERITY and STS$MSG fields. To display or interpret the STS$FAC_NO or STS$MSG_NO fields, you could use the following:

UNSPEC(I) = STS$FAC_NO;
PUT SKIP LIST('Customer facility number',I);

You do not need to use the UNSPEC built-in function or the UNSPEC pseudovariable to set or interpret the 1-bit fields STS$SUCCESS, STS$CUST_DEF, STS$FAC_SP, or STS$INHIB_MSG. See the OpenVMS Messages Utility Reference Manual for more information on user-defined messages.

11.8. Examples of Calling System Routines

This section provides complete examples of calling system routines, including some SORT/MERGE examples, from PL/I. In addition to the examples provided here, the VMS Run-Time Library Routines Volume and the OpenVMS System Services Reference Manual also provide examples for selected routines. Refer to these manuals for help on the use of a specific system routine. For additional information on the OpenVMS SORT/MERGE utility, refer to the VMS Sort/Merge Utility Manual.

The system service examples on the next few pages illustrate a number of system service calls. These examples illustrate the following tasks:

  • Translating a logical name

  • Creating and deleting a mailbox

  • Using timer and time conversion routines

  • A Ctrl/c routine

  • Obtaining status and performance information about the current job or process

All the sample programs use the system service INCLUDE files in PLI$STARLET to declare the system services. The text of each sample program shows the INCLUDE file for the system service.

All the examples also include the module $STSDEF; however, the contents of this text module are not shown in the examples. The contents of $STSDEF are listed in Section 11.7, “Condition Values”.

11.8.1. Logical Name Translation

Example 11.2, “Translating a Logical Name” illustrates a call to the Translate Logical Name (SYS$TRNLNM) system service. This system service returns the result of a single logical name translation. In this example, the routine ORION translates the logical name CYGNUS and displays the result on the terminal. If the name is not defined, the routine displays a message indicating that fact. The following notes are keyed to Example 11.2, “Translating a Logical Name”:

❶

The procedure declares the logical name to be translated, the logical name table to be searched, a structure that describes to SYS$TRNLNM what information is desired, and where this information should be stored.

❷

The reference to SYS$TRNLNM specifies the logical name table, the logical name CYGNUS, and the structure that contains pointers to the variables that will receive the translated logical name length and the logical name. The routine reference does not specify all the arguments. At run time, the argument list for this routine will contain zeros for the omitted arguments.

Example 11.2. Translating a Logical Name
ORION: PROCEDURE OPTIONS(MAIN) RETURNS(FIXED BINARY(31));

    %INCLUDE SYS$TRNLNM;
    %INCLUDE $LNMDEF;
    %INCLUDE $SSDEF;
    %INCLUDE $STSDEF;

    %REPLACE MAXLEN BY 256;

    DECLARE
       CYGDES CHARACTER(6) STATIC INITIAL ('CYGNUS'),
       NAMETAB CHARACTER(17) STATIC INITIAL ('LNM$PROCESS_TABLE'),
       1 RESULTS,
         2 BUFFER_LENGTH FIXED BINARY(15) INITIAL(MAXLEN),
         2 ITEM_CODE FIXED BINARY(15) INITIAL(LNM$_STRING),
         2 BUFFER_ADDRESS POINTER,
         2 RETURN_LENGTH_ADDRESS POINTER,
         2 TERMINATOR FIXED BINARY(31) INITIAL(0),
       BUFFER CHARACTER(MAXLEN),
       RETURN_LENGTH FIXED BINARY(15);                      ❶

    RESULTS.BUFFER_ADDRESS = ADDR(BUFFER);
    RESULTS.RETURN_LENGTH_ADDRESS = ADDR(RETURN_LENGTH);
    STS$VALUE = SYS$TRNLNM( , NAMETAB, CYGDES, , RESULTS);  ❷
    IF STS$VALUE = SS$_NOLOGNAM
    THEN
       PUT SKIP LIST('CYGNUS not defined');
    ELSE
       IF STS$SUCCESS
       THEN
          PUT SKIP LIST('CYGNUS is',SUBSTR(BUFFER,1,RETURN_LENGTH));

    RETURN(STS$VALUE);

    END ORION;

11.8.2. Mailbox Services

A mailbox is a virtual I/O device that is used for communication among processes in the system. The routines CREATE_MAILBOX and DELETE_MAILBOX in the following example illustrate the creation and deletion, respectively, of a mailbox.

11.8.2.1. Creating the Mailbox

Example 11.3, “Creating a Mailbox” illustrates a call to the Create Mailbox and Assign Channel (SYS$CREMBX) system service. This service returns the number of an I/O channel to the calling program. In PL/I, this number is not needed except for the deletion of the mailbox. You must, however, declare a FIXED BINARY(15) variable to receive the number returned by the system service. The following notes are keyed to Example 11.3, “Creating a Mailbox”:

❶

The reference to SYS$CREMBX specifies the permanent flag, output field for the channel number, maximum message size, protection mask, and mailbox logical name. Commas indicate arguments that are not specified; PL/I places zeros in the argument list in these places.

❷

The user privilege to create a permanent mailbox (PRMMBX) is required to call this service with the prmflg argument set to true. A permanent mailbox is not deleted when its creator exits. If this were not a permanent mailbox, the system would automatically delete the mailbox when this procedure is completed.

SYSNAM privilege is required to place a logical name for a mailbox in the system logical name table.

❸

The MAXMSG argument to SYS$CREMBX specifies the maximum length of any message sent to the mailbox.

❹

The PROMSK argument specifies a 16-bit protection mask for the mailbox. The protection code 'FF00'B4 restricts access to the owner and the system and denies access to the group and the world. Note that the value specified as 'FF00' to PL/I actually sets the low 8 bits of the parameter given the internal representation of bit-strings in PL/I.

Example 11.3. Creating a Mailbox
CREATE_MAILBOX: PROCEDURE OPTIONS(MAIN) RETURNS(FIXED BINARY(31));

    %INCLUDE SYS$CREMBX;
    %INCLUDE $STSDEF;
    %REPLACE MESSAGE_SIZE BY 132;
    %REPLACE PERMANENT BY '1'B;

    DECLARE
        CHANNEL FIXED BINARY(15),
        MAILBOX_NAME CHARACTER(11)
                    STATIC INITIAL('PLI_MAILBOX');
    /*
     * Call SYS$CREMBX omitting optional arguments.
     * (Note that trailing optional arguments cannot
     * be omitted for system services unless specifically
     * indicated in the service description.)
     */
    STS$VALUE = SYS$CREMBX(                              ❶
                        PERMANENT,                       ❷
                        CHANNEL,
                        MESSAGE_SIZE,                    ❸
                        'FF00'B4,,                       ❹
                        MAILBOX_NAME);
    /*
     * Return to command level with status. If SYS$CREMBX
     * completed with an error, the appropriate message is
     * displayed at the command level.
     */
    RETURN(STS$VALUE);
    END CREATE_MAILBOX;

11.8.2.2. Deleting the Mailbox

Example 11.4, “Deleting a Mailbox” illustrates a call to the Delete Mailbox (SYS$DELMBX) system service. The procedure DELETE_MAILBOX deletes the mailbox PLI_MAILBOX. A mailbox is deleted when a channel number is specified; this program assigns a channel to the mailbox to obtain a number to be specified in deleting the mailbox. The following notes are keyed to Example 11.4, “Deleting a Mailbox”:

❶

The procedure declares the system services Assign I/O Channel (SYS$ASSIGN) and Delete Mailbox (SYS$DELMBX). The channel number output by SYS$ASSIGN is used as input to SYS$DELMBX.

❷

The call to SYS$ASSIGN specifies the logical name of the mailbox and the variable CHANNEL to allow the system service to return a channel number. The optional arguments that are not specified in this call are represented by the required commas at the end of the argument list.

❸

The channel number output by SYS$ASSIGN is passed to SYS$DELMBX by value. The mailbox PLI_MAILBOX is deleted when all programs that have opened the mailbox have closed it.

Example 11.4. Deleting a Mailbox
DELETE_MAILBOX: PROCEDURE OPTIONS(MAIN) RETURNS(FIXED BINARY(31));

    %INCLUDE SYS$ASSIGN;                               ❶
    %INCLUDE SYS$DELMBX;
    %INCLUDE $STSDEF;

    DECLARE
        MAILBOX_NAME CHARACTER(11)
                STATIC READONLY INITIAL('PLI_MAILBOX')
        CHANNEL FIXED BINARY(15);
    /*
     * Call SYS$ASSIGN and check return; if not successful exit
     */
    STS$VALUE = SYS$ASSIGN(MAILBOX_NAME,CHANNEL,,,);   ❷
    IF ^STS$SUCCESS
    THEN
       RETURN(STS$VALUE);
    /*
     * Call SYS$DELMBX and check return
     */
    STS$VALUE = SYS$DELMBX(CHANNEL);                   ❸
    RETURN(STS$VALUE);

    END DELETE_MAILBOX;

11.8.3. Timer and Time Conversion Routines

The system services that depend on time, either an absolute time or a delta time, refer to a time value that is maintained in a 64-bit field. There are system services that convert a character string that specifies a time to its binary equivalent and vice versa.

11.8.3.1. Obtaining a Time Value in System Format

The PL/I procedure GETBINTIM, shown in Example 11.5, “Obtaining a System Time Value”, accepts a character-string time value as a parameter and returns the binary time value to the point of the procedure's invocation. The following notes are keyed to Example 11.5, “Obtaining a System Time Value”:

❶

GETBINTIM declares the system service SYS$BINTIM, which converts an ASCII string to a binary time value.

❷

GETBINTIM invokes SYS$BINTIM as a function and tests the return status. An error results if the ASCII time value is not specified correctly. When an error is returned, GETBINTIM returns a zero to the point of the procedure's invocation.

This procedure may be invoked as follows to supply a date and time value for a file in an ENVIRONMENT option:

DECLARE GETBINTIM ENTRY( CHAR(*) ) RETURNS BIT(64) ALIGNED,
        (CREATED_DATE,EXPIRE_DATE) BIT(64) ALIGNED;

    CREATED_DATE = GETBINTIM('17-JUN-1985 00:00:00.00');
    EXPIRE_DATE = GETBINTIM('31-DEC-1991 00:00:00.00');
    OPEN FILE(TAPEFILE) ENVIRONMENT(

           CREATION_DATE(CREATED_DATE),
           EXPIRATION_DATE(EXPIRE_DATE));
Example 11.5. Obtaining a System Time Value
/*
 *  This procedure converts a time given in ASCII format to a
 *  64-bit time value that is used internally by OpenVMS.
 *  Input strings must be of the form:
 *
 *  dd-mmm-yyyy-hh:mm:ss.cc (for an absolute date or time)
 *  dddd hh:mm:ss.cc        (for a delta time)
 */
GETBINTIM: PROCEDURE(ASCII_STRING) RETURNS(BIT(64) ALIGNED);

    %INCLUDE SYS$BINTIM;                               ❶
    %INCLUDE $STSDEF;

    DECLARE
        ASCII_STRING CHARACTER(*),
        BINARY_TIME BIT(64) ALIGNED;
    /*
     * If successful, return binary time to point of

     * invocation. Otherwise, return 0 - this results
     * in absolute time 17-NOV-1858.
     */
    STS$VALUE = SYS$BINTIM(ASCII_STRING,BINARY_TIME);  ❷
    IF STS$SUCCESS
    THEN
        RETURN(BINARY_TIME);
    ELSE
        RETURN((64)'0'B);
    END GETBINTIM;

11.8.3.2. Setting the Timer

The procedure in Example 11.6, “Setting a Timer” uses the Set Timer (SYS$SETIMR) system service. It issues a time request for some activity to occur in 10 seconds and specifies the number of an event flag to be set when the 10 seconds have elapsed. The following notes are keyed to Example 11.6, “Setting a Timer”:

❶

This procedure uses the GETBINTIM function to convert an ASCII time value to the system's 64-bit format.

❷

LIB$GET_EF allocates an event flag from a process-wide pool and returns the event flag number.

❸

The procedure invokes SYS$SETIMR, specifying by its first argument that SYS$SETIMR should set the specified event flag when the time expires. The argument list contains a reference to GETBINTIM, which returns the system time value for 10 seconds.

❹

The procedure uses the Wait for Event Flag (SYS$WAITFR) system service to wait for the event flag specified in the call to SYS$SETIMR. When the flag is set, the procedure displays a message and exits.

Example 11.6. Setting a Timer
SET_TIMER: PROCEDURE OPTIONS(MAIN) RETURNS(FIXED BINARY(31));
    %INCLUDE LIB$GET_EF;
    %INCLUDE LIB$FREE_EF;
    %INCLUDE SYS$SETIMR;
    %INCLUDE SYS$WAITFR;
    %INCLUDE $STSDEF;
    DECLARE
        GETBINTIM ENTRY(CHAR(*))  /* character string time */   ❶
            RETURNS(BIT(64) ALIGNED);
    DECLARE EVENT_FLAG_NUM FIXED BIN(31);
    /*
     * Get an event flag to use.
     */
    STS$VALUE = LIB$FREE_EF(EVENT_FLAG_NUM);                     ❷
    IF ^STS$SUCCESS
    THEN RETURN(STS$VALUE);

    /*
     * Set the timer for 10 seconds.
     */
    STS$VALUE = SYS$SETIMR(EVENT_FLAG_NUM,
                    GETBINTIM('0 00:00:10'),,);                 ❸
    IF ^STS$SUCCESS
    THEN RETURN(STS$VALUE);

    /*
     * Wait for the event flag, and display a
     * message when the timer completes.
     */
    STS$VALUE = SYS$WAITFR(EVENT_FLAG_NUM);                     ❹
    IF ^STS$SUCCESS
    THEN RETURN(STS$VALUE);
    PUT SKIP LIST('Timer up!');
    /*
     * Release the event flag.
     */
    STS$VALUE = LIB$GET_EF(EVENT_FLAG_NUM);
    RETURN(STS$VALUE);
    END SET_TIMER;

11.8.4. A Ctrl/c-Handling Routine

A Ctrl/c routine is a subroutine that is given control when the execution of the program is interrupted externally by the Ctrl/c function. To enable a Ctrl/c routine, you must code a call to the SYS$QIO (Queue I/O Request) system service, which performs I/O. In this call to SYS$QIO, you specify the name of an external procedure that will be executed when the interruption occurs. This type of procedure is called an asynchronous system trap (AST) routine because it may be executed at any time.

The sample programs in this section interact as follows:

  • The procedure SET_CTRLC in Example 11.7, “Establishing a Ctrl/c Routine” establishes the Ctrl/c routine. It calls SYS$ASSIGN and SYS$QIO and specifies the name of the external AST routine, C_AST.

  • The procedure C_AST in Example 11.8, “Ctrl/c Handler” is the AST routine itself. C_AST sets the CNTRL_C_INTER bit to signal that an interrupt has occurred.

  • The test program TESTC in Example 11.9, “Testing the Ctrl/c Routine” calls SET_CTRLC to establish the Ctrl/c handler. Because it is a test program, it does not do any more than enable the Ctrl/c handler, place itself in an infinite loop, and signal when a Ctrl/c interrupt occurs. The execution of this program must be interrupted by Ctrl/y. If you create your own versions of this program, you should note the different effects of Ctrl/c and Ctrl/y.

11.8.4.1. Establishing a Ctrl/c-Handling Routine

The following notes are keyed to Example 11.7, “Establishing a Ctrl/c Routine”:

❶

SET_CTRLC includes the declarations for the SYS$ASSIGN, SYS$QIO, and SYS$WAITFR system services. The SYS$QIO system service requires as an argument a channel number, that is, an I/O path to a device. The SYS$ASSIGN system service obtains a channel number.

❷

The I/O function codes IO$_SETMODE and IO$M_CTRLCAST are declared in $IODEF, which is obtained from PLI$STARLET.TLB. In a call to SYS$QIO, the specific I/O request is indicated by a symbolic name. These names have the following meanings:

  • IO$_SETMODE is a function code that specifies a type of I/O request; it sets the terminal mode.

  • IO$M_CTRLCAST is a function modifier that indicates the mode setting performed by this request, that is, to enable Ctrl/c interrupts.

These function codes and modifiers must be ORed together to obtain the correct result, as shown in the invocation of SYS$QIO (Note 9).

❸

The variable TTCHAN receives the channel number assigned to the current terminal device.

❹

An I/O status block is an 8-byte structure that is filled with status information when an I/O request is completed. The first two bytes always contain the status of the I/O request.

❺

To define a Ctrl/c AST routine, the name of the entry is passed as an argument to SYS$QIO. In PL/I, this must be the name of an external entry, because it must be passed by immediate value.

When an AST routine is specified in a system service, you have the option of specifying an argument to be passed as a parameter of the AST routine. AST parameters are always passed by value in the argument list. For an AST routine written in PL/I to correctly interpret the parameter, the AST routine must receive the parameter by reference-this means that the AST parameter must be passed by a pointer to its value.

❻

The CNTRL_C_INTER bit is declared and set to zero. This flag is used to call a procedure that signals that an interrupt has occurred.

❼

The variable IO_SUCCESS is used to ensure that the I/O completed successfully.

❽

The procedure calls SYS$ASSIGN to assign a channel to the current terminal. The simplest way to do this when the terminal may not always be the same physical device is to specify the logical name TT. When the service completes successfully, the variable TTCHAN contains the terminal channel number.

❾

The call to SYS$QIO specifies an event flag, the channel number, the I/O function to be performed for the device, the address of the I/O status block, and the name of the AST entry.

SYS$QIO does not actually perform an I/O operation, but merely queues it, as its name suggests. A successful return from SYS$QIO indicates that the request is queued. Proper programming practice requires that the caller of SYS$QIO either wait until the I/O completes, or request notification of completion by the execution of an AST routine. In this example, the procedure waits for the event flag specified in the call to SYS$QIO. When the event flag is set, the I/O is completed. Note that it is generally advisable to use the RTL routines LIB$GET_EF and LIB$FREE_EF to avoid the overlap of event flags. Several other examples in this section show the use of these routines.

❿

Following the call to SYS$QIO, the procedure waits until the request is actually performed and then tests the status value in the I/O status block.

Example 11.7. Establishing a Ctrl/c Routine
SET_CTRLC: PROCEDURE RETURNS(FIXED BINARY(31));
    %INCLUDE SYS$ASSIGN;                                       ❶
    %INCLUDE SYS$QIO;
    %INCLUDE SYS$WAITFR;
    %INCLUDE $IODEF;                                           ❷
    %INCLUDE $STSDEF;

    DECLARE TTCHAN FIXED BINARY(15);                           ❸

    DECLARE
        1 IOSB,                                                ❹
          2 VALUE FIXED (15),         /* Return status */
          2 NOT_USED(3) FIXED (15),
        C_AST ENTRY(POINTER);    /* CTRL/C AST routine */      ❺

    DECLARE
        CNTRL_C_INTER STATIC BIT(1) ALIGNED GLOBALDEF,         ❻
        IO_SUCCESS BIT(1) ALIGNED BASED(ADDR(IOSB.VALUE));     ❼

    DECLARE VALUE BUILTIN;

    /*
     * Call Assign I/O channel to get a terminal channel and then
     * call Queue I/O Request to enable the terminal for CTRL/C.
     */
    STS$VALUE = SYS$ASSIGN ('TT',TTCHAN,,);                     ❽
    IF ^STS$SUCCESS
    THEN RETURN(STS$VALUE);

    STS$VALUE = SYS$QIO (1,TTCHAN,
                IO$_SETMODE|IO$M_CTRLCAST, /* function */  ❾
                IOSB,                   /* I/O status block */
                ,,                 /* omit QIO AST argument */
                VALUE(C_AST),  /* AST routine for IO$_CTRLCAST */
                ,,,,);           /* unspecified p2 through p6 */
    IF ^STS$SUCCESS
    THEN RETURN(STS$VALUE);

    STS$VALUE = SYS$WAITFR(1);
    IF ^IO_SUCCESS
    THEN RETURN(IOSB.VALUE);                                     ❿

    CNTRL_C_INTER = '0'B;
    RETURN(1);
    END SET_CTRLC;

11.8.4.2. Ctrl/c Routine

The following description refers to Example 11.8, “Ctrl/c Handler”.

Once a Ctrl/c handler has executed, it cannot be executed again unless the I/O request that establishes a handler is reexecuted. To keep a Ctrl/c handler active, it is common practice to reenable the Ctrl/c routine within the AST routine itself. The C_AST interrupt routine sets the CNTRL_C_INTER bit. When control is returned to the main routine, a Ctrl/c interrupt message is printed out. In addition, the Ctrl/c handler is reenabled by calling SET_CTRLC.

Example 11.8. Ctrl/c Handler
C_AST: PROCEDURE;
    DECLARE CNTRL_C_INTER STATIC BIT(1) ALIGNED GLOBALREF;
    CNTRL_C_INTER = '1'B;
    END C_AST;

11.8.4.3. Testing the Ctrl/c Routine

The procedure TESTC, in Example 11.9, “Testing the Ctrl/c Routine”, tests the SET_CTRLC and C_AST routines. The techniques used here can be applied to any procedure in which you want to detect and respond to an external interrupt via Ctrl/c.

Example 11.9. Testing the Ctrl/c Routine
TESTC: PROCEDURE OPTIONS(MAIN) RETURNS(FIXED BIN(31));

    /*
     * Field declarations for Return Status Values.
     */
    %INCLUDE $STSDEF;

    DECLARE SET_CTRLC ENTRY RETURNS(FIXED BIN(31));           ❶
    DECLARE CNTRL_C_INTER BIT(1) GLOBALREF;
    %REPLACE TRUE BY '1'B;


    SIGNAL_INTER: PROCEDURE;                                  ❷
        PUT SKIP LIST('Control/C interrupt');
        STS$VALUE = SET_CTRLC();   /* reenable CTRL/C handler */
        END;

    STS$VALUE = SET_CTRLC_();                                 ❸
    IF ^STS$SUCCESS
    THEN
        RETURN(STS$VALUE);

    DO WHILE (TRUE);                                          ❹
        IF CNTRL_C_INTER
        THEN
            CALL SIGNAL_INTER;
        END;

    END TESTC;

The following notes are keyed to Example 11.9, “Testing the Ctrl/c Routine”:

❶

The procedure declares the external routine SET_CTRLC and the CNTRL_C_INTER variable.

❷

If SIGNAL_INTER is called, a message is printed and SET_CTRLC is called to reenable the Ctrl/c handler.

A Ctrl/c handler can be much more elaborate: you may want to use it to close files, to advance processing to a labeled statement or block, and so on.

❸

The procedure calls SET_CTRLC to establish the Ctrl/c handler.

❹

The procedure places itself in an infinite loop. Each time Ctrl/c is entered, the procedure displays its message for the Ctrl/c interrupt and continues.

Note that when this program is run, it can be interrupted at the terminal and stopped only by the Ctrl/y function.

11.8.5. Obtaining Job/Process Information

The Get Job/Process Information (SYS$GETJPI) system service returns information about a specific aspect or attribute of a job that is currently being executed. A call to this service requires that you set up two buffers:

  • A buffer called an item list, which specifies what items of information you want

  • An output buffer to receive each item of information

The procedure TIME, shown in Example 11.10, “Calling SYS$GETJPI (VAX)”, uses SYS$GETJPI to acquire performance statistics about the execution of a program. It has two entry points:

  • The entry TIMRB is invoked at the beginning of the time for which statistics are to be accumulated.

  • The entry TIMRE is invoked at the end of the time for which statistics are to be accumulated.

The statistics that are displayed represent the differences between the values acquired at the entry TIMRE and those acquired at the entry TIMRB.

The following notes are keyed to Example 11.10, “Calling SYS$GETJPI (VAX)” and Example 11.11, “Calling SYS$GETJPI (AXP)”:

❶

The module $JPIDEF contains the definitions of the constant identifiers whose names and values correspond to the item codes required for SYS$GETJPI.

❷

The structure JPI_LIST contains a minor structure for each item requested. Each minor structure has the same required format; it contains the following information:

  • The length, in bytes, of the buffer you have declared for receiving the information

  • A numeric code, specified symbolically, that indicates the information requested

  • A pointer to the buffer you have declared for receiving the information

  • A variable which, if nonzero, must contain a pointer to a variable that will receive the length of the information returned by SYS$GETJPI

The list is terminated by a longword containing zero.

❸

The item codes for SYS$GETJPI are specified using the constant identifiers in the INCLUDE file for $JPIDEF. Each constant identifier specifies a unique numeric code that SYS$GETJPI uses to determine the information to be returned.

❹

Variables are declared for the return information for TIMRB and TIMRE. The FORTRAN procedure FOR$SECNDS is in the system run-time procedure library. It returns the current system time in seconds.

❺

At TIMRE and TIMRB, the item list for each item is initialized with a pointer to the appropriate return field.

❻

SYS$GETJPI is invoked as a function whose value is compared to the status code SS$_NORMAL. If they do not match, the procedure exits with a message.

❼

When entered at TIMRE, the procedure obtains the current information, calculates the differences in the statistics required by TIMRB and those obtained in the most recent call to SYS$GETJPI, and displays the results on the terminal. Then, it falls through to TIMRB to ensure that the fields are reinitialized.

❽

When entered at TIMRB, the start values for the statistics are initialized with the current values obtained from SYS$GETJPI, and the procedure exits.

Example 11.10. Calling SYS$GETJPI (VAX)
TIME: PROCEDURE;

    %INCLUDE SYS$GETJPI;

    /*
     * INCLUDE definitions required by SYS$GETJPI
     */
    %INCLUDE $JPIDEF;     /* item codes */                    ❶
    %INCLUDE $SSDEF;   /* System (SS$_*) status values */
    %INCLUDE $STSDEF;   /* status value variable */

    DECLARE
        1 JPI_LIST STATIC EXTERNAL,                           ❶
          2 JPI_BUFIO,        /* Buffered I/O count */
            3 LENGTH FIXED BIN(15) INITIAL(4),
            3 ITMCOD FIXED BIN(15) INITIAL(JPI$_BUFIO),       ❷
            3 BUFADR POINTER INITIAL(NULL()),
            3 RETLEN POINTER INITIAL(NULL()),

          2 JPI_CPUTIM,          /* CPU time */
            3 LENGTH FIXED BIN(15) INITIAL(4),
            3 ITMCOD FIXED BIN(15) INITIAL(JPI$_CPUTIM),      ❸
            3 BUFADR POINTER INITIAL(NULL()),
            3 RETLEN POINTER INITIAL(NULL()),

          2 JPI_DIRIO          /* Direct I/O count */
            3 LENGTH FIXED BIN(15) INITIAL(4),
            3 ITMCOD FIXED BIN(15) INITIAL(JPI$_DIRIO),       ❸
            3 BUFADR POINTER INITIAL(NULL()),
            3 RETLEN POINTER INITIAL(NULL()),
          2 JPI_PAGEFLTS          /* Page faults */
            3 LENGTH FIXED BIN(15) INITIAL(4),
            3 ITMCOD FIXED BIN(15) INITIAL(JPI$_PAGEFLTS),    ❸
            3 BUFADR POINTER INITIAL(NULL()),
            3 RETLEN POINTER INITIAL(NULL()),
          2 ENDLIST FIXED BIN(31) INITIAL(0);

    DECLARE
        (TO,CLOCK_TIME) FLOAT BIN(24) STATIC EXTERNAL,        ❹
        (BUFIO,END_BUFIO,CPUTIM,END_CPUTIM,DIRIO,
            END_DIRIO,PAGEFLTS,END_PAGEFLTS)
                    FIXED BIN(31) STATIC EXTERNAL,
        CPUSECONDS FLOAT BIN(24);

DECLARE FOR$SECNDS ENTRY (FLOAT BIN(24)) RETURNS(FLOAT BIN(24));

TIMRE:        ENTRY;
    JPI_BUFIO.BUFADR = ADDR(END_BUFIO);                       ❺
    JPI_CPUTIM.BUFADR = ADDR(END_CPUTIM);
    JPI_DIRIO.BUFADR = ADDR(END_DIRIO);
    JPI_PAGEFLTS.BUFADR = ADDR(END_PAGEFLTS);

    IF SYS$GETJPIW(,,,JPI_LIST,,,)^=SS$_NORMAL                ❻
    THEN
        PUT SKIP LIST ('Error from SYS$GETJPI');

    CLOCK_TIME = FOR$SECNDS(TO);
    CPUSECONDS = (END_CPUTIM-CPUTIM)/100E0;
    BUFIO = END_BUFIO-BUFIO;                                  ❼
    DIRIO = END_DIRIO-DIRIO;
    PAGEFLTS = END_PAGEFLTS-PAGEFLTS;

    PUT SKIP EDIT ('Times in seconds','Page','Direct','Buffered')
                (A(20),A(10),A(10),A(10));
    PUT SKIP EDIT ('CPU','Elapsed','Faults','I/O','I/O')
                (A(10),A(10),A(10),A(10),A(10));
    PUT SKIP EDIT (CPUSECONDS,CLOCK_TIME,PAGEFLTS,DIRIO,BUFIO)
                (F(7,1),COLUMN(11),F(9,1),COLUMN(21),F(7,0),COLUMN(31),
                F(7,0),COLUMN(41),F(7,0));


    /*
     * After calling TIMRE, fall through here to reinitialize.
     */

TIMRB:        ENTRY;                                          ❽
    TO = FOR$SECNDS(0E0);
    JPI_BUFIO.BUFADR = ADDR(BUFIO);                           ❺
    JPI_CPUTIM.BUFADR = ADDR(CPUTIM);
    JPI_DIRIO.BUFADR = ADDR(DIRIO);
    JPI_PAGEFLTS.BUFADR = ADDR(PAGEFLTS);

    IF SYS$GETJPIW(,,,JPI_LIST,,,)^=SS$_NORMAL
    THEN
        PUT SKIP LIST ('Error from SYS$GETJPI');

    RETURN;

    END TIME;

Example 11.11. Calling SYS$GETJPI (AXP)
TIME: PROCEDURE OPTIONS (MAIN);

    %INCLUDE SYS$GETJPI;

    /*
     * INCLUDE definitions required by SYS$GETJPI
     */
    %INCLUDE $JPIDEF;     /* item codes */                    ❶
    %INCLUDE $SSDEF;   /* System (SS$_*) status values */
    %INCLUDE $STSDEF;   /* status value variable */

    DECLARE
        1 JPI_LIST STATIC EXTERNAL,                           ❷
          2 JPI_BUFIO,        /* Buffered I/O count */
            3 LENGTH FIXED BIN(15) INITIAL(4),
            3 ITMCOD FIXED BIN(15) INITIAL(JPI$_BUFIO),       ❸
            3 BUFADR POINTER INITIAL(NULL()),
            3 RETLEN POINTER INITIAL(NULL()),

          2 JPI_CPUTIM,          /* CPU time */
            3 LENGTH FIXED BIN(15) INITIAL(4),
            3 ITMCOD FIXED BIN(15) INITIAL(JPI$_CPUTIM),      ❸
            3 BUFADR POINTER INITIAL(NULL()),
            3 RETLEN POINTER INITIAL(NULL()),

          2 JPI_DIRIO          /* Direct I/O count */
            3 LENGTH FIXED BIN(15) INITIAL(4),
            3 ITMCOD FIXED BIN(15) INITIAL(JPI$_DIRIO),       ❸
            3 BUFADR POINTER INITIAL(NULL()),
            3 RETLEN POINTER INITIAL(NULL()),
          2 JPI_PAGEFLTS          /* Page faults */
            3 LENGTH FIXED BIN(15) INITIAL(4),
            3 ITMCOD FIXED BIN(15) INITIAL(JPI$_PAGEFLTS),    ❸
            3 BUFADR POINTER INITIAL(NULL()),
            3 RETLEN POINTER INITIAL(NULL()),
          2 ENDLIST FIXED BIN(31) INITIAL(0);

    DECLARE
        (TO,CLOCK_TIME) FLOAT BIN(24) STATIC EXTERNAL,        ❹
        (BUFIO,END_BUFIO,CPUTIM,END_CPUTIM,DIRIO,
            END_DIRIO,PAGEFLTS,END_PAGEFLTS)
                    FIXED BIN(31) STATIC EXTERNAL,
        CPUSECONDS FLOAT BIN(24);

DECLARE DFOR$SECNDS ENTRY (FLOAT BIN(24)) RETURNS(FLOAT BIN(24));

TIMRE:        ENTRY;
    JPI_BUFIO.BUFADR = ADDR(END_BUFIO);                       ❺
    JPI_CPUTIM.BUFADR = ADDR(END_CPUTIM);
    JPI_DIRIO.BUFADR = ADDR(END_DIRIO);
    JPI_PAGEFLTS.BUFADR = ADDR(END_PAGEFLTS);

    IF SYS$GETJPIW(,,,JPI_LIST,,,)^=SS$_NORMAL                ❻
    THEN
        PUT SKIP LIST ('Error from SYS$GETJPI');

    CLOCK_TIME = DFOR$SECNDS(TO);
    CPUSECONDS = (END_CPUTIM-CPUTIM)/100E0;
    BUFIO = END_BUFIO-BUFIO;                                  ❼
    DIRIO = END_DIRIO-DIRIO;
    PAGEFLTS = END_PAGEFLTS-PAGEFLTS;

    PUT SKIP EDIT ('Times in seconds','Page','Direct','Buffered')
                (A(20),A(10),A(10),A(10));
    PUT SKIP EDIT ('CPU','Elapsed','Faults','I/O','I/O')
                (A(10),A(10),A(10),A(10),A(10));
    PUT SKIP EDIT (CPUSECONDS,CLOCK_TIME,PAGEFLTS,DIRIO,BUFIO)
                (F(7,1),COLUMN(11),F(9,1),COLUMN(21),F(7,0),COLUMN(31),
                F(7,0),COLUMN(41),F(7,0));


    /*
     * After calling TIMRE, fall through here to reinitialize.
     */

TIMRB:        ENTRY;                                          ❽
    TO = DFOR$SECNDS(0E0);
    JPI_BUFIO.BUFADR = ADDR(BUFIO);                           ❺
    JPI_CPUTIM.BUFADR = ADDR(CPUTIM);
    JPI_DIRIO.BUFADR = ADDR(DIRIO);
    JPI_PAGEFLTS.BUFADR = ADDR(PAGEFLTS);

    IF SYS$GETJPIW(,,,JPI_LIST,,,)^=SS$_NORMAL
    THEN
        PUT SKIP LIST ('Error from SYS$GETJPI');

    RETURN;

    END TIME;

11.8.6. Using SORT Routines

Example 11.12, “Sorting Files” shows a sample procedure that calls the SORT routines to perform an alphabetic sort on a file. The following notes are keyed to Example 11.12, “Sorting Files”:

❶

The definitions for the SORT routines and several groups of constants are included from PLI$STARLET.

❷

The input and output file specifications are character-string arguments, initialized to the logical names INFILE and OUTFILE. INFILE must be equated to a file whose records are no longer than 80 characters.

❸

The key buffer specifies the information required for SOR$BEGIN_SORT.

❹

The SORT routines that are required to sort a file must be invoked in this order:

  1. SOR$PASS_FILES specifies the input and output file specifications. These can be logical names.

  2. SOR$BEGIN_SORT specifies the sizes of the records, key data types, sort sequences, and so forth.

  3. SOR$SORT_MERGE initiates the sorting.

  4. SOR$END_SORT calls SORT to clean up its work areas and close its temporary files.

❺

The procedures are invoked in the order listed in note 1. Each procedure returns its return value to STS$VALUE. If there are errors, the procedure returns with the value of STS$VALUE.

Example 11.12. Sorting Files
/*
 * Sort a file
 */
SORTEM: PROCEDURE RETURNS(FIXED BINARY(31));

    /*
     * Include the declarations of the SORT procedures required
     * for a sort using the file interface.
     */
    %INCLUDE SOR$PASS_FILES; /* SORT File Spec Procedure */    ❶
    %INCLUDE SOR$BEGIN_SORT;    /* SORT Init Procedure */
    %INCLUDE SOR$SORT_MERGE; /* Procedure to Initiate Sort */
    %INCLUDE SOR$END_SORT;   /* Sort Termination Procedure */

    /*
     * Include constants and return status variable.
     */
    %INCLUDE $DSCDEF;    /* Include data type definitions */
    %INCLUDE $FABDEF;         /* FAB declarations */
    %INCLUDE $STSDEF;  /* Declarations for return status value */

    /*
     * Additional constants not currently available in PLI$STARLET.
     * (SORT constants described in the SOR$BEGIN_SORT documentation.)
     */
    %REPLACE ASCENDING_ORDER BY 0;
    %REPLACE DESCENDING_ORDER BY 1;

    /*
     * Declare the input and output files; these are logical names
     * which must be defined before the program is run.
     */
    DECLARE
        INPUT_FILE  CHARACTER(6) STATIC INIT('INFILE'),        ❷
        OUTPUT_FILE CHARACTER(7) STATIC INIT('OUTFILE');

    /*
     * Declare the key buffer array required to sort the first 80
     * characters of any record. (Note that while the SORT documentation
     * describes this as an array, it is more obviously expressed in
     * PL/I as a structure. An array of FIXED BIN(15) elements could
     * be used instead.)
     */
    DECLARE
        1 KEY_BUFFER STATIC,
          2 NUMBER_OF_KEYS FIXED BINARY(15) INIT(1),           ❸
          2 KEY_TYPE FIXED BINARY(15) INIT(DSC$K_DTYPE_T), /* character */
          2 KEY_ORDER FIXED BINARY(15) INIT(ASCENDING_ORDER),
          2 START_POS FIXED BINARY(15) INIT(0),
          2 KEY_LENGTH FIXED BINARY(15) INIT(80),
        LONGEST_RECORD FIXED BINARY(15) STATIC INIT(80);

    /*
     * Call the SORT routines in the required order.           ❹
     * After each call to SORT, check STS$SUCCESS.
     */
    STS$VALUE = SOR$PASS_FILES(
                        INPUT_FILE,    /* Input file name */
                        OUTPUT_FILE,   /* Output file name */
                        FAB$C_REL,     /* File organization */
                        FAB$C_VAR);    /* Record type */
    IF ^STS$SUCCESS
    THEN
        GOTO ERROR;

    STS$VALUE = SOR$BEGIN_SORT(KEY_BUFFER,LONGEST_RECORD);
    IF ^STS$SUCCESS
    THEN
        GOTO ERROR;

    STS$VALUE = SOR$SORT_MERGE();
    IF ^STS$SUCCESS
    THEN
        GOTO ERROR;

    STS$VALUE = SOR$END_SORT();
    IF ^STS$SUCCESS
    THEN
        GOTO ERROR;
    RETURN(1);

ERROR:                                                  ❺
    PUT SKIP(2) EDIT ('SORT Failed. Error Code',STS$VALUE) (A,X,F(8));
    RETURN(STS$VALUE);

    END SORTEM;

Example 11.13, “A Record Sort” shows a procedure that performs a record sort, processing each record before passing it to the SORT program. The following notes are keyed to Example 11.13, “A Record Sort”:

  1. A record sort requires that SORT routines be called in the following order:

    1. SOR$BEGIN_SORT specifies the key data types, record sizes, collating sequence, and so on, in a key buffer area.

    2. SOR$RELEASE_REC passes each record to SORT.

    3. SOR$SORT_MERGE requests SORT to perform the sort on the records it receives.

    4. SOR$RETURN_REC requests SORT to pass a single record back. SORT returns the records in sorted order.

    5. SOR$END_SORT finishes the SORT.

  2. $SSDEF contains the symbol SS$_ENDOFFILE. SORT returns this value when SOR$RETURN_REC requests a record after all records have been returned.

  3. Within the key buffer, the START_POS field indicates that the key field within each record begins in position 25, that is, the capital field (note that this is equivalent to an offset of 24 bytes).

  4. The LONGEST_RECORD variable specifies the value SIZE(STATE_RECORD). This is the length of each record in the file, plus the length of the key on which the records are to be sorted.

  5. The structure STATE_RECORD contains the key field on which the records are to be sorted, as well as the structure declaration of the records in the file STATE_FILE.

  6. STATE_RECORD_CHAR is a character string that overlays the STATE_RECORD structure. It is used to pass records to SOR$RELEASE_REC and to obtain records from SOR$RETURN_REC.

  7. The procedure declares input and output files, and calls SOR$BEGIN_SORT to begin the sorting process.

  8. The records are passed to SOR$RELEASE_REC.

  9. SOR$SORT_MERGE is invoked to perform the merge.

  10. SOR$RETURN_REC returns each record individually, without the key field, to the structure STATE. When there are no more records, SOR$RETURN_REC returns with the function value SS$_ENDOFFILE.

  11. The sort is completed.

Example 11.13. A Record Sort
/*
 * This progam sorts the file STATE_FILE based on the field CAPITAL.NAME
 * in each record.  Logical name equivalences are required for the input
 * file STATE_FILE and an output file SORTED_FILE.
 */

STATESORT: PROCEDURE OPTIONS(MAIN) RETURNS(FIXED BINARY(31));
    /*
     * Declare SORT routine
     */
    %INCLUDE $DSCDEF;          /* Include data type definitions */
    %INCLUDE SOR$BEGIN_SORT;   /* SORT init procedure */       ❶
    %INCLUDE SOR$RELEASE_REC;  /* SORT procedure to send records */
    %INCLUDE SOR$SORT_MERGE;   /* Procedure to initiate SORT */
    %INCLUDE SOR$RETURN_REC;   /* SORT procedure to retrieve records */
    %INCLUDE SOR$END_SORT;     /* SORT termination procedure */
    %INCLUDE $STSDEF;          /* Declare return status values */
    %INCLUDE $SSDEF;           /* Status codes */              ❷

    DECLARE EOF BIT(1) INIT('0'B);

    /*
     * Key buffer and data for SORT routines
     */
    DECLARE 1 KEY_BUFFER STATIC,
            2 NUMBER_OF_KEYS FIXED BINARY(15) INIT(1),
            2 KEY_TYPE FIXED BINARY(15) INIT(DSC$K_DTYPE_T), /* char keys */
            2 KEY_ORDER FIXED BINARY(15) INIT(0),   /* ascending order */
            2 START_POS FIXED BINARY(15) INIT(24),             ❸
            2 KEY_LENGTH FIXED BINARY(15) INIT(20),
         LONGEST_RECORD FIXED BINARY(15)                       ❹
                     INIT(SIZE(STATE_RECORD));

    /*
     * Declare a buffer to construct each record to be passed to SORT
     */
    DECLARE 1 STATE_RECORD STATIC,  /* complete record */      ❺
              3 NAME CHARACTER(20),
              3 POPULATION FIXED BINARY(31),
              3 CAPITAL,
                4 NAME CHARACTER(20),
                4 POPULATION FIXED BINARY(31),
              3 LARGEST_CITIES(2),
                4 NAME CHARACTER(30),
                4 POPULATION FIXED BINARY(31),
              3 SYMBOLS,
                4 FLOWER CHARACTER(30),
                4 BIRD CHARACTER(30),
        STATE_RECORD_CHAR CHARACTER(SIZE(STATE_RECORD))        ❻
                    BASED(ADDR(STATE_RECORD));

    /*
     * Input and output files
     */
    DECLARE STATE_FILE FILE INPUT RECORD SEQUENTIAL,
            SORTED_FILE FILE RECORD OUTPUT SEQUENTIAL;         ❼

    /*
     * Call SOR$BEGIN_SORT
     */
    STS$VALUE = SOR$BEGIN_SORT(KEY_BUFFER,LONGEST_RECORD);
    IF ^STS$SUCCESS
    THEN RETURN(STS$VALUE);

    /*
     * Enter DO-loop to read the input file STATE_FILE.
     * Then call SOR$RELEASE_REC.
     */
    OPEN FILE(STATE_FILE);
    ON ENDFILE(STATE_FILE) EOF = '1'B;
    READ FILE(STATE_FILE) INTO(STATE_RECORD);
    DO WHILE (^EOF);
        STS$VALUE = SOR$RELEASE_REC(                           ❽
                          STATE_RECORD_CHAR);
        IF ^STS$SUCCESS
        THEN RETURN(STS$VALUE);
        READ FILE(STATE_FILE) INTO(STATE_RECORD);
        END;
    CLOSE FILE(STATE_FILE);
    PUT SKIP LIST('**** ALL RECORDS RELEASED');

    /*
     * Call SOR$SORT_MERGE to sort the records that were released
     */
    STS$VALUE = SOR$SORT_MERGE();                              ❾
    IF ^STS$SUCCESS
    THEN RETURN(STS$VALUE);

    /*
     * Loop through the DO-group to get back each record and
     * write it to the sorted output file.
     */
    STS$VALUE = 1;
    OPEN FILE(SORTED_FILE) OUTPUT;
    DO WHILE (STS$VALUE ^=SS$_ENDOFFILE);
        STS$VALUE = SOR$RETURN_REC(STATE_RECORD_CHAR);         ❿
        IF STS$SUCCESS
        THEN WRITE FILE(SORTED_FILE) FROM(STATE_RECORD);
        ELSE
            IF ^STS$SUCCESS & (STS$VALUE ^= SS$_ENDOFFILE)
            THEN RETURN(STS$VALUE);
        END;
    CLOSE FILE(SORTED_FILE);

    /*
     * Call SOR$END_SORT to finish up
     */
    STS$VALUE = SOR$END_SORT();                                (11)
    IF ^STS$SUCCESS
    THEN RETURN(STS$VALUE);
    RETURN(1);                   /* successful completion */
    END;