REXXSEM:The Reference

By Darin McBride

SemLoad
Use this to load all the routines. Sample usage: call RxFuncAdd 'SemLoad', 'RexxSem', 'SemLoad' call SemLoad /* this will load all the routines */

SemDrop
Use this to unload all the routines. All semaphores in use will still be in use, though! You will have to physically unload the DLL to kill the semaphores at this point (i.e., exit your CMD session!). Sample usage: call SemDrop /* doesn't return anything */

SemVersion
Current version of the DLL. Returns "REXXSEM v, Compiled on ". Try parsing, if necessary, with: parse value SemVersion using 'REXXSEM v' major '.' minor ','. if \datatype(right(minor, 1), 'N') then do  betalevel = right(minor, 1) minor    = left(minor, length(minor) - 1) end Most of the time, however, you won't need to know the version - just to check that the right version is the one you think you're using. (Ever have the same DLL on your system twice?)

SemLogState
This function simply logs the current state - which semaphores are available, and what the next handle will be. This is just for debugging purposes and may not give the same output next release. Sample usage: call SemLogState 'my.log.file' The my.log.file will be the file to log all the information to. The most important thing would be to ensure that you are using SemMClose/SemEClose on all available semaphores before your script ends as this may block a lot of other threads if you forget! The logfile will tell you which semhandles have not been closed for this purpose.

If all semhandles are closed, the logfile will not be created.

See: SemMLogState, SemELogState.

This function simply does the job of both of these.

SemSetLanguageDll
If you don't want to use English, use this function to change the DLL to the translated DLL that you want to use. This is useless without having "SemReturnValidAlways" on, as the only messages displayed are the ones passed in and errors. call SemSetLanguageDll 'rxsem_de' /* German! */

SemReturnValidAlways
This function actually prevents ANY function from returning "Invalid function call" and aborting your script. This is primarily intended for debugging your rexx scripts. This functionality became so useful to me that I left it in to help you before having to email me.

Sample usage: call SemReturnValidAlways 'YES' /* never aborts script - debug mode */ call SemReturnValidAlways 'NO'  /* always abort script on error */ Note, however, that timing out on a timed claim is _not_ an error, and will never abort the script. Whenever an error occurs, the function will always return a string that starts with 'ERROR:'. The rest of the string will contain an explanation of the error.

SemTraceFile
Turns on tracing - only useful if submitting a problem report to the author.

SemMCreate32 (was SemCreateMutex)
This function will create a 32-bit mutex semaphore without claiming it. You must name the semaphore something that starts with "\sem32\", otherwise this function will abort your script. It is up to you to ensure this! It will return a semhandle that will be required in all future semaphore operations.

Sample usage: semhandle = SemMCreate32("\sem32\some\semaphore.sem") Note that if the semaphore is already "created" (for example, by another process), this will simply open it and return the handle for that.

Also, even though the system will accept /sem32/blah/blah, this rexx function will not. This may be addressed in future versions. The system does not see a difference, however, between /sem32/blah/blah and \sem32\blah\blah. Since translating "/" to "\" is such an easy thing to do in REXX, it's pretty low priority to change. Also note that mutex and event semaphores cannot share the same name.

SemMCreate16 (was SemCreateSem16)
This function will create a 16-bit mutex semaphore without claiming it. You must name the semaphore something that starts with "\sem\", otherwise this function will abort your script. It is up to you to ensure this! It will return a semhandle that will be required in all future semaphore operations.

Sample usage: semhandle = SemMCreate16("\sem\squish\default") Note that if the semaphore is already "created" (for example, by another process), this will simply open it and return the handle for that.

Also, even though the system will accept /sem/blah/blah, this rexx function will not. This may be addressed in future versions. The system does not see a difference, however, between /sem/blah/blah and \sem\blah\blah. Since translating "/" to "\" is such an easy thing to do in REXX, it's pretty low priority to change. Also note that mutex and event semaphores cannot share the same name. Not that this is a big deal because REXXSEM doesn't support 16-bit event semaphores...

SemMCreate
This function will create either a 32-bit or 16-bit mutex semaphore without claiming it. You must name the semaphore something that starts either with "\sem32\" for a 32-bit semaphore, or "\sem\" for a 16-bit semaphore, otherwise this function will abort your script. It is up to you to ensure this! It will return a semhandle that will be required in all future semaphore operations.

