═══ 1. Application Usage ═══ Rexx Dialog allows REXX scripts to present an enduser with a PM interface, allowing him to use the mouse and keyboard to manipulate scrolling lists of text, menus, sliders, buttons, etc, as well as being able to arrange all of these in multiple windows that also can be manipulated with the mouse and keyboard. RX.EXE is an application that launches a REXX script written for Rexx Dialog. RXDLG.DLL is the REXX function library that makes the PM interface possible. It's also possible to utilize RXDLG.DLL in your own C applications (ie, just like RX.EXE does). By doing so, this makes it very easy to add a REXX interface to your application. In fact, it's easier than if you tried to add such an interface from scratch because RXDLG has functions your C app can call which you'd probably have to duplicate if you wrote your own REXX interface. When your app uses RXDLG.DLL, it inherits all of the Rexx Dialog commands, and therefore any REXX script that your app launches can open PM windows with PM controls and do message loops, etc. But, RXDLG.DLL also easily allows your app to add new REXX commands which your app has its own functions to handle. ═══ 1.1. App requirements ═══ There are a few mandatory things that your app must be and do. They are: 1. Your app must be a Presentation Manager app. RXDLG.DLL can't be used by a windowed or full-screen app. 2. The thread that is going to launch the REXX script must call PM's WinInitialize() to get an anchor block, and WinCreateMsgQueue() to get a message queue. The handle of the anchor block must be stored in the Hab field of your app's REXXSPEC. A REXXSPEC is a special structure defined by Rexx Dialog (in RX.H) which RXDLG.DLL automatically creates and makes available to your app. 3. The thread must open a PM standard (ie, frame and client) window and place the handle of the client in the REXXSPEC's Hwnd field. The ID of this client, and the IDs of any direct children of it (ie, not children of children) should be less than 7680. This then becomes the "Main Window" into which any windows opened by a REXX script open. Alternately, if you want the REXX script to create its own Main Window (ie, on the Desktop), then instead of setting the REXXSPEC's Hwnd field, you must supply an icon for the Main Window's CLOSE ICON in your executable's resource (ie, .RC file). This icon must have an ID of 256. 4. If your app wishes to add new REXX commands (ie, in addition to the Rexx Dialog commands), you must fill in the REXXSPEC's Cmds, Prefix, and Handlers fields with a string containing the new commands, and an array of pointers to the functions in your app which handle those commands. 5. The thread must call RXDLG's RexxSet() once to initialize the REXX environment. Afterward, the app can launch REXX scripts, and use various RXDLG.DLL functions. 6. Before ending, the thread should call RexxFree() to free up any REXX resources. Also the message queue created in step 1 should be freed with WinDestroyMsgQueue() and PM resources freed with WinTerminate(). In order to avoid name conflicts with RXDLG.DLL, it's recommended that you not name any of your functions or variables starting with the letters Rexx or Dlg. RXDLG.DLL utilizes window message IDs of WM_USER to WM_USER+49. Therefore, if your app defines message IDs of its own, it should start those IDs at WM_USER+50. ═══ 1.2. RX.EXE ═══ RX.EXE is an example of an app that does the minimum required setup. It then calls the RXDLG function RexxRunScript() which launches and runs a REXX script. RexxRunScript is passed the null-terminated name of the REXX script to execute. (You supply the desired script name as an argument when running RX.EXE from the command line. Remember, you can run PM apps from an OS/2 command line too). When RexxRunScript() returns, the REXX script will have been completely executed. RX.EXE simply ends after the script has been executed once (doing the necessary cleanup). RX.EXE doesn't open a Main Window on behalf of the REXX script. So, it has an icon resource (RX.ICO) with an ID of 256 bound to its executable (ie, via RX.RC). RX.EXE doesn't add any REXX commands of its own, so it doesn't set the REXXSPEC Cmds, Prefix, and Handlers fields. RX.EXE also uses the RXDLG function DlgErrMsg() to display an error message for any error number returned by RexxSet, and DlgMsgStr() to display any error messages that RX.EXE creates and wants to present to the user. These 2 functions simply pop up a PM message box to display the error message. Examine the source code RX.C to see an example of an app that uses RXDLG.DLL to launch a Rexx Dialog REXX script, and use it to launch any of the example REXX scripts included in this archive. ═══ 1.3. Supplying a Main Window ═══ You may wish to supply a Main Window into which all of the REXX script's windows open. For example, your app will most likely have its own window anyway, and you may want to keep all your app's windows in one place, including ones opened by REXX scripts that your app launches, so that minimizing your app's window also minimizes all of those child windows. Furthermore, a MODAL window opened by the script will disable your app window and its children. Note that your app can open windows, but still choose to not supply a Main Window on behalf of the REXX scripts. The scripts will simply open their own Main Window on the Desktop. But, if your app windows are opened by the thread that calls RexxRunScript(), then you should follow the guidelines in App Window Procedure. RX2.C adds code to RX.C in order to demonstrate supplying a Main Window. In order to properly support the script opening MODAL windows, the Main Window must have the class style CS_HITTEST (and may have other styles of your choosing) and its window procedure must call the RXDLG function DlgCheckModal() when receiving a WM_HITTEST message. Furthermore, for proper updating of the display, the WM_PAINT handling for the Main Window should clear the invalid portion of its window using its desired background color. (This eliminates a need to handle WM_ERASEBACKGROUND). After this, you may then do any other painting that is desired in your Main Window. RX2.C launches the REXX script whenever you double-click the mouse 1 button. This is done in the Main Window's window procedure myClientWndProc(), for the WM_BUTTON1DBLCLK case. Since it's possible for the window procedure to be called while the REXX script is executing (ie, because the user may have manipulated our Main Window while the script was doing user interaction with RXDLG), and we don't want to launch another Rexx Dialog script while one is already running, we first check that the REXXSPEC's EXECUTING Flag is not set. RexxRunScript() sets this Flag when a script is running, and clears this after a script is done executing. It's also possible to subsequently open a Main Window on behalf of the script after you've already called RexxSet() to setup the REXX interface. You could open the Main Window before calling RexxRunScript(), storing the client handle in RexxSpec.Hwnd, and after RexxRunScript() returns, close the Main Window and clear (ie, set to 0) RexxSpec.Hwnd. ═══ 1.4. App Window Procedure ═══ Even if you don't supply a Main Window, but if the thread that creates the message queue and calls RexxRunScript opens other application windows, then it's possible that your application window procedure could be called while a REXX script is executing. The scenario would be as follows -- the REXX script creates one or more windows and then calls RXDLG to do user interaction (ie, Operation 0 or 1). The user then manipulates one of your application windows while the script is "sleeping" in RXDLG. Your app window procedure gets called with the appropriate message for the user's action (ie, if he double-clicks the mouse 1 button, then your window procedure receives a WM_BUTTON1DBLCLK message). If it's important that your window procedure know if there's a REXX script being executed, check the EXECUTING Flag of REXXSPEC's Flags field. This will be set if there's a REXX script executing. One thing that you shouldn't do when there's a REXX script executing is call RexxRunScript() to launch another REXX script. Doing so may result in the second REXX script closing any windows opened by the first script, when the second script ends, and vice versa. Your app should only execute one REXX script at a time. Another caveat is receiving a WM_CLOSE message, while a REXX script is running, for the Main Window that you supply to the script. You could ignore this message by returning 0 if you want to allow the script to end of its own accord. Or if you want to close up your Main Window, abort the REXX script, and allow any WinGetMsg()/WinDispatchMsg() loop that you do in your app to drop out (due to WM_QUIT), you can simply let the OS/2 WinDefWindowProc() handle the WM_CLOSE. If the script is written to EXIT when RXDLG returns RXID = -99 (or any negative value), then the script should properly abort, and your app will get its WM_QUIT. If you close any Main Window that you've supplied to the script, then you should clear (ie, set to 0) REXXSPEC's Hwnd. (If your app is going to terminate now, this isn't really necessary). You should clear this field when processing the WM_DESTROY message of your window procedure for that Main Window. ═══ 1.5. App REXX Commands ═══ You can add your own REXX commands to the set of standard REXX commands (ie, PARSE, LEFT, etc) as well as Rexx Dialog commands (ie, RXDLG, RXSET, etc). For each new REXX command that you add, you must specify a function in your app to be called for that command. Whenever RXDLG.DLL detects one of these commands being issued in the REXX script, it calls your app function associated with that command, passing any arguments (ie, additional text supplied by the script after the command). In setting up a REXX interface for your app, you need to decide if you'd like a prefix on your command set. For example, you'll note that all of the Rexx Dialog commands begin with the 2 letters RX. This makes it easy to distinguish Rexx Dialog commands from standard REXX commands as well as labels in the REXX script. (ie, When writing a script, it's easier to avoid using any label that starts with RX, rather than having to remember every Rexx Dialog command so that you don't inadvertently use one of those for a label. This would cause the REXX interpreter to misinterpret what may be intended to be a call to a Rexx Dialog function). It's recommended that you choose a prefix for your command set. We'll arbitrarily choose the prefix APP for our example command set. You need to place a pointer to this null-terminated string in REXXSPEC's Prefix field. The string must be in upper case. For example: RexxSpec.Prefix = "APP"; If you don't want any prefix on your commands, leave the Prefix field cleared. Next, you need to decide what particular commands you'd like to create. For example, let's say that we want a REXX command called APPADD which will add 2 numbers supplied by the script, and return the sum to the script. Here's how we want the script to call APPADD to add 5 and 384. APPADD 5 384 Let's say that we also want a REXX command called APPSUBTRACT which will subtract 2 numbers supplied by the script, and return the difference to the script. Here's how we want the script to call APPSUBTRACT to subtract 5 from 384. APPSUBTRACT 384 5 Now that we have our new command set (consisting of 2 commands), we need to set the REXXSPEC's Cmds field to point to a UCHAR array containing these commands. The first UCHAR of the array will be a count of how many characters are in the first command name, followed by that first command name. In other words, the strings are stored in Pascal style. Then, the second command's length, and name follows. After the last command, there must be a 0 UCHAR to mark the end of the commands in the array. The command names must be in upper case. They are not null-terminated. Do not include the prefix on the commands (ie, the first command is ADD, not APPADD). Here's how we setup the array for our command set. UCHAR cmdArray[] = {3, 'A', 'D', 'D', 8, 'S', 'U', 'B', 'T', 'R', 'A', 'C', 'T', 0 }; RexxSpec.Cmds = &cmdArray[0]; Finally we need to setup an array of pointers to our functions for those commands. Let's say that we have a function called Add2() to handle the APPADD command, and a function called Subtract2() to handle the APPSUBTRACT command. The functions must be listed in the same order as the commands are specified in the Cmds array (ie, since we specified APPADD first, then Add2() is first in the functions array). Here's how we setup the array for our functions. PFUNC cmdFuncs[] = { Add2, Subtract2 }; RexxSpec.Handlers = &cmdFuncs[0]; Now let's talk about those functions. When a function is called, REXXSPEC's FromStr points to any text that was supplied by the script. Using our above example of the script calling APPADD, when Add2() is called, RexxSpec.FromStr points to the null-terminated string "5 384". REXX does not supply numeric variables in binary form. Rather, REXX always supplies args the way that any argv[] args are supplied to your main() function; as ascii strings. Of course, you get all of the args that the REXX script supplies, as one long string that you have to parse. For example, to add the two numbers, we have to parse the string into its separate elements of "5" and "384", and convert those two strings to binary (as you might using atoi) to perform the addition. When we return the sum to the REXX script, we have to convert that binary value back into an ascii string (as you might using itoa). RXDLG.DLL has several functions for parsing the arg string, and functions for converting ascii strings to binary and vice versa. For example, DlgAsciiToNum() parses an ascii string for the next numeric value, converts it to binary, and optionally returns an updated pointer to the parsed string (ie, points to after the numeric value which has been converted). This makes it very trivial to parse and convert the arg string passed to Add2(). DlgAsciiToNum() takes 3 args; the pointer to the string to parse (in this case RexxSpec.FromStr), a handle where to place the updated pointer (or 0 if we don't want that updated pointer returned), and a format char which is 'H' if the value is expressed as hexidecimal, 'B' if the value is expressed as binary, or anything else means that the value is expressed in decimal. Since we expect the script to supply decimal values, we'll pass a format of 0. Because we have to parse 2 numbers from the string, we'll ask for the updated pointer to be returned when we parse the first number, and we'll have it returned in RexxSpec.FromStr. Finally, we need to return the value to the script. RXDLG.DLL has a function called RexxReturnNum() which converts a binary value to an ascii string, and indicates that we want that returned to the script in the special variable RC. So, here's our Add2() function. VOID EXPENTRY Add2(VOID) { LONG num1, num2; /* Get first number */ num1 = DlgAsciiToNum(RexxSpec.FromStr, &RexxSpec.FromStr, 0); /* Get second number */ num2 = DlgAsciiToNum(RexxSpec.FromStr, 0, 0); /* Add numbers and return it to the script */ RexxReturnNum(num1+num2); } The Subtract2() function will be the same except that we'll return num1-num2. The REXX script gets its return in the special REXX variable RC as so: APPADD 5 384 RXSAY '5 + 384 =' RC Suppose you wanted to return the value in the REXX variable SUM instead of RC. The function RexxSetNumVar() will convert the binary value to an ascii string, and set the value of a REXX variable to that. The variable name that you supply must be in upper case, and a legal REXX variable name (including stem and compound variable names). So, let's substitute RexxSetNumVar() for RexxReturnNum(). VOID EXPENTRY Add2(VOID) { LONG num1, num2; /* Get first number */ num1 = DlgAsciiToNum(RexxSpec.FromStr, &RexxSpec.FromStr, 0); /* Get second number */ num2 = DlgAsciiToNum(RexxSpec.FromStr, 0, 0); /* Add numbers and return it to the script */ RexxSetNumVar("SUM", num1+num2); } Now the REXX script gets its return in the REXX variable SUM (case is irrelevant) as so: APPADD 5 384 RXSAY '5 + 384 =' Sum By not returning the value in RC, you can then use RC to indicate an error condition. For example, let's say that the Subtract2() function wants to indicate an error if the second number is larger than the first (ie, a negative result would be flagged as an error). DlgMsgStr() allows your function to return an error message or number (for RC, which is otherwise a null string or 0 for success) to the script. It also displays that error message in a PM message box if the display level that you specify is lower than the severity level set by RXERR. Finally it raises a FAILURE in the REXX script. VOID EXPENTRY Subtract2(VOID) { LONG num1, num2; /* Get first number */ num1 = DlgAsciiToNum(RexxSpec.FromStr, &RexxSpec.FromStr, 0); /* Get second number */ num2 = DlgAsciiToNum(RexxSpec.FromStr, 0, 0); /* Check if num1 < num2. If so, then return an error. We arbitrarily choose an error number of ERRAPP+DISPLAYLEVEL, and a display level of DISPLAYLEVEL (defined in RX.H) */ if (num1 < num2) { DlgMsgStr("Second number smaller than first", ERRAPP+DISPLAYLEVEL, DISPLAYLEVEL); return; } /* Subtract numbers and return it to the script in the variable "ANSWER.0" */ RexxSetNumVar("ANSWER.0", num1-num2); } Now the REXX script can check for an error. (Assume the script wants error messages instead of numbers -- ie, it hasn't set RXERR to return numbers, and therefore RC = "" for success rather than 0). APPSUBTRACT 384 5 IF RC <> "" THEN RXSAY RC ELSE RXSAY '384 - 5 =' Answer.0 Note: Case is irrelevant when the REXX script calls one of your app's REXX functions. For example, AppAdd 384 5 would achieve the same result as APPADD. RX3.C adds code to RX.C to demonstrate adding the above 2 REXX commands. TESTRX3.CMD is a REXX script that tests those commands. It makes one call to APPADD and one call to APPSUBTRACT, printing out the results of those calls. Then, if you press the ENTER key, another call to APPSUBTRACT is made, with args that will cause Subtract2() to return its error message. The following sections detail the RXDLG.DLL functions that your script can utilize in fetching and setting the values of REXX variables, parsing args, converting strings to binary values and vice versa, setting/displaying error messages and numbers, etc. ═══ 1.5.1. RexxSkipSpaces ═══ Template RexxSkipSpaces(); Description Skips any blank spaces in the string that RexxSpec.FromStr points to, updating RexxSpec.FromStr to point to the next non-blank character (could be the terminating null byte). This is useful when parsing the arg string from a REXX script into separate arguments where spaces may be separating the arguments. ═══ 1.5.2. RexxParseArg ═══ Template length = RexxParseArg(); Description Extracts the next arg in the string pointed to by RexxSpec.FromStr, up to a | character or the terminating null byte, and copies that arg to the buffer pointed to by RexxSpec.InStr. Leading and trailing blanks are trimmed off of the extracted arg. Updates RexxSpec.FromStr to point past the extracted arg. Returns the length of the extracted arg (ie, will be 0 if there was no arg to extract). This can be used to extract args where a | character is used as a separator, such as the way that RXSAY separates the Message arg from the optional Type and Heading args. Note: Since FromStr and InStr point to the same buffer, and may overlap, once you've extracted an arg and then wish to save it beyond another call to RexxParseArg(), you should copy the null-terminated arg that InStr points to, to some other buffer. You can use OutStr as that buffer if you don't need to call RexxParseArg2() or RexxSetVar() before you're done with the arg. Returns UCHAR length ═══ 1.5.3. RexxParseArg2 ═══ Template length = RexxParseArg2(); Description Extracts the next arg in the string pointed to by RexxSpec.FromStr, up to a space or the terminating null byte, and copies that arg to the buffer pointed to by RexxSpec.OutStr. Leading and trailing blanks are trimmed off of the extracted arg. Updates RexxSpec.FromStr to point past the extracted arg. Returns the length of the extracted arg (ie, will be 0 if there was no arg to extract). This can be used to extract args where one or more spaces are used as a separator, such as extracting a numeric value. Returns UCHAR length ═══ 1.5.4. RexxParseArg3 ═══ Template length = RexxParseArg3(); Description Extracts the next arg in the string pointed to by RexxSpec.FromStr, up to a space or the terminating null byte, and copies that arg to the buffer pointed to by RexxSpec.InStr. Leading and trailing blanks are trimmed off of the extracted arg. If the arg is enclosed in quotes, then everything in between the quotes is extracted (ie, the arg can have embedded as well as leading and trailing, spaces). Updates RexxSpec.FromStr to point past the extracted arg. Returns the length of the extracted arg (ie, will be 0 if there was no arg to extract). This can be used to extract args where either one or more spaces are used as a separator, such as extracting a numeric value, or args where the script wants to allow embedded spaces (and therefore the arg is enclosed in a set of quotes) such as how the WindowTitle arg is specified in RXDLG. Note: When REXX passes an arg to an app, it "eats" the outer set of quotes. Therefore, it's necessary to use a second set of quotes around the arg. For example, in order for your app to receive the string "hello there" (ie, with the quotes), then the script must specify '"hello there"'. RexxParseArg3 will recognize double or single quotes (ie, so "'hello there'" would achieve the same result). Note: Since FromStr and InStr point to the same buffer, and may overlap, once you've extracted an arg and then wish to save it beyond another call to RexxParseArg(), you should copy the null-terminated arg that InStr points to, to some other buffer. You can use OutStr as that buffer if you don't need to call RexxParseArg2() or RexxSetVar() before you're done with the arg. Returns UCHAR length ═══ 1.5.5. RexxReturnNum ═══ Template RexxReturnNum(number); Description Takes the binary number, converts it to an ascii string, and returns it to the script in the special variable RC, without causing a FAILURE in the REXX script. This may be used by a function which wishes to return a numeric value, and the function doesn't need to reserve RC to return an error number (ie, the function doesn't do anything that would necessitate reporting an error to the script). This function clears RexxSpec.InStr, which indicates to RXDLG.DLL that the RC value is to be set to the string that RexxSpec.ErrStr points to. So, you should not call any function that references RexxSpec.InStr such as RexxParseArg(), RexxParseArg3(), RexxFetchVar(), or RexxFetchStemVar(), nor should your function reference RexxSpec.InStr. It's best to call RexxReturnNum() only when your function is prepared to return immediately after. Args LONG number ═══ 1.5.6. RexxSetNumVar ═══ Template error = RexxSetNumVar(var_name, value); Description Converts the binary value to an ascii (numeric) string, and sets the specified REXX variable to that value. This is used to set a REXX variable in the script to some numeric value. Note: This may only be called by the same thread that launched the script with RexxRunScript(). Args UCHAR * var_name -- a pointer to the null-terminated name of the REXX variable. Must be upper case. Can be a stem or compound variable name. LONG val -- the signed value to set the REXX variable to. Returns UCHAR error -- 0=success, non-zero is an error in setting the REXX variable. Note: It's not necessary to display an error message, nor setup an error return to the script. RXDLG.DLL takes care of that. Your function merely needs to check for a non-zero return, and if encountered, abort whatever it's doing and return. ═══ 1.5.7. RexxSetVar ═══ Template error = RexxSetVar(var_name); Description Sets the specified REXX variable to the string contained in the buffer that RexxSpec.OutStr points to. This is used to set a REXX variable in the script to some value (numeric or otherwise). Before calling RexxSetVar(), you must copy the desired, null-terminated string to the buffer pointed to RexxSpec.OutStr. (Setting the first byte of RexxSpec.OutStr to 0, ie, nulling the buffer, causes the REXX variable to be set to a null string). You must not copy more than 255 bytes to the buffer. Note: This may only be called by the same thread that launched the script with RexxRunScript(). Args UCHAR * var_name -- a pointer to the null-terminated name of the REXX variable. Must be upper case. Can be a stem or compound variable name. Returns UCHAR error -- 0=success, non-zero is an error in setting the REXX variable. Note: It's not necessary to display an error message, nor setup an error return to the script. RXDLG.DLL takes care of that. Your function merely needs to check for a non-zero return, and if encountered, abort whatever it's doing and return. ═══ 1.5.8. RexxFetchVar ═══ Template error = RexxFetchVar(var_name); Description Fetches the value of the specified REXX variable (ie, whatever value the REXX script has set that variable to), and copies that value string into the buffer pointed to by RexxSpec.InStr. Resets RexxSpec.FromStr to RexxSpec.InStr (ie, resets it to point to the fetched value string so that string can be parsed with RexxParseArg(), RexxParseArg2(), or RexxParseArg3()). Note: Fetching a variable's value overwrites the original arg string that was passed to your function from the script. It's best to parse the original arg string before fetching any variable values. You can use RexxSpec.OutStr as a 256 byte temporary buffer to hold any strings you wish to save while fetching values. Note: This may only be called by the same thread that launched the script with RexxRunScript(). Args UCHAR * var_name -- a pointer to the null-terminated name of the REXX variable. Must be upper case. Can be a stem or compound variable name. Returns UCHAR error -- 0=success, non-zero is an error in fetching the REXX variable. If the REXX script has failed to initialize the variable to some value, then this is flagged as an error. Note: It's not necessary to display an error message, nor setup an error return to the script. RXDLG.DLL takes care of that. Your function merely needs to check for a non-zero return, and if encountered, abort whatever it's doing and return. ═══ 1.5.9. RexxFetchStemVar ═══ Template error = RexxFetchStemVar(var_name, stem); Description Fetches the value of the specified REXX variable (ie, whatever value the REXX script has set that variable to), and copies that value string into the buffer pointed to by RexxSpec.InStr. Resets RexxSpec.FromStr to RexxSpec.InStr (ie, resets it to point to the fetched value string so that string can be parsed with RexxParseArg(), RexxParseArg2(), or RexxParseArg3()). The difference from RexxFetchVar() is that the binary stem arg is appended to the var_name in order to create a stem variable. So, this function is used to specifically fetch a stem variable's value. For example, assume that you wish to fetch the value of the variable MyVar.30: error = RexxFetchStemVar("MYVAR", 30); The variable name should be limited to 22 characters. It's also possible to fetch a compound variable, if var_name already has one stem on it. For example, assume that you wish to fetch the value of the variable MyVar.0.30: error = RexxFetchStemVar("MYVAR.0", 30); Note: Fetching a variable's value overwrites the original arg string that was passed to your function from the script. It's best to parse the original arg string before fetching any variable values. You can use RexxSpec.OutStr as a 256 byte temporary buffer to hold any strings you wish to save while fetching values. Note: This may only be called by the same thread that launched the script with RexxRunScript(). Args UCHAR * var_name -- a pointer to the null-terminated name of the REXX variable. Must be upper case. ULONG stem -- the stem that is appended to the variable name. Returns UCHAR error -- 0=success, non-zero is an error in fetching the REXX variable. If the REXX script has failed to initialize the variable to some value, then this is flagged as an error. Note: It's not necessary to display an error message, nor setup an error return to the script. RXDLG.DLL takes care of that. Your function merely needs to check for a non-zero return, and if encountered, abort whatever it's doing and return. ═══ 1.5.10. RexxAppendStem ═══ Template ptr = RexxAppendStem(var_name, buffer, stem); Description Copies the var_name to the buffer, adding the stem. The buffer should be large enough to hold the final, null-terminated variable name. This may be used to create a variable name that is to be passed to RexxFetchVar(), RexxSetVar(), or RexxSetNumVar(). For example, to create the variable name MYVAR.0 in a buffer: UCHAR buffer[8]; RexxAppendStem("MYVAR", &buffer[0], 0); A pointer to the end of the new variable name (ie, the address of its null byte) is returned. This can be used to find the length of the string by subtracting it from the buffer arg passed to RexxAppendStem. Args UCHAR * var_name -- a pointer to the null-terminated name of the REXX variable. UCHAR * buffer -- a pointer to the buffer where the new variable name is created. ULONG stem -- the stem that is appended to the new variable name. Returns UCHAR * ptr -- a pointer to the null byte of the new variable name. ═══ 1.5.11. DlgAsciiToNum ═══ Template number = DlgAsciiToNum(source, &updatedPtr, format); Description Converts the source string (ie, points to a numeric string) to a 32-bit binary value. (This is essentially a 32-bit version of atoi). Skips leading spaces on the source. If updatedPtr is not 0 (ie, it's a handle to UCHAR *), then the updated pointer (ie, points to after the converted numeric string) is returned. This is useful for making multiple calls to DlgAsciiToNum in order to parse several numeric args from one arg string. The format arg is 'H' if the source string was specified in hexidecimal (for example, "3FF0"). Format is 'B' if the source string was expressed in binary "bits" (for example, "0011111111110000" is the binary equivalent of hex "3FF0"). Any other value for format means that the source string is expressed in decimal. Args UCHAR * source -- points to the numeric string to be converted. UCHAR ** updatedPtr -- a handle to where the updated UCHAR * is returned. If 0, then the updated pointer isn't returned. UCHAR format -- 'H' is hexidecimal, 'B' is binary, any other is decimal. Returns LONG number ═══ 1.5.12. DlgNumToAscii ═══ Template ptr = DlgNumToAscii(number, buffer, flag); Description Converts the binary number into a null-terminated ascii numeric string, storing it in the buffer. (This is a 32-bit version of itoa). The buffer must be large enough to hold the converted string. Flag is a 1 if commas are desired after every third digit (ie, the way a human might write the number). Flag is 0 if no commas are desired. (Commas should not be used if the resulting string is going to be used to set the value of a REXX variable which will be used in some mathematical way. REXX doesn't like commas in numeric values that it must perform math upon). A pointer to the end of the converted string (ie, the address of its null byte) is returned. This can be used to find the length of the string by subtracting it from the buffer arg passed to DlgNumToAscii. Args LONG number -- the signed binary value to be converted. UCHAR * source -- points to the buffer where the numeric string is created. UCHAR flag -- 1 if commas are desired, 0 otherwise. Returns UCHAR * ptr -- a pointer to the null byte of the converted string. ═══ 1.5.13. RexxSet ═══ Template error = RexxSet(); Description This must be called once when setting up RXDLG.DLL for use by the app. RexxSpec.Hab must first be set to a PM anchor block, and if your app adds its own REXX commands, it's best to setup the Cmds, Handlers, and Prefix fields of the REXXSPEC also prior to calling RexxSet. Returns ULONG error -- 0 if success, non-zero if an error. If there's an error, it is up to your script to display an error message to the enduser. You can call DlgErrMsg() to have the DLL post a PM message box containing an appropriate error message. The errors that RexxSet() returns are "Window registration failed." (3) or "Can't install REXX." (4). These are described in the Errors section of the Rexx Dialog 1.0 manual, under the discussion of errors that aren't related to Rexx Dialog commands (ie, the bottom of the page). ═══ 1.5.14. RexxFree() ═══ Template RexxFree(); Description Closes down the REXX interface. This will also cause any currently executing REXX script to abort. This must be called once before the app ends. ═══ 1.5.15. RexxRunScript ═══ Template error = RexxRunScript(scriptname); Description This executes a REXX script, where scriptname points to the null-terminated name of the script to execute. It sets the EXECUTING Flag of REXXSPEC while the script is executing, and clears that Flag when the script is done. It then returns an error indication. The error number is also returned in RexxSpec.ErrNum. RexxRunScript() may end up passing on messages to any app window procedure, for windows opened by the same thread that called RexxRunScript(), while the REXX script is executing. It may also call any REXX command functions that your app added, whenever those commands are issued by the script. Args UCHAR * scriptname -- the null-terminated name of the script to run. It should be fully qualified (ie, with the drive and path) if the script is not in the current directory. Returns ULONG error -- 0 if the script successfully executed, non-zero otherwise. Note: It's not necessary to display an error message. RXDLG.DLL takes care of that. ═══ 1.5.16. DlgErrMsg ═══ Template DlgErrMsg(msgNum, errNum); Description Displays an appropriate error message for any error number returned by RexxSet(), and sets RexxSpec.ErrNum to the specified errNum. Args ULONG msgNum -- the error number returned by RexxSet(). ULONG errNum -- the error number that you want RexxSpec.ErrNum to be set to. It is recommended that you add the constant ERRAPP to msgNum. That will result in error numbers that are unique from anything else returned by RXDLG.DLL functions such as RexxRunScript(). ═══ 1.5.17. DlgMsgStr ═══ Template DlgMsgStr(errMsg, errNum, displayLevel); Description Returns the specified error message or error number (depending upon whether the script has indicated that it wants messages or numbers returned for errors) to the script (in the special variable RC). It also displays the error message if the script has set the display level at least as high as the specified displayLevel. It also causes a FAILURE in the REXX script. The error number should be 20000 (ie, ERRAPP) or greater since RXDLG.DLL reserves lower error numbers, which it may return to the script. In this way, a script can always distinguish a problem with your app (ie, calling RexxSet or RexxRunScript, or an error returned by your own REXX functions) versus an error returned by one of the Rexx Dialog functions. Furthermore, it's recommended that you set any error numbers generated by your REXX functions to be greater than ERRAPP+DISPLAYLEVEL. Each error returned by your functions should be a unique number, as this number gets passed on to the REXX script (if it asks for error numbers returned). Your app can also call DlgMsgStr whenever a REXX script is not executing (ie, to display error messages for functions not related to REXX), in which case, a message box with the error message is always presented. Args UCHAR * errMsg -- the null-terminated error message to return to the script. ULONG msgNum -- the error number to return to the script. ULONG displayLevel -- the display level for this error. A message box will be displayed if the script has set the display level to at least this amount (using RXERR). Otherwise, no message box is displayed. (ie, It will up to the REXX script to post an error indication to the enduser, as the script will receive notice of this error). ═══ 1.5.18. DlgCheckModal ═══ Template value = DlgCheckModal(hwnd, mp1); Description This is only called during the WM_HITTEST processing in a window procedure that services windows opened by the same thread that has called RexxRunScript(). It allows the script to properly utilize MODAL windows. The app windows should have the class style CS_HITTEST. Args HWND hwnd -- the window handle arg passed to the app's window procedure from OS/2. MPARAM mp1 -- the message parameter 1 arg passed to the app's window procedure from OS/2. Returns MRESULT value -- the value that your window procedure's WM_HITTEST case returns (to OS/2). ═══ 1.6. Returning RC values to the REXX script ═══ The special RC REXX variable is usually used to return error messages or numbers to the REXX script. (The script uses RXERR to set whether it wants messages or numbers returned for errors). If a function executes successfully, it sets RC to either a null string (ie, "") if messages are to be returned, or 0 if numbers are to be returned. Non-null strings or non-zero values for RC indicate that the function encountered some error. In this case, RC is either an error messsage or an error (non-zero) number. The script can then check the RC value after each call to a Rexx Dialog or your app's own REXX commands to determine whether an error occurred or not. Your app must use DlgMsgStr() to display error messages that your app creates and wishes to return to the script. (DlgErrMsg() may be optionally used to display messages corresponding to error numbers returned by RexxSet()). This function properly sets up the error return for RC, and it also causes the FAILURE flag to be set in the REXX script (so that the script can use SIGNAL ON FAILURE to automatically jump to error handling whenever your app returns an error). Also, DlgMsgStr() takes care of automatically displaying the error message in a PM message box if the script desires that (ie, the script uses RXERR to set the display level to determine which error messages if any it wants automatically displayed). Note: Your REXX function need do nothing if it wants to return a successful RC value. RXDLG.DLL defaults to returning that, until such time as your app specifically calls DlgMsgStr() to set an error return. If your REXX function always succeeds (ie, there's no scenario in which an error condition may arise), then you can instead use the RC variable to return a value from your function. Use the RexxReturnNum() function to return a numeric value to the script. Note: RexxReturnNum() is passed a signed long, so if you're passing a ULONG instead, you're limited to a range of 0 to 2,147,483,647. RexxReturnNum() clears the RexxSpec.InStr pointer, (which indicates to RXDLG.DLL that the RC value is to be set to the string that RexxSpec.ErrStr points to). So, after calling RexxReturnNum(), you should not call any function that references RexxSpec.InStr such as RexxParseArg(), RexxParseArg3(), RexxFetchVar(), RexxFetchStemVar(), DlgMsgStr(), or DlgErrMsg(), nor should your function reference RexxSpec.InStr. It's best to call RexxReturnNum() only when your function is prepared to return immediately after. RexxReturnNum() does not raise the FAILURE flag in the script. If your function wishes to return a string (rather than a numeric value) to a script (without causing a FAILURE in the script), simply set RexxSpec.ErrStr to point to the desired string, clear RexxSpec.InStr, and return. For example, here is a function that simply returns the string "Hello" to the script. VOID EXPENTRY ReturnHello(VOID) { /* Set ErrStr to point to the desired string. Obviously, it can't be in a local buffer (ie, on the stack) because we need to return it */ RexxSpec.ErrStr = "Hello"; /* Clear InStr to let RXDLG.DLL know that we want to return ErrStr in the RC variable */ RexxSpec.InStr = 0; /* Return control back to the script */ return; } For example, if your app associated ReturnHello() with a APPHELLO command, the script would do: APPHELLO /* Print the returned "Hello" */ RXSAY RC ═══ 1.7. InStr and OutStr ═══ The RexxSpec pointers InStr and OutStr each point to a (different) 256 byte buffer. These pointers are valid ONLY while RXDLG.DLL is calling one of your app's REXX functions. You can determine that this is the case by checking the OutStr pointer. If it's not zero, then InStr and OutStr are valid. Initially, InStr points to the arg string that was received by the script. You can use these buffers for any purpose that you desire, although some of the RXDLG.DLL functions also use these buffers. RexxParseArg(), RexxParseArg3(), RexxFetchVar(), and RexxFetchStemVar() use InStr (and its buffer), and RexxParseArg2() and RexxSetVar() use OutStr (and its buffer). It's best to not alter these 2 pointers if you're going to be calling the preceding RXDLG.DLL functions. ═══ 1.8. Documentation ═══ If you want to provide an online book (ie, .INF file, like this one) detailing your app's REXX interface, the best approach is to create an INF file that lists only those new REXX commands that your app implements. For example, RX3.IPF is example IPFC source code that documents the 2 commands APPADD and APPSUBTRACT that we added to RX3.C. It has a heading page, followed by a page for each REXX command. The layout is similiar to that used for the online book for Rexx Dialog (ie, RX.INF). Then, create a desktop object that invokes OS/2's VIEW.EXE utility. For the Parameters field of the object, add RX.INF and the name of the INF file describing your new functions. For example: RX+RX3 The result is a book that combines the 2 INF files into one.