Resources


TS-575

(TS-575) Preliminary documentation for CALL MODULE

This documentation is applicable to both 6.09E and the Version 8 releases; however, in Version 8 a SASCBTBL file is required.

There are several other MODULE routines: MODULEN (returns numeric value, MODULEC (returns character), MODULEI (MODULE for SAS/IML Software), MODULEIN (MODULEN for SAS/IML Software), and MODULEIC (MODULEC for SAS/IML Software). These are not documented in this preliminary documentation, but that documentation is forthcoming.


INTRODUCTION

There have been many requests over time for the DATA step to be able to access COBOL subroutines. The development staff at SAS Institute has attempted to fill this need with a new DATA step call-routine called MODULE. The routine can also be used to access routines written in languages other than COBOL.

This documentation describes the MODULE call-routine. This documentation is quite extensive because MODULE has extensive capabilities.

The first part of the documentation, the BACKGROUND section, gives background information on why users need the capability provided by MODULE. The SOLUTION section that follows describes the proposed method used by MODULE to provide a solution to the users' needs. The REFERENCE section gives complete documentation on how to use MODULE. The HOST SPECIFICS section gives information that is specific to a given host or group of hosts. The EXAMPLES section gives several examples to demonstrate the various capabilities of MODULE.

BACKGROUND

A unique feature of COBOL subroutines is their ability to be invoked from a non-COBOL environment. The subroutine will establish the necessary COBOL environment, and run to completion, as long as it is provided the proper parameters. Because of this dynamic loading feature, users find it attractive to be able to invoke these dynamically loadable modules from within the DATA step environment.

Consider a simple example. The following COBOL routine accepts four arguments. Each argument is of a different data type. Each argument's value is changed by incrementing by 1.

      ****************************************************************
       IDENTIFICATION DIVISION.
       PROGRAM-ID.    DECUSED.
       AUTHOR.        RICHARD D. LANGSTON

      ****************************************************************
       ENVIRONMENT DIVISION.

      ****************************************************************
       DATA DIVISION.
       WORKING-STORAGE SECTION.

       LINKAGE SECTION.
        01 ZD4-1     PIC S999V9 DISPLAY.
        01 PD4-1     PIC 99999V9 PACKED-DECIMAL.
        01 IB2-1     PIC S999V9 BINARY.
        01 PIC4-1    PIC 999V9.

      ****************************************************************
       PROCEDURE DIVISION USING ZD4-1, PD4-1, IB2-1, PIC4-1.
           ADD 1 TO ZD4-1  GIVING ZD4-1.
           ADD 1 TO PD4-1  GIVING PD4-1.
           ADD 1 TO IB2-1  GIVING IB2-1.
           ADD 1 TO PIC4-1 GIVING PIC4-1.
           GOBACK.

       END PROGRAM DECUSED.

From COBOL, the above COBOL subroutine would be called as follows:

        ....
        01 ARG1      PIC S999V9 DISPLAY.
        01 ARG2      PIC 99999V9 PACKED-DECIMAL.
        01 ARG3      PIC S999V9 BINARY.
        01 ARG4      PIC 999V9.
        ....
           MOVE 1 TO ARG1.
           MOVE 2 TO ARG2.
           MOVE 3 TO ARG3.
           MOVE 4 TO ARG4.
           CALL "DECUSED" USING A, B, C, D.
        ....

After the call to DECUSED, the values of ARG1, ARG2, ARG3, and ARG4 would be 2, 3, 4, and 5, respectively. What COBOL and SAS users would like is to be able to call the routine in a similar fashion in the DATA step:

        DATA _NULL_;
             ARG1=1; ARG2=2; ARG3=3; ARG4=4;
             CALL DECUSED(ARG1,ARG2,ARG3,ARG4);
             PUT ARG1= ARG2= ARG3= ARG4=;
             RUN;

Where ARG1-ARG4 would have the values of 2, 3, 4, and 5, respectively.

This seems like a simple request, but it is actually very complicated. The main difficulty lies in the fact that the caller of a module does not know the characteristics of the arguments, so that they can be converted to the proper representation. In the DECUSED example above, the first argument is zoned decimal with 1 place to the right of the decimal. Argument 2 is packed decimal, also with 1 place to the right of the decimal. Unless the DATA step function can convert the floating point arguments into the proper binary streams to pass to the COBOL program, the program cannot function correctly.

Of course, if the DATA step user bears the burden of converting the values to the proper binary streams, then the problem is much less difficult:

        DATA _NULL_;
             ARG1=1; ARG2=2; ARG3=3; ARG4=4;
             XARG1=PUT(1,ZD4.1);
             XARG2=PUT(1,PD4.1);
             XARG3=PUT(1,IB2.1);
             XARG4=PUT(1*10,4.);
             CALL DECUSED(XARG1,XARG2,XARG3,XARG4);
             ARG1=INPUT(XARG1,ZD4.1);
             ARG2=INPUT(XARG2,PD4.1);
             ARG3=INPUT(XARG3,IB2.1);
             ARG4=INPUT(XARG4,4.1);
             PUT ARG1= ARG2= ARG3= ARG4=;
             RUN;

This burden is acceptable to some users, and not to others. For those users who find this unacceptable, a method to automatically convert the values is desired.

SOLUTION

The proposed solution to the problem is the introduction of the MODULE call-routine. This routine is responsible for accepting a COBOL routine name and a series of arbitrary arguments. The routine name is looked up in an optional attribute table that can be supplied by the user. The table will contain all the attributes for the arguments. MODULE will be responsible for converting the user's arguments into the proper format to be passed to the COBOL routine. The COBOL routine is dynamically loaded and invoked with the proper calling sequence. Once the COBOL routine returns, MODULE will convert the arguments back into the proper representation to return to the DATA step. Using our example above:

        DATA _NULL_;
             ARG1=1; ARG2=2; ARG3=3; ARG4=4;
             CALL MODULE('DECUSED',ARG1,ARG2,ARG3,ARG4);
             PUT ARG1= ARG2= ARG3= ARG4=;
             RUN;

The values of ARG1-ARG4 would be 2, 3, 4, and 5 respectively.

The attribute table is a file referred to by the SASCBTBL fileref, which is optionally given. The attribute information is provided in a special syntax described in the ATTRIBUTE TABLE section. Here is the attribute table that would be used in conjunction with DECUSED:

routine decused minarg=4 maxarg=4;
arg 1 update format=zd4.1;
arg 2 update format=pd4.1;
arg 3 update format=ib2.1;
arg 4 update format=4.1;

To allow for the user who does not wish to use the attribute table, the MODULE routine will allow calls to the COBOL routines without using a table. Here is a revision of the SAS program given above, calling the DECUSED module without an attribute table:

        DATA _NULL_;
             ARG1=1; ARG2=2; ARG3=3; ARG4=4;
             XARG1=PUT(1,ZD4.1);
             XARG2=PUT(1,PD4.1);
             XARG3=PUT(1,IB2.1);
             XARG4=PUT(1*10,4.);
             CALL MODULE('DECUSED',XARG1,XARG2,XARG3,XARG4);
             ARG1=INPUT(XARG1,ZD4.1);
             ARG2=INPUT(XARG2,PD4.1);
             ARG3=INPUT(XARG3,IB2.1);
             ARG4=INPUT(XARG4,4.1);
             PUT ARG1= ARG2= ARG3= ARG4=;
             RUN;

The values of ARG1-ARG4 would be 2, 3, 4, and 5 respectively, as they were in the previous example.

This method may actually by preferred by many users, since many COBOL users pass their arguments as character strings or simple numeric pictures, which are easily handled via character variables.

What has been described so far concerns each MODULE argument (after the module name) corresponding to the same argument being passed to the COBOL program. What users find also useful is to pass a block of parameters to correspond to one or more COBOL field definitions. Consider this COBOL program:

      ****************************************************************
       IDENTIFICATION DIVISION.
       PROGRAM-ID.    FDTEST.

      ****************************************************************
       ENVIRONMENT DIVISION.

      ****************************************************************
       DATA DIVISION.

       LINKAGE SECTION.
        01 FD-1.
           10 KEY-1 PIC X(10).
           10 AGE-1 PIC 999 DISPLAY.
           10 NAME-1 PIC X(20).
        01 FD-2.
           10 SEX-1 PIC X.
           10 BDATE-1 PIC X(6).
           10 VALUE-1 PIC 9999 DISPLAY.
      ****************************************************************
       PROCEDURE DIVISION USING FD-1, FD-2.
           IF KEY-1 = "ABCDEFGHIJ"
              THEN PERFORM GET-ABCDEFGHIJ
           ELSE IF KEY-1 = "1234567890"
              THEN PERFORM GET-1234567890.
           GOBACK.

         GET-ABCDEFGHIJ.
           MOVE 25 TO AGE-1
           MOVE "JANE JONES" TO NAME-1
           MOVE "F" TO SEX-1
           MOVE "102767" TO BDATE-1
           MOVE 133 TO VALUE-1
           GOBACK.

         GET-1234567890.
           MOVE 38 TO AGE-1
           MOVE "RICK LANGSTON" TO NAME-1
           MOVE "M" TO SEX-1
           MOVE "031955" TO BDATE-1
           MOVE 427 TO VALUE-1.
           GOBACK.

       END PROGRAM FDTEST.

This COBOL program is intended to be passed two parameters, both of which are field definitions. A key (KEY-1) is passed as part of the first field definition. Based on that key value, the remaining values of both field definitions are filled in. This is similar to what a "real-life" COBOL application would do: look for data corresponding to a key (via a key read in a file or a database) and map that data record back into the field definition. The above example simply copies in data based on two different key values, but the method for obtaining the proper data is totally irrelevant to MODULE.

The above FDTEST program would be invoked using MODULE with the S option in the control string to indicate that the arguments being passed are divided into field definitions:

        DATA _NULL_;
            LENGTH KEY $10;
            LENGTH AGE $3 VALUE $4 NAME $20 SEX $1 BDATE $6;
            KEY='ABCDEFGHIJ';
            CALL MODULE('*S/','FDTEST','/',KEY,AGE,NAME,
                          '/',SEX,BDATE,VALUE);
            VALUEX=INPUT(VALUE,ZD4.);
            PUT _ALL_;
            RUN;

The above code produces the following on the log:

 KEY=ABCDEFGHIJ AGE=025 VALUE=0133 NAME=JANE JONES SEX=F BDATE=102767
 VALUEX=133

(The above code assumes we are NOT using an attribute table entry for FDTEST).

The above code uses as its first argument a character string beginning with *. This means that the first argument is a control string and its value has special meaning. See the reference section under CONTROL STRING for a complete discussion on the valid values in the control string.

The / character after the S option in the control string indicates the separator character that delimits the list of arguments for each field definition. The / character should appear as a separate argument that is one character long, either as a constant or as a LENGTH $1 variable. You should choose your separator character such that no valid argument could be that one-byte character value. The default separator character is an asterisk (*), but is only interpreted as a separator character when the S option appears in the control string without a corresponding separator character.

Note that you can indicate field definition separation in the attribute table by using the FDSTART option on the ARG statement. That is described in the reference section in the ARG statement description.

REFERENCE SECTION

Calling convention

The MODULE call routine is invoked as follows:

     CALL MODULE(,module,arg1,arg2,...argn);

where

  cntl          optional control string (see the CONTROL STRING
                section for completed details)


  module        a character string that
                contains the name of the COBOL routine to
                be dynamically loaded. The string does not
                have to be in all upper case. See the HOST SPECIFICS
                section for discussion on where the module should
                be located in order to be loaded.
  arg1,...      the arguments to be passed to the COBOL routine.
                Complete details on supported attributes and their
                matching variable types appear below.

The control string is recognized by a * in the first byte. If the * does not appear, the first argument is assumed to be the module name. See the CONTROL STRING section for complete details on the valid values for the control string.

Examples of invocation:

     CALL MODULE('*I','XYZ',X,Y);
     CALL MODULE('XYZ',X,Y);

In the first example, the first argument is treated as a control string because it begins with a *. This means that the control string is '*I' and the module name is 'XYZ'. In the second example, the first argument does not begin with a *, so it is treated as a module name. In this example, there is no control string, and the module name is 'XYZ'.

Any numeric missing value, regardless of which one (., ._, .A-.Z), will be converted to a zero before calling the COBOL routine.

Once MODULE reads the arguments, it will build a parameter list, load the module, and invoke it using the parameter list. After returning from the module, it will update the arguments. Since MODULE does not know anything about the arguments being passed to the COBOL routine, it can rely on a user- supplied table entry to obtain this information. Each argument will be converted to the proper attribute. The table entries are in the file referenced by the fileref SASCBTBL. The SASCBTBL file is not required for MODULE to function. If you will not be using SASCBTBL for your routine, see the NOT USING SASCBTBL section.

SASCBTBL File

The syntax of the table entries in the SASCBTBL file is as follows:

     * any comment;
     ROUTINE module MINARG=minarg MAXARG=maxarg;
     ARG argnum INPUT|OUTPUT|UPDATE
                NUM|CHAR
                REQUIRED|NOTREQD
                FDSTART
                FORMAT=format;

COMMENT statement

Any text between the * and the semicolon is ignored.

ROUTINE statement

One routine statement should appear for each module whose attributes will be described. The ROUTINE keyword should be followed by the module name. The MINARG= option specifies the minimum number of arguments that the routine will allow. If not specified, no minimum is required. The MAXARG= option specifies the maximum number of arguments that the routine will allow. If not specified, any number of arguments can appear. Examples:

     ROUTINE ABC MINARG=3 MAXARG=6;
     ROUTINE XYZ;

ARG statement

The ARG statement describes each argument to the module. The ARG statements should appear in order after their corresponding ROUTINE statement, and before any other ROUTINE statement. If INPUT is specified, the argument is assumed to be input only, meaning that the value will not be updated after calling the module. OUTPUT is used when the input value is irrelevant, but the output value is to be written back to the specified variable. UPDATE means that the input value is relevant, and its value is also updated. UPDATE is the default. NUM is used to indicate the argument is numeric, and CHAR is used to indicate it is character. The default is NUM. REQUIRED means that the argument must be specified (i.e., no null argument is allowed). NOTREQD means that the argument can be omitted. REQUIRED is the default. FDSTART means that the argument is the first argument of a field definition. FORMAT= indicates the format that is used to convert the SAS variable value into the value to be passed to the module. Also, the informat of the same name is used to convert the new value back into the value for the SAS variable. See the end of this section for further discussion on null arguments.

The FORMAT= option should not be omitted. Also, the width specification is required, because this tells MODULE how large to make the parameter value. A default width cannot be assumed.

Examples:

     ARG 1 NUM NOTREQD FORMAT=PD4.;
     ARG 2 CHAR INPUT FORMAT=$CHAR3.;
     ARG 3 NUM OUTPUT FORMAT=5.3;

The formats that COBOL arguments generally assume are as follows:

     FORMAT=     COBOL equivalent      description
     ----------- --------------------- -------------------------------------
     ZDw.        PIC Sxxxx DISPLAY     zoned decimal
     S370FZDUw.  PIC  xxxx DISPLAY     zoned decimal unsigned
     S370FZDLw.  PIC Sxxxx DISPLAY     zoned decimal leading sign
                     SIGN LEADING
     S370FZDSw.  PIC Sxxxx DISPLAY     zoned decimal leading sign separate
                     SIGN LEADING SEPARATE
     S370FZDTw.  PIC Sxxxx DISPLAY     zoned decimal trailing sign separate
                     SIGN TRAILING SEPARATE
     IBw.        PIC Sxxxx BINARY      integer binary
     S370FIBUw.  PIC  xxxx BINARY      integer binary unsigned
     PDw.        PIC Sxxxx             packed decimal
                     PACKED-DECIMAL
     S370FPDUw.  PIC  xxxx             packed decimal unsigned
                     PACKED-DECIMAL
     RB8.        COMP-2                double-precision floating point
     RB4.        COMP-1                single-precision floating point
     Fw.         PIC  xxxx or Sxxxx    printable numeric
     $CHARw.     PIC yyyy              character

(In the above table, xxxx refers to any number of '9' digits, followed by an optional V and more '9' digits. yyyy refers to any number of X character indicators).

Any other format values can also be used, as long as a corresponding informat of the same name is available.

If MODULE encounters a numeric variable to be associated with a character format, the numeric value will be converted to a character string of 'w' bytes using the BEST format. After the COBOL routine returns, the changed character value is converted back into a number using the standard informat.

If the argument passed to MODULE is character, and a numeric format is specified in the argument definition, the character string is converted to a number using the standard informat. Upon return from the COBOL routine, the number is formatted back to the character string using the BEST format for the length of the character argument.

For example, consider that the first argument to the COBOL routine QQQ is a character argument of length 6, and the second argument is a numeric argument using the PD format with a length of 4. The attribute specifications in the table would be

     arg 1 format=$char6.;
     arg 2 format=pd4.;

MODULE is called as follows:

     LENGTH X1 8 X2 $3;
     X1=5; X2='1';
     CALL MODULE('QQQ',X1,X2);

MODULE will convert 5, the value of X1, into the character string of '5', which is a 5 that has been right-justified into a 6-byte string. X2 has a character value of '1', and it will be converted into '0000001C'X, which is a 4-byte packed decimal 1. Suppose QQQ changes the value of the first argument to '123 ' and the second argument to '0000105C'X (105 in packed decimal representation). The character string '123 ' will be converted to the number 123 to be updated in X1. The numeric value of 105 will be converted into '105' to be updated in X2.

If a character value cannot be converted to a number, the standard missing value is used. This is the case before or after the COBOL routine is invoked. Since missing values are converted to zero to be passed to the COBOL routine, this will result in zero values for bad conversions. However, missing values may be set upon return to the MODULE routine. Consider the above example with different values for X1 and X2:

     LENGTH X1 8 X2 $3;
     X1=10; X2='$';
     CALL MODULE('QQQ',X1,X2);

X1 can be properly converted into the character string of ' 10', but X2 cannot be converted into a valid numeric value, so it becomes a missing value. And since missing values must be converted to zero before calling QQQ, a further change is made to set it to zero. QQQ is then called with ' 10' and 0. Suppose it sets the value of the first argument to 'ABC ' and the second argument to 123. When attempting to convert back to the variables, ABC is not a valid number, so X1 will be set to a standard missing value. X2, however, being a character variable, is properly set to '123'.

No error messages appear when conversion errors occur, unless the "I" or "E" control string option is used.

Consider one other scenario, involving truncation. Suppose the 2 arguments are both character, and their definitions look like this:

     arg 1 format=$char6.;
     arg 2 format=$char2.;

The DATA step code to invoke the COBOL routine is:

     LENGTH X1 8 X2 $3;
     X1='ABCDEFGH'; X2='XYZ';
     CALL MODULE('QQQ',X1,X2);

The first argument to the COBOL routine is supposed to be 6 bytes long, but we have provided 8. The COBOL routine will only operate on the first 6 bytes. If the argument is to be updated, only the first 6 bytes will be changed, and the last two will be set to blanks. For the second argument, the COBOL routine expects to be provided with 4 bytes. Since there are only three in X2, the COBOL routine receives a temporary argument blank padded to 4 bytes. If the COBOL routine updates these four bytes, only the first three bytes can be written back to X2. No error messages appear with the truncation or padding conditions.

Note that a "null argument" is one that is omitted but the argument place is preserved with a comma. For example, suppose a routine XYZ has three arguments, any of which can be null:

     call module('XYZ',a,,); /* args 2 and 3 are null */
     call module('XYZ',,b,c); /* arg 1 is null */
     call module('XYZ',,,);  /* args 1-3 are null */

The only time you would allow null arguments would be when your program will not be concerned with a particular argument. For example, if XYZ will update the second argument only when the first argument is 1, you might be able to pass a null as the second argument when the first argument is not 1.

CONTROL STRING

The control string begins with an asterisk (*). It can contain any number of options, each consisting of a single character, followed by an optional second character, depending on the options. The options are not case-sensitive. The allowed options are:

 E              error messages should be printed on the log
 I              parameter lists are printed on the log
 A              don't use table attributes (even if SASCBTBL available)
 Z              don't invoke IGZERRE (see IBM MAINFRAME section)
 B              copy arguments below the line (see IBM MAINFRAME)
 T              print attribute information on the log
 Sx             use separator character 'x' to delimit field definitions
 H              print brief help listing on the log

Any letter other than the ones listed above will be ignored. Detailed explanations on each option follow (except for Z and B, which are described in the HOST SPECIFICS section).

E option

Error messages will be printed only in certain conditions that are deemed irrecoverable. For example, if you provide less than the minimum number of arguments, or more than the maximum number of arguments (both defined in the ROUTINE statement in SASCBTBL), you will receive a message indicating the problem with the argument count. If you omit the E option, you will receive the "invalid argument" message, but with no further indication of the problem. In the following examples, DECUSED is referenced. DECUSED is a sample COBOL program whose source and attribute table are provided later in this documentation. The attribute table entry states that there must be exactly four arguments passed.

 45         *-----USING LESS THAN MINIMUM-----;
 46         DATA _NULL_;
 47              X1=1;
 48              CALL MODULE('DECUSED',X1);
 49              RUN;

 NOTE: Invalid argument to function MODULE at line 48 column 11.
 X1=1 _ERROR_=1 _N_=1

 50         *-----USING MORE THAN MAXIMUM-----;
 51         DATA _NULL_;
 52              ARRAY X X1-X5;
 53              DO OVER X; X=_I_; END;
 54              CALL MODULE('DECUSED',OF X1-X5);
 55              RUN;

 NOTE: Invalid argument to function MODULE at line 54 column 11.
 _I_=6 X1=1 X2=2 X3=3 X4=4 X5=5 _ERROR_=1 _N_=1

 56         *-----REPEAT USING E OPTION TO SHOW MESSAGE-----;
 57         DATA _NULL_;
 58              X1=1;
 59              CALL MODULE('*E','DECUSED',X1);
 60              RUN;

 NOTE: Module DECUSED was not given its minimum argument count of 4.
 NOTE: Invalid argument to function MODULE at line 59 column 11.
 X1=1 _ERROR_=1 _N_=1

 61         *-----USING MORE THAN MAXIMUM-----;
 62         DATA _NULL_;
 63              ARRAY X X1-X5;
 64              DO OVER X; X=_I_; END;
 65              CALL MODULE('*E','DECUSED',OF X1-X5);
 66              RUN;

 NOTE: Module DECUSED was given over its maximum argument count of 4.
 NOTE: Invalid argument to function MODULE at line 65 column 11.
 _I_=6 X1=1 X2=2 X3=3 X4=4 X5=5 _ERROR_=1 _N_=1


Here is another example where an unknown module name is given:

 MPRINT(DOTEST):   DATA _NULL_;
 MPRINT(DOTEST):   CALL MODULE("*",'NOTTHERE','XYZ');
 MPRINT(DOTEST):   RUN;
 NOTE: Invalid argument to function MODULE at line 354 column 147.
 _ERROR_=1 _N_=1
 MPRINT(DOTEST):   DATA _NULL_;
 MPRINT(DOTEST):   CALL MODULE("*E",'NOTTHERE','XYZ');
 MPRINT(DOTEST):   RUN;
 NOTE: Module NOTTHERE could not be loaded.
 NOTE: Invalid argument to function MODULE at line 355 column 161.
 _ERROR_=1 _N_=1

In the above examples, an additional log message with more information appear when the E option is specified, but is otherwise omitted.

I option

The I option provides a dump of the parameter list. This is useful if you feel that the COBOL routine is not functioning correctly, since you will see exactly the arguments that it is receiving. If the I option is used, the E option will also be implied.

As an example, consider the DECUSED COBOL routine (its source appears in the MORE EXAMPLES section later in this document). It will receive four arguments, all using different binary numeric attributes, but each with one decimal place. Each argument is incremented by one before returning. A "normal" invocation without the "I" control option is as follows:

 MPRINT(DOTEST):   DATA _NULL_;
 MPRINT(DOTEST):   LENGTH Y1 $4 Y2 $4 Y3 $2 Y4 $4;
 MPRINT(DOTEST):   ARRAY X X1-X4;
 MPRINT(DOTEST):   DO OVER X;
 MPRINT(DOTEST):   X=_I_;
 MPRINT(DOTEST):   END;
 MPRINT(DOTEST):   Y1=PUT(X1,ZD4.1);
 MPRINT(DOTEST):   Y2=PUT(X2,PD4.1);
 MPRINT(DOTEST):   Y3=PUT(X3,IB2.1);
 MPRINT(DOTEST):   Y4=PUT(X4*10,Z4.);
 MPRINT(DOTEST):   PUT 'BEFORE CALL: ' X1= X2= X3= X4=;
 MPRINT(DOTEST):   PUT 'INTERMEDIATE VALUES: ' Y1=$HEX8. Y2=$HEX8.
                        Y3=$HEX4. Y4=$HEX8.;
 MPRINT(DOTEST):   CALL MODULE("*A",'DECUSED',OF Y1-Y4);
 MPRINT(DOTEST):   PUT 'INTERMEDIATE VALUES: ' Y1=$HEX8. Y2=$HEX8.
                       Y3=$HEX4. Y4=$HEX8.;
 MPRINT(DOTEST):   X1=INPUT(Y1,ZD4.1);
 MPRINT(DOTEST):   X2=INPUT(Y2,PD4.1);
 MPRINT(DOTEST):   X3=INPUT(Y3,IB2.1);
 MPRINT(DOTEST):   X4=INPUT(Y4,4.1);
 MPRINT(DOTEST):   PUT 'AFTER CALL: ' X1= X2= X3= X4=;
 MPRINT(DOTEST):   RUN;

 BEFORE CALL: X1=1 X2=2 X3=3 X4=4
 INTERMEDIATE VALUES: Y1=F0F0F1C0 Y2=0000020C Y3=001E Y4=F0F0F4F0
 INTERMEDIATE VALUES: Y1=F0F0F2C0 Y2=0000030F Y3=0028 Y4=F0F0F5F0
 AFTER CALL: X1=2 X2=3 X3=4 X4=5

Here is the same call, using the "I" option to produce additional output:

 MPRINT(DOTEST):   DATA _NULL_;
 MPRINT(DOTEST):   LENGTH Y1 $4 Y2 $4 Y3 $2 Y4 $4;
 MPRINT(DOTEST):   ARRAY X X1-X4;
 MPRINT(DOTEST):   DO OVER X;
 MPRINT(DOTEST):   X=_I_;
 MPRINT(DOTEST):   END;
 MPRINT(DOTEST):   Y1=PUT(X1,ZD4.1);
 MPRINT(DOTEST):   Y2=PUT(X2,PD4.1);
 MPRINT(DOTEST):   Y3=PUT(X3,IB2.1);
 MPRINT(DOTEST):   Y4=PUT(X4*10,Z4.);
 MPRINT(DOTEST):   PUT 'BEFORE CALL: ' X1= X2= X3= X4=;
 MPRINT(DOTEST):   PUT 'INTERMEDIATE VALUES: ' Y1=$HEX8. Y2=$HEX8.
                   Y3=$HEX4. Y4=$HEX8.;
 MPRINT(DOTEST):   CALL MODULE("*AI",'DECUSED',OF Y1-Y4);
 MPRINT(DOTEST):   PUT 'INTERMEDIATE VALUES: ' Y1=$HEX8. Y2=$HEX8.
                   Y3=$HEX4. Y4=$HEX8.;
 MPRINT(DOTEST):   X1=INPUT(Y1,ZD4.1);
 MPRINT(DOTEST):   X2=INPUT(Y2,PD4.1);
 MPRINT(DOTEST):   X3=INPUT(Y3,IB2.1);
 MPRINT(DOTEST):   X4=INPUT(Y4,4.1);
 MPRINT(DOTEST):   PUT 'AFTER CALL: ' X1= X2= X3= X4=;
 MPRINT(DOTEST):   RUN;

 BEFORE CALL: X1=1 X2=2 X3=3 X4=4
 INTERMEDIATE VALUES: Y1=F0F0F1C0 Y2=0000020C Y3=001E Y4=F0F0F4F0
 ---PARM LIST FOR MODULE ROUTINE---
 CHR PARM 1 0A2264F1 5CC1C9 (*AI)
 CHR PARM 2 0A2264F1 C4C5C3E4E2C5C4 (DECUSED)
 CHR PARM 3 0A226500 F0F0F1C0
 CHR PARM 4 0A226504 0000020C
 CHR PARM 5 0A226508 001E
 CHR PARM 6 0A22650A F0F0F4F0
 ---ROUTINE DECUSED LOADED AT ADDRESS 8A038740 (PARMLIST AT 000D810C)---
 PARM 1 000DA3A0 F0F0F1C0
 PARM 2 000DA3A4 0000020C
 PARM 3 000DA3A8 001E
 PARM 4 800DA3AA F0F0F4F0
 ---VALUES UPON RETURN FROM DECUSED ROUTINE---
 PARM 1 000DA3A0 F0F0F2C0
 PARM 2 000DA3A4 0000030F
 PARM 3 000DA3A8 0028
 PARM 4 800DA3AA F0F0F5F0
 ---VALUES UPON RETURN FROM MODULE ROUTINE---
 CHR PARM 3 0A226500 F0F0F2C0
 CHR PARM 4 0A226504 0000030F
 CHR PARM 5 0A226508 0028
 CHR PARM 6 0A22650A F0F0F5F0
 INTERMEDIATE VALUES: Y1=F0F0F2C0 Y2=0000030F Y3=0028 Y4=F0F0F5F0
 AFTER CALL: X1=2 X2=3 X3=4 X4=5

(Note that the following discussion pertains to the hex representations of the EBCDIC characters, because the above log was produced on an IBM mainframe. The same discussion would applied to ASCII systems, except that the hex values would be different, and some binary values might be represented differently).

In the first section provided by the I option, the parameters to MODULE are printed. The first argument, 5CC1C9 in hex, is *AI, the value of the control string. The second argument, C4C5C3E4E2C5C4, is DECUSED, the module name. Both the control string and the module name are printed with regular characters parenthetically after the hex characters. The remaining arguments are shown as hex representations of the character data. The next section shows the arguments that are passed to the DECUSED return. The address of each argument is shown, along with the hex representation of the data. For argument 1, at address DA3A0, the data passed are F0F0F1C0, or a zoned value of 0010. MODULE has multiplied 1 by 10 to ensure the decimal place is preserved. The second argument, at DA3A4, is a packed decimal representation of 20 (2 * 10). The third argument is a binary representation of 30 (3 * 10), and the last argument is a simple numeric representation of 40 (4 * 10). After DECUSED returns, the I option prints out the parameters again, in case any of them have changed. In this case, all four have changed, having been incremented by one (although it looks like an incrementation of 10 because of the decimal place). The I option also shows the values of the arguments when MODULE is about to return back to the DATA step, in case any arguments have changed. Only those arguments after the module name are displayed.

Note that the argument addressed passed to MODULE are not the same as those passed to DECUSED. This is because, on IBM systems, the DECUSED routine is marked as an AMODE 24 application, meaning that all data are copied "below the line." If DECUSED were an AMODE 31 application, or if it were being used on non-IBM hosts, the addresses would be the same, because MODULE will not copy arguments if it doesn't have to alter them or copy them below the line.

A option

If the "A" option is specified, any attributes that may be associated with the module are ignored, and all arguments are passed exactly as provided in the DATA step. It is VERY IMPORTANT that if you do not have an attribute table for a COBOL routine, or if you use the "A" option, that you pass the arguments exactly as the COBOL routine expects. If you do not do this, the COBOL routine or the MODULE routine may abend. In our example in the "I" option discussion, the "A" option was also supplied, meaning that we had to pass character strings representing the binary streams to pass to DECUSED. If we had omitted "A" and supplied an attribute entry, we could have passed numeric arguments.

S option

The S option is used to indicate a separator character. This separator character indicates the beginning of a new field definition. The character immediately following the S in the control string is the separator character. The separator character must not be an alphabetic character. If the S is last character of the control string, or if the next character is alphabetic, the separator character is assumed to be '*'.

The separator character should appear as an individual argument in the MODULE call. Each time it appears, this is a signal to MODULE that the arguments up to the next separator are to be blocked together into one argument.

If the S option is specified and the first argument is not the separator character, one is assumed.

The normal action of MODULE is to place each argument as a different parameter. However, your COBOL routine may need to deal with blocks of values as different arguments. Consider a simple COBOL routine where the arguments are passed separately:

      ****************************************************************
       IDENTIFICATION DIVISION.
       PROGRAM-ID.    ABC.

      ****************************************************************
       ENVIRONMENT DIVISION.

      ****************************************************************
       DATA DIVISION.
       WORKING-STORAGE SECTION.
        01 Z         PIC X(3).

       LINKAGE SECTION.
        01 X         PIC X(3).
        01 Y         PIC X(3).
      ****************************************************************
       PROCEDURE DIVISION USING X, Y.
           MOVE X TO Z.
           MOVE Y TO X.
           MOVE Z TO Y.
           GOBACK.

       END PROGRAM ABC.

In this routine, the parameters X and Y are passed to ABC, and their values are swapped. Consider that X has the value AAA and Y has the value of BBB upon entry. Using the IBM internal calling convention, register 1 will point to a parameter list that contains two pointers. These pointers will point to the values of X and Y, respectively. The ABC routine will then operate on the data at those addresses. For example:

     X1='AAA'; X2='BBB';
     CALL MODULE('ABC',X1,X2);
     PUT X1= X2=;

Upon entry to ABC: R1->0436C968
At 0436C968: 0436C874 8436C978
At 0436C874: C1C1C1 (AAA)
At 0436C978: C2C2C2 (BBB)

Upon return from ABC:
At 0436C874: C2C2C2 (BBB)
At 0436C978: C1C1C1 (AAA)

(Note that the address of 8436C978 above is actually 0436C978, but with the high-order bit, the "VL" bit, set on to indicate that it is the last argument being passed to the ABC routine).

Now consider the same ABC program as above, with a slight change.

      ****************************************************************
       IDENTIFICATION DIVISION.
       PROGRAM-ID.    ABC.

      ****************************************************************
       ENVIRONMENT DIVISION.

      ****************************************************************
       DATA DIVISION.
       WORKING-STORAGE SECTION.
        01 Z         PIC X(3).

       LINKAGE SECTION.
        01 DATA-BLOCK.
        02 X         PIC X(3).
        02 Y         PIC X(3).
      ****************************************************************
       PROCEDURE DIVISION USING DATA-BLOCK.
           MOVE X TO Z.
           MOVE Y TO X.
           MOVE Z TO Y.
           GOBACK.

       END PROGRAM ABC.

The difference is that ABC now has exactly one argument, the DATA-BLOCK, instead of each parameter separately. Notice how the internal calling sequence changes:

     X1='AAA'; X2='BBB';
     CALL MODULE('*S/','ABC','/',X1,X2);
     PUT X1= X2=;

Upon entry to ABC: R1->0436C830
At 0436C830: 8436C8A0
At 0436C8A0: C1C1C1 C2C2C2 (AAA BBB)

Upon return from ABC:
At 0436C8A0: C2C2C2 C1C1C1 (AAA BBB)

You can have as many blocks of data as needed by the COBOL program. Each block can consist of any number of arguments.

T option

If the T option is specified, a one-line listing of the attribute information from SASCBTBL will be printed on the log for each module whose attributes are defined. If the module name is also specified, only the attributes for that module will be listed, and that module will be invoked normally. If no module name appears, MODULE returns after the attributes are printed.

Examples:

Consider the following SASCBTBL file:

routine iefbr14 minarg=1 maxarg=1;
arg 1 update format=wontfnd5.;

routine change4 minarg=1 maxarg=1;
arg 1 update format=$char4.;

routine fdtest minarg=6 maxarg=6;
arg 1 input char format=$10. fdstart;     * key         ;
arg 2 output num format=3.;               * age         ;
arg 3 output char format=20.;             * name        ;
arg 4 output char format=1. fdstart;      * sex         ;
arg 5 output num format=6.;               * birthdate   ;
arg 6 output num format=zd4.;             * value       ;

Now consider the MODULE call to list the contents of the file:

 27         data _null_;
 28              call module('*T');
 29              run;



 ATTR: modname=IEFBR14 arglen=5 argndec=0 argiou=UPDATE argreqd=1
 argtype=1 argfdst=0 infmtname/fmtname=WONTFND
 ATTR: modname=CHANGE4 arglen=4 argndec=0 argiou=UPDATE argreqd=1
 argtype=2 argfdst=0 infmtname/fmtname=$CHAR
 ATTR: modname=FDTEST arglen=10 argndec=0 argiou=INPUT argreqd=1 argtype=
 2 argfdst=1 infmtname/fmtname=$F
 ATTR: modname=FDTEST arglen=3 argndec=0 argiou=OUTPUT argreqd=1 argtype=
 1 argfdst=0 infmtname/fmtname=F
 ATTR: modname=FDTEST arglen=20 argndec=0 argiou=OUTPUT argreqd=1
 argtype=2 argfdst=0 infmtname/fmtname=$F
 ATTR: modname=FDTEST arglen=1 argndec=0 argiou=OUTPUT argreqd=1 argtype=
 2 argfdst=1 infmtname/fmtname=$F
 ATTR: modname=FDTEST arglen=6 argndec=0 argiou=OUTPUT argreqd=1 argtype=
 1 argfdst=0 infmtname/fmtname=F
 ATTR: modname=FDTEST arglen=4 argndec=0 argiou=OUTPUT argreqd=1 argtype=
 1 argfdst=0 infmtname/fmtname=ZD

H option

If the H option is specified, a brief help listing will be produced to inform the user about how to use MODULE. No other processing takes place if the H option is used and all other arguments and elements of the control string are ignored.

NOT USING SASCBTBL

If you choose to use MODULE with a COBOL routine that has no entry in SASCBTBL, or if you are not using a SASCBTBL file at all, you must ensure that the data passed to MODULE is in the exact format needed by your COBOL program. This means that for character arguments, you must pass a character variable whose length is exactly the same as the corresponding argument in the COBOL program. And this means that for numeric arguments, except for COMP-2 (double-precision floating point), you must convert the argument to the proper representation of the proper length before calling MODULE, and then you must convert the value back again if the COBOL routine can change the argument. Also, if the value to be passed is numeric, you must adjust the decimal places accordingly. For example, if the argument is to be a PIC 99V9 value, and your value to pass is 25, you must ensure that you pass a 3- byte character value of '250' so that the COBOL program will intepret the value correctly. See the MORE EXAMPLES section below for an example that shows the conversions for each data type.

MORE EXAMPLES

This COBOL function, called COBOLTST, receives 12 arguments. Each argument represents one of the valid argument types typically used by COBOL. For each numeric argument, 1 is added to the value. For the 12th argument, a character argument, a fixed value of '1234567890' is set.

      ****************************************************************
       IDENTIFICATION DIVISION.
       PROGRAM-ID.    COBOLTST.

      ****************************************************************
       ENVIRONMENT DIVISION.

      ****************************************************************
       DATA DIVISION.
       WORKING-STORAGE SECTION.

       LINKAGE SECTION.
      ****EXTERNAL DECIMAL
        01 ZD        PIC S9999 DISPLAY.
        01 ZDU       PIC  9999 DISPLAY.
        01 ZDL       PIC S9999 DISPLAY SIGN LEADING.
        01 ZDLS      PIC S999  DISPLAY SIGN LEADING SEPARATE.
        01 ZDTS      PIC S999  DISPLAY SIGN TRAILING SEPARATE.

      ****BINARY
        01 IB        PIC S9999 BINARY.
        01 IBU       PIC  9999 BINARY.

      ****INTERNAL DECIMAL
        01 PD        PIC S9999 PACKED-DECIMAL.
        01 PDU       PIC  9999 PACKED-DECIMAL.

      ****FLOATING POINT
        01 RB8       COMP-2.
        01 RB4       COMP-1.

      ****CHARACTER
        01 CHAR-DATA PIC X(10).

      ****************************************************************
       PROCEDURE DIVISION USING ZD, ZDU, ZDL, ZDLS, ZDTS, IB, IBU,
                                PD, PDU, RB8, RB4, CHAR-DATA.
           ADD 1 TO ZD   GIVING ZD  .
           ADD 1 TO ZDU  GIVING ZDU .
           ADD 1 TO ZDL  GIVING ZDL .
           ADD 1 TO ZDLS GIVING ZDLS.
           ADD 1 TO ZDTS GIVING ZDTS.
           ADD 1 TO IB   GIVING IB  .
           ADD 1 TO IBU  GIVING IBU .
           ADD 1 TO PD   GIVING PD  .
           ADD 1 TO PDU  GIVING PDU .
           ADD 1 TO RB8  GIVING RB8 .
           ADD 1 TO RB4  GIVING RB4 .
           MOVE "1234567890" TO CHAR-DATA.
           GOBACK.

       END PROGRAM COBOLTST.

This COBOL program is identical to COBOLTST defined above, except that it expects its arguments to be passed in a structure instead of separate arguments.

      ****************************************************************
       IDENTIFICATION DIVISION.
       PROGRAM-ID.    COBOLTS2.

      ****************************************************************
       ENVIRONMENT DIVISION.

      ****************************************************************
       DATA DIVISION.
       WORKING-STORAGE SECTION.

       LINKAGE SECTION.
        01 DATA-BLOCK.
      ****EXTERNAL DECIMAL
        05 ZD        PIC S9999 DISPLAY.
        05 ZDU       PIC  9999 DISPLAY.
        05 ZDL       PIC S9999 DISPLAY SIGN LEADING.
        05 ZDLS      PIC S999  DISPLAY SIGN LEADING SEPARATE.
        05 ZDTS      PIC S999  DISPLAY SIGN TRAILING SEPARATE.

      ****BINARY
        05 IB        PIC S9999 BINARY.
        05 IBU       PIC  9999 BINARY.

      ****INTERNAL DECIMAL
        05 PD        PIC S9999 PACKED-DECIMAL.
        05 PDU       PIC  9999 PACKED-DECIMAL.

      ****FLOATING POINT
        05 RB8       COMP-2.
        05 RB4       COMP-1.

      ****CHARACTER
        05 CHAR-DATA PIC X(10).

      ****************************************************************
       PROCEDURE DIVISION USING DATA-BLOCK.
           ADD 1 TO ZD   GIVING ZD  .
           ADD 1 TO ZDU  GIVING ZDU .
           ADD 1 TO ZDL  GIVING ZDL .
           ADD 1 TO ZDLS GIVING ZDLS.
           ADD 1 TO ZDTS GIVING ZDTS.
           ADD 1 TO IB   GIVING IB  .
           ADD 1 TO IBU  GIVING IBU .
           ADD 1 TO PD   GIVING PD  .
           ADD 1 TO PDU  GIVING PDU .
           ADD 1 TO RB8  GIVING RB8 .
           ADD 1 TO RB4  GIVING RB4 .
           MOVE "1234567890" TO CHAR-DATA.
           GOBACK.

       END PROGRAM COBOLTS2.

Here is the SASCBTBL for these three COBOL programs. Note that the entries for COBOLTST and COBOLTS2 are identical except for the FDSTART option in the COBOLTS2 ARG 1 statement.

routine coboltst minarg=1 maxarg=12;
arg 1  format=zd4.;
arg 2  format=s370fzdu4.;
arg 3  format=s370fzdl4.;
arg 4  format=s370fzds4.;
arg 5  format=s370fzdt4.;
arg 6  format=ib2.;
arg 7  format=s370fibu2.;
arg 8  format=pd3.;
arg 9  format=s370fpdu3.;
arg 10 format=rb8.;
arg 11 format=rb4.;
arg 12 format=$char10.;


routine cobolts2 minarg=1 maxarg=12;
arg 1  format=zd4. fdstart;
arg 2  format=s370fzdu4.;
arg 3  format=s370fzdl4.;
arg 4  format=s370fzds4.;
arg 5  format=s370fzdt4.;
arg 6  format=ib2.;
arg 7  format=s370fibu2.;
arg 8  format=pd3.;
arg 9  format=s370fpdu3.;
arg 10 format=rb8.;
arg 11 format=rb4.;
arg 12 format=$char10.;

This DATA step shows how each of the numeric data types are converted to character strings so that the attribute table entry is avoided. It is advised to use an attribute table entry whenever possible, to avoid having to add code such as that described below.

       DATA _NULL_;
            LENGTH V1-V11 8 V12 $8 ;
            LENGTH X1 X2 X3 X4 X5 $4 X6 X7 $2 X8 X9 $3 X10 8 X11 $4 X12 $10;
            ARRAY V V1-V11;

            DO OVER V; V=1; END;
            V12='ABCDEFGH';

            LINK SETARGS;
            CALL MODULE('*IA', 'COBOLTST',OF X1-X12);
            LINK GETARGS;

            PUT V1= V2= V3= V4= V5= V6= V7= V8= V9= V10= V11= V12=;
            RETURN;

       SETARGS:;
            X1  =   PUT(V1 ,ZD4.      );
            X2  =   PUT(V2 ,S370FZDU4.);
            X3  =   PUT(V3 ,S370FZDL4.);
            X4  =   PUT(V4 ,S370FZDS4.);
            X5  =   PUT(V5 ,S370FZDT4.);
            X6  =   PUT(V6 ,IB2.      );
            X7  =   PUT(V7 ,S370FIBU2.);
            X8  =   PUT(V8 ,PD3.      );
            X9  =   PUT(V9 ,S370FPDU3.);
            X10 =       V10            ;
            X11 =   PUT(V11,RB4.      );
            X12 =       V12;
            RETURN;

       GETARGS:;
            V1  = INPUT(X1 ,ZD4.      );
            V2  = INPUT(X2 ,S370FZDU4.);
            V3  = INPUT(X3 ,S370FZDL4.);
            V4  = INPUT(X4 ,S370FZDS4.);
            V5  = INPUT(X5 ,S370FZDT4.);
            V6  = INPUT(X6 ,IB2.      );
            V7  = INPUT(X7 ,S370FIBU2.);
            V8  = INPUT(X8 ,PD3.      );
            V9  = INPUT(X9 ,S370FPDU3.);
            V10 =       X10            ;
            V11 = INPUT(X11,RB4.      );
            V12 =       X12;
            RETURN;

Here is the log output for the above DATA step:

 ---PARM LIST FOR MODULE ROUTINE---
 CHR PARM 1 0A2340F8 5CC9C1 (*IA)
 CHR PARM 2 0A2340F8 C3D6C2D6D3E3E2E3 (COBOLTST)
 CHR PARM 3 0A234203 F0F0F0C1
 CHR PARM 4 0A234207 F0F0F0F1
 CHR PARM 5 0A23420B C0F0F0F1
 CHR PARM 6 0A23420F 4EF0F0F1
 CHR PARM 7 0A234213 F0F0F14E
 CHR PARM 8 0A234217 0001
 CHR PARM 9 0A234219 0001
 CHR PARM 10 0A23421B 00001C
 CHR PARM 11 0A23421E 00001F
 NUM PARM 12 0A2345B0 4110000000000000
 CHR PARM 13 0A234221 41100000
 CHR PARM 14 0A234225 C1C2C3C4C5C6C7C8
 ---ROUTINE COBOLTST LOADED AT ADDRESS 8A038490 (PARMLIST AT 000B810C)---
 PARM 1 000BA3A0 F0F0F0C1
 PARM 2 000BA3A4 F0F0F0F1
 PARM 3 000BA3A8 C0F0F0F1
 PARM 4 000BA3AC 4EF0F0F1
 PARM 5 000BA3B0 F0F0F14E
 PARM 6 000BA3B4 0001
 PARM 7 000BA3B6 0001
 PARM 8 000BA3B8 00001C
 PARM 9 000BA3BB 00001F
 PARM 10 000BA3BE 4110000000000000
 PARM 11 000BA3C6 41100000
 PARM 12 800BA3CA C1C2C3C4C5C6C7C8
 ---VALUES UPON RETURN FROM COBOLTST ROUTINE---
 PARM 1 000BA3A0 F0F0F0C2
 PARM 2 000BA3A4 F0F0F0F2
 PARM 3 000BA3A8 C0F0F0F2
 PARM 4 000BA3AC 4EF0F0F2
 PARM 5 000BA3B0 F0F0F24E
 PARM 6 000BA3B4 0002
 PARM 7 000BA3B6 0002
 PARM 8 000BA3B8 00002C
 PARM 9 000BA3BB 00002F
 PARM 10 000BA3BE 4120000000000000
 PARM 11 000BA3C6 41200000
 PARM 12 800BA3CA F1F2F3F4F5F6F7F8
 ---VALUES UPON RETURN FROM MODULE ROUTINE---
 CHR PARM 3 0A234203 F0F0F0C2
 CHR PARM 4 0A234207 F0F0F0F2
 CHR PARM 5 0A23420B C0F0F0F2
 CHR PARM 6 0A23420F 4EF0F0F2
 CHR PARM 7 0A234213 F0F0F24E
 CHR PARM 8 0A234217 0002
 CHR PARM 9 0A234219 0002
 CHR PARM 10 0A23421B 00002C
 CHR PARM 11 0A23421E 00002F
 NUM PARM 12 0A2345B0 4120000000000000
 CHR PARM 13 0A234221 41200000
 CHR PARM 14 0A234225 F1F2F3F4F5F6F7F8
 V1=2 V2=2 V3=2 V4=2 V5=2 V6=2 V7=2 V8=2 V9=2 V10=2 V11=2 V12=12345678

The values of V1-V11 were all converted from 1 to 2. V12 was reset with the truncated 1234567890, resulting in 12345678.

Now consider the following, much simpler DATA step, which uses the attribute table. No conversion via the Xn character variables is necessary, because this is all handled by MODULE:

      DATA _NULL_;
            LENGTH V1-V11 8 V12 $8 ;
            ARRAY V V1-V11;

            DO OVER V; V=1; END;
            V12='ABCDEFGH';

            CALL MODULE('*I','COBOLTST',OF V1-V12);
            PUT V1= V2= V3= V4= V5= V6= V7= V8= V9= V10= V11= V12=;

            RUN;

 ---PARM LIST FOR MODULE ROUTINE---
 CHR PARM 1 0A2350F8 5CC9 (*I)
 CHR PARM 2 0A2350F8 C3D6C2D6D3E3E2E3 (COBOLTST)
 NUM PARM 3 0A2353D8 4110000000000000
 NUM PARM 4 0A2353E0 4110000000000000
 NUM PARM 5 0A2353E8 4110000000000000
 NUM PARM 6 0A2353F0 4110000000000000
 NUM PARM 7 0A2353F8 4110000000000000
 NUM PARM 8 0A235400 4110000000000000
 NUM PARM 9 0A235408 4110000000000000
 NUM PARM 10 0A235410 4110000000000000
 NUM PARM 11 0A235418 4110000000000000
 NUM PARM 12 0A235420 4110000000000000
 NUM PARM 13 0A235428 4110000000000000
 CHR PARM 14 0A2350FA C1C2C3C4C5C6C7C8
 ---ROUTINE COBOLTST LOADED AT ADDRESS 8A038490 (PARMLIST AT 000B810C)---
 PARM 1 000BA3A0 F0F0F0C1
 PARM 2 000BA3A4 F0F0F0F1
 PARM 3 000BA3A8 C0F0F0F1
 PARM 4 000BA3AC 4EF0F0F1
 PARM 5 000BA3B0 F0F0F14E
 PARM 6 000BA3B4 0001
 PARM 7 000BA3B6 0001
 PARM 8 000BA3B8 00001C
 PARM 9 000BA3BB 00001F
 PARM 10 000BA3BE 4110000000000000
 PARM 11 000BA3C6 41100000
 PARM 12 800BA3CA C1C2C3C4C5C6C7C84040
 ---VALUES UPON RETURN FROM COBOLTST ROUTINE---
 PARM 1 000BA3A0 F0F0F0C2
 PARM 2 000BA3A4 F0F0F0F2
 PARM 3 000BA3A8 C0F0F0F2
 PARM 4 000BA3AC 4EF0F0F2
 PARM 5 000BA3B0 F0F0F24E
 PARM 6 000BA3B4 0002
 PARM 7 000BA3B6 0002
 PARM 8 000BA3B8 00002C
 PARM 9 000BA3BB 00002F
 PARM 10 000BA3BE 4120000000000000
 PARM 11 000BA3C6 41200000
 PARM 12 800BA3CA F1F2F3F4F5F6F7F8F9F0
 ---VALUES UPON RETURN FROM MODULE ROUTINE---
 NUM PARM 3 0A2353D8 4120000000000000
 NUM PARM 4 0A2353E0 4120000000000000
 NUM PARM 5 0A2353E8 4120000000000000
 NUM PARM 6 0A2353F0 4120000000000000
 NUM PARM 7 0A2353F8 4120000000000000
 NUM PARM 8 0A235400 4120000000000000
 NUM PARM 9 0A235408 4120000000000000
 NUM PARM 10 0A235410 4120000000000000
 NUM PARM 11 0A235418 4120000000000000
 NUM PARM 12 0A235420 4120000000000000
 NUM PARM 13 0A235428 4120000000000000
 CHR PARM 14 0A2350FA F1F2F3F4F5F6F7F8
 V1=2 V2=2 V3=2 V4=2 V5=2 V6=2 V7=2 V8=2 V9=2 V10=2 V11=2 V12=12345678

We get the same results as before, with much less user burden.

NUMERIC/CHARACTER CONVERSIONS

Consider this COBOL program. It has two arguments, a numeric argument and a character argument (length 3). It sets the character argument to 123 if the numeric argument is 1. If the numeric argument is 2, it swaps the first and third bytes of the character argument. For all other values of the numeric argument, it sets the character argument to ABC. In all cases, the numeric argument is incremented by 1.

      ****************************************************************
       IDENTIFICATION DIVISION.
       PROGRAM-ID.    NUMCHAR.

      ****************************************************************
       ENVIRONMENT DIVISION.

      ****************************************************************
       DATA DIVISION.
       WORKING-STORAGE SECTION.
        01 TMP PIC X.

       LINKAGE SECTION.
        01 NUMVALUE  PIC S9999 DISPLAY.
        01 CHRVALUE  PIC X(3).
        01 CHRVALUEX REDEFINES CHRVALUE.
           10 CHRVALUEY PIC X OCCURS 3 TIMES.
      ****************************************************************
       PROCEDURE DIVISION USING NUMVALUE, CHRVALUE.
           IF NUMVALUE = 1
              THEN MOVE "123" TO CHRVALUE
           ELSE IF NUMVALUE = 2
              THEN PERFORM SWAP-1-AND-3
           ELSE MOVE "ABC" TO CHRVALUE.
           ADD 1 TO NUMVALUE GIVING NUMVALUE.
           GOBACK.

         SWAP-1-AND-3.
           MOVE CHRVALUEY(1) TO TMP
           MOVE CHRVALUEY(3) TO CHRVALUEY(1)
           MOVE TMP TO CHRVALUEY(3)
           GOBACK.

       END PROGRAM NUMCHAR.

Here is its SASCBTBL entry:

routine numchar minarg=2 maxarg=2;
arg 1 format=zd4.;
arg 2 format=$char3.;

The NUMCHAR program allows us to test the process of converting arguments from numeric to character and vice versa. If the first argument is passed as numeric, no conversion is necessary. If passed as character, the MODULE function must internally convert the character string to a number, then pass that number TO NUMCHAR, then convert the number back into a character string upon return before returning back to the DATA step. If the second argument is passed as character, no conversion is necessary. If passed as numeric, MODULE must internally convert the numeric value into a character string and pass that string to NUMCHAR, then convert the string back into a number before returning back to the DATA step.

A numeric argument to MODULE that should be character will be converted to character using the BESTw. format, where w is the width given in the attribute table. In our example, BEST3. will be used to convert the second argument from numeric to character. Once back from NUMCHAR, the character string is converted back to numeric using standard informatting. If the conversion is not successful, the numeric value will be set to a standard missing value.

A character argument to MODULE that should be numeric will be converted to numeric using standard informatting. If the conversion is unsuccessful, the standard missing value is used. And since all missing values are converted to zero, a zero will be passed to NUMCHAR. Upon return from NUMCHAR, the numeric value will be converted to character using BESTw., where w is the width of the incoming character variable. In our example below, the width of X3 is 8, so BEST8. will be used.

 256        DATA _NULL_;
 257             LENGTH X1 8 X2 $3 X3 $8 X4 8;
 258
 259             X2='XYZ';
 260             DO I=1 TO 3;
 261                X1=I;
 262                LINK CALLIT1;
 263                END;
 264
 265             X2='XYZ';
 266             DO X3='1','2','3','XXX';
 267                LINK CALLIT2;
 268                END;
 269
 270             X4=1;
 271             DO I=1 TO 3;
 272                X1=I;
 273                LINK CALLIT3;
 274                END;
 275
 276             X4=1;

 277             DO X3='1','2','3','XXX';
 278                LINK CALLIT4;
 279                END;
 280
 281             RETURN;
 282        CALLIT1:;
 283             PUT 'BOTH ARGUMENTS HAVE CORRECT ATTRIBUTES';
 284             PUT 'BEFORE CALL: ' X1= X2=;
 285             CALL MODULE("*&I.",'NUMCHAR',X1,X2);
 286             PUT 'AFTER CALL: ' X1= X2=;
 287             RETURN;
 288        CALLIT2:;
 289             PUT 'ARG 1 MUST BE CONVERTED FROM CHAR TO NUM';
 290             PUT 'BEFORE CALL: ' X3= X2=;
 291             CALL MODULE("*&I.",'NUMCHAR',X3,X2);
 292             PUT 'AFTER CALL: ' X3= X3=$HEX16. X2=;
 293             RETURN;
 294        CALLIT3:;
 295             PUT 'ARG 2 MUST BE CONVERTED NUM TO CHAR';
 296             PUT 'BEFORE CALL: ' X1= X4=;
 297             CALL MODULE("*&I",'NUMCHAR',X1,X4);
 298             PUT 'AFTER CALL: ' X1= X4=;
 299             RETURN;
 300        CALLIT4:;
 301             PUT 'ARG 1 MUST BE CONVERTED FROM CHAR TO NUM';
 302             PUT 'ARG 2 MUST BE CONVERTED NUM TO CHAR';
 303             PUT 'BEFORE CALL: ' X3= X4=;
 304             CALL MODULE("*&I.",'NUMCHAR',X3,X4);
 305             PUT 'AFTER CALL: ' X3= X3=$HEX16. X4=;
 306             RETURN;
 307        RUN;

Here is the log output from the above code. In the first section, the arguments are passed with proper types:

 BOTH ARGUMENTS HAVE CORRECT ATTRIBUTES
 BEFORE CALL: X1=1 X2=XYZ
 AFTER CALL: X1=2 X2=123
 BOTH ARGUMENTS HAVE CORRECT ATTRIBUTES
 BEFORE CALL: X1=2 X2=123
 AFTER CALL: X1=2 X2=321
 BOTH ARGUMENTS HAVE CORRECT ATTRIBUTES
 BEFORE CALL: X1=3 X2=321
 AFTER CALL: X1=4 X2=ABC

In the following section, and in any other case where the first argument is passed as a character string, the resulting value is formatted back using BEST8. When using named output to print the value, the leading blanks are trimmed. That is why we also show the value displayed using $HEX16. to ensure that the leading blanks are indicated:

 ARG 1 MUST BE CONVERTED FROM CHAR TO NUM
 BEFORE CALL: X3=1 X2=XYZ
 AFTER CALL: X3=2 X3=40404040404040F2 X2=123
 ARG 1 MUST BE CONVERTED FROM CHAR TO NUM
 BEFORE CALL: X3=2 X2=123
 AFTER CALL: X3=2 X3=40404040404040F2 X2=321
 ARG 1 MUST BE CONVERTED FROM CHAR TO NUM
 BEFORE CALL: X3=3 X2=321
 AFTER CALL: X3=4 X3=40404040404040F4 X2=ABC
 ARG 1 MUST BE CONVERTED FROM CHAR TO NUM
 BEFORE CALL: X3=XXX X2=ABC
 NOTE: Invalid argument to function MODULE at line 354 column 242.
 AFTER CALL: X3=XXX X3=E7E7E74040404040 X2=ABC

Note that if the character argument passed cannot be converted to a numeric value, such as XXX above, a note will appear indicating an invalid argument to function. (Using the "I" option will supply more information).

In the third section, the second argument must be converted to a character. Also, it must be reconverted back to numeric after MODULE gets control back from NUMCHAR.

 ARG 2 MUST BE CONVERTED NUM TO CHAR
 BEFORE CALL: X1=1 X4=1
 AFTER CALL: X1=2 X4=.
 ARG 2 MUST BE CONVERTED NUM TO CHAR
 BEFORE CALL: X1=2 X4=.
 AFTER CALL: X1=2 X4=.
 ARG 2 MUST BE CONVERTED NUM TO CHAR
 BEFORE CALL: X1=3 X4=.
 NOTE: Invalid argument to function MODULE at line 354 column 242.
 AFTER CALL: X1=4 X4=.

In the last case listed above, argument 1 being set to 4 means that the second argument is returned as ABC, which cannot be converted to a numeric value, hence the message about invalid argument (again, the "I" option would supply additional information).

In the fourth section, both argument 1 and argument 2 must be converted:

 ARG 1 MUST BE CONVERTED FROM CHAR TO NUM
 ARG 2 MUST BE CONVERTED NUM TO CHAR
 BEFORE CALL: X3=1 X4=1
 AFTER CALL: X3=2 X3=40404040404040F2 X4=.
 ARG 1 MUST BE CONVERTED FROM CHAR TO NUM
 ARG 2 MUST BE CONVERTED NUM TO CHAR
 BEFORE CALL: X3=2 X4=.
 AFTER CALL: X3=2 X3=40404040404040F2 X4=.
 ARG 1 MUST BE CONVERTED FROM CHAR TO NUM
 ARG 2 MUST BE CONVERTED NUM TO CHAR
 BEFORE CALL: X3=3 X4=.
 NOTE: Invalid argument to function MODULE at line 354 column 67.
 AFTER CALL: X3=4 X3=40404040404040F4 X4=.
 ARG 1 MUST BE CONVERTED FROM CHAR TO NUM
 ARG 2 MUST BE CONVERTED NUM TO CHAR
 BEFORE CALL: X3=XXX X4=.
 NOTE: Invalid argument to function MODULE at line 354 column 67.
 AFTER CALL: X3=XXX X3=E7E7E74040404040 X4=.
 X1=4 X2=ABC X3=XXX X4=. I=4 _ERROR_=1 _N_=1

USING THE C LANGUAGE INSTEAD OF COBOL

As mentioned earlier, you can use MODULE with C routines as well as with COBOL routines. The requirements are that the C routine must be able to create its own environment and that all arguments it receives must be call-by-address arguments.

On MVS and CMS, the SAS/C compiler can produce C routines that establish their own environment (called "framework" in SAS/C parlance). To do this, one uses the -INDEP option when compiling the C program. On VAX/VMS, this happens automatically because of a common run-time environment used on that host.

Here is a example demonstrating a C program and how it is used with MODULE. We show a C implementation of the FDTEST COBOL program described above.

void FDTEST(fd_1,fd_2)
struct FD_1 {
char key_1[10];
char age_1[3];
char name_1[20];
} *fd_1;
struct FD_2 {
char sex_1;
char bdate_1[6];
char value_1[4];
} *fd_2;
{
if (memcmp(fd_1->key_1,"ABCDEFGHIJ",10) == 0) {
   memcpy(fd_1->age_1,"025",3);
   memcpy(fd_1->name_1,"JANE JONES          ",10);
   fd_2->sex_1 = 'F';
   memcpy(fd_2->bdate_1,"102767",6);
   memcpy(fd_2->value_1,"0133",4);
   }
else if (memcmp(fd_1->key_1,"1234567890",10) == 0) {
   memcpy(fd_1->age_1,"038",3);
   memcpy(fd_1->name_1,"RICK LANGSTON       ",10);
   fd_2->sex_1 = 'M';
   memcpy(fd_2->bdate_1,"031955",6);
   memcpy(fd_2->value_1,"0427",6);
   }
}

It is invoked the same way as the COBOL program (except that we don't attempt zoned decimal processing).

        DATA _NULL_;
            LENGTH KEY $10;
            LENGTH AGE $3 VALUE $4 NAME $20 SEX $1 BDATE $6;
            KEY='ABCDEFGHIJ';
            CALL MODULE('*S/','FDTEST','/',KEY,AGE,NAME,
                          '/',SEX,BDATE,VALUE);
            VALUEX=INPUT(VALUE,4.);
            PUT _ALL_;
            RUN;

The above code produces the following on the log:

 KEY=ABCDEFGHIJ AGE=025 VALUE=0133 NAME=JANE JONES SEX=F BDATE=102767
 VALUEX=133

Note that we CANNOT receive any arguments in the C program that are passed by value:

void CANTDO(arg1,arg2)
double arg1;
char *arg2;
{
if (arg1 == 1)
   memcpy(arg2,"ONE",3);
else if (arg1 == 2)
   memcpy(arg2,"TWO",3);
else memcpy(arg2,"OTHER",3);
}

The problem with the above program is that arg1 is passed in by value; MODULE passes all arguments by address. If you need to access a program like CANTDO, but you can't change its calling sequence, create a new program to call it:

void CANTDOX(arg1,arg2)
double *arg1;
char *arg2;
{
CANTDO(*arg1,arg2);
}

PERFORMANCE CONSIDERATIONS

MODULE will attempt to load a module only once during a DATA step, but will build a parameter list with each call. Consider the following DATA step:

     data _null_;
          call module('abc','111',y);
          do i=1 to 10;
             call module('def',i,z);
             call module('def',i+1,z2);
             end;
          call module('ghi');
          run;

In this DATA step, MODULE will be called once to load and call the ABC module. It will be called 20 times to call the DEF module, with two different argument lists. MODULE will be called once to load and call GHI. When MODULE is called to load and call ABC, it will verify whether ABC has yet been called. Since it will not have been, MODULE will load it, then build the argument list and call ABC. When MODULE is called the first time to load and call DEF, it will determine that DEF has not yet loaded and will load it. The next 19 times that MODULE is called to invoke DEF, it will determine that DEF has been loaded, and will not load DEF again. However, the parameter list will be built for each call. This is necessary because the values passed can change from one invocation to the next, as can the addresses of those values. In the above example, DEF is to be called with the argument list of I and Z, while it is also to be called with the argument list of I+1 and Z2.

Note that on IBM mainframe systems that the IGZERRE routine is invoked once to initialize the COBOL run-time environment, so that it is not re-established with each invocation of a COBOL module.

Note that once the DATA step completes, all loaded modules are purged. If the above DATA step is repeated, for example, all modules will be reloaded.

PASSING CONSTANTS AS ARGUMENTS

If you pass an argument to MODULE that is intended to be updated by the module, you will receive a special log message, regardless of whether you are using the "I" or "E" control string options. Consider the following, where INTUP1 increments each long integer argument by 1:

       data _null_;
            call module('intup1','00000001'x);
            x = '00000001'x;
            run;

The following warning appears on the log:

 WARNING: Argument 2 to MODULE was a constant, but INTUP1 attempted to
          update it. MODULE prevented an update of the actual
          constant. You should change your call to MODULE to use a
          variable for this argument. Value to module was 00000001 in
          hex, while value from module was 00000002.

In the above case, INTUP1 incremented the binary 1 to a binary 2, but MODULE recognized that the second argument was a constant, and gave the message. If no message appeared, and MODULE allowed the constant to be updated, the value of X would have been '00000002'x instead, since the address of the hex constant is a location in the DATA step's constant table. This would be disastrous, and is therefore prevented. The warning appears because the user must be misunderstanding the routine if an constant is being passed for an argument that will be updated.

Note that if a non-constant expression (but not a variable) is passed, no message appears, but the updated value will be lost:

       data _null_;
            call module('intup1',put(1,ib4.));
            run;

The result of the PUT function is placed in temporary storage, which is passed on to MODULE, but is unavailable to the DATA step user.

HOST SPECIFICS

IBM MAINFRAME (MVS, VM/CMS, VSE)

The VS COBOL II environment initializer, IGZERRE, will automatically be invoked by MODULE. If for whatever reason you do not wish to have this routine invoked, you can supply the Z option in the control string. This will suppress the invocation of IGZERRE. If IGZERRE does not exist, no error message is displayed, so it is not necessary to supply this option if your site does not have COBOL installed.

MODULE automatically recognizes that the routine you are invoking is RMODE (residency mode) 24 or AMODE (addressing mode) 24. If either mode 24 condition occurs, MODULE will ensure that all arguments passed to the routine are below-the-line. MODULE will also switch modes from AMODE 31 to AMODE 24 when calling any AMODE 24 module, and switch back when returning. (This will only happen when the SAS System is running in AMODE 31). You can use the control string option B if your routine is RMODE 31 but will be invoking AMODE 24 applications within it. This is necessary because MODULE will assume that if the invoked routine is RMODE 31 that the parameters can also reside above the line. You do not have to use the B option if the invoked routine is RMODE 24 or AMODE 24.

To demonstrate the behavior, let us first look at invoking the INT31001 routine, a sample routine that increments a 4-byte integer argument by 1. Here is how it is invoked, along with the 'I' control string option to display our arguments in hex:

       data _null_;
            ib=put(1,ib4.);
            call module('*ia','int31001',ib);
            i=input(ib,ib4.); put i= ' (should be 2)';
            run;

 ---PARM LIST FOR MODULE ROUTINE---
 CHR PARM 1 0A2254D0 5C8981 (*ia)
 CHR PARM 2 0A2254D0 8995A3F3F1F0F0F1 (int31001)
 CHR PARM 3 0A2254E1 00000001
 ---ROUTINE INT31001 LOADED AT ADDRESS 89F00360 (PARMLIST AT 000D410C)---
 PARM 1 8A2254E1 00000001
 ---VALUES UPON RETURN FROM INT31001 ROUTINE---
 PARM 1 8A2254E1 00000002
 ---VALUES UPON RETURN FROM MODULE ROUTINE---
 CHR PARM 3 0A2254E1 00000002
 I=2  (should be 2)

As is seen above, the single parameter to INT31001 is at A2254E1, the original location passed to MODULE. Note, however, that the parmlist itself is allocated at D410C, which is below the line (this is always the case, for simplicity's sake).

Now consider a routine that is AMODE 31 but RMODE 24:

       data _null_;
            length a b c $4;
            call module('*ia','r24a31',a,b,c);
            PUT A= B= C= ' (SHOULD BE ABCD EFGH IJKL)';
            run;




---PARM LIST FOR MODULE ROUTINE---
 CHR PARM 1 0A2253CE 5C8981 (*ia)
 CHR PARM 2 0A2253CE 99F2F481F3F1 (r24a31)
 CHR PARM 3 0A2253EC 40404040
 CHR PARM 4 0A2253F0 40404040
 CHR PARM 5 0A2253F4 40404040
 ---ROUTINE R24A31 LOADED AT ADDRESS 000D38A0 (PARMLIST AT 000D410C)---
 PARM 1 000D63A0 40404040
 PARM 2 000D63A4 40404040
 PARM 3 800D63A8 40404040
 ---VALUES UPON RETURN FROM R24A31 ROUTINE---
 PARM 1 000D63A0 C1C2C3C4
 PARM 2 000D63A4 C5C6C7C8
 PARM 3 800D63A8 C9D1D2D3
 ---VALUES UPON RETURN FROM MODULE ROUTINE---
 CHR PARM 3 0A2253EC C1C2C3C4
 CHR PARM 4 0A2253F0 C5C6C7C8
 CHR PARM 5 0A2253F4 C9D1D2D3
 A=ABCD B=EFGH C=IJKL  (SHOULD BE ABCD EFGH IJKL)

In this example, the original arguments are passed in at A2253EC, A2253F0, and A2253F4. They are passed to R24A31 at D63A0, D63A4, and D63A8, respectively. Once updated by R24A31 and that routine returns MODULE will copy the values at those below-the-line locations back to the original locations.

USING ASSEMBLER LANGUAGE INSTEAD OF COBOL

You can use IBM assembly language routines with MODULE. As long as all arguments are expected to be received in a standard parmlist pointed to by R1, and all values in the parmlist are pointers, with the last argument having the VL bit set, then you can use an assembler routine with MODULE. Here is a sample assembler routine that increments each integer argument passed to it. It increments each argument until the VL bit is seen:

         SASREGS
         USING INTUP1,R15
INTUP1   CSECT
         STM   R14,R12,12(R13)
NEXTARG  L     R2,0(R1)
         L     R3,0(R2)
         LA    R3,1(R3)
         ST    R3,0(R2)
         TM    0(R1),X'80'
         BO    RETURN
         LA    R1,4(R1)
         B     NEXTARG
RETURN   LM    R14,R12,12(R13)
         BR    R14
         LTORG
         END

Here is the SASCBTBL entry that can be used with this routine:

routine intup1 minarg=1 maxarg=10;
arg 1 num format=ib4.;
arg 2 num format=ib4.;
arg 3 num format=ib4.;
arg 4 num format=ib4.;
arg 5 num format=ib4.;
arg 6 num format=ib4.;
arg 7 num format=ib4.;
arg 8 num format=ib4.;
arg 9 num format=ib4.;
arg 10 num format=ib4.;

     data _null_;
          array x x1-x10;
          do over x; x=_i_; end;
          call module('intup1',of x1-x10);
          put _all_;
          run;

The above DATA step will indicate that all the values from X1 to X10 have been incremented by 1.

If you choose not to use a SASCBTBL entry, then you create IB4 representations of the proper number of arguments:

    data _null_;
          length x1-x50 $4;
          array x x1-x50;
          do over x; x=put(_i_,ib4.); end;
          call module('*a','intup1',of x1-x50);
          run;

After the call to MODULE, the binary streams will have each been incremented by 1.

IBM MVS ONLY

In batch mode on MVS, you must ensure that the SAS jobstep has access to the COBOL run-time library. The best way to determine if you have default access to this library is to examine a batch job in which a COBOL program is invoked. If the STEPLIB DD statement refers to only the library where the COBOL program resides, then this means that you have the COBOL run-time library in a linklist or the link pack area (LPA). If there are additional STEPLIB DD statement than just the one for the COBOL program, you will need to include those DD statements in the STEPLIB concatenation for the SAS job. Also, the library containing the COBOL program to invoke must be either in STEPLIB or SASLIB.

IBM VM/CMS ONLY

Under CMS, you are only able to invoke MODULE files. The MODULE file can reside on any disk to which you currently have access.

VAX/VMS

For an executable to be dynamically loadable by MODULE, it must have been linked with the /SHARE option. You are not limited strictly to COBOL on VAX/VMS. PL/I and FORTRAN executables can also be loaded, as well as MACRO-32 and C executables as long as you recognize call-by-address in your argument list. The EXE file that is created by the LINK/SHARE command should be either in your current directory when you run the SAS System, or you should create a logical symbol that refers to the module, using the same name as its reference in MODULE:

     $ DEFINE XYZ [mydir]XYZ.EXE
     ...
     call module('xyz',arg1,arg2);
     ...