When you are developing a system that involves the use of CSUBs, the BASIC program should be written first. Then determine what the CSUB should do, the parameters to be passed, and which variables should be accessible (global) to both the BASIC program and the CSUB. After you have completed the BASIC program, you can then begin developing the Pascal CSUB. Keep a listing of the BASIC program; it is very helpful when running BUILDC later.
When writing CSUBs, certain constructs must be understood before you can use them in CSUBs. The CSUBs must be contained in a module which can be compiled independently. The module body surrounds the Pascal routine(s). The EXPORT statement defines the procedures that will be CSUBs. The IMPORT statement names all other modules on which the present one depends. If no module of that name can be found, the Compiler must search library files on mass storage to satisfy the IMPORT declarations. The library files to be searched are specified by $SEARCH$ unless the library is part of the current system library, then, this is unnecessary. If the files are in the system volume (*) use $SEARCH '*file_name'$. Code for the CSUBs is found in the IMPLEMENT section of the module. For example:
MODULE TEST;
$SEARCH '*CSUBDECL'$
IMPORT CSUBDECL;
EXPORT
PROCEDURE Find_string (file_dim : DIMENTRYPTR; VAR StrX: bstringvaltype);
IMPLEMENT
PROCEDURE Find_string;
.
.
END;
END.
There may be other things in the IMPLEMENT section besides just procedures. This material is explained in greater detail in the "Pascal Compiler" section under "Modules" of the Pascal Workstation System manual.
The procedure names that will be CSUBs must be included in a Pascal EXPORT statement. Other routines may be included in the IMPLEMENT section, but unless their names are included in the EXPORT section and the subprograms defined in the BUILDC program, the routines will not show up as separate CSUB entry points.
For example:
$SEARCH 'CSUBDECL'$
IMPORT CSUBDECL;
EXPORT
PROCEDURE SQRIT(X: BINTEGER_PARM; Y: BREAL_PARM);
PROCEDURE READIT(Z: BINTEGER_PARM);
IMPORTing the module CSUBDECL allows the use of the types BINTEGER_PARM and BREAL_PARM, as well as several others. These are used to simplify parameter passing from BASIC. They pass a BASIC INTEGER and a BASIC REAL number into the CSUB, and return the value from the CSUB. Both are pointers to the values.
Declarations of types and procedures in other user-created libraries may also be IMPORTed into the CSUB modules. These additional libraries must then be added to the GENC stream file. Details of how this is done are found in the "Linking the CSUB Elements" section in the "Preparing CSUBs for Use by BASIC" chapter.
As far as Pascal is concerned, BASIC parameters are always passed by-reference. That is, a pointer to the actual value is passed. When you think you are passing a parameter by-value, BASIC actually makes a copy of the value and passes a pointer to it. When you return to the calling program, the copy is destroyed. Pascal VAR parameters are pass-by-reference, so they can be used to "receive" BASIC parameters passed by-reference.
For example, the following program uses pass-by-reference to obtain a pointer to a REAL variable realvar.
PROGRAM obvious;
VAR realvar: REAL;
PROCEDURE p(VAR R: REAL);
BEGIN
R:=-31178.0;
END;
BEGIN
p(realvar); {Compiler will emit code to pass a pointer}
END.
The following program accomplishes the same thing using pass-by-value. It passes the pointer to realvar by value.
PROGRAM not_so_obvious;
TYPE real_ptr = ^REAL;
VAR realvar: REAL;
PROCEDURE p2(RP: real_ptr); {Pass by value}
BEGIN
RP^:=-31178.0; {Must use the dereference symbol "^"}
END;
BEGIN
p2(ADDR(realvar)); {Compiler passes the pointer by-value}
END.
CSUBDECL exports types which can be used to declare either pass-by-reference or pass-by-value parameters. The choice is dependent mainly on personal programming style. Two key points to remember: BASIC always passes a pointer and BASIC has no idea if a user written CSUB has been properly coded to use that pointer. Obviously, errors occur if this distinction is overlooked.
The BASIC parameter types are not necessarily the same as their Pascal counterparts. It is important that the format of the parameters is correct so that BASIC can interface properly. Some of these differences were mentioned previously. This section will explain the formats in detail.
REAL numbers are the same in both BASIC and Pascal. This means that it is possible to define a procedure as:
PROCEDURE X (VAR Y: REAL);
which is exactly the same as:
PROCEDURE X (Y_PTR: BREAL_PARM);
because BREAL_PARM is defined as a pointer to REAL in CSUBDECL.
A variable defined in BASIC as COMPLEX is a floating point value with real and imaginary parts. There is no built-in COMPLEX type in Pascal.
A Pascal declaration for a COMPLEX value would be:
TYPE
BCMPLXVALTYPE=RECORD
RE:REAL;
IM:REAL;
END;
If CSUBDECL is imported into a module, you need not include the BCMPLXVALTYPE type declaration. For example:
IMPORT CSUBDECL; {Exports the type BCMPLXVALTYPE}
EXPORT
PROCEDURE X (VAR Y: BCMPLXVALTYPE); {Pass-by-reference}
Below BCOMPLEX_PARM is defined in CSUBDECL as a pointer to BCMPLXVALTYPE. See the "Useful TYPE Declarations" appendix for these definitions.
IMPORT CSUBDECL
EXPORT
PROCEDURE X (Y_PTR: BCOMPLEX_PARM); {Pass-by-value of pointer}
If the VAR method is used, the values are accessed as Y.RE and Y.IM. If the latter method is used, the values are accessed as Y_PTR^.RE and Y_PTR^.IM.
A variable defined as an INTEGER in Pascal is not the same as a BASIC INTEGER. BASIC INTEGERs are 16-bit. Pascal INTEGERs are 32-bit.
EXPORT
PROCEDURE X (VAR Y: INTEGER); {WILL NOT WORK!!}
This code will pass a pointer to a 16-bit quantity; however, Pascal will try to use 32-bits starting at that location (with obviously incorrect results). If an attempt is made to assign a value to the parameter, it is possible other variables will be overwritten.
Instead the code must be of the form:
TYPE
TWOBYTES = -32768..32767;
BINTVALTYPE = TWOBYTES;
EXPORT
PROCEDURE X (VAR Y: BINTVALTYPE);
If CSUBDECL is imported into the module, you need not include the BINTVALTYPE type declaration above. For example:
IMPORT CSUBDECL; {Exports the type BINTVALTYPE}
EXPORT
PROCEDURE X (VAR Y: BINTVALTYPE); {Pass-by-reference}
Below BINTEGER_PARM is defined in CSUBDECL as a pointer to BINTVALTYPE. See the "Useful TYPE Declarations" appendix for these definitions.
IMPORT CSUBDECL;
EXPORT
PROCEDURE X (Y_PTR: BINTEGER_PARM); {Pass-by-value of pointer}
If the VAR method is used, the value is accessed as Y. If the latter method is used, the value is accessed as Y_PTR^. (Note the de-reference symbol, ^)
Strings are different in BASIC and Pascal, both in their structure and the way they are passed. The structure of the Pascal STRING type is a one-byte length field followed by the STRING characters. The BASIC STRING has a two-byte length field followed by the STRING characters.
BASIC passes STRINGs as two parameters. The first parameter is a pointer (DIMENTRYPTR) to the dimension record. A dimension record contains information about arrays, strings, maximum lengths, lower and upper bounds, etc. It is expressed in Pascal as a variant record. This variant record is of TYPE DIMENTRY and is exported from CSUBDECL. For the case of a scalar (non-array) STRING, the variant is a short integer (16-bits) expressing the dimensioned (maximum) length of the string.
The second parameter is a pointer (BSTRING_PARAM) to the STRING value. The value area contains the two-byte length followed by the packed array of characters. The following types can be used to access a BASIC string in a CSUB. They are EXPORTed from CSUBDECL.
CONST stringlimit = 32767;
TYPE BSTRINGVALTYPE = RECORD
len : shortint;
c : PACKED ARRAY[1..stringlimit] of CHAR;
END;
BSTRING_PARM = ^BSTRINGVALTYPE;
An example of how this would look in a Pascal module would be:
MODULE EXAMPLE;
IMPORT CSUBDECL;
EXPORT PROCEDURE GETSTRING (dim_len: DIMENTRYPTR; b: BSTRING_PARM); IMPLEMENT PROCEDURE GETSTRING; {Not necessary to duplicate parameter list} VAR s : STRING[80]; i : shortint; BEGIN WRITELN('Enter an 80 character or less string:'); READLN(s); IF STRLEN(s)>dim_len^.maxlen THEN SETSTRLEN (s,dim_len^.maxlen); b^.len:=STRLEN(s); FOR i:=1 TO b^.len DO b^.c[i]:=s[i]; END; END.
This program copies a Pascal string into BASIC string. An explicit check has been made to insure that the Pascal code does not access beyond the maximum length of the string.
NOTE |
---|
It is the programmer's responsibility to insure the correctness of all type checking, range checking, etc. on parameters passed to and returned from a CSUB. |
Notice the Pascal STRING value must be put into the proper BASIC STRING value (b) before being passed back.
As far as BASIC and BUILDC are concerned, there is still only one parameter of type STRING to be passed:
10 DIM Str$[80]
20 CALL GETSTRING(Str$)
30 END
An I/O path is a block of storage used to keep track of the state of a file or I/O device. See "File Access Library Topic" in "Advanced Topics" for details on how you would use an I/O path as a file control block with the File Access Library routines.
The module CSFA in the file CSUBDECL contains the necessary type declarations to pass I/O path parameters to Pascal.
The following example shows how to pass a pointer to an I/O path. This is the appropriate method, since the FAL routines require this form for their file control block argument.
$SEARCH 'CSUBDECL'$
IMPORT CSFA; {Export the type fcb_prt_type}
EXPORT
PROCEDURE X (Y_PTR: fcb_prt_type); {Pass_by_value of pointer}
Typical use of this parameter would take the form:
FAL_OPEN ("MyFile",Y_PTR);
It is also possible to use:
PROCEDURE X (VAR Y : fcb_type);
but this will require the addr function:
FAL_OPEN ("MyFile",addr(Y));
when using the I/O path.
Arrays are also passed as two parameters, a pointer to the dimension record and a pointer to the value area. The dimension record, in this case, is more complicated. It is defined by CSUBDECL module's DIMENTRY declaration as shown here:
maxdim = 6;
maxarraysize = 16777215;
type
byte = 0..255;
shortint = -32768..32767;
valuetype = (breal, binteger, bstring, bsubstring,
batname, bcomplex, spare2, spare3);
dimtype = 0..maxdim;
boundentry = record
low : shortint;
length : shortint;
end;
boundtype = array[1..maxdim] of boundentry;
dimentry = packed record
case dimtype of
0: (maxlen : shortint);
1,2,3,4,5,6:
(dims : byte;
totalsize : 0..maxarraysize;
case valuetype of
breal, binteger, bcomplex:
(bound : boundtype);
bstring:
(stringarray : packed record
maxlen : shortint;
bound : boundtype;
end))
end;
The actual records for the REAL, INTEGER, COMPLEX and String arrays are represented by Figures 2-1 and 2-2. dims is a byte containing the number of dimensions, total_size is the number of bytes in the entire array, low(1) is the lower bound of the left-most dimension and length is the number of elements in that dimension. There are up to six lows and lengths. That is the maximum number of dimensions in an array. In a STRING array, maxlen is the maximum length of any element in the array.
Numeric Array Dimension Record
String Array Dimension Record
If this structure is needed, its declaration and the pointer to it (DIMENTRYPTR) are available in the CSUBDECL module. The dimensions of an array can be extracted from this structure. The element TYPEs should be known when the CSUB is written.
An example of receiving an INTEGER array from BASIC might be:
EXPORT
TYPE
arrint = ARRAY [1..10] OF BINTVALTYPE; {actual values, not pointers}
PROCEDURE X (d: DIMENTRYPTR; VAR arr: arrint);
Again, this is a single parameter in BASIC. In order for this to be correct, BASIC would have to be sending it an array defined:
INTEGER Arr(1:10)
CALL X(Arr(*))
The Pascal array should be defined the same as the BASIC array. This is not mandatory, but helpful. Remember, an array [1..5] is the same as an array [6..10]; both are five-element arrays. If a BASIC array defined as INTEGER Arr(6:10) is passed to a Pascal CSUB defined ARRAY [1..5] OF TWO_BYTES;, the BASIC element "6" is the same as element "1" in the Pascal CSUB. Arrays are row-major in both BASIC and Pascal.
The DIM statement should be used in conjunction with the Pascal array declaration to define the space for the array. The REDIM statement does not affect the size of that space. It does affect the BASE and SIZE functions and the length and low values in the dimension record. The Pascal CSUB should check the dimension record to find the useful dimensions of the array. This is demonstrated later in "Example One" (PAS_FIG) from the "Complete Examples" chapter.
If you use the BASIC REDIM on a Pascal array that is 2 or more dimensioned, the BASIC and Pascal declarations will not match. Therefore, when you access Arr(2,3) in Pascal, you will not get Arr(2,3) from BASIC. You can declare a single dimensioned array in Pascal, use REDIM in BASIC and get the correct results by doing explicit subscript calculations based on the information in the dimension record.
You can declare some or all of the CSUB parameters as optional through responses to BUILDC. Optional indicates that any parameters that follow are not required in the pass parameter list of the calling code. See "Subprograms" in the BASIC Programming Techniques manual for more information about optional parameters.
You must provide for all parameters in your Pascal subprogram as if they all were required parameters. BASIC passes a NIL pointer when a parameter that has been declared as optional is omitted. This is a good time to use pass a pointer by-value versus pass a variable by-reference. An explicit NIL check can be made before trying to use the value (after de-referencing). If the CSUB has been compiled with $RANGE ON$ (default), the compiler emits code which will perform the NIL check. Error -3 is generated by Pascal code; however, BASIC will report Error 397. Refer to the section on "CSUB Errors" in the "Errors" chapter of this manual for more information.
If the variable is passed by-reference, use the ADDR function to perform the NIL check explicitly. This requires the $SYSPROG$ compiler directive.
For example, if the BASIC declaration of a CSUB is:
100 CSUB My_csub(REAL R, OPTIONAL REAL Opt)
Variable Opt will have an address of NIL if it is not passed. In Pascal this looks like:
PROCEDURE My_csub(VAR required,optional: REAL);
BEGIN
IF ADDR(optional)<>NIL THEN....{It was passed in}
Any variables declared before the procedures are global variables. All modules containing global variables must be linked together before executing BUILDC so that BUILDC can allocate the correct amount of space for them in the COM block. Modules not containing any global variables can be linked at this time also or they can be linked in the GENC stream file.
Here is an example of global variables in a CSUB:
EXPORT
TYPE
WORD = -32768..32767
PROCEDURE ...
IMPLEMENT
VAR
X : WORD;
Y : WORD;
Z : WORD;
PROCEDURE ...
The space that is needed for these is set up in the BASIC subprogram definition as a COM block. The BUILDC program asks the name of the code file that contains the user-defined procedures. It examines this file to determine if any global space is needed. If so, it asks for a COM name by which to identify the global variables. No response will generate the default name Csub_globals or Csub_io_globals. If you are planning to use more than one CSUBs package, each must have a distinct name. Otherwise you will get COM mismatch errors or COMs, which should be in distinct areas of memory, overlapping.
BUILDC determines how much space is needed for the global variables and generates a COM statement like the one below for each COM in the CSUB or CSUB library.
COM /label/ INTEGER Global(1:number)
label is the response given to BUILDC and is optional; the default is Csub_io_globals. The number is derived according to the following formula.
number = (global_bytes = 22) DIV 2
where global_bytes comes from the code file containing the fully-linked user CSUB. The extra 22 bytes are for the implicit compiler globals. The sum is DIVided by 2 because 2 bytes fit in one BASIC INTEGER.
For example:
TYPE
WORD = -32768..32767
VAR
X : WORD;
Y : WORD;
Z : WORD;
BUILDC will generate the following in the header file:
COM /given_name/ INTEGER Global(1:14)
NOTE |
---|
The CSUB must be in memory or the COM statement(s) somewhere in the BASIC program at pre-run. If not, LOADSUB of the CSUB will cause error 145 May not build COM at this time when the program is running. |
In order to access this space from BASIC, your BASIC program must have an equivalent statement.
50 COM /given_name/ INTEGER Global(1:14)
For an example of this, see "Example Two" in the "Complete Examples" chapter.
The variables are placed in the BASIC INTEGER array in reverse order. The Pascal variable Z is found in Global(1), Y is found in Global(2) and X is found in Global(3). If the Pascal declaration is X, Y, Z : WORD; the variables are not placed in reverse order. Global(4) thru Global(14) are the implicit compiler SYSGLOBALS and should not be tampered with. For details on this, see in the "Advanced Topics" chapter "Implicit Compiler Global Space". The elements of an array are not in reverse order.
There is no way to access global variables other than of type WORD (type INTEGER) from BASIC. All globals are mapped into an INTEGER array regardless of how they are declared in the Pascal subprogram.
If you are linking modules before executing BUILDC, you can determine the number of bytes of global space you need by looking at the compiler listing or a link map.
Be careful about using global COM to store pointers. Both the COM value area and the BASIC subprograms are subject to being moved around in memory. If a global variable is used to store a pointer into a routine, a structured constant, or another global variable that is subsequently moved, the pointer is no longer valid. This rule also holds true for procedure variables, whose values are execution addresses of procedures. COM is subject to being moved in memory at RUN, LOAD, and GET time. BASIC subprograms are subject to move at RUN, LOAD, LOADBIN, GET, and DELSUB time. The heap is implemented in COM and these restrictions apply to it also. Using the heap is described later in this chapter.
For details on "Implicit Compiler Global Space" and "Explicit CSUB Module Global Space", see these sections in the "Advanced Topics" chapter.
In order to access BASIC COM from the Pascal CSUB routine, it is necessary to call a function found in module COMSTUFF. The name of the function is FIND_COM and its calling sequence is shown later. By passing the name of the COM that is to be accessed, it will return a pointer to the beginning of the value area of that COM. Upper and lower case letters are significant in the COM name; it must be the same as BASIC would list it back, otherwise FIND_COM will not find the COM block. To access a blank COM, use a single blank for the COM name. If FIND_COM is unable to find the COM block requested, it will return the NIL pointer.
The user is required to know the layout of this COM in advance. (See the following example and the "File Access Library Topic" section in the "Advanced Topics" chapter for more information.) There is no way of determining this from the Pascal routine.
This is an example of accessing BASIC COM. Suppose the COM is defined in BASIC as:
OM /Num1/ INTEGER A,B(5),REAL C
To access this from a CSUB:
$SEARCH 'CSUBDECL'$
IMPORT COMSTUFF;
EXPORT PROCEDURE X ...
IMPLEMENT
PROCEDURE X...
TYPE COMTYPE = PACKED RECORD
C: REAL;
B: PACKED ARRAY (0..5) OF BINTVALTYPE;
A: BINTVALTYPE; {Notice the reverse order}
END;
VAR COMPTR : ^COMTYPE;
VALA : BINTVALTYPE;
BEGIN
COMPTR:=FIND_COM('Num1'); {Observe case of letters as BASIC does}
VALA:=COMPTR^.A;
...
This technique may be used for both reading information from a COM area or putting new values into COM. It should also be stated that there is nothing to prevent you from destroying the COM inadvertently or writing beyond its boundaries.
When executing BUILDC, it is not required that you declare the COM that will be accessed in the CSUB. By declaring it, however, the COM will remain in memory even if any BASIC routine that defines it is removed, as long as a CSUB that defines it is present. If you wish to define the COM with the CSUB, answer [Y] when asked "Is there COM in this procedure?".
The following prompts will appear:
How many COM blocks are there?:
Enter COM label:
Item name:
Item type (I/R/C/S/P for Integer/Real/Complex/String/ioPath):
Is this an array? (y/n):
The number of COM blocks will depend on the number of labeled COMs that you want to access. The other prompts are repeated for each COM block. The COM label may be a name or could be blank. Remember that an unlabeled COM must be defined in the main routine and there may be only one. Other labeled COMs need not be defined in the main program. The questions concerning name, type and array are the same as for the parameters, with one exception. If this is an array, the bounds of that array must be specifically entered.
The prompts for these are:
Enter number of dimensions or *:
Dimension n lower bound:
Dimension n upper bound:
If the number of dimensions is entered as "*" the rest of the dimension prompts are not displayed. This may be used only if the array is defined either in the calling BASIC routine or a previous procedure. If there is an explicit number of dimensions, the lower and upper bound must be entered for each one.
If the type of the item is STRING, the following prompt appears:
String length:
The user should enter the DIMensioned STRING length allowed for this COM entry.
The final question about the COM entry is:
Will this be used as a BUFFER? (y/n):
You should answer [N] unless you plan to use the variable as a buffer in a TRANSFER statement. Read the "Advanced Transfer Techniques" chapter of the BASIC Interfacing Techniques manual for details.
These prompts will be repeated until all the information about all the parameters in all the procedures is gathered.
@names and COMPLEX variables are allowed by BUILDC. See the "File Access Library Topic" section in the "Advanced Topics" chapter for details of using @names.
The Pascal statements NEW, MARK, RELEASE and DISPOSE are supported in CSUBs. Because they must operate in a BASIC environment, their implementation is different than in standard Pascal. The heap space is another labeled COM that is accessible from both BASIC and Pascal. This COM may be defined in either the BASIC main routine or may be defined in the subprogram. By defining it in the main program, as shown in the following labeled COM statement, it is possible to vary the size of the heap, without changing the CSUB.
10 COM /HEAP/ INTEGER A(999)
Assuming the size of the heap (2 Kbytes in this example) is defined in the BASIC calling program, respond with "*" to the BUILDC prompt:
Enter number of dimensions or *:
The Pascal statements for accessing the heap are exactly as defined in Pascal. However, before these statements are used, the routine HEAP_INIT found in the CSUBLIB module HEAP must be called. This takes the name of the COM to be used for heap as a parameter. Observe the same rules for the COM name as discussed in "Accessing BASIC COM from Pascal".
If there is a heap overflow, a Pascal ESCAPE(-2) is generated by the heap routine, which is reported as BASIC error 398.
This is an example of a CSUB that uses the Pascal heap:
MODULE HEAPSTUFF;
$SEARCH 'CSUBDECL'$
IMPORT HEAP;
EXPORT
PROCEDURE USEHEAP...
IMPLEMENT
PROCEDURE USEHEAP...
TYPE
myheap = RECORD...
VAR
x : ^myheap;
BEGIN
heap_init('Heap'); {'Heap' -- the BASIC COM block labeled HEAP.}
NEW(x);
...
END;
END. {module HEAPSTUFF}
There are two ways that you may include heap accessing statements in a CSUB. If NEW, MARK, and RELEASE are adequate to manage the heap, it is necessary to link the COMSTUFF and HEAP modules to your Pascal module. COMSTUFF is needed because COM is used to implement heap. With $HEAP_DISPOSE OFF$ (default), the DISPOSE statement does not reclaim memory. If you want to reclaim memory with DISPOSE, compile with $HEAP_DISPOSE ON$ and link in HPM from CSUBLIB.
Thus, when the Pascal statement DISPOSE is used in the CSUB it is necessary to do two things: