DrDialog, or: How I learned to stop worrying and love REXX - Part 12

By Thomas Klein

This month's article is the last one that will deal with REXX itself. By now, you should know a little more about how REXX works and what can be achieved with it. Today, we'll take a look at some of the most important and commonly used functions. The next parts of the series will get us back into dealing with DrDialog. Some parts that deal with REXX will be discussed within the DrDialog subject like basic File-I/O, or the basic use of the parse statement. If time allows and people are interested, we'll have 'specials' on PARSE, stream handling and all that stuff we didn't discuss so far. But my intention is to get you back to a sample application in DrDialog, this is why we'll stop talking 'REXX only' by concluding with this issue.

Commonly used REXX functions
Well, of course there's a lot more to REXX functions than what will be contained in here, but... the following functions are those that you'll either use mostly in your programs or those that'll help you solve common programming issues.

DATE and TIME

Although these ones are mostly used to simply retrieve the current date and time strings from the machine's internal clock, the TIME function can be used to act as a stop watch... but let's take a look at the DATE syntax diagram first: result = DATE( [ option ] ) The short story: If you run DATE without options, it'll return the current date using the form DD MMM YYYY (with MMM being the first three letters of the months name). If run on the 4th of July 2004, it would give you 04 Jul 2004. (Note the spaces between the date parts). While this might look 'good' to humans, you won't be able to perform anything like sort actions with that format nor could you use it as part of file or directory names for example and other countries have different date formats they might prefer. This is where the option comes in: Note that you only need to specify the first character of the option and that it's not case sensitive (thus, you can use 'Sorted', 'S', 's', 'sORted', etc.). In addition, you don't need to enclose the option string in quotes.

That's a lot of options for such a simple function. Unfortunately, you can't supply it a date string that it should work upon - it'll always use the current system date. Thus, if you want to perform date calculations or formatting with a user-specified date string, I'll recommend the 'datergf' package by Rony G. Flatscher which is publicly available (on Hobbes for example). This is a REXX script (not a DLL) that you can use to perform almost each and every date algorithm you might imagine like
 * 'real' week number calculation (according to German DIN standard)
 * retrieve date of week/quarter/semester begin for a given date
 * date difference calculation
 * time functions and time difference calculation

and much more besides what the date function does too, all by considering leap years and so on... absolutely a must-have for doing date stuff in REXX.

Time for TIME

The time function works in the same way like what we said about DATE above: It uses a single (optional) option string. But besides returning different formats for the current system time (as date does for the current system date), it also offers an easy to use stop-watch feature! Here we go with the syntax diagram and options explanation table: result = TIME( [ option ] ) Again, here's for the short story: If you run TIME without options, it'll return the current system time using the form HH:MM:SS using a 24 hour-scheme. If run at 4:17 pm (and 32 seconds) it would thus give you 16:17:32. And again options are used to return a different format that might be preferred in other countries or required to solve specific programming issues: Remember what we said about option notation for the date function? The same applies here: You only need to specify the first character of the option and the case doesn't matter. But stop - there are two additional options for controlling the built-in stop-watch feature of time: Now this might require some additional information...

When the program is started, the internal timer is off an set to 0. The first "stop-watch" call to the time function (either "elapsed" or "reset") will simply start the timer and return 0. Any subsequent call will now return the time elapsed since the timer was started. While "elapsed" only returns the elapsed time, "reset" in addition will rest the timer to 0.

Example: Note that there is no call to "stop" the timer. The timer simply runs on and on and on... either, until it is reset, the program is shut down or the seconds will increase to more than 999999999.990000 - which is approximately about 31.7 years. I won't mind that if we were Windows users, because there simply is no windows machine able to run 31.7 years without the need for a reboot... you already have to reboot it every two years in order to install the new version... but hey: This is OS/2. So be careful when you trigger the timer and leave your program running! ;)

filespec

This functions is quite useful when dealing with drive letters and file and path names. Given a specific filename, it'll return you the drive letter, the path or the file name including the extension (if any) depending on the option specified. Here's the syntax diagram: result = FILESPEC( option, ) Option is either "drive", "path" or "name" (including the quotes). Again, this can be abbreviated to the first letter only like in SAY FILESPEC( "p", myfilename ) Compared to most of the functions that are coded manually and are used to split up a file name, FILESPEC perfectly handles situations with "unusual" file names. UNC-style file names for example (having a leading double backslash) are recognized, as well as file and path names that contain blanks. Also, root-based files like "c:\myfile" are returned a path of "\" instead of something like or "\myfile". ;) If you ever tried to code such a function by your own, you might know the possible pitfalls it can contain... and you'll like the benefits of filespec.

directory

If called without any parameter, it will tell (return) you the current directory: say directory or currdir = directory

Optionally, you can use it to change the current directory by specifying the directory you want to change to. will then first change the directory and then return the current directory's name if the previous change was successful (i.e. the directory exists): newdir = directory("\mmos2") say newdir If the directory exists, you'll get the name of it in return. Note that in contrast to the simple " " command,  supports drive letters - thus, you don't need to change the drive first. Note too, that  doesn't simply return the name of the directory changed to "like it is", but in the same way that you specified it in the call: say directory("c:\MPtn\dLl") will display c:\MPtn\dLl</tt> (if the directory exists), instead of its possible "real" name. Thus,  can easily be used to check if a directory exists by simple comparison of the return value without any modifications (like uppercase conversion, etc.). Of course there's other ways of performing such a check, but this is just a code example (if you want to retype this, omit the leading line numbers - they're just for referencing later): 1   currdir = directory 2   newdir = "S:\OS2Image\SomeDir" 3   if directory(newdir) = newdir then 4      say "directory exists" 5   else 6      say "directory does not exist" 7   call directory(currdir)

Line 1 is used to retrieve the current directory in order to re-change there after the check is done. Line 2 stores the directory name that we want to check in a variable called. Lines 3 to 6 represent the actual check with line 3 calling the  function and comparing its return value with the directory name we wanted to check. The last line (7) finally changes back to the directory that was the current one before the whole thing started. In addition it shows again, how a function can simply be "called" without having to deal with any return value. Actually we only need to do the final re-change in case that the check was successful, because otherwise (if the  doesn't exist), we would still be in the current directory. But hey - code tuning is for advanced REXX programmers and we're doing a beginners lesson here. ;)

One final note: Make sure you don't use a trailing backslash in the name of the directory you want to change to (or check), because  doesn't like it. Again,  doesn't behave like the " " command, which won't complain about the trailing backslash...

value ...is used to set and/or retrieve values for a variable. This applies to variables of the REXX environment (your program for example) as well as the OS/2 environment. In latter case,  deals with so-called environment variables of OS/2. An environment variable basically is defined by a  command in. But let's first take a look at the command syntax: result = VALUE(, [newvalue], [ ] )

Okay - the  parameter is optional. This means that if you don't want to alter a symbols (variables) value, you simply omit that parameter. The  parameter is used to tell the   function, which environment to "search" for the specified variable. If this parameter is omitted,  will check the REXX environment. If "os2environment" is specified,  will deal with OS/2s environment variables. Take a look at the following code to get a clue: /* REXX code sample */ path = "C:\Apps\MyApps\RexxCode\Sample1" say value("path") say value("path",,"os2environment")

Your program might contain a variable called  to store a specific directory name for whatever reason. As you might know, OS/2 itself uses - amongst others - a "path" variable which is used to scan for executables if they are not found in the current directory. The first  command above will retrieve the program internal path variable and thus display. The second  command simply uses the OS/2 environment to "resolve" the variable name and thus will display the current path environment variable setting... something like   and so on, depending on your systems path.

As you might imagine,  thus could be used to change environment variable settings of OS/2 by using the "os2environment" parameter and additionally specifying a new value. Yes, this can be done, but if you're interested in this kind of stuff, take an additional look at the  and   functions. The use of this functions also is demonstrated in the  function help text of. You might wonder about the necessity to use  to retrieve a variables value in your REXX program. does not actually retrieve a variables name, but rather evaluates an expression which then is used. Take a look at the following code: /* another REXX code sample */ xyz = "This is the content of variable 'xyz'..." myvar = "xyz"

Now, if you would code say myvar it'll display xyz