Sample usage: semhandle = SemMCreate("\sem\squish\default") semhandle = SemMCreate("\sem32\another\semaphore") Note that if the semaphore is already "created" (for example, by another process), this will simply open it and return the handle for that. Also, even though the system will accept /sem/blah/blah, this rexx function will not. This may be addressed in future versions. The system does not see a difference, however, between /sem/blah/blah and \sem\blah\blah. Since translating "/" to "\" is such an easy thing to do in REXX, it's pretty low priority to change. Also note that mutex and event semaphores cannot share the same name.

SemMClaim
This function claims a mutex semaphore, with an optional timeout value and an optional message.

Sample usage: rc = SemMClaim(semhandle, 150) rc = SemMClaim(semhandle) rc = SemMClaim(semhandle, ,'Waiting for \sem32\somesemaphore.sem') The semhandle can be any mutex semhandle created by this library. Whether it is a Sem16 or a Mutex semaphore does not matter. If you want to change whether you use a 16-bit or 32-bit semaphore, you only have to change the create function, but nothing else. If the semhandle is not a valid pending handle, this will abort your script.

The optional second parameter is a timeout in milliseconds that if the semaphore is not available within, the failed return value ("TIMEOUT") will be returned. If this is not given, this to means indefinite (wait forever).

The optional third parameter is a message to display. The message will be displayed (with a newline) to the user of your REXX script (on stdout) to alert the user that the semaphore is being waited on if it has already been claimed. It will then proceed to wait for the timeout. If the semaphore has not been claimed by another thread or process, the message will not be displayed.

If the timeout passes without the semaphore becoming available, rc will be "TIMEOUT", otherwise it will be "CLAIMED".

SemMRelease
This function releases a mutex semaphore.

Sample usage: call SemMRelease semhandle The semhandle can be any semhandle created by this library. Whether it is a Sem16 or a Mutex semaphore does not matter. If you want to change whether you use a 16-bit or 32-bit semaphore, you only have to change the create function, but nothing else. If the semhandle is not a valid pending handle, this will abort your script. There is never a value returned.

SemMClose
This function closes the mutex semaphore after you no longer need it.

Sample usage: call SemMClose semhandle The semhandle can be any mutex semhandle created by this library. Whether it is a Sem16 or a Mutex semaphore does not matter. If you want to change whether you use a 16-bit or 32-bit semaphore, you only have to change the create function, but nothing else. If the semhandle is not a valid pending handle, this will abort your script. The semhandle is no longer a valid pending handle after this function is called.

SemMCloseAll
This function closes ALL mutex semaphores.

Sample usage: call SemMCloseAll This will force all mutex semaphores created in this process to be released and closed. No semaphore handle is valid after this. Semaphores created by other processes using this DLL are still valid.

SemMLogState
This function simply logs the current state - which mutex semaphores are available, and what the next handle will be. This is just for debugging purposes and may not give the same output next release.

Sample usage: call SemMLogState 'my.log.file' The my.log.file will be the file to log all the information to. The most important thing would be to ensure that you are using SemMClose on all available semaphores before your script ends as this may block a lot of other threads if you forget! The logfile will tell you which semhandles have not been closed for this purpose. If all semhandles are closed, the logfile will not be created.

SemMGetAllSems
This function is similar to SemMLogState, except that, rather than logging to a file, it returns the open semaphores in a 'standard' REXX array.

Sample usage: call SemMGetAllSems 'sems.' do i = 1 to sems.0 say 'MutexSem #'i 'is' sems.i  call SemMClose sems.i   say 'Woops, make that *was*' sems.i'. It''s closed now.' end Note that if you do not include the '.' at the end of the name, it will be very difficult to loop through them. RXSEM will not put the dot on the end for you.

There is one, somewhat minor, exception to the no dot issue. A subarray is created with the name that was given to each semaphore. For example, if you created the semaphore with "\SEM32\mysem", and this is the first semaphore you created, the following code: drop name mysem = SemMCreate('\SEM32\mysem') call SemMGetAllSems 'sems.' do i = 1 to sems.0 say 'MutexSem #'i 'is opened as' sems.i.name end would have an output similar to: MutexSem #1 is opened as \SEM32\mysem

SemMQuerySemHandle
This function will query for one of the existing semaphores already opened by the current process which is also referring to the semaphore name given.

