!this version of runoff does not have literal mode!! - !all of its special reserved punctuation characters (metacharacters) !are interpreted in what it calls "literal" mode, and there is apparently !no way to turn them off except by doing as we do below - changing them !to unused control characters. this has the added advantage of turning !off interpretation in non-literal mode as well. the control characters !in the following commands may not be visible as you look at this. !note that interpretation of = (hyphenate) and < (caps) is disabled. !the only special punctuation characters we leave to be interpreted are: ! $ for runoff's & (underline) ! ^ (for turning on underline mode) ! \ (for turning off underline mode) .change ob  ($ to ^A) .change ul $ (& to $) .change ss  (@ to ^B) .change sb  (~ to ^C) .change ic  (! to ^D) .change cs  (% to ^E) .change sp  (# to ^F) .change qu  (> to ^G) .tm 2 !top margin .ps 58 !page size .number 1 .plm 0 !permanent left margin .prm 80 !permanent right margin .left margin 2 .right margin 78 .tab stops 6,14,22,30,38,46,54,62,70 .literal ***************************************** * * * How to interface between C and PL/I * * * ***************************************** .end literal .b 3 Making a C routine PL/I-callable .br -------------------------------- .b 1 .x plient .x C subroutines, PL/I-callable .x PL/I-callable C subroutines .x calling C from PL/I Place a call to plient as the first statement in your C routine, and it becomes a PL/I-callable routine. Other C routines then cannot call it directly but can call it through the callpli routine described below. The calling sequence is plient("XYZ"), where XYZ is the name of your routine. You are automatically returned to the PL/I caller when you exit the routine, whether implicitly by reaching the end or explicitly via C's return statement. .b 1 Thus a routine taking two FIXED BIN(15) arguments and returning the sum of the two in the second could look like this: .x argument passing from PL/I to C .x number of arguments in a call .b 1 ^$ C subroutine PL/I caller \$ .literal ISUM(narg, a, b) DCL ISUM ENTRY(BIN(15),BIN(15)); int narg, *a, *b; DCL I BIN(15); { plient("ISUM"); CALL ISUM(7,I); *b += *a; } .end literal .x function returning a value, PL/I-callable The same thing in the form of a function returning the sum as its value could look like this: .b 1 ^$ C subroutine PL/I caller \$ .literal ISUM(narg, a, b, v) DCL ISUM ENTRY(BIN(15),BIN(15)) int narg, *a, *b, *v; RETURNS(BIN(15)); { DCL (I,J) BIN(15); plient("ISUM"); *v = *a + *b; J=ISUM(7,I); } .end literal The narg variable receives the number of arguments in the call; this allows variable length argument lists. Note that it is passed by value, while all actual arguments are passed by reference (pointer). Ignore the C compiler warning 'no reference for "narg"'. Either the PL/I caller must have an ENTRY statement giving the number of arguments the compiler should check for, or you must test narg at run time before accessing any arguments. Do not modify the narg variable. For an example of handling a variable number of arguments, see the signalling section below. .b 1 .x returning a value (PL/I-callable function) Note that in the case of a function returning a value, an extra argument is passed for the return value; it is included in the narg count. Return a value by assigning to this argument, not through C's return statement. .b 1 .x array arguments, PL/I to C .x structure arguments, PL/I to C To handle an array or structure argument, the PL/I caller must explicitly pass the address; otherwise you'll get a descriptor. To handle character strings, see the special section below. .b 1 .x stack overflow plient checks for hardware stack overflow and has PL/I generate an error message if it occurs. However C does not check for stack overflow, so watch that your nesting of plain (non-PL/I-callable) C routines doesn't get too deep or use too much local (auto) storage. .b 1 .x errors, location of error given in PL/I messages Note: if the PL/I error handler generates an error message, it gives the name of the routine and the octal offset to the location the error occurred. If this occurs in a C routine, the name will be the name in the last-executed plient call, and the offset will be garbage. .page .b 3 Calling a PL/I procedure from C .br ------------------------------- .b 1 .x callpli .x PL/I procedure, calling from C .x calling PL/I from C Say callpli(pp, nn, a1, a2, ...), where pp is the address of the procedure, nn is the number of arguments you're passing, and a1, a2, etc are pointers to the arguments. Your program may blow up if nn is incorrect. .b 1 You can use callpli only if the main program is a PL/I program, but the C routine using callpli does not need to be PL/I-callable. In other words, you can callpli from a C routine nested several calls down from the last PL/I-called C routine. .b 1 .x FNDLUN, example of call .x LUN, use of FNDLUN to allocate a LUN Examples: .b 1 ^$ C source PL/I equivalent \$ .literal XXX(narg, lun, a) XXX: PROCEDURE(LUN, A); int narg, *lun, *a; DCL (LUN,A) BIN(15); { int i,j,t1,t2; DCL (I,J) BIN(15); extern FNDLUN(); DCL FNDLUN ENTRY(BIN(15)); extern X(),Y(); DCL X ENTRY (BIN(15),BIN(15)) RETURNS(BIN(15)), Y ENTRY; plient("XXX"); callpli(&FNDLUN, 1, lun); CALL FNDLUN(LUN); callpli(&FNDLUN, 1, &i); CALL FNDLUN(I); . . . . . . t1=9; t2=*a+1; callpli(&X, 3, &t1, &t2, &j); J=X(9, A+1); . . . . . . callpli(&Y, 0); CALL Y; .end literal .x argument passing, C to PL/I .x array arguments, C to PL/I .x structure arguments, C to PL/I Note that if passing a variable that is also one of our arguments, we pass it directly (we actually received its address), but if passing a local variable, we must remember to explicitly pass the address, and if passing a constant or expression, we must in addition pass it through a temporary (this is both because the C compiler won't handle an expression of the form &9 and good practice to insulate yourself from modifications). If passing arrays or structures, the callee must be set up to handle a pointer (declare the array or structure BASED on the argument). For character strings, see the section below. .page .b 3 Sending PL/I signals from C .br --------------------------- .b 1 .x signal .x conditions, signalling .x user conditions, signalling .x defcond .x enddefcond usage: .b 1 ^$ C source PL/I equivalent \$ .literal #include ; /*(defines condition code names)*/ XXX: PROCEDURE(...); defcond "UCOND"; DCL UCOND CONDITION; char ucond[]="UCOND "; enddefcond; XXX(...) { . . . . . . signal(ZERODIVIDE); SIGNAL ZERODIVIDE; . . . . . . signal(CONDITION,&ucond); SIGNAL CONDITION(UCOND); .end literal In other words, include ; to signal a system condition, simply give the PL/I name of the condition; to signal user-defined conditions, pass the "CONDITION" condition code and a reference to the user condition. To refer to a user-defined condition follow the defcond-enddefcond sequence above, putting it in the external section of your module, and note that the contents of the string (after the =) must contain 6 characters, blank padded if necessary. .b 1 You can use signal only if the main program is a PL/I program, but the C routine using signal does not need to be PL/I-callable. In other words, you can signal from a C routine nested several calls down from the last PL/I-called C routine. .b 1 .x errors, location of error given in PL/I messages If PL/I generates a message (the condition is enabled and there is no ON unit), it gives a routine name and offset. The name will be the name in the last-executed plient call and the offset will be meaningless. .page .x arguments, optional .x calling with variable number of arguments .x variable number of arguments in a call .x number of arguments in a call .x terminating a PL/I program from C .x exiting a PL/I program from C .x NUMARGS, signalling .x ERROR, signalling .x FINISH, signalling .x error handling from C .x plient, example .x signal, example .x CLREF example .x event flag, CLREF example As an example of handling optional arguments and terminating a PL/I program from C, let's say we want a routine to clear an event flag like the PL/I CLREF utility, taking an event flag number and optionally a variable to receive the DSW (error code), except that if the DSW argument is not supplied, the routine, on encountering an error, is to automatically print an error message and terminate the program. It could look like this: .x msgti, example .x clang.c include file .literal #include ; /*defines for nicer language syntax*/ #include ; /*C executive library include*/ #include ; CLREF(narg, evflag, dswvar /*optional*/) int narg, *evflag, *dswvar; { int idsw; plient("CLREF"); iff narg<1 or narg>2 then signal(NUMARGS); idsw = clef(*evflag); /*use C executive library routine*/ iff narg>1 then *dswvar = idsw; else { msgti("ERROR CLEARING EVENT FLAG (CLEF DIRECTIVE), DSW = %", idsw); /*write to console (see I/O section below)*/ signal(ERROR); } } .end literal In other words if you detect a call with the wrong number of arguments, signal NUMARGS, same as PL/I does. To terminate a program from C due to an error, signal ERROR. To terminate a program without error, signal FINISH. Don't use C's exit and related routines. .page .x string argument passing from PL/I to C .x character string argument passing from PL/I to C .x argument passing, strings - PL/I to C .b 3 Handling PL/I character strings .br ------------------------------- .b 1 .x plistring To handle a PL/I string argument, include and declare the argument as of type plistring. This defines it to be what PL/I actually passes for a string argument, namely a pointer to a string descriptor. To pass a structure consisting of more than one string, the caller should pass STRING(struct)--you then receive the whole thing as one ordinary character string containing the concatenation of all the parts. The following routines are provided, where S is a plistring. Note that they work for either varying or fixed length strings--ordinarily you won't know or care which kind you're dealing with. .b 1 .x plistr plistr(S) .left margin +14 function returning a pointer to the first character of the string. depending on how you use it, you may need to type this function: char *plistr(). .left margin -14 .b 1 .x plilen .x LENGTH, C equivalent (plilen) plilen(S) .left margin +14 function returning the equivalent of PL/I LENGTH(S). .left margin -14 .b 1 .x plicpy .x string assignment to PL/I strings plicpy(S,p,n) .left margin +14 stores in S a new value, namely the string of characters of length n whose first character is pointed to by pointer p. does a full PL/I style assign, truncating if the new value is longer than will fit, blank padding if S is fixed length and the new value is shorter, and setting the current length if S is varying; except that if truncation occurs, plicpy returns a value of true rather than raising the STRINGSIZE condition. .left margin -14 .b 1 .x pliset .x string copying, C string to PL/I string (pliset) pliset(S,cs) .left margin +14 does a plicpy to S, given C-style string cs (i.e., pointer to null-terminated string of characters). in particular, pliset(S,"") sets S to the null string (zero length) if varying or all blanks if fixed length (same as PL/I S=''). returns the same value (overflow indicator) as plicpy. .left margin -14 .b 1 .x plzcpy .x string copying, PL/I string to C string (plzcpy) plzcpy(cs,cl,S) .left margin +14 copies PL/I string S into C string (character array) cs. the resulting string can be used the same as any other C string (i.e., it's null-terminated). the cl argument gives the number of characters available in cs (counting the null); plzcpy, like plicpy, returns a value of true if the string was truncated to fit. .left margin -14 .b 2 Thus if SS and S are both plistrings, then .b 1 iff plicpy(SS,plistr(S),plilen(S)) then signal(STRINGSIZE); .b 1 does exactly what the PL/I statement SS=S does. .fill .page As an example, here is a function taking a string argument and returning a string value equal to the argument with leading and trailing blanks removed: .x TRIM .x string arguments, example of handling .x function returning string value, example .x plistring, example .x plistr, example .x plilen, example .x plicpy, example .x signal, example .literal #include ; #include ; #include ; TRIM(narg, s, v) int narg; plistring s, v; { register charpointer s_strt, s_end; plient("TRIM"); s_strt = plistr(s); s_end = s_strt+plilen(s)-1; while (s_strt<=s_end and *s_strt==' ') doo s_strt++; while (s_end>s_strt and *s_end==' ') doo s_end--; iff plicpy(v, s_strt, s_end-s_strt+1) then signal(STRINGSIZE); } A PL/I caller could declare it DECLARE TRIM ENTRY(CHAR(*) VARYING) RETURNS(CHAR(#) VARYING); or DECLARE TRIM ENTRY(CHAR(*)) RETURNS(CHAR(#) VARYING); .end literal There is nothing to keep you from declaring it RETURNS(CHAR(#)) except for the limited usefulness in this particular case. .page .x string descriptors .x descriptors, PL/I string descriptors The following additional string facilities are provided, where SS is a plistring (pointer to string descriptor). .b 2 .x plisd .nofill plisd sd defines local variable sd to be a PL/I string descriptor. .fill .b 1 .x plisdi plisdi(&sd,stype,sp,sz) .left margin +14 initializes string descriptor sd to the type stype (varying or fixed length), pointing to the string area pointed to by sp, and with string size sz. note that only the descriptor is affected; thus the string itself either must be constant or must be initialized separately, such as through plicpy or pliset. sz must be less than 16K. if initializing a varying descriptor, keep in mind that sp must be even, the string area pointed to must have 2 extra bytes for the current length (in addition to sz bytes for the string itself), and that this length word is not initialized (it is initialized by a subsequent plicpy or pliset). .left margin -14 .b 1 .x isvarying .x varying character strings, testing for .nofill SS isvarying as an expression evaluates to true if SS is a varying string .fill .b 1 .x isfixed .x unvarying character strings, testing for .nofill SS isfixed as an expression evaluates true if SS is a fixed length string .fill .b 1 .x plisize .x size allocated to PL/I string (plisize) .nofill SS plisize as an expression evaluates to the size of the string--i.e., .fill .left margin +14 the number given in the CHAR(#) [VARYING] declare statement, or in other words, the maximum possible length (if SS is fixed length this is of course the same as the current length). .left margin -14 .b 3 .x strings, defining local PL/I string variables .x argument passing, strings, C to PL/I .x string argument passing, C to PL/I .x character string argument passing, C to PL/I To define a local PL/I string variable (for example to call a PL/I string-valued function), separately define a string descriptor and space for the string itself. .b 1 To call a PL/I procedure with a string argument, pass the address of the descriptor. To pass a string constant (literal), it's safest to make a copy in a local string variable; point a descriptor directly to a string constant only if you are sure the callee will not modify it. .b 1 See the following examples. .page .x callpli, examples with string arguments examples: .b 1 ^$ C source PL/I equivalent \$ .literal #include ; F(narg,z) F: PROCEDURE(Z); int narg; plistring z; DCL Z CHAR(*) VARYING; { /*OR DCL Z CHAR(*);*/ plisd xd; char x[8]; DCL X CHAR(8); plisd yd; char y[8+2]; DCL Y CHAR(8) VARYING; plient("F"); plisdi(&xd, fixed, x, sizeof(x)); plicpy(&xd, plistr(z), plilen(z)); X=Z; plisdi(&yd, varying, y, sizeof(y)-2); callpli(&TRIM, 2, z, &yd); Y=TRIM(Z); /*safe way to pass a constant:*/ plisdi(&xd, fixed, x, 3); pliset(&xd, "ABC"); callpli(&FFF, 1, &xd); CALL FFF('ABC'); /*another safe way:*/ plisdi(&xd, varying, x, sizeof(x)-2); pliset(&xd, "ABC"); callpli(&FFF, 1, &xd); CALL FFF('ABC'); /*or, only if you are sure FFF will not modify its argument:*/ plisdi(&xd, fixed, "XYZ", 3); callpli(&FFF, 1, &xd); CALL FFF('XYZ'); .end literal .page .x I/O from C in a PL/I program .x cioini .b 3 Doing I/O from C under PL/I .br --------------------------- .b 1 To do I/O from C, call CIOINI before making any calls to C I/O routines. Normally this would be done by placing the following at the beginning of your PL/I program: .literal DECLARE CIOINI ENTRY; CALL CIOINI; .end literal This initializes the C I/O environment, in particular opening C's standard input and output files on the console, and setting the default UIC for disk file access. Your PL/I-called C routines can then use any of the regular C I/O runtime routines, such as printf, fopen, getchar, etc. If necessary CIOINI could be called from C via callpli. .b 1 .x msg C's msg routine (write a line to the user's console, TI:) works fortuously (it writes to lun 1, which both C and PL/I open as the console). So if the only I/O you do is through msg, then the CIOINI call is unnecessary. Note that if the next PL/I PUT is without a SKIP, it will overwrite the line from msg unless you include a new-line at the end, and that msg .x event flag (msg uses event flag 1) uses event flag 1. .b 1 .x msgti .x error messages from C (msg and msgti) .x messages, giving error messages from C An additional routine is provided to print a message on TI: with insertion of a variable number of integers, a sort of stripped down printf: .literal msgti("message" [,int1,int2,...] ); .end literal where "message" is a string of text with a percent sign marking each place an integer argument is to be inserted into the resulting message line. None of the printf options may be used; only unformatted integers may be included. You can have any number of integer arguments (including zero), so long as the number corresponds with the number of percent signs in the message. A carriage-return and line-feed are inserted both before and after the message, ensuring that your message starts at the beginning of a line, and that it doesn't get overwritten by a subsequent PUT. If the resulting message does not fit on an 80 character line, it will be broken into more than one line; integers will not be broken in the middle, but a word of text will be. The only reasons for using msgti rather than printf are: 1) since msgti uses msg, it does not require a prior CIOINI call, and 2) it takes less memory. Thus msgti is well suited for giving error messages. Note that event flag 1 is used. .b 1 Much of the C I/O runtime is not sharable. .page .x memory management .x dynamic memory management .x ALLOCATE .x FREE .x malloc .x free .x alloc .x calloc .x talloc .x tfree .x salloc .x realloc .x brk .x sbrk .x flex routines .x vstring routines .b 3 Dynamic memory management .br ------------------------- .b 1 Versions of C's malloc and free library routines are supplied which call PL/I's runtime for ALLOCATE and FREE, so they and the other runtime routines based on them can be used freely in PL/I-called C routines. This includes alloc, calloc, talloc, and tfree, but does not include salloc, realloc, brk, sbrk, flex stuff, or vstring stuff. It would not be hard to support the flex and vstring stuff by writing a realloc to work with PL/I. .b 6 Task building .br ------------- .b 1 .x task building .x TKB command lines .x libraries for task building .x I/O, task building .x PLIMMR library .x C libraries .x executive library (CX.OLB) To use the routines described herein, include LB:[1,1]PLIMMR/LB in your task build, and if doing I/O from C, additionally LB:[1,1]C/LB. .b 2 example of a TKB indirect file for a program using no C I/O: .literal TEST/FP/CP,TEST/-SP=TEST LB:[1,1]PLIMMR/LB ;INCLUDES C-PL/I INTERFACE ROUTINES LB:[1,1]PLIUTL/LB ;PL/I UTILITY LIBRARY LB:[1,1]PLILIB/LB ;PL/I RUNTIME LIBRARY / STACK=2000 LIBR=PLIRES:RO // example for a program using C I/O, and having a C module that uses C executive library routines: TEST/FP/CP,TEST/-SP=TEST,CMOD LB:[1,1]PLIMMR/LB LB:[1,1]PLIUTL/LB LB:[1,1]PLILIB/LB LB:[1,1]CX/LB ;C EXECUTIVE LIBRARY LB:[1,1]C/LB ;C LIBRARY, FOR I/O / STACK=2000 LIBR=PLIRES:RO // .end literal .page .do index