That's nothing special. But...: If you would code say value(myvar) it'll display This is the content of variable 'xyz'...

because  will first evaluate the contents of   (which is xyz) and then retrieve the contents of the variable named. But watch your step: If you code the following instead (note the quotes!): say value("myvar") it will simply display myvar Why? Because you gave it a literal string (by using the quotes) instead of a symbol. Got it?

random This is the function that you might rely upon when programming some kind of game in REXX. Simply drop it a minimum and maximum value and it'll return you a random number from that range. To simulate rolling a dice, all to have to do is: diceval = random(1,6) Great huh? But there's something even more interesting about that function. A base number used for generating the random numbers. In order to understand what this is used for, I suggest you fire up. You know that the tile positions are randomly generated - right?. So far, so good. But try this: Start a new game and select a game number. Look at the tile positions, then start over a different game, then restart a new game and select the same number from the first game again: The tile positions are again the same. How do they do it? Do they use a predefined list of games with tile positions? Naaahh... they use the base number "trick": If you don't specify a base number, the random number each time is generated randomly indeed. But if you give it a base number, the randomized numbers are rather determined mathematically for all subsequent calls, which leads to a predictable (or say "reproducible") sequence of numbers for that base number. For instance: /* random with base number */ basenum=45678 sequence="" sequence = random(1,6,basenum) do 69 sequence = sequence || random(1,6) end say sequence Paste the above code in an empty editor window and save it as  (and use plain-text format for instance if requested). Then run it. Run it again. And again. It will show you a random sequence of digits ranging from 1 to 6. But the sequences will always match. This is what the base number does. If you want to check what will be happening, you can then remove the  parameter from the first random function call to read it sequence = random(1,6)

like the subsequent call and re-run the script a few times. Now it'll always show different sequences. Once a base number was specified in you program, all subsequent calls to the  function will work based on that number, as long as you don't specify a different base number (which again could be a random number). What the above code does: It starts with an empty string  which will be added a new number by the first call to. The following  will generate 60 other random numbers (without a base number) which are appended to the string one after another. Finally, the string is displayed. And here's the syntax diagram of random:

syntax 1: result = RANDOM(, , [ ] ) syntax 2: result = RANDOM(, [ ] )

In the second syntax scheme, it'll use zero as the  parameter. Thus the following two lines of code have the same meaning: result = RANDOM(100) result = RANDOM(0,100)

Common REXXutil functions
There are many reasons why REXX is an incredibly versatile programming language. One of the most important ones is, that REXX's capabilities can easily be extended by using external function libraries. There are lots of function libraries available for REXX in OS/2, for almost each and every purpose - and most of them are available for free. And there's one library that already comes with OS/2 (or eComStation) right from the start: rexxutil.dll</tt>. Rexxutil provides various functions, but we'll focus on a subset here: Those that you might require most.

In order to use functions contained in external libraries from within REXX, you need to "load" them first (make them available to REXX). Before going on, here's a short introduction about "libraries": Libraries contain one or more functions and maybe other stuff like text strings, bitmaps and so. Programs actually don't use libraries - they rather use functions or resources which are contained in that library. This results in a request to load the library into memory. Such requests thus are originated by a program, but are processed and handled by the operating system. This enables the operating system to load a library only once, even it was requested multiple times from different programs: If the library is already loaded by one program, it won't be loaded again if another program requests a load. Instead, the operating system will create another "instance" of the library. Such an instance basically is an application-specific memory region to store the data shared between one program and the library. From the operating system viewpoint, a library consists of the library "stem" which contains the data and... ehh... well, let's call it a "data instance" that is established for each requester (program). The idea behind this is that it saves memory and thus improves performance, because the library "stem" is loaded only once. The actual parts used by one specific requester program are handled by the instance. If five programs are running, all using a specific library (by using any one function or resource of it), the library "stem" is loaded only once and five "data" instances are created instead of loading the entire stiff five times (which would equal the same space as five "stems" plus five "data instances").

Okay, let's get back to where we were: Loading functions from libraries. REXX provides a set of built-in functions to deal with external functions:

RxFuncAdd This loads an external function from a library. result = RXFUNCADD(<my-funcname>,<dll-name>,<dll-funcname>)

The result is simply a return code that'll tell you if the function was loaded successfully or not. A result of 0 (zero) will mean that loading was successful, anything else means that there was a problem. While  is the name of the library (without the ".dll" - in our case it would be  ) the   specifies the function to be loaded from the library. This must be the name that the library uses to identify its contained functions. The first parameter  tells REXX the name that you want to use for the function in your program (in your CALL to the function that's to say). In general, you should use the same name as the  in order to keep things clear. But at least note that you're free to use a different name for it. The drawback with it is, that other programs don't know how YOU called that function and thus can't check if it was already loaded. Thus you should always use the  in your   as well. This is what most REXX programmers do and thus provides a convenient way of checking (see also ). Note that if you don't want to, you are not forced to use the  (return code). This is the same as for all other functions in REXX - in this case, use the following notation: call RXFUNCADD <my-funcname>,<dll-name>,<dll-funcname>

Note that the function parameters are not enclosed in brackets when using this type of call (as always).

RxFuncDrop This unloads a previously loaded function from memory. Actually it unloads the instance only and the operating system will unload the library, if no one uses it any more... result = RXFUNCDROP(<my-funcname>) The same we said for the result of  above applies to   as well. Zero means okay, anything else means trouble. Note that you have to use the same name here that you used for  in the.

RxFuncQuery Now there's a lot of people telling you that in order to optimize performance and stability, you should check if the function was already loaded prior to do the load request and - in return - not to request to unload the library if a load wasn't necessary. This is what  is used for: It checks if an external function was already loaded "somewhere else". We'll talk about that, yes, but let me just tell you that as far as I'm concerned, this is "advanced" optional stuff that you might neglect for now and get back to once you know how to deal with REXX and your programming issues are solved. ;) Nevertheless, here's its syntax: result = RXFUNCQUERY When a function was registered (loaded) by one program, the associated  is kind of posted to a REXX-internal list of functions which is shared among the REXX programs. Thus the programs can check whether "someone else" already loaded a function of that name and save the work of loading it themselves. The problem with function names again is that if you load a function giving it some arbitrary name (different from its dll-internal name), this name might be used by another program which might get the picture of its library already being loaded. But as the function loaded is completely different from what the second program expects... oops. This is why you should try to load functions with their dll-internal name.

One last comment about the function stuff: As a beginner in REXX, you should know how to load libraries/functions with  and simply forget the rest for know. If your program loads libraries or functions without first checking if they were loaded and terminates without unloading them, your machine won't go up in flames, neither will other programs crash. It will simply still work without problems. Okay? Now that we know how to use them, let's go for the  functions. Note that I'll prefix the function names in the paragraph headers by "rexxutil:" to emphasize that they are no REXX built-in functions.

rexxutil: SysLoadFuncs and SysDropFuncs They are used to load or unload all functions of RexxUtil at once. is used to load them all into memory,  is used to unload them.We might need an example here: Imagine that you might want to load the functions ,  ,   and   from   for use in your program. You could now either code rc = RxFuncAdd('SysFileTree', 'rexxutil', 'SysFileTree') rc = RxFuncAdd('SysFileSearch', 'rexxutil', 'SysFileSearch') rc = RxFuncAdd('SysFileDelete', 'rexxutil', 'SysFileDelete') rc = RxFuncAdd('SysSleep', 'rexxutil', 'SysSleep')

to load them (with the need of unloading them again one by one later)... or simply use: rc = RxFuncAdd('SysLoadFuncs', 'rexxutil', 'SysLoadFuncs') call SysLoadFuncs And that's it. All functions from rexxutil.dll are now loaded and available for being called. For unloading it's just call SysDropFuncs and you're done. Note that  loads all functions from rexxutil, but still it has to be loaded itself first!

rexxutil: SysFileTree Don't panic - there's no directory tree drawing here. This rather is a dir-like function used to retrieve file names (and file information) and is able to use wildcards and recurse into subdirectories for its search. Of course, this can lead to a whole bunch of matching files. In order to be able to store the possible large amount of data,  uses a user-specified stem variable. result = SysFileTree(, , [options], [searchattrib], [newattrib] )