This should be used with care that one thread doesn't close a semaphore still in use by another thread. If you aren't using multi-thread, but using multi-process, you can't use this anyway: creating a semaphore in one process will not make it available in another - you must create it in both processes. This is likely to be a "slow" operation. Everything is internally stored in hash tables, but indexed on handle, not name.

Sample usage: semh = SemMQuerySemHandle('\SEM32\mysem') if semh \= '' then do  /* something with the handle */ end else do  /* it wasn't already there! */ end

SemECreate
This function creates an event semaphore. Since only 32-bit semaphores plan to be supported, there is no 16/32-bit version of this function. You must name the semaphore something that starts with "\sem32\" otherwise this function will abort your script. It is up to you to ensure this! It will return a semhandle that will be required in all future semaphore operations.

Sample usage: semhandle = SemECreate("\sem32\event\semaphore.sem") sem2 = SemECreate("\sem32\event\other.sem", "POSTONE", "AUTORESET"); The second example uses both of the optional arguments. These are undocumented, but if you grab a copy of os2undoc.zip, it has a section on Event semaphore extensions. These won't do any good, as it is documented there, before fixpak 29 on Warp 3, or fixpak 4 for Warp 4.

For example, let's say thread 1 is high-priority and waiting on event semaphore "e". Let's say thread 2 is a low-priority thread that will post the semaphore "e". The first time thread 2 posts its semaphore, thread 1 is awakened. Thread 1, having higher priority, will immediately get to execute, and run. Before thread 2 gets a chance to reset the semaphore, thread 1 tries to wait on it again. But, since "e" is already in posted state, thread 1 comes back immediately. AUTORESET will reset the semaphore before thread 1 really gets to execute.
 * POSTONE: After SemEPost is called, only one thread is awakened. This implies AUTORESET.
 * AUTORESET: After the thread(s) waiting for the event are awakened, the semaphore automatically goes back into reset mode. This means, as far as I can tell, that not only do you not need to call SemEReset on the semaphore, but any thread that starts waiting after the "instantaneous" post won't accidentally hit the temporarily-posted semaphore, and continue right through, possibly hogging system resources, especially if it's a tight loop running at high priority.

Note that if the semaphore is already "created" (for example, by another process), this will simply open it and return the handle for that. Also, even though the system will accept /sem32/blah/blah, this rexx function will not. This may be addressed in future versions. The system does not see a difference, however, between /sem32/blah/blah and \sem32\blah\blah. Since translating "/" to "\" is such an easy thing to do in REXX, it's pretty low priority to change.Also note that mutex and event semaphores cannot share the same name.

SemEClose
This function closes the event semaphore after you no longer need it.

Sample usage: call SemEClose semhandle

SemECloseAll
This function closes ALL event semaphores.

Sample usage: call SemECloseAll This will force all event semaphores created in this process to be closed. No semaphore handle is valid after this. Semaphores created by other processes using this DLL are still valid.

SemEPost
This function posts that the event that this semaphore represents has happened.

Sample usage: call SemEPost semhandle Any processes/threads waiting on this event will be woken up and executed at the next point that OS/2 feels it wants to wake them up (i.e., their next normal time slice).

SemEReset
This function resets the number of posts to zero. Since OS/2 will not wait on an event semaphore with any posts to it, you need to reset it so you can wait for the next event. Unfortunately, there's no way to tell it to "decrement by one", so you'll need to always try to do everything possible, and assume multiple events have been posted.

For example: /* server code. */ do forever call SemEWait semhandle do while queued parse pull line processLine(line) end call SemEReset semhandle end Note that the reset could happen immediately after the wait, it really doesn't matter - so long as it happens before the next wait.

SemEWait
This function waits for the event to be posted, with an optional timeout value and an optional message.

Sample usage: rc = SemEWait(semhandle, 150) rc = SemEWait(semhandle) rc = SemEWait(semhandle, ,'Waiting for \sem32\somesemaphore.sem') The optional second parameter is a timeout in milliseconds that if the semaphore is not posted within, the failed return value ("TIMEOUT") will be returned. If this is not given, this means indefinite (wait forever). The optional third parameter is a message to display. The message will be displayed (with a newline) to the user of your REXX script (on stdout) to alert the user that the semaphore is being waited on if it has not already been posted. It will then proceed to wait for the timeout. If the semaphore has been posted by another thread or process, the message will not be displayed.If the timeout passes without the semaphore being posted, rc will be "TIMEOUT", otherwise it will be "CLAIMED".