Note: In addition,  can also be used to not only retrieve files matching certain attributes (like read-only, system, etc.) but also to change the attributes that files. This is some advanced "feature" we won't be discussing here. We just use  to search for files - if you're interested in the other features, I recommend you to read the rexx.inf section on  ... So far so good. But how to specify the pattern and options and what is actually returned in the stem variable? Here's how it works: For instance, if you want to search for all .DLL files on your entire hard disk C:, the  would be "C:\*.DLL" and the   would be "FS" (for files, including subdirectories). If you want to search C:\OS2\DLL only (and no subdirectories of it) for the .DLL files, you would select "C:\OS2\DLL\*.DLL" for the  and simply "F" for the. The stem variable is up to you to specify - something like "files" for example is a quite good idea: returncode = SysFileTree("C:\*.DLL", "files", "FS") is the function call for the above first example. Upon completion (this might take a while and you'll notice your hard disk working) the returncode variable might contain 0 (zero) if the call was successful or something else to indicate an error code. The stem variable will contain all files found (and the additional file data) by providing one entry for each file. In addition, entry #0 of the stem variable tells you the number of files contained (=found). If the function did not succeed, this will be zero. To search and display all the .DLL files on your hard disk C:, the code would go like this: /* display all .DLL files of C: */ /* this line loads the function from the rexxutil library */ call RxFuncAdd 'SysFileTree', 'rexxutil', 'SysFileTree' /* this line now calls the function */ returncode = SysFileTree("C:\*.DLL", "files", "FS") /* loop through all returned entries of the stem variable */ do i = 1 to files.0 say files.i end /* --- end of code sample --- */ The entries themselves would look like this (excerpt of output): 8/27/02  4:19p       96359  A  C:\TCPIP\dll\TCPUNX.DLL 8/27/02  4:19p       46080  A  C:\TCPIP\dll\tnls16.dll 9/18/01  3:52p      103540  A  C:\TCPIP\dll\unzip.dll 8/27/02  4:19p       70255  A  C:\TCPIP\dll\VT100.DLL 9/18/01  5:05p       64236  A  C:\TCPIP\dll\wptelnet.dll 9/04/01  2:02p       49664  A  C:\TCPIP\dos\bin\wftpapi.dll 8/21/00 10:44a       83817  A  C:\TCPIP\dos\bin\winsock.dll This looks quite "messed up" at first sight. if you prefer to have "sortable" time/date information, use the option character "T" in addition, to read the call: returncode = SysFileTree("C:\*.DLL", "files", "FST") and the output will become: 02/08/27/16/19      41497  A  C:\TCPIP\dll\TCPOOCSJ.DLL 01/09/18/15/13     503845  A  C:\TCPIP\dll\TCPOOCSX.DLL 02/08/27/16/19      96359  A  C:\TCPIP\dll\TCPUNX.DLL 02/08/27/16/19      46080  A  C:\TCPIP\dll\tnls16.dll 01/09/18/15/52     103540  A  C:\TCPIP\dll\unzip.dll 02/08/27/16/19      70255  A  C:\TCPIP\dll\VT100.DLL 01/09/18/17/05      64236  A  C:\TCPIP\dll\wptelnet.dll 01/09/04/14/02      49664  A  C:\TCPIP\dos\bin\wftpapi.dll 00/08/21/10/44      83817  A  C:\TCPIP\dos\bin\winsock.dll And if you don't care about file date/time or file size and attributes, use the "O" option string to read the call

This will give you entries of the following format: C:\TCPIP\dll\TCPUNX.DLL C:\TCPIP\dll\tnls16.dll C:\TCPIP\dll\unzip.dll C:\TCPIP\dll\VT100.DLL C:\TCPIP\dll\wptelnet.dll C:\TCPIP\dos\bin\wftpapi.dll C:\TCPIP\dos\bin\winsock.dll You might get a picture of what the function behaves. If you don't want to scan within subdirectories of what you possibly specified in, just omit the "S". Ever wanted a directory list of your hard drive? Here's how it works: /* display directories on C: */ call RxFuncAdd 'SysFileTree', 'rexxutil', 'SysFileTree' returncode = SysFileTree("C:\*", "files", "DSO") do i = 1 to files.0 say files.i end rexxutil: SysFileSearch Doesn't search for files but rather searches within files for a specific string. It'll return all the lines of a specified file that contain the specified text to search for. result = SysFileSearch(, , [, options ] )