SemEQueryPostCount
This function returns the current post count for the event semaphore.

Sample usage: if SemEQueryPostCount(semhandle) == 0 then do   say 'Not posted yet.' end This will not reset the post count.

SemELogState
This function simply logs the current state - which event semaphores are available, and what the next handle will be. This is just for debugging purposes and may not give the same output next release.

Sample usage: call SemELogState 'my.log.file' The my.log.file will be the file to log all the information to. The most important thing would be to ensure that you are using SemEClose on all available semaphores before your script ends. This isn't as severe as mutex semaphores, but it still shows a leak of resources if you're forgetting to close the event semaphores. The logfile will tell you which semhandles have not been closed for this purpose. If all semhandles are closed, the logfile will not be created.

SemEGetAllSems
This function is similar to SemELogState, except that, rather than logging to a file, it returns the open semaphores in a 'standard' REXX array.

Sample usage: call SemEGetAllSems 'sems.' do i = 1 to sems.0 say 'EventSem #'i 'is' sems.i  call SemEClose sems.i   say 'Woops, make that *was*' sems.i'. It''s closed now.' end Note that if you do not include the '.' at the end of the name, it will be very difficult to loop through them. RXSEM will not put the dot on the end for you.

There is one, somewhat minor, exception to the no dot issue. A subarray is created with the name that was given to each semaphore. For example, if you created the semaphore with "\SEM32\mysem", and this is the first semaphore you created, the following code: drop name mysem = SemECreate '\SEM32\mysem' call SemEGetAllSems 'sems.' do i = 1 to sems.0 say 'EventSem #'i 'is opened as' sems.i.name end would have an output similar to: EventSem #1 is opened as \SEM32\mysem

SemEQuerySemHandle
This function will query for one of the existing semaphores already opened by the current process which is also referring to the semaphore name given.

This should be used with care that one thread doesn't close a semaphore still in use by another thread. If you aren't using multi-thread, but using multi-process, you can't use this anyway: creating a semaphore in one process will not make it available in another - you must create it in both processes. This is likely to be a "slow" operation. Everything is internally stored in hash tables, but indexed on handle, not name.

Sample usage: semh = SemEQuerySemHandle('\SEM32\mysem') if semh \= '' then do  /* something with the handle */ end else do  /* it wasn't already there! */ end

RxSemListWindows
This function queries the window list - the same list you get by pressing Ctrl-Esc. It returns all sorts of information on this list, probably more than you need, so once you're done with it, you're best off dropping the variable stem you pass in. call RxSemListWindows 'stem', 'V' /* default - get list of visible-only windows */ call RxSemListWindows 'stem', 'I' /* get list of invisible windows */ call RxSemListWindows 'stem', 'B' /* get list of both visible and invisible windows (ALL) */ This function will fill stem in the standard way (stem.0 will have the number of items in the "array"). Extra data will be stored as below, where stem represents the stem you passed in, and n represents the number of the entry:
 * stem.n is the window title
 * stem.n.pid shows the process ID of the window.
 * stem.n.hwnd</tt> shows the handle to the window.
 * stem.n.session</tt> shows the session ID of the window.
 * stem.n.visible</tt> is 1 if the window is visible in the window list, 0 otherwise.
 * stem.n.hswitch</tt> shows the handle to the switch-list entry of the window.
 * stem.n.progtype</tt> shows the program type of the window which will be one of:
 * DEFAULT</tt>
 * FULLSCREEN</tt>
 * WINDOWABLEVIO</tt>
 * PM</tt>
 * VDM</tt>
 * WINDOWEDVDM</tt>

RxSemToggleSwitchVisibility
This function will go through all of its parameters and attempt to toggle any and all window list entries (see RxSemListWindows above) between their visible and invisible states.

Each parameter may be one of the following:

This is the hswitch</tt> given by RxSemListWindows. This is a case sensitive (for now) search for any and all matches. Each entry will be toggled. This is a standard REXX array. Each entry may be either an HSWITCH or a partial name. No, you can't have another stem inside.
 * HSWITCH
 * Partial Name
 * Stem (including the ending dot)