The  (return code) and the stem variable entry .0 behave in the same way like what we said about the   function. Thus, let's move on to a quick sample code: /* display all lines of config.sys that contain a "basedev" command */ /* including the line number within config.sys */ /* load the function */ call RxFuncAdd 'SysFileSearch', 'rexxutil', 'SysFileSearch' /* call the function */ returncode = SysFileSearch("basedev", "c:\config.sys", "lines", "N") /* loop through all entries of the stem variable, displaying the contents */ do i = 1 to lines.0 say lines.i end /* --- end of code sample --- */

rexxutil: SysMkDir Creates a directory. This is a quite simple yet fast function that enables you to check for various error codes if the creation did not succeed. The syntax is: rc = SysMkDir

Note that  cannot create nested new directories, that's to say, it can only create one new level of directories at once. If you want to create a new directory named "test1" which should contain a new directory named "test1sub1", you cannot create "\test1\test1sub1" on the fly. Instead, you should call it two times... like this: /* sysmkdir sample */ call rxfuncadd "sysmkdir", "rexxutil", "sysmkdir" call sysmkdir "c:\test1" call sysmkdir "c:\test1\test1sub1"

Okay, this sample lacks some error handling (e.g. the directory c:\test1</tt> already exists) so here's one more: /* sysmkdir sample */ call rxfuncadd "sysmkdir", "rexxutil", "sysmkdir" if sysmkdir("c:\test1") = 0 then if sysmkdir("c:\test1\test1sub1") = 0 then say "creation successful" else say "cannot create test1sub1" else say "cannot create test1"

For possible error codes of the  function, take a look at the   help file section.

rexxutil: SysRmDir Removes a directory and behaves almost equal to : rc = SysRmDir Example: /* sysrmdir sample */ call rxfuncadd "sysrmdir", "rexxutil", "sysrmdir" if sysrmdir("c:\test1") = 0 then say "removal successful" else say "cannot delete test1"

Note that it won't delete directories unless they are empty and are not the current directory. Again, use the appropriate section of  to see possible error codes and reasons.

rexxutil: SysFileDelete Removes a file (and does not support wildcards!). It uses one single parameter - the file name: result = SysFileDelete /* delete single file sample */ call rxfuncadd "sysfiledelete", "rexxutil", "sysfiledelete" if sysfiledelete("c:\temp.tst") = 0 then say "deletion successful" else say "cannot delete test1" /* -- end of sample code -- */ The following sample demonstrates a combination of  and   by making use of   to load the required functions: /* find and delete temporary files */ /* Caution: No delete confirmation requests! Use carefully! */ call rxfuncadd "sysloadfuncs", "rexxutil", "sysloadfuncs" call sysloadfuncs call SysFileTree "c:\var\temp\*.tmp", "files", "FO" do i = 1 to files.0 call SysFileDelete files.i end /* -- end of code sample -- */

rexxutil: SysSleep Suspends program execution for a given interval of seconds. Note that you can also use it with fractions of a second (which is not stated in the  help by the way). Simply use 0.1 to make it wait 1/10 of a second (100 milliseconds). It recognizes values down to 0.0000001 seconds but I I didn't test it for correct behaviour. Also, in addition, be careful with its precision, because this largely depends on whether the system load is high or not...

Syntax:

Note that there is no return code from. /* SysSleep sample */ call rxfuncadd "syssleep", "rexxutil", "syssleep" do 5 call SysSleep 1 say "tick" end /* -- end of code sample -- */

The above sample displays 5 times a string of "tick" after having waited 1 second each time.

rexxutil: SysIni This is used to process INI files. But this is not the Windows-style plain text INI files you might know but rather the OS/2 style which means binary files that are not human-readable. There are several tasks involved in dealing with INI files. Let's have a brief discussion about how an INI file is organized first. INI files contain applications which are like "root entries" in the INI file. Each application can have one or more key(s) (like sub-entries) that are associated (or not) a single value each. Take a look at the following screenshot of OS/2's own "registry editor" (REGEDIT2.EXE) that can be used to manually work with INI files:



The above screenshot was taken while  had loaded "C:\IBMLVL.INI". The upper part of the screen shows the applications contained in the file. "IBM_LS" was selected and the lower part displays all keys contained for the application selected. One key was highlighted to show it's associated value. To conclude this topic, here's a scheme to show how INI files are organized: 1 Ini-File can contain N applications 1 application can contain N keys 1 key can contain 1 value Most of the time, you'll be dealing with the basic tasks like reading and/or writing a key and value to an application entry. But due to the organization of INI files, the SysIni function supports a whole lot of tasks like getting a list of all keys for an application entry and so on. In addition,  can handle OS/2's own INI files (  and  ) as well as a user-specified INI file. To be able to specify which INI file to work with, the  parameter of   can contain
 * "SYSTEM" to refer to the OS/2 system-related INI file ("os2sys.ini</tt>")
 * "USER" to refer to the OS/2 user-related INI file ("os2.ini</tt>")
 * "BOTH" in order to use both system INI files at once (like a single, large INI file just as  does by default)
 * A full-qualified file name (including drive and path) to refer to a user-specified INI file (like "C:\MyApp\MyApp.INI" for example)

Here's an overview of how the function has to be called in order to achieve specific tasks: In order to have a little example that you can use to extend and check what it's about, I've prepared a small application. You can paste the lines into an empty editor window (for example e.exe), save and run the file: /* SysIni sample application */ call rxfuncadd "sysini", "rexxutil", "sysini" myini = "C:\test.ini" if SysIni(myini, "app1", "key1") = "ERROR:" then call CreateIni say "Displaying all applications of the INI file:" call SysIni myini, 'ALL:', 'apps' do i = 1 to apps.0 say apps.i end say "press enter to continue" pull line say "Displaying all keys of application 'app1' of the INI file:" call SysIni myini, 'app1', 'ALL:', 'apps' do i = 1 to apps.0 say apps.i end say "press enter to continue" pull line say "Displaying value of 'key2':" inival = SysIni(myini, 'app1', 'key2') say "value is:" inival say "Now altering it... press enter." pull line call SysIni myini, 'app1', 'key2', 'newvalue' inival = SysIni(myini, 'app1', 'key2') say "new value is:" inival say "press enter to continue" pull line say "Deleting key 'key2' of application 'app1':" call SysIni myini, 'app1', 'key2', 'DELETE:' say "done - press enter to continue" pull line say "Displaying all keys of application 'app1' again:" call SysIni myini, 'app1', 'ALL:', 'apps' do i = 1 to apps.0 say apps.i end say "press enter to continue - program will quit." pull line exit CreateIni: call SysIni myini, "app1", "key1", "value1-1" call SysIni myini, "app1", "key2", "value1-2" call SysIni myini, "app1", "key3", "value1-3" call SysIni myini, "app2", "key1", "value2-1" call SysIni myini, "app2", "key2", "value2-2" call SysIni myini, "app3", "key1", "value3-1" return /* -- end of sample code -- */ The program checks to see if the ini file exists by retrieving the value of app1/key1. If this fails, the INI file (probably) doesn't exist and the program will call the CreateIni</tt> subroutine. It will create some INI entries and then return. All applications are displayed, then all keys of app1. Next, the program will alter the value for key "key2" of "app1" and display the (new) value again. Then it will delete the key and re-display all (remaining) keys of app1. Have fun playing around with it.

What's coming up?
The next part will take us back to DrDialog! We'll do a short tour of how to use and control more than just one dialog window. We'll examine the pitfalls, benefits and drawbacks. This should leave you with an understanding of what to consider when dealing with multiple dialogs. Next we'll take a look at Chris Wohlgemuth's set of enhancement functions to DrDialog: A progress bar control, fly-over-help control, a picture control capable of dealing with all formats supported by OS/2 (through MMPM/2) and additional features to control master/slave behavior of multiple dialogs. Greetings. Stay tuned.