The logic used says that it tries the stem first (by putting a '0' after the parameter), then hswitch (by looking to see if there is an hswitch with that number, if the parameter is purely numerical), then partial name.

The return value will be the number of entries successfully toggled, if any. Note that only hswitch's will toggle exactly one entry, partial names may match multiple entries.

Sample code: call RxSemListWindows 'stem', 'I' /* get list of invisible windows */ do i = 1 to stem.0 /* make each one visible */ if 0 = RxSemToggleSwitchVisibility(stem.i.hswitch) then say "Can't make" stem.i "visible" else say "Made" stem.i "visible" end or  stem.0 = 1 stem.1 = 'Netscape' call RxSemToggleSwitchVisibility 'stem.' /* note the ending .! */

OpenNetscapeWindow

 * This function will use an existing Netscape process, if available, to display the given URL. If Netscape is not currently running, it will start it. It will optionally cause Netscape to create a new window (same process) if necessary.

call OpenNetscapeWindow 'http://www.yahoo.com'       /* old window */ call OpenNetscapeWindow 'http://www.yahoo.com', 'OLD' /* old window */ call OpenNetscapeWindow 'http://www.yahoo.com', 'NEW' /* new window */

ProcessIDsOf (Registered)
This function will return all the PIDs of the given process, if any. Returns in the given stem. call ProcessIDsOf 'CMD', 'pids.' Note that the process name may not include a pathname nor the extension (implementation: the scan looks for the process' name ignoring the path and extension, so providing an extension here will find nothing.) Further, you can cascade the searches with process/stem pairs. For example: call ProcessIDsOf 'CMD', 'cmds.', 'NETSCAPE', 'netscapes.' This can continue on practically forever. Note, however, that each stem given this way must have different names.

WordWrap (Registered)
This function will word-wrap the given string to the given width. If no width is given, it will attempt to determine the width of the current VIO session to use its width. rc = SemMCreateMutex('\SEM32\MY\SEM.SEM') if left(rc, 6) = 'ERROR:' then say WordWrap(rc) else semhandle = rc Not recommended for use in DBCS languages. If you don't know if your language is DBCS, that probably means it isn't. (The only languages I know of that are DBCS are Japanese, Korean, and Traditional/Simplified Chinese.)

RxWinSetTitle (Registered)
This function will set the title of the current VIO window. No guarantees on how this will function in a PM-based rexx program. call RxWinSetTitle 'Look, ma, new title!'

PriorityQuery (Registered)
This function returns the priority level of current thread. For example, parse value PriorityQuery with pri rank select when pri == 'I' then say "Idle priority" when pri == 'R' then say "Regular priority" when pri == 'S' then say "Foreground Server priority!!!" when pri == 'T' then say "Time Critical priority!!!!" otherwise say "Unknown priority of" pri"?" end say "Rank within this class:" substr(pri, 2) As you can tell, the string returned is of form c dd where c is one of I, R, S, or T, and dd is a number between 00 and 31, inclusive.

PrioritySet (Registered)
This function sets the priority level. The parameters are all based on the DosSetPriority</tt> Control Program API, but are in a somewhat different order in attempt to allow for more reasonable defaults. Note that since most REXX scripts are single-threaded, the defaults can be left as is. (In fact, even in multi-threaded applications, the defaults would usually be exactly what you wanted anyway.)

The function looks like: PrioritySet(delta, class, scope, processIdentifier) where:

If the class is changing (and it must be an actual change that the new class is different from the old class), the rank becomes 0 again.
 * delta</tt>: is from -31 to +31 for changing the rank in the current class. Note that OS/2 will truncate the range at a range of 0 to 31, so specifying -31 will ensure rank 0, and specifying +31 will ensure rank 31. Higher rank means higher priority. If not specified, will default to 0 (no change).
 * class</tt>: is one of:
 * N No change (default if not specified)
 * I Idle-time (lowest class)
 * R Regular (dynamic ranking)
 * S Foreground Server
 * T Time Critical (highest class)


 * scope</tt>: is one of:
 * P Process - all the threads of the specified process
 * R Tree - all the threads of the specified process, and all of its descendants.
 * T Thread - the specified thread of the current process (default)


 * <tt>processIdentifier</tt>: If the <tt>scope</tt> is either P or R, this is the process identifier. If the <tt>scope</tt> is T, this is the thread identifier. Default is 0, which is the current process or thread, as appropriate.

Note that any processes started are started with the current thread's priority, so it is better to only change the current thread's priority than to change the priority for the entire process, especially since background threads may be spawned by other REXX functions, and they may have legitimate performance reasons to be at whatever priority they are at.

Quick lesson: Anything that is I/O-bound (say, a disk cache, listening to the network, etc.) should be at high priority. Anything that is CPU-bound (i.e., computationally intensive) should be put at low priority. Anything at high-priority should do as little as possible when servicing the I/O device, while it is a given that CPU-bound functions are long-time processes. Anything that blocks often can be a candidate to be called I/O. For example, something that is often waiting on an event semaphore, but does very little with the event, could be higher priority to ensure that when the event is posted, it reacts quickly. However, since it spends most of its time completely blocked, it doesn't impact the general performance of the system.

Sample usage: parse value PriorityQuery with pri rank call PrioritySet -31, 'I' /* set myself to lowest-possible priority */ 'foo.exe' /* foo will run at the lowest-possible priority unless it           * changes its own priority. */ call PrioritySet rank, pri /* reset my priority */

KillProcess (Registered)
This function commits unspeakable acts against another process. (Committing this act against one's own process is called suicide.) Be very careful with this... call ProcessIDsOf 'NETSCAPE', 'netscapes.' call KillProcess 'netscapes.' Good way to get rid of all the copies of netscape currently running...

Also will accept process ID's directly. 'detach foo.exe | rxqueue' parse pull pidFoo /* some time later ... */ call Killprocess pidFoo /* done with it now */ Another example is reading Apache's "httpd.pid" file and using that to kill Apache. There are endless ways to commit murder here, and it's all under your control! BWAhahahahaha ... er... nevermind. You must promise to never use this power for evil, nor to program late at night (like I'm doing).

MyProcessID (Registered)
This function returns the current process's ID. Quite useful if you're going to kill a bunch of processes, but don't want to kill your own. Also can be used as a system-wide unique number. For example, if you wanted to create a temporary file, you could use the process ID as part of the filename so that no other copies of the same REXX program would use that file. The only real advantage this has over using <tt>SysTempFileName</tt> is that with this method, you can write a clean-up program that can kill wayward programs which may have left their files behind, first <tt>KillProcess</tt>ing them, then deleting their file. Sample: mypid = MyProcessID call KillProcess mypid /* suicide is illegal in some states/countries, don't try this at home */

SetTextScreenSize (Registered)
This function will reset your screen size. call SetTextScreenSize col, row /* much like mode col,row ! */ This function complements the SysTextScreenSize function in REXXUTIL.

ChecksumString (Registered)
This function will continue a checksum of data. While you can do this easily enough in REXX, I've found that the REXX version is extremely(!) slow. This function will do it internally, and give a great boost of speed. I've found that if I'm going to do checksums, it's a lot of them, and, while speed may not count for much, waiting five minutes to process what should take less than ten seconds is too much. chksum = 0 do while lines(file) > 0 chksum = ChecksumString(linein(file), chksum, 16) end say 'The 16-bit checksum of' file 'is' chksum This function supports 1 to 32 bit checksums. However, if you want to use 32-bit checksums, be warned that by default, REXX only uses 9-character precision, and 9 decimal digits cannot hold 32-bit precision. You can increase your precision to 10 digits by using <tt>NUMERIC DIGITS 10</tt>. Then again, you only actually need to do this if you want to do further calculations after creating a 32-bit checksum. If you only print it/save it/treat it as a string, then you don't need to change your NUMERIC settings as doing so can slow down the rest of your REXX script that does anything numerically.

ChecksumFile (Registered)
This function, much like the <tt>ChecksumString</tt> function, will create a checksum of a string, this time one stored in a file. Rather than passing in a string, you pass in the filename. One extra parameter at the end is a flag saying text or binary when reading the file. This defaults to binary. say 'The 20-bit checksum of' file 'is' ChecksumFile(file, 0, 20, 't') All comments for <tt>ChecksumString</tt> apply to <tt>ChecksumFile</tt> as well. This function only provides convenience and a bit of speed over the <tt>ChecksumString</tt> function when dealing with files. <tt>ChecksumString</tt> is merely a general version of this function.