home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 32 Periodic
/
32-Periodic.zip
/
edmi1-6.zip
/
EDMI1-6.INF
(
.txt
)
< prev
next >
Wrap
OS/2 Help File
|
1993-11-03
|
126KB
|
2,804 lines
ΓòÉΓòÉΓòÉ 1. Title Page ΓòÉΓòÉΓòÉ
Welcome to EDM/2 - The Electronic OS/2 Developers Magazine!
Portions copyright (c) by Larry Salomon Jr.
Volume 1, issue 6
Copyright Notice and Other Stuff
The editor of this electronic magazine is Larry Salomon, Jr.
Portions of EDM/2 are copyrighted by the editors. This publication may be
freely distributed in electronic form provided that all parts are present in
their original unmodified form. A reasonable fee may be charged for the
physical act of distribution; no fee may be charged for the publication itself.
All articles are copyrighted by their authors. No part of any article may be
reproduced without permission from the original author.
Neither this publication nor the editors are affiliated with International
Business Machines Corporation.
OS/2 is a registered trademark of International Business Machines Corporation.
Other trademarks are property of their respective owners. Any mention of a
product in this publication does not constitute an endorsement or affiliation
unless specifically stated in the text.
Administrivia
Volume 1, issue 5 went out with only a minor glitch, which is better than a
major glitch. The really exciting news is that the auto-mailer worked quite
well, so we should be able to handle higher volumes of subscriptions. My
thanks go to the person that informed me of the already-written 'majordomo'
which also does mailing lists but I found out that panix does not have it
installed and that the system administrators have said before that they would
not like mailing lists to be done from panix (and, futhermore, they would
probably charge by the KB sent per month if a mailing list ate up too much
CPU). So, it looks like I will have to continue the mailing using my
home-grown 'submail'.
This issue has some really good stuff in it. The Want Ads section resulted in
a flurry of "wanna-write" notes on REXX. While we actually received only one
article for this issue, the interest of knowledgeable people to become an
author seems high. Also from the Want Ads, I hope that everyone will enjoy the
- not one, but - two articles on multithreading. Finally, I must apologize to
everyone for not writing part 4 of the "Container" series, but I have this
annoying problem called "a job" and it has been eating up a lot of time lately.
I, as well as Andre Asselin ("OS/2 Installable File Systems"), will try to
finish up next month.
EDM/2 officially welcomes IBM to the network distribution, courtesy of David
Singer who runs the IBM (internal and external) gopher servers.
We would like to thank Gordon Zeglinski for rewriting the installation program.
It no longer depends on the system installation to work properly.
Functions submitted for the Snippet(s) of the Month portion of Scratch Patch
will be included as snippet.zip in the distribution from this issue on.
New look (again)
We are still tweaking a few things regarding presentation and are interested in
your feedback. Tell us what you like and dislike about the format of the
magazine (no promises that we will listen to you, however... :)
Annoucement
This is from IBM's "Software Developer Support & Services" group. Many of you
probably saw the same in comp.os.os2.announce.
IBM announces the PS (Personal Software, which includes DOS, OS/2 and LAN
Systems) Worldwide Developer Assistance Program mailbox for users of the
Internet. If you have a question about our program or would like to join,
please write to us at wwdap@vnet.ibm.com.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2. This Issue's Features ΓòÉΓòÉΓòÉ
The following articles constitute this issue's features:
o Customizing the Enhanced Editor
o Rexx-ercising Your Applications
o Threads in PM Applications
o Writing a C++ Thread Class
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.1. Customizing the Enhanced Editor ΓòÉΓòÉΓòÉ
Written by JФrg Schwieder
Introduction
If you talk with OS/2 developers, you will soon get the impression that most of
them are desperately looking for a good programming editor even though OS/2
includes EPM, a very capable and flexible editor. One of the reasons for the
bad reputation EPM seems to have may be its standard configuration which makes
it resemble the system editor, OS/2's version of the Windows Notepad. Another
reason may be that - though it is very flexible - it shares the problem of a
lot of software that was 'born to use': that is it was written to be used not
to be sold or that evolved from a line of more simple product improved over the
time. Its user-interface clearly shows some of the author's preferences and
remainders from former stages of development. For example, this means that EPM
is almost completely incapable of using 'hard' tabs (which doesn't matter if
you don't like them but if you're used to using such features it is quite a
drawback).
So if you want to use EPM you may need to customize it. To do this you need
the complete EPM toolkit available on various ftp sites such as cdrom.com. It
includes an updated (and reconfigured) version of EPM along with a better
documentation and a compiler for the E macro language (ETPM). This article
will cover custom configuration of your editor using this toolkit and - through
examples - provide you with some nice additional features such as file
autoloading and positioning and keyboard undo.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.1.1. Introduction ΓòÉΓòÉΓòÉ
Written by JФrg Schwieder
If you talk with OS/2 developers, you will soon get the impression that most of
them are desperately looking for a good programming editor even though OS/2
includes EPM, a very capable and flexible editor. One of the reasons for the
bad reputation EPM seems to have may be its standard configuration which makes
it resemble the system editor, OS/2's version of the Windows Notepad. Another
reason may be that - though it is very flexible - it shares the problem of a
lot of software that was 'born to use': that is it was written to be used not
to be sold or that evolved from a line of more simple product improved over the
time. Its user-interface clearly shows some of the author's preferences and
remainders from former stages of development. For example, this means that EPM
is almost completely incapable of using 'hard' tabs (which doesn't matter if
you don't like them but if you're used to using such features it is quite a
drawback).
So if you want to use EPM you may need to customize it. To do this you need
the complete EPM toolkit available on various ftp sites such as cdrom.com. It
includes an updated (and reconfigured) version of EPM along with a better
documentation and a compiler for the E macro language (ETPM). This article
will cover custom configuration of your editor using this toolkit and - through
examples - provide you with some nice additional features such as file
autoloading and positioning and keyboard undo.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.1.2. The EPM Programming Model ΓòÉΓòÉΓòÉ
Programming EPM
There are two ways of programming EPM: using REXX and using the E macro
language. Using REXX you may write EPM macros that don't need to be compiled
and you don't have to learn the E macro language. This is quite powerful and
lets you use most of EPM's features including custem menus, etc. and so it may
be great for user macros but you cannot change the default configuration using
REXX and since its interpreted and not compiled its quite slow so we will use
the macro language here.
What is EPM?
EPM is not a "program" as such. The key element of the editor is the Enhanced
Multi-Line Edit control (E-MLE) that is defined if the E-Toolkit (ETK) DLL's.
It may also be used in custom programs (see the ETK documentation). Most of
the basic functionality as well as macro language and REXX support are provided
by the control. It can be used to edit multiple files in a "ring" which may be
as large as system memory permits. EPM itself is just a small program that
manages E-MLEs and the user interface (e.g. dialogs).
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.1.3. The Macro Language ΓòÉΓòÉΓòÉ
Most of the features that make EPM were added using the E macro language and
therefore may be rewritten or changed as well as used within other programs.
For example, it would be possible to write applications using E-MLEs for their
own editing purpose but incorporating the user's individual EPM setup. Given
its capability to use the REXX language this makes for a great shot in the way
of finding an individual standard user interface (a paradox within conventional
application design).
Okay, let's get back to EPM. Macros that were written using the macro language
(*.E) are compiled with the macro compiler ETPM and become ETPM modules (*.EX).
An E-MLE has one main module (EPM.EX for EPM) which is executed upon creation
and which contains startup and close-down code as well as basic functions and
commands, menus, key-definitions etc. There may be additional modules that
define external commands, they can be executed at runtime or linked into a
running system meaning they're loaded permanently.
The macro language itself closely resembles REXX with some compiler specific
differences especially regarding variable handling. See the EPM Technical
Reference included in the complete EPM distribution.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.1.4. Changing Your Configuration ΓòÉΓòÉΓòÉ
The EPM main module is called EPM.E. (Well, in fact EPM.E currently does
nothing else than including E.E which is the real main module but in the
documentation the main module is said to be EPM.E so we call it that.) It
includes the other EPM modules and is compiled to EPM.EX. Changes to the
default configuration have to be recognized by EPM.E; usually this is done by
changing a set of files that are included in EPM.E: MYCNF.E, MYKEYS.E,
MYSTUFF.E, MYMAIN.E, MYSELECT.E and MYKEYSET.E.
MYCNF.E is a configuration file you should use to set configuration switches
whereas the other ones are thought to include code. You may recompile your
standard configuration by just recompiling EPM.E.
o Changing the default configuration via MYCNF.E
o Adding your own key definitions via MYKEYS.E
o Your own commands and procedures via MYSTUFF.E
o Editor startup code via MYMAIN.E
o File initializations and focus changes via MYSELECT.E
You should use MYKEYSET.E to define a whole new keyset. This would be a little
too much for this article so we won't cover it here.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.1.5. Changing The Default Configuration via MYCNF.E ΓòÉΓòÉΓòÉ
EPM's standard configuration is stored in a file calles STDCNF.E. Every
definition primitive is given a default value here. You should not change this
file since it may be changed with the next release of EPM and you had to
rewrite all your changes. Instead you should write a file called MYCNF.E which
is included before STDCNF.E andso settings you specify here override settings
in STDCNF.E.
Configuration Constants
Configuration constants are constants so you can set them using the const
statement:
const
FOO = 'VALUE'
There are, well, quite a lot of configuration constants within EPM so we will
cover only a subset which I think are the more important ones. See the EPM
User's Manual for the complete set of constants. Along with the constants the
default values are given:
ALTERNATE_KEYSET=1 Defines if support for alternate keysets is included. The
default is to include it. Support for alternate keysets
allows you to define user keysets and switch between them.
It is needed for C, E, Pascal, or REXX syntax assist. If
it is set to 0 these are not included. Also MYKEYSET.E
would not be included.
AUTOSAVE_PATH='' Specifies the path where "autosave" files will be placed.
The default value means that they're placed in the current
directory. There is also an AUTOSAVE PATH setting in EPM's
settings notebook. The path specified there overrides the
setting in MYCNF.E
BACKUP_PATH='' Specifies the path setting for backup files. Same as for
AUTOSAVE_PATH but there is no setting in the notebook for
this one and it has another option: '=' will place the
backup file in the same directory as the saved one (and
give it the extension '.BAK'). The backup directory must
end with a backslash.
COMPILER_ERROR_COLOR=RED+WHITEB When used within the Workframe/2, EPM displays
compiler errors. This constant is used to set the color
used for the errors. Default is red on white. See
Configuring Colors
C_SYNTAX_ASSIST=1 Set this to 1 to include syntax assist for the C language.
Syntax assist automatically expands specific keywords into
the corresponding language constructions.
C_TABS=3 Specifies the default tab size for C files.
DEFAULT_PASTE='' Use this setting to define the default behaviour of the
paste key (Shift+Insert). '' always pastes as new lines,
'B' pastes as a block and 'C' pastes as a stream of
characters.
DRAG_ALWAYS_MARKS=0 In advanced marking mode on dragging the mouse a new block
is only marked if there is no one currently marked. This
can be helpful, e.g. if you want to set the cursor using
the mouse and don't want the current mark to be deselected.
It can be quite annoying if you're not used to it so you
can set this switch to 1 to turn it off. DRAG_ALWAYS_MARKS
has no effect in CUA-marking mode.
ENHANCED_ENTER_KEYS=0 Set this one to 1 to be able to set the action performed
by the Enter keys with EPMs settings notebook. Using the
default value only lets you use configuration constants
(ENTER_ACTION and C_ENTER_ACTION, see EPM Users Guide).
ENHANCED_PRINT_SUPPORT=0 Set this to 1 to tell EPM to bring up the "Print"
dialog when you specify the print command from the menu
instead of just printing the file.
EPATH='EPMPATH' Specifies the environment variable used to store EPM's
search path.
EXTRA_EX=0 This must be set to 1. Code size for .EX-files is limited
to 64K, which is too small for the standard configuration.
If EXTRA_EX is set to one it is split into two files,
EPM.EX and EXTRA.EX both of which are loaded on startup.
E_SYNTAX_ASSIST=1 Enables syntax assist for the E language. See
C_SYNTAX_ASSIST.
E_TABS=3 Sets the default tab setting for .E files. See C_TABS.
HELPFILENAME='epmhelp.qhl' Sets the filename for the "Quick Help" file.
HIGHLIGHT_COLOR= Sets the color that is used to draw these fancy circles
around found search strings. There is no default so you
have to specify it. See Configuring Colors
INCLUDE_WORKFRAME_SUPPORT=1 Specifies if support for IBM WorkFrame/2 is
included in the configuration.
MARKCOLOR=Blue+GreyB Sets the color used to mark marked marks in your file.
See Configuring Colors
MENU_LIMIT=0 You can edit several files in one EPM window using the file
ring. Usually these files can be accessed with the Ring
menu command which brings up a dialog box containing the
files that are currently being edited. Specifying any
number greater than 0 (n) for this constant tells EPM to
add n files to the Ring menu and bring up the dialog box
only if there are more than n files in the ring. There is
no limit for n but 120 makes no sense.
MESSAGECOLOR=Light_Red+WhiteB Sets the color used to display the message line
or error messages. See Configuring Colors
MY_DEFAULT_EDIT_OPTIONS='' Set default options for the edit command. These
default options will be overridden by options specified on
each specific edit command.
MY_DEFAULT_SAVE_OPTIONS='' Same as MY_DEFAULT_EDIT_OPTIONS for the save
command.
MY_DEFAULT_SEARCH_OPTIONS='' Another one, this time for search commands!
MY_SAVE_WITH_TABS=0 Set this to 1 to tell EPM to automatically compresses
spaces to tabs when saving. This is the same as specifying
'/t' for MY_DEFAULT_SAVE_OPTIONS.
NLS_LANGUAGE='english' Sets the language for National Language Support.
P_SYNTAX_ASSIST=1 Includes Pascal syntax assist. See C_SYNTAX_ASSIST.
P_TABS=3 Sets the tab size for Pascal files. See C_TABS.
REXX_SYNTAX_ASSIST=1 Includes REXX syntax assist. See C_SYNTAX_ASSIST.
REXX_TABS=3 Sets the tab size for REXX files. See C_TABS.
SETSTAY=0 This option controls where the cursor is located after a
change command. 0 means it is located on the last changed
string, 1 means it stays where it was before the command
and '?' defines a STAY command to change this behavior.
STATUS_TEMPLATE='Line %l of %s Column %c %i %m %f' This is the status
string used to configure the status line. The template can
be up to 128 characters long and may contain characters and
status tags. A status tag make EPM display certain status
information. The following tags are defined:
%A Number of changes made since last autosave.
%C Current column number
%F Number of files in ring
%I Insert or replace state
%L Current line number
%M Modified status
%S Total number of lines in current file
%X Hexadecimal value of current character
%Z ASCII value of current character
SUPPORT_TECHREF=0 Set this to 1 to add a 'View Technical Reference' command
to the help menu.
SUPPORT_USERS_GUIDE=0 Set this to 1 to add a 'View Users Guide' command to the
help menu.
SUPPORT_USER_EXITS=0 User Exits are procedures that are called by certain
command, e.g. load and save. Setting SUPPORT_USER_EXITS to
1 enables this feature. See Your own commands and
procedures via MYSTUFF.E
SYNTAX_INDENT='3' Specifies the number of spaces used for syntax indent when
syntax assist is used.
TEXTCOLOR=Black+WhiteB Specifies the color used to display normal unmarked text
in the edit window. See Configuring Colors
TOGGLE_ESCAPE=0 Set this to 1 to define the ESCAPEKEY command.
TOGGLE_TAB=0 Set this to 1 to define the TABKEY command.
TRASH_TEMP_FILES=0 If set to 1 temporary files (which begin with a '.') will
be quit without warning even if not saved. This may be
useful (e.g. if you use the ALL command and don't want to
be asked if you wanted to save the '.ALL' file upon exit).
UNDERLINE_CURSOR=0 Specifies the style of the cursor used in the editor.
Default is a vertical bar in front of the actual character.
Set to 1 changes it to a horizontal bar below the actual
character.
UNMARK_AFTER_MOVE=0 Use this switch if you want marked text to be unmarked
after a move operation. Default is that it is not.
USE_APPEND=0 Set this to 1 to make EPM search for files along DPATH if
they can't be found in the current directory.
WANT_ALL=0 Set this to 1 to include the ALL command.
WANT_BOOKMARKS='LINK' Specifies whether or not bookmark support is included.
The default is that bookmark support is linked at runtime
(or included in EXTRA.EX if this is used).
WANT_CHAR_OPS=1 Set this to 0 to disable character marking mode support.
WANT_CUA_MARKING=0 Set this to 1 to use the CUA marking style instead of the
EPM block marking. A value of SWITCH adds a switch for
this to the Preferences menu.
WANT_DRAW='F6' Includes the DRAW command. Default is to include it and
define the 'F6' key for it. Other options are 1 and 0.
WANT_DYNAMIC_PROMPTS=1 Includes dynamic prompt support. If it is included
(default) there is a Prompting option in the Frame Controls
menu. Prompting shows a description of the currently
selected menu item in the message line.
WANT_ET_COMMAND=1 Includes the ET command to issue ETPM from EPM.
WANT_KEYBOARD_HELP=0 If keyboard help is activated the CTRL-H key is redefined
to issue a keyword help procedure that looks through the
files specified in the HELPNDX environment variable for the
keyword under the cursor. If it is found help for this
keyword is given as specified in the .NDX-file, usually by
invoking VIEW.EXE.
WANT_LONGNAMES=0 Includes support for long names. A value of SWITCH defines
a Longnames command.
WANT_PROFILE=0 Includes support for a REXX profile. If profile support is
included EPM looks for a file called PROFILE.ERX upon
startup and executes it if found. A value of SWITCH defines
the Profile command.
WANT_STACK_CMDS=0 Includes a set of stack commands. The commands are
PushPos, PopPos, SwapPos, PushMark, PopMark, SwapMark and
can be used to create a stack to store cursor positions and
marks. A value of SWITCH defines an entry in the
Preferencs menu.
WANT_STREAM_MODE=0 Set this to 1 to make EPM use a stream mode approach to
file editing that is the edited file is seen as a
continuing stream of characters. In the default mode a
file is interpreted as a set of lines. A value of SWITCH
enables you to switch between these modes at runtime using
the Preferences menu.
See Example MYCNF.E
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.1.6. Configuring Colors ΓòÉΓòÉΓòÉ
EPM includes the file COLORS.E which defines a set of color values to be used
within EPM macros. The available constants are:
Foreground Colors Background Colors
BLACK = 0 BLACKB = 0
BLUE = 1 BLUEB = 16
GREEN = 2 GREENB = 32
CYAN = 3 CYANB = 48
RED = 4 REDB = 64
MAGENTA = 5 MAGENTAB = 80
BROWN = 6 BROWNB = 96
LIGHT_GRAY = 7 LIGHT_GRAYB = 112
DARK_GREY = 8 DARK_GREYB = 128
LIGHT_BLUE = 9 LIGHT_BLUEB = 144
LIGHT_GREEN = 10 LIGHT_GREENB = 160
LIGHT_CYAN = 11 LIGHT_CYANB = 176
LIGHT_RED = 12 LIGHT_REDB = 192
LIGHT_MAGENTA = 13 LIGHT_MAGENTAB = 208
YELLOW = 14 YELLOWB = 224
WHITE = 15 WHITEB = 240
These constants may be used to set the display color variables. Note that
these constants may only be used if COLORS.E was included. This may not be the
case for external commands that include MYCNF.E. Use the COMPILE IF statement
to include them if the color constants are defined. Also note that the color
variables are not constants and therefore should be define'd.
compile if defined(BLACK)
define
TEXTCOLOR = BLACK + WHITEB
MARKCOLOR = BLUE + GREYB
STATUSCOLOR = BLACK + WHITEB
MESSAGECOLOR = LIGHT_RED + WHITEB
DRAGCOLOR = YELLOW + MAGENTAB
HIGHLIGHT_COLOR =
compile endif
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.1.7. Example MYCNF.E ΓòÉΓòÉΓòÉ
Here's an example MYCNF.E file.
const
NLS_LANGUAGE = 'ENGLISH'
AUTOSAVE_PATH = '\OS2\EPM\AUTO\'
BACKUP_PATH = '\OS2\EPM\BACK\'
TEMP_PATH = '\OS2\EPM\TEMP\'
EPATH = 'EPMPATH'
C_TABS = '4'
E_TABS = '4'
REXX_TABS = '4'
P_TABS = '4'
SYNTAX_INDENT = '4'
USE_APPEND = 1
SETSTAY = '?'
TRASH_TEMP_FILES = 1
WANT_ALL = 1
WANT_RETRIEVE = 1
WANT_KEYWORD_HELP = 1
WANT_LONGNAMES = 'SWITCH'
WANT_STACK_CMDS = 'SWITCH'
WANT_CUA_MARKING = 'SWITCH'
WANT_STREAM_MODE = 'SWITCH'
WANT_DYNAMIC_PROMPTS = 1
WANT_PROFILE = 'SWITCH'
MY_DEFAULT_EDIT_OPTIONS = '/t'
SUPPORT_TECHREF = 1
SUPPORT_USERS_GUIDE = 1
SUPPORT_USER_EXITS = 1
EXTRA_EX = 1
DEFAULT_PASTE = ''
TOGGLE_ESCAPE = 1
TOGGLE_TAB = 1
DRAG_ALWAYS_MARKS = 1
MENU_LIMIT = 25
SMARTFILE = 1
compile if defined(BLACK)
define
HIGHLIGHT_COLOR = GREEN + GREENB
DRAGCOLOR = LIGHT_GREY + DARK_GREYB
compile endif
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.1.8. Adding your own key definitions via MYKEYS.E ΓòÉΓòÉΓòÉ
The file MYKEYS.E should be used for your own key definitions and
redefinitions. It is included into E.E after most of the definition stuff and
directly before MYSTUFF.E. Currently it does not matter which of those you use
to store your key DEFs but that may change.
Key definitions start with a DEF statement and give the key name with an
additional S_ for Shift+key, A_ for Alt+key or C_ for Ctrl+key in front of it.
Alphanumeric keys are addressed by their alphanumeric value while special keys
are listed below:
F1-F12 The function keys
DEL The delete key
INS The insert key
UP, DOWN, LEFT, RIGHT The cursor keys
PGUP, PGDN The paging keys
HOME, END Home and End :)
ENTER, ESC, TAB The respective keys.
PADENTER The pad-enter key on AT or MFII keyboards.
PAD5 (S_ only) The pad-number 5 on AT or MFII keyboards
EQUAL (A_ only) =
LEFTBRACKET, RIGHTBRACKET (A_ and C_ only) (,)
MINUS (A_ and C_ only) -
BACKSPACE Backspace
BACKSLASH (C_ only) \
PRTSC (C_ only) Print Screen
Following the DEF statement you simply start writing your key function.
def a_s=
'saveall'
This makes ALT+S execute the SAVEALL command.
You may, of course, also include other key definition files in MYKEYS.E, e.g.
REVERSE.E and GLOBFIND.E from the EPM distribution demos:
include 'reverse.e'
include 'globfind.e'
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.1.9. Example MYKEYS.E ΓòÉΓòÉΓòÉ
The following example MYKEYS.E defines an undo key for ALT+U. It uses EPM's
undoaction command to take back changes one by one. So you don't have to use
the UNDO-dialog if you just want to take back a few changes. The one thing
making this a little bit complicated is the fact that the undo itself is
regarded as a change and therefore is stored by undoaction. So if you did undo
twice you would restore the state before the undo. However the intention of
the undo key is to be able to take back more than one step. The solution is to
store information about which steps in the undo-chain are UNDOs itself and to
skip them.
/* This is an example MYKEYS.E file defining an undo key
<C> 1993 Joerg Schwieder, Berlin, Germany */
def a_u= -- define ALT-U
universal undostates -- define an universal variable to store UNDOs taken
undoaction 6, states -- get the first and the last actions stored in the UNDO-tree
parse value states with sfirst slast -- parse the 2 values into sfirst and slast
undoakt = undostates -- copy our undo-states
newstate = slast -- save the actual state to our new UNDO-state list
slast = slast - 1 -- last entry we could UNDO to
while slast >= sfirst do -- loop through the UNDO-tree
parse value undoakt with thisone undoakt -- look at our last undo
if (thisone < slast) or (not thisone) or (thisone < sfirst) then
leave -- if its older than the last tree entry or if
-- we never undid - leave, it's OK!
endif
if thisone then newstate = newstate' 'thisone; endif -- add our last UNDO to our new list
if thisone = slast then slast = slast - 1; endif -- if the actual tree entry is an old UNDO
-- look at the one before
endwhile
if slast >= sfirst then -- if a possible tree-entry was found,
newstate = newstate' 'slast -- add it to the start of the new UNDO-list
endif
while undoakt and (thisone >= sfirst) do -- add the remaining old UNDO list to the new one
newstate = newstate' 'thisone -- but strip entries no longer in the UNDO tree
parse value undoakt with thisone undoakt
endwhile
undostates = newstate -- set undostates to the new list
if slast >= sfirst then -- if an entry was fond
undoaction 7, slast -- UNDO
else
parse value undostates with . undostates -- else remove the actual entry from the list
endif
/* This is the sample ALT-S definition for SAVEALL */
def a_s=
'saveall'
/* Include REVERSE and GLOBFIND keys from the EPM distribution demos if present. */
tryinclude 'reverse.e'
tryinclude 'globfind.e'
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.1.10. Your own commands and procedures via MYSTUFF.E ΓòÉΓòÉΓòÉ
This is the place to put your own commands and procedures. Usually you won't
put them into the file directly but include other files. Don't be concerned if
these files contain key definitions as well. EPM includes MYSTUFF.E directly
after MYKEYS.E. The distinction is for organizational purposes. You might want
to put everything in MYSTUFF.E, especially if you write large extensions part
of which are key definitions and if you don't want to split them up into
several files.
include 'saveall.e'
This would put the SAVEALL command into the default configuration.
Commands are defined using the DEFC statement followed by the command's name,
arguments are passed through arg(1) which returns a parameter string:
defc gocol
.col = arg(1)
This defines a GOCOL command that moves the cursor to the column passed as
parameter.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.1.11. Example MYSTUFF.E ΓòÉΓòÉΓòÉ
The following sample MYSTUFF.E file uses another EPM feature - user exits.
Several hooks are spread in the editor code which give user procedures notice
of certain events, e.g. a file is being saved. User exits are procedures that
are called by EPM if they are defined. We will use them to write an autostart
feature that "reminds" of its closedown state. If EPM is closed with open
files, these filenames are stored in EPM.INI; should EPM be started later
without parameters, it will reload these files. Additionally the cursor
position in each open file is stored in extended attributes upon shutdown;
reloading this file later repositions the cursor right where it was.
This feature is quite useful when you are editing several large files. If you
want to leave EPM but continue the next day just close down EPM and if you
restart it the next day without parameters; you are right back where you were
without having to load all your files and having to find your way through your
code back to where you left.
There is just one drawback: Since defmain_exit has to be called from MAIN.E it
has to be included before it and therefore has to be included into MYCNF.E. We
include this section as a file called MAINEXIT.E into MYCNF.E.
MYSTUFF.E:
include 'saveall.e'
defc gocol
.col = arg(1)
/* For the autoload feature we maintain a list of the currently open files in a profile entry named
STARTUP_FILES. The first one is the file actually edited to make shure it comes up on restart */
defselect /* This one makes shure that the file that was on top on
shutdown will reappear there on startup. Defselects are
called whenever a file is selected */
universal appname, app_hini /* The application name and INI-handle
are stored in these global variables */
psave_pos(spos) -- Save cursor position
filelist = queryprofile( $HINI_PARM appname, 'STARTUP_FILES') -- get actual filelist
if wordpos(.filename, filelist) then -- find actual file
filelist = delword(filelist, wordpos(.filename, filelist), 1) -- and remove it
endif
if (not wordpos(.filename, filelist)) and (substr(.filename, lastpos('\', .filename) + 1, 1) <> '.') then
filelist = .filename' 'filelist -- add the actual filename at the beginning
endif
setprofile( $HINI_PARM appname, 'STARTUP_FILES', filelist ) -- write the new list to the INI-file
prestore_pos(spos) -- Restore cursor position
defproc postsave_exit (fname) -- called just after file was saved
universal appname, app_hini, oldfile /* oldfile is the old filename if
file was renamed */
psave_pos(spos) -- Save cursor position
filelist = queryprofile( $HINI_PARM appname, 'STARTUP_FILES') -- get the old file list
if wordpos(oldfile, filelist) then -- if old filename is in the list
filelist = delword(filelist, wordpos(oldfile, filelist), 1) -- delete it
endif
if (not wordpos(fname, filelist)) and (substr(fname, lastpos('\', fname) + 1, 1) <> '.') then
filelist = fname' 'filelist -- Add actual filename if its not a tempfile ('.---')
-- remove the 'and (substr...' section if you want to restart temp files
endif
setprofile( $HINI_PARM appname, 'STARTUP_FILES', filelist ) -- store the new list
prestore_pos(spos) -- Restore cursor position
defproc rename_exit (oldfile, fname) -- called if file was renamed
universal appname, app_hini
psave_pos(spos) -- Save cursor position
filelist = queryprofile( $HINI_PARM appname, 'STARTUP_FILES') -- same as for postsave_exit
if wordpos(oldfile, filelist) then
filelist = delword(filelist, wordpos(oldfile, filelist), 1)
endif
if (not wordpos(fname, filelist)) and (substr(fname, lastpos('\', fname) + 1, 1) <> '.') then
filelist = fname' 'filelist
endif
setprofile( $HINI_PARM appname, 'STARTUP_FILES', filelist )
prestore_pos(spos) -- Restore cursor position
defproc quit_exit (fname) -- called if file is closed
universal appname, app_hini
filelist = queryprofile( $HINI_PARM appname, 'STARTUP_FILES') -- just delete entry
if wordpos(fname, filelist) then
filelist = delword(filelist, wordpos(fname, filelist), 1)
endif
setprofile( $HINI_PARM appname, 'STARTUP_FILES', filelist )
-- Defload functions are called whenever a file was loaded
defload /* same as for namefile and savefile,
just that there are no old names to delete */
universal appname, app_hini
filelist = queryprofile( $HINI_PARM appname, 'STARTUP_FILES')
if (not wordpos(.filename, filelist)) and (substr(.filename, lastpos('\', .filename) + 1, 1) <> '.') then
filelist = .filename' 'filelist
endif
setprofile( $HINI_PARM appname, 'STARTUP_FILES', filelist )
/* Here comes the Cursor position feature */
defproc presave_exit -- issued just before a file is saved
universal oldfile -- used to store the filename for postsave_exit
delete_ea('EPM.POS') -- delete old 'EPM.POS' entry in the EAs
psave_pos(screenpos) -- get cursor and screen position,...
'addea EPM.POS' screenpos -- and store it in the EA
oldfile = .filename -- this one's needed for the restart feature
defload -- This one's for setting the Cursor and window position for the loaded file
if find_ea('EPM.POS', ea_seg, ea_ofs, ea_ptr1, ea_ptr2, ea_len, ea_entrylen, ea_valuelen) then
-- if there's an EPM.POS EA...
getfileid myid -- that's the actual file
'postme restore_ORG_pos 'myid get_EAT_ASCII_value('EPM.POS')
/* restore postions. have to post it 'cause the screen may not yet be painted.
Also I had to make this the last of my defloads to make it work correctly */
endif
defc restore_ORG_pos -- This sets the cursor and screen-positions. It does the same as
-- prestore_pos except for being passed the fileid as first parameter
parse value arg(1) with myid fline fcol cx cy
myid.cursorx = cx
myid.cursory = cy
myid.line = fline
myid.col = fcol
MAINEXIT.E:
define
compile if EVERSION >= '5.20' -- 5.20 adds a HINI to the *Profile calls.
HINI_PARM = 'app_hini,'
compile else
HINI_PARM = ' '
compile endif
defproc defmain_exit (var cmdline) -- called just before files are loaded upon startup
universal appname, app_hini
if cmdline = 'e ' then -- this is the command line + 'e '. If no parameters are specified ...
cmdline ='e 'queryprofile( $HINI_PARM appname, 'STARTUP_FILES' )
-- ...add the ones from 'STARTUP_FILES'
endif
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.1.12. Editor startup code via MYMAIN.E ΓòÉΓòÉΓòÉ
MYMAIN.E is included and executed directly after the editors own startup code
so you can put code here that has to be executed once after the editor was
initialized e.g. recognizing messages for other applications (sorry, could not
think of anything else). With the exception of the different include position
handling of MYMAIN.E is the same as for MYSTUFF.E
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.1.13. File initializations and focus changes via MYSELECT.E ΓòÉΓòÉΓòÉ
MYSELECT.E is another file that does not differ much from MYSTUFF.E. It is
included after MYMAIN.E but before MYSTUFF.E. Theoretically, it should contain
DEFSELECTs, code that is executed whenever a file is selected, but this does
not necessarily have to be the case so we put this code into MYSTUFF.E also.
See the MYSTUFF.E example.
Typical tasks for DEFSELECTs are cursor positioning, file-type specific changes
to the keyset or the messageline or adding of file-type specific menus etc.
Using DEFSELECTs you can change the behavior of the editor depending on the
file-type.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.1.14. Summary ΓòÉΓòÉΓòÉ
We have seen how the EPM toolkit can be used to change your EPM configuration
and how commands that you write can be integrated into the system. A little
thought can yield a good editor for your uses at a great price. Also, support
for EPM-related topics can already be found in comp.os.os2.programmer.misc and
is provided by Larry "Mr. Macro" Margolis, who wrote most of the standard
macros provided by EPM and is currently a member of the EPM development group
within IBM.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.2. Rexx-ercising Your Applications ΓòÉΓòÉΓòÉ
Written by Gordon W. Zeglinski
Introduction
Rexx/2 provides an excellent mechanism by which developers can easily add
scripting, macro, and programming abilities into their products. Yet there is
still some cloud of mystery surrounding the use of REXX in general
applications. It is hoped that this article will dispel this cloud and
encourage other developers to incorporate REXX abilities into their apps.
After reading this article, you should:
1. Understand a bit more about how .cmd files are executed.
2. Know how to start Rexx/2 from within an application.
3. Be able to extend Rexx/2 by adding your own external functions to it.
Note: The REXXSAA.H file shipped with the 2.1 Toolkit and the C++ patched 2.0
toolkit are not C++ compatible. A C++ compatible version is included with this
issue for use with the C-Set++ compiler.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.2.1. Introduction ΓòÉΓòÉΓòÉ
Written by Gordon W. Zeglinski
Rexx/2 provides an excellent mechanism by which developers can easily add
scripting, macro, and programming abilities into their products. Yet there is
still some cloud of mystery surrounding the use of REXX in general
applications. It is hoped that this article will dispel this cloud and
encourage other developers to incorporate REXX abilities into their apps.
After reading this article, you should:
1. Understand a bit more about how .cmd files are executed.
2. Know how to start Rexx/2 from within an application.
3. Be able to extend Rexx/2 by adding your own external functions to it.
Note: The REXXSAA.H file shipped with the 2.1 Toolkit and the C++ patched 2.0
toolkit are not C++ compatible. A C++ compatible version is included with this
issue for use with the C-Set++ compiler.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.2.2. Some Basics ΓòÉΓòÉΓòÉ
In REXX, there are three different categories of executable statements:
instructions, commands, and functions. Instructions are built into REXX and
cannot be supplemented. Commands and functions are definable by the
application evoking the REXX interpreter. However, functions are more flexible
in nature than commands because they can be integrated into expressions and can
be added at run time via the (built-in) RxFuncAdd() function. This article
will focus on functions.
Before we look closer at REXX functions, we should look a bit at Command (.CMD)
files. Command files are REXX programs that are meant to be executed under the
"CMD" REXX environment. The "CMD" environment is a subcommand handler
registered with REXX using the name "CMD". It is important to note that
command files are not able to be executed unless the "CMD" command handler is
used when starting REXX if these files contain commands like "dir", "copy",
etc. This discussion will be continued in part 2.
More About Functions
In REXX, functions come in two flavors: internal and external functions. From
the REXX programmers point of view, once the external function has been
registered, there is no difference between the two. However, xternal functions
can either be based in DLL's or within the executable itself. Take the
following REXX program, SHOWDIR.CMD .
/******************************************/
/* Simple Rexx/2 file that opens the */
/* default view for the current directory */
/******************************************/
Call RxFuncAdd 'SysLoadFuncs', 'REXXUTIL', 'SysLoadFuncs'
Call SysLoadFuncs
DIR=directory();
If SysSetObjectData(DIR,"OPEN=DEFAULT") Then
Say DIR "has been opened."
Else
Say DIR "was not opened."
SHOWDIR.CMD illustrates several important points:
o External functions must be registered before they can be used. In the case
of DLL-based external functions, they can either be registered by using a
REXX function or by the application.
o There are two methods to call functions, either using call or (). Unlike in
C or C++, if the () method is used then the line must follow the same syntax
as as line 4 in the above example, DIR=directory();. Strictly speaking in
REXX terminology, if the call method is used then the procedure being called
is referred to as a subroutine. If the () method is used, then the procedure
being called is referred to as a function.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.2.3. About RXSTRINGs ΓòÉΓòÉΓòÉ
The RXSTRING structure is used through out REXX and warrants a detailed
examination. The maximum length of a RXSTRING is 4 gigabytes (the maximum
number that can be held in a ULONG variable). Unlike C, where strings are zero
terminated, the string component of an RXSTRING is not zero-terminated.
Additional important points about RXSTRING's are listed below.
typedef struct {
ULONG strlength; // Length of string
PCH strptr; // Pointer to string
} RXSTRING;
o An RXSTRING is said to have a value if the strptr field is not NULL.
Consequently, it is said to be empty if the strptr field is NULL.
o An RXSTRING is said to be a null string if the strptr field points to the
value "" and the strlength field is zero.
o The length stored in the strlength field does not include the terminating
null character.
o Conveniently, the REXX interpreter adds the terminating zero character to the
strptr field when it calls external functions, subcommand handlers and exit
handlers. This allows one to use the C library string functions on this
field. However, there is no guarantee that this will be the only zero
character. The developer will have to decide whether the data could have
extra zero characters.
o When a return value is expected from an external function, subcommand handler
or exit handler, the REXX interpreter sets up a default RXSTRING of length
256. If the value to be returned is requires less space than this, it is
simply copied into the strptr buffer and the strlength filed is set to the
shorter length. If the default RXSTRING is too small, a new RXSTRING can be
created using DosAllocMem(). The REXX interpreter will free the memory used
by the new RXSTRING.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.2.4. Starting Rexx/2 From Within an Application ΓòÉΓòÉΓòÉ
We will start our trek into the work of REXX by looking at how we can start the
Rexx/2 interpreter from within our apps. The function RexxStart() allows us to
do this.
(LONG)RexxStart(ArgCount,
ArgArray,
Name,
Instore,
Environment,
RXCOMMAND,
ExitHandler,
&RC,
&ReturnString);
(Because the RexxStart() function can do so many things, this is bound to be
some what bewildering. Don't panic, examples will be used to explore the
various parameter combinations in this and future articles in the series.)
ArgCount (LONG) - input
The number of elements in the ArgList array. This value will be
returned by the ARG() built-in REXX function. ArgCount should
include RXSTRINGs which represent omitted arguments. Omitted
arguments will be empty RXSTRINGs (strptr will be NULL). See
the Discussion on RXCOMMAND for further information.
ArgArray (PRXSTRING) - input
An array of RXSTRING structures that are the REXX program
arguments.
Name (PSZ) - input
Points to the ASCIIZ "name" of the REXX procedure and can have
many different interpretations depending on the value of
Instore. If Instore is NULL, then it is the name of the file
containing the REXX procedure. If Instore is not NULL, then it
is either the name of the subroutine or function in the
Macrospace.
If Name is the name of a file, a default extension of .CMD is
used if none is supplied. Optionally, "name" could be the fully
qualified filename.
Instore (PRXSTRING) - input
An array of two RXSTRINGs that store REXX procedures in memory.
If both RXSTRINGs are empty, the interpreter searches for REXX
procedure Name in the macrospace. If the procedure is not found
in the macrospace, the RexxStart() function returns an error
code.
If either RXSTRING is not empty, Instore is used to execute a
REXX procedure directly from memory.
Instore[0] input
An RXSTRING containing an REXX procedure stored in the exact
same manner as a disk file. The image must be complete with
carriage returns, line feeds, and end-of-file characters.
Instore[1] input and output
If Instore[1] is empty, the REXX interpreter will return the
tokenized image in here.
If Instore[1] is not empty, interpreter will execute the
tokenized image directly. Instore[0] can be empty in this
case, provided the REXX program does not require meaningful
data from the SOURCELINE built-in REXX function. If
Instore[0] is empty and SOURCELINE function is used,
SOURCELINE will return null strings for the REXX procedure
source lines.
If Instore[1] is not empty, but does not contain a valid REXX
tokenized image, unpredictable results can occur. The REXX
interpreter might be able to determine that the tokenized
image is incorrect and retokenize the source.
If the procedure is executed from disk, the Instore pointer
must be NULL. If the first argument string in Arglist
contains the string "//T" and CallType is RXCOMMAND, the
interpreter will tokenize the procedure source and return the
tokenized image without running the program.
The program using the RexxStart() function must release
Instore[1] using DosFreeMem() when the tokenized image is no
longer needed.
The format of the tokenized image may not be the same across
interpreter versions. Therefore, the tokenized image should
not be stored to disk or transferred to other systems. It is
meant to be used multiple times with in the same application
in which it was created to allow the faster execution of the
REXX program during the second and subsequent executions.
Environment (PSZ) - input
Address of the ASCIIZ initial ADDRESS environment name. The
ADDRESS environment is a subcommand handler registered using
RexxRegisterSubcomExe() or RexxRegisterSubcomDll(). Environment
is used as the initial setting for the REXX ADDRESS instruction.
If Environment is NULL, the file extension is used as the
initial ADDRESS environment. The environment name cannot be
longer than 250 characters.
If Environment is set to "CMD", .CMD files may be executed from
within the application.
CallType (LONG) - input
The type of REXX procedure execution. Allowed execution types
are:
RXCOMMAND The REXX procedure is an OS/2 operating system command or
application command. REXX commands normally have a single
argument string. The REXX PARSE SOURCE instruction will
return "COMMAND" as the second token.
RXSUBROUTINE The REXX procedure is a subroutine of another program. The
subroutine can have multiple arguments and does not need to
return a result. The REXX PARSE SOURCE instruction will
return "SUBROUTINE" as the second token.
RXFUNCTION The REXX procedure is a function called from another program.
The subroutine can have multiple arguments and must return a
result. The REXX PARSE SOURCE instruction will return
"FUNCTION" as the second token.
ExitHandler (PRXSYSEXIT) - input
An array of RXSYSEXIT structures defining exits the REXX
interpreter will use.
RC (PLONG) - output
If Result is a whole number in the range of -(2**15) to 2**15-1,
it will be converted to a numeric form and and returned in RC as
well as Result.
Result (PRXSTRING) - output
The string returned from the REXX procedure with the REXX RETURN
or EXIT instruction. A default RXSTRING can be provided for the
returned result. If a default RXSTRING is not provided or the
default is too small for the returned result, the REXX
interpreter will allocate an RXSTRING using DosAllocMem(). The
caller of the RexxStart() function must release the RXSTRING
storage with DosFreeMem().
The REXX interpreter does not add a terminating zero to Result.
Executing REXX Procedures From a File
If we were to look at code samples to illustrate all of the various things that
RexxStart() can do, we wouldn't be able to get to creating external functions
in this issue. Therefore, we will look at one of the more fundamental uses of
RexxStart() - executing file-based REXX procedures. The code below illustrates
this:
VOID RunScript(PSZ ScriptFile) {
LONG return_code; // interpreter return code
RXSTRING argv[1]; // program argument string
RXSTRING retstr; // program return value
SHORT rc; // converted return code
argv.strptr=NULL;
argv.strlength=0;
retstr.strptr=new char [1024];
retstr.strlength=1024;
return_code = RexxStart(0, // No arguments
argv, // dummy entry
ScriptFile, // File name
NULL, // NULL InStore
"CMD", // use the "CMD" command processor
RXCOMMAND, // execute as a command
NULL, // No exit handlers
&rc, // return code from REXX routine
&retstr); // return string from REXX routine
delete [] retstr.strptr;
}
In the above example, the return value string is set to be 1K in size. (it is
assumed that the REXX interpreter will not require more space than this.) It
should also be noted that because REXX commands typically do not execute
quickly that the above function should be called from a separate thread in PM
programs so that it will not hang the message queue.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.2.5. External Functions ΓòÉΓòÉΓòÉ
Finally, we can look at creating external functions. There are two types of
external functions, EXE-based and DLL-based. The EXE-based version must be
registered with REXX by the application in which they reside and can only be
used by REXX programs started from within that application. Typically, one
would use this type of external function to support macro and scripting
features within the application.
DLL-based functions are mainly used to provide additional utility libraries for
REXX. Once registered, all REXX programs have access to DLL-based functions.
Both DLL- and EXE-based functions follow the same basic declaration syntax.
The following sample function will clear the screen if called from a VIO-based
REXX program.
ULONG _System SysCls(UCHAR *name,
ULONG numargs,
RXSTRING args[],
PSZ *queuename,
RXSTRING *retstr)
{
BYTE bCell[2];
// If arguments, return non-zero to indicate error
if (numargs)
return 1;
bCell[0] = 0x20;
bCell[1] = 0x07;
VioScrollDn(0,
0,
(USHORT)0xFFFF,
(USHORT)0XFFFF,
(USHORT)0xFFFF,
bCell,
NULLSHANDLE);
VioSetCurPos(0, 0, NULLSHANDLE);
// return 0 to the caller
retstr.strlength=1;
strcpy(retstr.strptr,"0");
return 0;
}
Things to remember
External REXX functions must use the C calling convention.
When creating DLL-based external functions, remember to export the function and
use multiple data segments (DATA MULTIPLE NONSHARED).
The default return string is 255 bytes long.
Registering Your External Function
Having previously seen how to register DLL-based external functions from within
REXX, we will now look at way in which you can register external functions from
within an application. The function RexxRegisterFunctionDll() is used to
register DLL-based external functions and RexxRegisterFunctionExe() is used to
register external EXE-based functions.
(APIRET)RexxRegisterFunctionDll(PSZ pszFunction,
PSZ pszModule,
PSZ pszEntryPoint);
(APIRET)RexxRegisterFunctionExe(PSZ pszFunction,
PFN pfnEntryPoint);
For RexxRegisterFunctionDll(), the following parameters are specified:
pszFunction Points to the string specifying the name of the function as it
will be known by REXX.
pszModule Points to the string specifying the DLL containing the function
to be registered.
pszEntryPoint Points to the exported function name within pszModule which
specifies the function to be registered.
For RexxRegisterFunctionExe(), the following parameters are specified:
pszFunction Points to the string specifying the name of the function as it
will be known by REXX.
pfnEntryPoint Points to the function to be registered.
Both functions return RXFUNC_OK, RXFUNC_DEFINED, or RXFUNC_NOMEM to indicate
successful completion, function is already defined, and out of memory,
respectively.
Additionally, there are functions to query the existance of a function and to
deregister a function.
(APIRET)RexxQueryFunction(PSZ pszFunction);
(APIRET)RexxDeregisterFunction(PSZ pszFunction);
For both functions, pszFunction points to the function name (as registered
using one of the above functions) and return FXFUNC_OK or RXFUNC_NOTREG to
indicate that the function exists and that the function does not exist,
respectively.
So now we've seen how to start Rexx/2 from within our applications, and seen
how to register external functions. It's time to put our newly acquired
knowledge to use.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.2.6. Putting it all Together ΓòÉΓòÉΓòÉ
In this section, we will create a simple application that will register an
external function and execute .CMD files. The following program (included as
REXXSAMP.CPP) illustrates the procedure of registering an external EXE-based
function and starting the Rexx/2 interpreter.
#define INCL_RXFUNC /* external function values */
#define INCL_VIO
#include <rexxsaa.h>
#include <iostream.h>
#include <string.h>
ULONG _System SysCls(UCHAR *name,
ULONG numargs,
RXSTRING args[],
PSZ *queuename,
RXSTRING *retstr)
{
BYTE bCell[2];
// If arguments, return non-zero to indicate error
if (numargs)
return 1;
bCell[0] = 0x20;
bCell[1] = 0x07;
VioScrollDn(0,
0,
(USHORT)0xFFFF,
(USHORT)0XFFFF,
(USHORT)0xFFFF,
bCell,
NULLSHANDLE);
VioSetCurPos(0, 0, NULLSHANDLE);
// return 0 to the caller
retstr.strlength=1;
strcpy(retstr.strptr,"0");
return 0;
}
INT main(VOID) {
char Input[CCHMAXPATH];
LONG return_code; /* interpreter return code */
RXSTRING argv[1]; /* program argument string */
RXSTRING retstr; /* program return value */
SHORT rc; /* converted return code */
RexxRegisterFunctionExe((PSZ)"EDM_SysCls",(PFN)&SysCls);
cout << "Sample EDM/2 REXX Demonstration Program" << endl << "by Gordon Zeglinski" << endl;
cout << "Type rexxsamp <ENTER> to execute supplied smaple" << endl;
cin >> Input;
if (!strlen(Input))
strcpy(Input,"REXXSAMP.CMD");
cout << "Executing Sample Program " << Input << endl;
cout << "-----------------" << endl;
retstr.strptr=new char [1024];
retstr.strlength=1024;
return_code = RexxStart(0, // No arguments
argv, // dummy entry
Input, // File name
NULL, // NULL InStore
"CMD", // use the "CMD" command processor
RXCOMMAND, // execute as a command
NULL, // No exit handlers
&rc, // return code from REXX routine
&retstr); // return string from REXX routine
delete [] retstr.strptr;
return 0;
}
For those of you who can compile the sample code, feel free to experiment with
it and a debugger.
Files Included With This Issue
REXXSAMP.EXE Compiled version of REXXSAMP.CPP
REXXSAMP.CPP Source code to sample Rexx/2 invocation program
REXXSAMP.MAK Makefile for C-Set++
REXXSAMP.DEF Definition file for use with linker
REXXSAMP.CMD Sample REXX program to be started from REXXSAMP.EXE
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.2.7. Summary ΓòÉΓòÉΓòÉ
This concludes our look at Rexx/2 for this issue. You should now be able to
start Rexx/2 from with in an application, and create external functions that
are either DLL- or EXE-based. Future issues will explore the other features of
RexxStart(), macroSpaces and sub-command handlers.
As usual, question and comments are welcome.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.3. Threads in PM Applications ΓòÉΓòÉΓòÉ
Written by Larry Salomon, Jr.
Introduction
Because of what is often perceived as a design flaw in PM (but I am not passing
judgement), tasks that require more time than is suggested by IBM's
"well-behaved" application guideline (1/10 second) should be performed in a
thread separate from that which contains the main dispatch loop (denoted by the
calls to WinGetMsg() and WinDispatchMsg()). However, the issue of
communication between the user-interface and additional threads created by the
user-interface arises for which there is no recommended design to follow. This
article will attempt to design an architecture that is easy to implement yet
expandible and requires no global variables (always a good thing).
Historical Caveat
In the years that I have written applications for PM, I have tried every
conceivable technique to accomplish mulithreading in a smooth fashion. For
those actions that require the user to initiate the task that requires the
additional thread, I have found the solution that will be detailed here to be
the best for my purposes. It should be stressed here that your mileage may
vary and that for your design "methodologies" this may not work as well for
you. It is encouraged then to use the information herein as a basis and not as
the final result.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.3.1. Introduction ΓòÉΓòÉΓòÉ
Written by Larry Salomon, Jr.
Because of what is often perceived as a design flaw in PM (but I am not passing
judgement), tasks that require more time than is suggested by IBM's
"well-behaved" application guideline (1/10 second) should be performed in a
thread separate from that which contains the main dispatch loop (denoted by the
calls to WinGetMsg() and WinDispatchMsg()). However, the issue of
communication between the user-interface and additional threads created by the
user-interface arises for which there is no recommended design to follow. This
article will attempt to design an architecture that is easy to implement yet
expandible and requires no global variables (always a good thing).
Historical Caveat
In the years that I have written applications for PM, I have tried every
conceivable technique to accomplish mulithreading in a smooth fashion. For
those actions that require the user to initiate the task that requires the
additional thread, I have found the solution that will be detailed here to be
the best for my purposes. It should be stressed here that your mileage may
vary and that for your design "methodologies" this may not work as well for
you. It is encouraged then to use the information herein as a basis and not as
the final result.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.3.2. From User-events to Multi-threading ΓòÉΓòÉΓòÉ
What happens when the user selects the "Open..." menu item from your
application's menu? Ignoring specifics, we can construct a timeline like the
one below:
1. The user selects "Open..." from the menu
2. The application is notified of this selection
3. The application prompts the user for a filename
4. The application reads the selected file
5. The user is then allowed to perform operations on the file's data
This is an example of a user-request that, once the relevant information is
gathered, can be performed without any further intervention. Because of its
simplicity, this lends itself nicely to multithreading: the thread is started,
it performs its task to completion (successful or otherwise), and it notifies
the user-interface that it is finished. Before multithreading can be
implemented, however, some thought to design must be given.
Designing the Architecture
Because the only communication required between the owner thread and the owned
thread is upon initialization and completion, we immediately eliminate the
system methods of IPC, such as pipes, queues, and shared memory because they
are too cumbersome. This does not mean that they will not work, but when you
consider that any PM application you write that does more than draw a box or
two will require many different types of threads, you will want a design that
is the easiest to implement so that you can concentrate on the rest of the
coding. For this reason, I chose the communication path to be a structure
whose address is passed via the single thread parameter for initialization and
posted messages to the owner window for completion of the thread. This treats
the thread as though it were a "black-box", i.e. you put something in one end
and out from the other end comes a result.
An Objective Approach
"What is Life? Who is God? What is the grass green and the sky blue? What are
the common characteristics of the threads we've been discussing?" Although you
could ponder the first three questions for a long time, the last one is a
no-brainer given the power of hindsight. :) These commonalities I encapsulated
in the THREADINFO structure, shown below:
typedef struct _THREADINFO {
//----------------------------------------------------------------------
// Initialized by the main thread
//----------------------------------------------------------------------
ULONG ulSzStruct;
HWND hwndOwner;
BOOL bKillThread;
//----------------------------------------------------------------------
// Initialized by the secondary thread
//----------------------------------------------------------------------
HAB habThread;
BOOL bThreadDead;
BOOL bResult;
} THREADINFO, *PTHREADINFO;
ulSzStruct This specifies the size of the structure.
hwndOwner This specifies the handle of the owning window.
bKillThread This is set to TRUE by the owner when the request is to be
aborted.
habThread This specifies the anchor block handle of the thread.
Before I start receiving hate-mail from the purists out
there claiming that this isn't necessary, I answer that it
is necessary and that you should keep reading to see why.
bThreadDead This is set to TRUE by the thread when it is dead. Again,
keep reading to see how this is possible.
bResult This is a blanket indicator of success or failure complete
the task.
From here we can add our task-specific variables in the following manner: for
each task type, define a structure to contain the fields specifies to that
task, but define the first field to always be of type THREADINFO. What does
this buy us? Since the THREADINFO structure contains common (that is the key
word) fields, we can write code that initializes them regardless of the task to
be performed by casting the task-specific structure to type THREADINFO. Again,
I realize that the purists are going to tell me that casting is a "bad thing",
but I counter with the statement that casting - like goto statements - can be
very helpful as long as it is not abused and is used in the proper way. If you
are the sensitive type regarding these issues, stop reading now 'cause we are
going to "break" a few more rules along the way.
In addition to defining a task-specific structure, we need to define a constant
that uniquely describes this task. The purpose of this will be seen later. We
can illustrate these things with an example.
#define ASYNC_OPEN 0x00000001L
typedef struct _OPENTHREADINFO {
THREADINFO tiInfo;
CHAR achFilename[CCHMAXPATH];
PFILEDATA pfdData; // Make-believe data type for illustrative
// purposes
} OPENTHREADINFO, *POPENTHREADINFO;
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.3.3. Add a Tire or Two ΓòÉΓòÉΓòÉ
Although a tire does not provide the impulse energy to make a car go forward,
without it the car is not going anywhere. Let's add a tire or two to our
design. Backing up a bit, we stated that a window has a menu with a menu item
that results in a thread being created to service the request associated with
that menu item. Thus, the window is said to own the thread. Since conceivably
a window can have the need to perform the same service in different places, it
would be nice to have a single point of entry and exit for all requests. Enter
"thing one" and "thing two" (for those of you who remember "The Cat in the
Hat").
#define MYM_BASE (WM_USER)
#define MYM_STARTTHREAD (MYM_BASE)
#define MYM_ENDTHREAD (MYM_BASE+1)
For what are these user-messages used? MYM_STARTTHREAD is sent by the window
to itself to start a thread which will perform some task. MYM_ENDTHREAD is sent
by the thread to the owning window (whose handle is in the THREADINFO
structure, you'll remember) to indicate that processing has completed. Both
messages expect the thread type constant to be specified in LONGFROMMP(mpParm1)
and a pointer to the thread-specific structure in PVOIDFROMMP(mpParm2). The
skeleton code for these two messages is shown below:
typedef VOID (* _Optlink PFNREQ)(PVOID);
MRESULT EXPENTRY windowProc(HWND hwndWnd,
ULONG ulMsg,
MPARAM mpParm1,
MPARAM mpParm2)
{
switch (ulMsg) {
:
case MYM_STARTTHREAD:
{
ULONG ulBit;
PTHREADINFO ptiInput;
PFNREQ pfnThread;
PVOID pvParm;
ulBit=LONGFROMMR(mpParm1);
ptiInput=(PTHREADINFO)PVOIDFROMMP(mpParm2);
ptiInput->hwndOwner=hwndWnd;
ptiInput->bKillThread=FALSE;
switch (ulBit) {
case ASYNC_OPEN:
{
POPENTHREADINFO potiInfo;
ptiInput->ulSzStruct=sizeof(OPENTHREADINFO);
potiInfo=(POPENTHREADINFO)malloc(sizeof(OPENTHREADINFO));
if (potiInfo==NULL) {
WinMessageBox(HWND_DESKTOP,
hwndWnd,
"There is not enough memory.",
"Error",
0,
MB_OK|MB_ICONEXCLAMATION|MB_MOVEABLE);
return MRFROMSHORT(FALSE);
} /* endif */
memcpy(potiInfo,ptiInput,sizeof(OPENTHREADINFO));
pfnThread=(PFNREQ)openThread;
pvParm=(PVOID)potiInfo;
}
break;
default:
WinMessageBox(HWND_DESKTOP,
hwndWnd,
"There is an internal error.",
"Error",
0,
MB_OK|MB_ICONEXCLAMATION|MB_MOVEABLE);
return MRFROMSHORT(FALSE);
} /* endswitch */
if (_beginthread(pfnThread,NULL,0x4000,pvParm)==-1) {
free(pvParm);
WinMessageBox(HWND_DESKTOP,
hwndWnd,
"The thread could not be created.",
"Error",
0,
MB_OK|MB_ICONEXCLAMATION|MB_MOVEABLE);
return MRFROMSHORT(FALSE);
} /* endif */
}
break;
case MYM_ENDTHREAD:
{
ULONG ulBit;
PTHREADINFO ptiInput;
ulBit=LONGFROMMR(mpParm1);
ptiInput=(PTHREADINFO)PVOIDFROMMP(mpParm2);
//----------------------------------------------------------------
// Wait for the thread to finish dying. There is a bug in
// DosSleep() such that if 0 is the argument, nothing happens.
// Call it with 1 instead to achieve the same result.
//----------------------------------------------------------------
while (!ptiInput->bThreadDead) {
DosSleep(1);
} /* endwhile */
switch (ulBit) {
case ASYNC_OPEN:
{
POPENTHREADINFO potiInfo;
potiInfo=(POPENTHREADINFO)ptiInput;
free(potiInfo);
}
break;
default:
return MRFROMSHORT(FALSE);
} /* endswitch */
}
break;
:
default:
return WinDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
} /* endswitch */
}
Since the MYM_STARTTHREAD message can be called from different place and each
place can have different values, the thread-specific values are initialized
before this message is sent.
An important note is needed here: some time ago I received a note from a
columnist in a printed publication criticizing me for specifying so large a
stack on the call to _beginthread(). What it boiled down to was a
misunderstanding of the inner workings of OS/2 with regards to stacks and
actual memory usage.
The stack size specified in the .DEF file or the call to _beginthread() is
allocated stack space and not committed stack space. The distinction here is
important. Allocated memory is memory that has been assigned to a process but
does not consume any physical memory until it is committed. How does this
committment take place? Delving into a lot of system-specific stuff here that
you can skip if you are not interested, the system commits a small (1?) number
of pages for the stack when each thread starts, and sets the page following the
stack to be a guard page. When the stack grows beyond the committed length, a
guard page exception occurs, which is intercepted by the system's default
exception handler. Assuming that you have more space available, the system
commits enough pages to satisfy the memory requirements and sets the next page
to be the new guard page.
Thus, the amount of committed memory of the stack grows (and possibly shrinks;
here my knowledge is fuzzy at best) dynamically. The number specified in the
.DEF file, etc. is the maximum size that the stack should grow to. If,
however, you specify 32k and only use 4k, only 4k is committed.
End of sermon.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.3.4. Next Add the Engine ΓòÉΓòÉΓòÉ
The thread is defined like you would expect it to, but it performs a few
important tasks.
VOID openThread(POPENTHREADINFO potiInfo)
{
HAB habAnchor;
HMQ hmqQueue;
habAnchor=WinInitialize(0);
hmqQueue=WinCreateMsgQueue(habAnchor,0);
WinCancelShutdown(hmqQueue,TRUE);
potiInfo->tiInfo.habThread=habAnchor;
potiInfo->tiInfo.bThreadDead=FALSE;
potiInfo->tiInfo.bResult=FALSE;
//----------------------------------------------------------------------
// Do nothing. This is strictly for the purposes of illustration.
//----------------------------------------------------------------------
WinMessageBox(HWND_DESKTOP,
potiInfo->tiInfo.hwndOwner,
"In the thread",
"Information",
0,
MB_OK|MB_MOVEABLE);
WinPostMsg(potiInfo->tiInfo.hwndOwner,
MYM_ENDTHREAD,
MPFROMLONG(ASYNC_OPEN),
MPFROMP(potiInfo));
WinDestroyMsgQueue(hmqQueue);
WinTerminate(habAnchor);
DosEnterCritSec();
potiInfo->tiInfo.bThreadDead=TRUE;
}
Initialization
The thread initializes itself by creating a message queue and initializing the
remainder of the common fields in the THREADINFO structure. Why do we need a
message queue? I claim it is by nature of the fact that this thread exists
because of a user action, you will likely call a function that requires it
(e.g. WinMessageBox() in the above code).
Termination
The thread terminates by posting a message to the owner window that it is about
to complete, destroys the message queues, and - what's that? - enters a
critical section to set the bThreadDead flag. Why do we need that?
We don't. The reason for this is purely historical, as is the posting (versus
sending) of the MYM_ENDTHREAD message. However, this illustrates a point that
has confused people in the past: if a thread enters a critical section and
then dies, the system automatically marks the critical section as having been
exited. Thus, all suspended threads will resume execution.
Although critical sections are a no-no when writing PM applications (because
the user-interface thread is also halted), this critical section takes little
time to execute, so we can let it be.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.3.5. Finally Add the Rollbars and Racing Stripes ΓòÉΓòÉΓòÉ
User feedback becomes a big issue when you deal with multiple threads. How do
you indicate that something important is going on in the background? Do you
disable the (appropriate) menu items? How are errors handled? etc. The
easiest answer is "it depends on the application". I realize that this does not
help much, so we will now discuss techniques that can be applied in your
applications.
Mouse Pointers
The most common method of indicating that something else is going on is to
change the mouse pointer. However, we all know what happens when you do this
regardless of the mouse position. The answer, then, lies in two important
messages that the system sends regarding the mouse - WM_CONTROLPOINTER and
WM_MOUSEMOVE. Simply stated, when you receive those mouse messages, you set
the mouse to an appropriate pointer (typically
WinQuerySysPointer(HWND_DESKTOP,SPTR_WAIT,FALSE)) if another thread is in
progress or you return WinDef*Proc() otherwise. How do you tell if there is
another thread? There is no documented system API that I know of that will
tell you this, so you will have to keep a counter in your instance data and
increment/decrement as necessary in the MYM_STARTTHREAD/MYM_ENDTHREAD messages.
Menu Items
Whenever a menu is activated, the system sends the client window a WM_INITMENU
message. By intercepting and "switch"-ing on the value of mpParm1 (which is
the identifier of the menu), you can enable or disable any items of interest.
Error Messages
Error messages, in my opinion, are best handled in the thread which detected
the error. You could, of course, add a numeric field in the THREADINFO
structure in which you place an application-defined error code and then check
that in the MYM_ENDTHREAD processing, but why clutter it up unnecessarily? If
you'll accept that, then the next question is what do you do when you encounter
an error in the thread? The strategy I follow is like this:
o In the initialization of the thread, set all variables which specify
resources to be allocated by the thread (bitmap handles, file handles, memory
pointers, etc.) to the equivalent of "unallocated". Also, set the bResult
field of the THREADINFO structure to FALSE.
o Prefix the termination section of the thread with a label (i.e. EXIT_PROC).
Just before the label, set the bResult field of the THREADINFO structure to
TRUE because if you have made it there, everything must have completed
successfully.
o If an error is encountered, display an error message (via WinMessageBox())
and execute goto EXIT_PROC.
Notes of interest:
1. You cannot name the label EXIT_THREAD, because that is a constant already
defined by the toolkit for the DosExit() API. Although this seems obvious,
unless you're already thinking about it, you will spend hours on a wild
goose chase through your own code looking for the "duplicate symbol
defined" error (like I did once).
2. Don't forget to check the resource handles to see if they are allocated
before you try to unallocate them. This is the purpose of the first step
in the list above.
A complete working sample application demonstrating this asynchronous design
has been provided in the async.zip file.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.3.6. Synchronicity ΓòÉΓòÉΓòÉ
As an advanced topic, another approach to this mess is to process the request
in a synchronous fashion. What? Yes, you read it correctly. What you need is
another way of looking at synchronicity.
Whoooooa Whoooooa Whooooa!
(I don't know how many Police fans there are out there.)
Synchronicity in the above paragraph is, as Einstein would have stated it,
based on frame of reference. What if we were somehow able to make the
processing look like a simple function call to the user-interface thread, but
in actuality process it asynchronously?
Before we can do this, we have to determine what is different between a simple
function call and what we are trying to accomplish here. The answer is that we
must remain responsive to the user-interface messages. This requires a little
"hocus-pocus".
Going back to "PM Programming Course 101", you'll remember that the system
changes focus by sending a bunch of messages to the windows losing and
receiving the focus. If either window does not respond to these messages
within a system-defined amount of time, you get the "The application is not
responding to system messages..." message. The ultimate question then becomes
"how do you process messages?" and the answer is trivial, my dear reader.
Simply go into a WinPeekMsg()/ WinDispatchMsg() loop.
Of course, things are a little more than that, so the code is presented below.
A complete working sample application has been provided in the sync.zip file.
#define DT_NOERROR 0
#define DT_QUITRECEIVED 1
#define DT_ERROR 2
typedef VOID (* _Optlink PFNREQ)(PVOID);
USHORT dispatchThread(HAB habAnchor,PFNREQ pfnThread,PTHREADINFO ptiInfo)
//-------------------------------------------------------------------------
// This is the thread dispatch procedure. It calls _beginthread() and
// goes into a WinPeekMsg()/WinDispatchMsg() loop until the thread is
// finished or WM_QUIT is received. Note the semantics of the latter event:
// if WM_QUIT is received, then it is assumed that the application will
// kill itself on return and thus any system resources will automatically
// be unallocated by the system when the application ends. So we do not
// set bKillThread=TRUE and wait but instead call DosKillThread() and
// return.
//-------------------------------------------------------------------------
{
TID tidThread;
BOOL bLoop;
QMSG qmMsg;
ptiInfo->bKillThread=FALSE;
ptiInfo->bThreadDead=FALSE;
tidThread=_beginthread(pfnThread,NULL,0x4000,ptiInfo);
if (tidThread==-1) {
return DT_ERROR;
} /* endif */
//----------------------------------------------------------------------
// WinGetMsg() cannot be used because it blocks if there is no message
// waiting. When the thread dies, therefore, the function will never
// return if the user takes his/her hands off of the keyboard, mouse,
// and no timers are started because we will never get a message!
//----------------------------------------------------------------------
WinPeekMsg(habAnchor,&qmMsg,NULLHANDLE,0,0,PM_REMOVE);
bLoop=((qmMsg.msg!=WM_QUIT) && (!ptiInfo->bThreadDead));
while (bLoop) {
WinDispatchMsg(habAnchor,&qmMsg);
WinPeekMsg(habAnchor,&qmMsg,NULLHANDLE,0,0,PM_REMOVE);
bLoop=((qmMsg.msg!=WM_QUIT) && (!ptiInfo->bThreadDead));
} /* endwhile */
if (qmMsg.msg==WM_QUIT) {
DosKillThread(tidThread);
return DT_QUITRECEIVED;
} /* endif */
return DT_NOERROR;
}
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.3.7. Summary ΓòÉΓòÉΓòÉ
In this article, much information was presented regarding multithreading and PM
application development. Even though our scope was limited to "one-shot"
threads, we can see how a complicated matter can be simplified with a little
thought. This is not to belittle the task of such a complex objective, but
instead is to illustrate the ability to implement an architecture that is
usable in the "real-world".
A summary follows:
o All "one-shot" threads share a common set of data fields.
o Exploitation of the "black-box" concept simplifies things considerably.
o Since threads of the same type can be created from different places, a
single-entry and exit point is desirable.
o User-feedback issues cannot be ignored.
o "One-shot" threads can be implemented with the owner having a synchronous
perspective.
All questions are welcome via email.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.4. Writing a C++ Thread Class ΓòÉΓòÉΓòÉ
Written by Gordon W. Zeglinski
Introduction
C++ provides an ideal mechanism for encapsulating thread specific variables and
other thread creation/maintenance related details. This article will introduce
a basic hierarchy which accomplishes these goals.
After reading this article, you should:
1. Understand how to use the function _beginthread() with C++ code
2. Understand how to use the thread objects presented here
3. Be able to extend these basic objects to provide support for thread
specific variables.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.4.1. Introduction ΓòÉΓòÉΓòÉ
Written by Gordon W. Zeglinski
C++ provides an ideal mechanism for encapsulating thread specific variables and
other thread creation/maintenance related details. This article will introduce
a basic hierarchy which accomplishes these goals.
After reading this article, you should:
1. Understand how to use the function _beginthread() with C++ code
2. Understand how to use the thread objects presented here
3. Be able to extend these basic objects to provide support for thread
specific variables.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.4.2. Using _beginthread ΓòÉΓòÉΓòÉ
The _beginthread() function is found in one of the following two forms,
depending upon which compiler you are using.
For Borland C++ for OS/2:
int _beginthread(VOID (*start_address)(PVOID),
unsigned stack_size,
PVOID arglist)
And for C-Set++, Watcom, and GCC/EMX:
int _beginthread(VOID (*start_address)(PVOID),
(PVOID)stack,
unsigned stack_size,
PVOID arglist);
The extra parameter stack is specified strictly for backwards compatibility and
is not used by the compilers which include it. In general, "start_address" is
the address to a function like :
VOID Foo(PVOID Ptr) {
:
/* Some body here */
:
}
What about member functions ?
Let's look at the following class foobar:
class foobar {
int i;
public:
foobar();
VOID ThreadStart(PVOID x);
};
Now suppose that on that one wanted to start a thread using the function
ThreadStart() as the starting point. This cannot be done using _beginthread()
because of the sometimes forgotten fact that in C++, there is a hidden pointer
that is always passed when non-static member functions are called. The actual
parameters passed to ThreadStart() are:
foo *this the hidden pointer
PVOID x the declared parameter
In theory, one could use a static member function as the starting function of a
thread, however, for some reason C-Set++ doesn't seem to accept this.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.4.3. C++ Thread Objects ΓòÉΓòÉΓòÉ
So we've seen the problem when trying to use _beginthread() to start threads on
C++ member functions. How do we work around this? The solution is quite
simple, we develop a snazzy C++ object hierarchy encapsulate the thread
creation process.
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
Γöé class Thread Γöé
ΓööΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÿ
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ Γöé ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
Γöé Γöé Γöé
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ Γöé ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
Γöé template< class C,class Arg > Γöé Γöé Γöé template< class C > Γöé
Γöé class ClassThreadwArg Γöé Γöé Γöé class ClassThreadwoArg Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ Γöé ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
Γöé
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
Γöé template< class C> Γöé
Γöé class StaticClassThreadwoArg Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
Figure 1. Snazzy Thread Hierarchy
Let's now look at how this hierarchy fits together by examining the classes in
it.
Thread
class Thread {
protected:
APIRET Error;
TID tid;
ULONG StackSize;
USHORT Started :1;
USHORT Suspended :1;
USHORT :(sizeof(USHORT)-2);
VOID SetThreadID(TID t){tid=t;}
public:
Thread(ULONG StackS=4096);
TID GetThreadID() {return tid;}
APIRET GetError() {return Error;}
VOID ChangeStackSize(ULONG S) {
if (!Started) StackSize=S;
}
virtual VOID Run(VOID)=0;
VOID Start();
VOID SuspendThread();
VOID KillThread();
VOID ResumeThread();
friend VOID ThreadStarter(PVOID);
};
The Thread class is an abstract class that forms the basis of this hierarchy.
It contains the basic functions for manipulating threads. The advantage of
using an abstract class is twofold: first, it groups all of the thread
manipulation functions into one class; and secondly, it provides an easy method
for the end user to extend the usefulness of the hierarchy.
But how does Thread work?
The function friend VOID ThreadStarter(PVOID) should look familiar from our
discussion of the _beginthread() function. It has the exact form of the
function that is required by the _beginthread() function and is declared as a
friend so that it can have access to the members of the Thread class. Ideally,
a static member function would be used, but do to some compiler quirks, a
friend is used instead. No big deal - the result is the same.
Using it is quite simple. To start a thread, one calls the member function
Start(), which then calls _beginthread() and uses ThreadStarter() as the
thread's starting point. ThreadStarter() in turn calls the member function
Run(), which has yet to be defined. The parameter thread starter takes is
declared as PVOID, but it really is a Thread * pointer. Thus, the instance of
Thread is known by ThreadStarter().
ClassThreadwoArg
template<class C>class ClassThreadwoArg:public Thread{
C * TheClass;
VOID (C::* TheFunc)(VOID);
public:
ClassThreadwoArg(C * tc,VOID (C::* tf)(VOID)): Thread() {
TheClass=tc;
TheFunc=tf;
}
ClassThreadwoArg(VOID (C::* tf)(VOID)): Thread() {
TheFunc=tf;
}
ClassThreadwoArg():Thread() {
TheClass=NULL;
TheFunc=NULL;
}
VOID SetInstance(C * tc) { TheClass=tc; }
VOID SetFunc(VOID (C::* tf)(VOID)) { TheFunc=tf; }
VOID Run(){
((TheClass)->*(TheFunc))();
}
};
The ClassThreadwoArg class is the most commonly used class. It starts a thread
on a member function which takes no arguments. Note that the function Run now
has a body, the beauty of which is its simplicity. However, its simplicity
hides the the power of this class. This class can be used to start threads in
any class's instance using any member function (taking no arguments) as a
starting point. Also, it is completely type-safe.
ClassThreadwArg
template<class C,class Arg>class ClassThreadwArg:public Thread{
C * TheClass;
VOID (C::* TheFunc)(Arg*);
Arg *TheArg;
public:
ClassThreadwArg(C * tc,VOID (C::* tf)(Arg*), Arg* ta): Thread() {
TheClass=tc;
TheFunc=tf;
TheArg=ta;
}
ClassThreadwArg(): Thread() {
TheClass=NULL;
TheFunc=NULL;
TheArg=NULL;
}
VOID SetInstance(C* tc){ TheClass=tc; }
VOID SetFunc(VOID (C::* tf)(Arg*)) { TheFunc=tf; }
VOID SetArg(Arg* ta) { TheArg=ta; }
VOID Run() {
(TheClass)->*(TheFunc))((TheArg));
}
};
The only difference between this class and ClassThreadwoArg, is that this class
allows the possibility of passing an pointer argument to the started member
function.
The last class will not be discussed in depth because it is basically the same
as ClassThreadwoArg but it is for static member functions.
Overview
Figure 2 shows the flow of function calls as a new thread is created.
Thread 1 Thread 2
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
Γöé Start ΓöÇ> _beginthread Γöé ΓöÇ> Γöé ThreadStarter ΓöÇ> Run ΓöÇ> (Some Member function) Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
Figure 2. Function flow when creating a thread.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.4.4. Using the C++ Threading Objects ΓòÉΓòÉΓòÉ
Now, let's take a look at how the threading objects can be used to implement
thread specific variables. Keeping with tradition, let's suppose we want to
create a bunch of foo threads. One could create the following class:
class foo {
Thread *TheThread;
:
/* thread-specific variables */
:
public:
foo( /* list of thread-specific initial values */ ) {
TheThread=new ClassThreadwoArg<foo>(this,
&foo::ThreadEntry);
:
/* do some initialization */
:
}
VOID ThreadEntry();
VOID StartThread(){TheThread->Start();}
:
/* Add whatever other functions are needed */
:
};
To create a new foo thread, one would create an instance of a foo object and
then call the StartThread() member function. The following code shows this
process.
INT main(VOID) {
foo **FooThreads;
FooThreads=new foo[2];
if (FooThreads==NULL)
exit(1);
FooThreads[0]=new foo(/* some thread specific info */);
FooThreads[1]=new foo(/* some more thread specific info */);
FooThreads[0]->StartThread();
FooThreads[1]->StartThread();
:
/* do something */
:
return 0;
}
Select this to go to the next section
ΓòÉΓòÉΓòÉ 2.4.5. Summary ΓòÉΓòÉΓòÉ
This brings us to the end of our C++ threading journey. Although, the objects
presented here are missing some bells and whistles, adding them should be
relatively easy. The reason _beginthread() cannot be directly used has been
discussed and a method to get around this limitation is presented. The sample
foo class illustrated how these thread objects can be used to develop thread
specific variables.
As usual, question and comments are welcome.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 3. Columns ΓòÉΓòÉΓòÉ
The following columns can be found in this issue:
o Scratch Patch
Select this to go to the next section
ΓòÉΓòÉΓòÉ 3.1. Scratch Patch ΓòÉΓòÉΓòÉ
Written by Larry Salomon, Jr.
Welcome to this month's "Scratch Patch"! Each month, I collect various items
that fit into this column sent to me via email. The ones that I feel contribute
the most to developers, whether in terms of information or as a nifty trick to
tuck into your cap, get published in this column.
To submit an item, send it via email to my address - os2man@panix.com - and be
sure to grant permission to publish it (those that forget will not be
considered for publication). This month, we have the following:
o Questions and Answers
o Snippet(s) of the Month
o Documentation Chop Shop
o Want Ads
ΓòÉΓòÉΓòÉ 3.1.1. Questions and Answers ΓòÉΓòÉΓòÉ
Before anything, a correction needs to be stated. Last month, I stated that
there appeared to be no difference between the WinLoadString() and
WinLoadMessage() functions. Unfortunately, I did not check into this other
than a perusal of the documentation, but the documentation seems to be
incomplete or incorrect. When I substituted WinLoadMessage() for
WinLoadString() in an application, incomprehensible text was returned.
Considering the function of DosGetMessage(), I imagine that there might be some
analogy between it and WinLoadMessage(). If anyone else has more information
to clarify this, I would appreciate it if it were emailed to me.
Mark Mathews (mark.mathews@channel1.com) writes: 1) What is the structure of
the delete files in D:\DELETE? These files are created every time I delete
files. Can you explain how these files are created and what happens when they
are undeleted? I am writing an undelete program. (Might be an article here).
To figure this out, I "installed" the undelete feature on my machine by
un-REM-ing the SET DELDIR=... statement in my CONFIG.SYS and creating the
directories specified. After deleting a few test files, I ran a utility to
dump the binary data for the control file - CONTROL.DEL. It seems that the
delete process works as such:
1. The user enters DEL MYFILE.DAT
2. If the size of the delete directory (default is \DELETE) plus the size of
the delete file exceeds the maximum size specified, the system removes the
files that have been "deleted" for the longest time, until this size meets
the requirements specified in CONFIG.SYS.
3. The system then moves the file into the delete subdirectory for that drive.
Since the file exists on the same drive as the delete subdirectory, a move
instead of a copy is executed, resulting in faster performance. Also, the
file is renamed to reflect the date and time that the "delete" was
performed.
4. The control file - CONTROL.DEL - is updated.
5. All newly created or updated files are set to be hidden (plus any other
attributes; I did not check the exact match).
The format of the control file, as best determined by the binary data, seems to
be one entry per file in the following format (all offsets are relative to the
beginning of the entry):
o Bytes 0x000-0x0FF - the fully qualified filename of the file that was
deleted, without the drive specifier.
o Byte 0x100 - trailing zero to make the filename an ASCIIZ string.
o Bytes 0x101-0x103 - seem to be reserved and set to zero.
o Bytes 0x104-0x10F - the new name of the deleted file as it exists in the
delete subdirectory
o Byte 0x110 - trailing zero to make the filename an ASCIIZ string.
o Bytes 0x111-0x114 - status bytes of some sort. You will notice that in the
binary data, '00 00 20 00' is used when there are more entries following and
'00 AA 20 00' is used for the last entry.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 3.1.2. Snippet(s) of the Month ΓòÉΓòÉΓòÉ
I received only one reader submission for this month, so there seems to be no
competition to see which will be published this month.
Remember (I cannot stress this enough), submitted functions must be modular in
fashion. This means that you should be able to compile them separately and
need only link them with your applications to get them to work properly (versus
actually including the source code as part of your application). The functions
below do not quite meet this criteria (they still require the application to
define a constant), but they are close enough.
The functions below are for saving and restoring window positions in an .INI
file. It should be noted, therefore, that OS/2 2.x provides a superset of this
functionality in the WinStoreWindowPos() and WinRestoreWindowPos() API's.
/* ========================================================================
Submitted by: Mark Harrison <harrison@lclark.edu>
These two routines will allow you to save the startup position of a
window in a private ini file. There are two calls that will do this
in the PM API ( WinStoreWindowPos() & WinRestoreWindowPos() ), but
these save your data in the system ini files. If you prefer to not
clutter the system files up, you need to take care of saving the
information in your own private file.
What these two routines do is to save and restore a windows position,
size and the previous position and size if a window is closed and
re-opened while it is maximized or minimized.
INI files have a three level heirarchy, set up as follows:
Application For example to store scores for a Basketball
Key Name Basketball program, the INI file Score
Key Data might look like this. 45 51
Although you can store more than one applications data in an INI file,
this probably isn't a good practice in most cases.
[ To use these functions, you need to #define the constant ID_INIPATH.
See below for more information. - Editor ]
======================================================================== */
VOID GetStartupData(HWND hWndFrame)
{
HAB hab;
CHAR szIniPath[64];
HINI hMyHini;
SWP Swp;
ULONG ulSwpSize = sizeof(SWP);
ULONG SwpOptions = SWP_ACTIVATE | SWP_MOVE | SWP_SIZE | SWP_SHOW;
USHORT RestoreValues[6];
ULONG ulRestoreValuesSize = sizeof(RestoreValues);
/* ========================================================================
Load the physical pathname from the resource file. It should be defined
something like this.
STRINGTABLE LOADONCALL MOVEABLE
BEGIN
ID_INIPATH "C:\\APP.INI"
END
======================================================================== */
hab = WinQueryAnchorBlock(hWndFrame);
WinLoadString(hab, 0, ID_INIPATH, sizeof(szIniPath), szIniPath);
if ((hMyHini = PrfOpenProfile(hab, szIniPath))!=NULLHANDLE)
{
if (PrfQueryProfileData(hMyHini,
"App",
"WindowSize",
(PVOID)&Swp,
(PULONG)&ulSwpSize))
{
if (Swp.fl & SWP_MAXIMIZE)
SwpOptions |= SWP_MAXIMIZE;
else if (Swp.fl & SWP_MINIMIZE)
SwpOptions |= SWP_MINIMIZE;
WinSetWindowPos(hWndFrame,
NULLHANDLE,
Swp.x,
Swp.y,
Swp.cx,
Swp.cy,
SwpOptions);
/* ========================================================================
Set the following flags in the frame window extra data. This will tell
the frame what size it should restore itself to if you restart minimized
or maximized.
======================================================================== */
if (PrfQueryProfileData(hMyHini,
"App",
"RestoreValues",
(PVOID)&RestoreValues,
(PULONG)&ulRestoreValuesSize))
{
WinSetWindowUShort(hWndFrame, QWS_XRESTORE, RestoreValues[0]);
WinSetWindowUShort(hWndFrame, QWS_YRESTORE, RestoreValues[1]);
WinSetWindowUShort(hWndFrame, QWS_CXRESTORE, RestoreValues[2]);
WinSetWindowUShort(hWndFrame, QWS_CYRESTORE, RestoreValues[3]);
WinSetWindowUShort(hWndFrame, QWS_XMINIMIZE, RestoreValues[4]);
WinSetWindowUShort(hWndFrame, QWS_YMINIMIZE, RestoreValues[5]);
}
}
else
WinSetWindowPos(hWndFrame, 0, 0L, 40L, 800L, 560L, SwpOptions);
}
else
WinSetWindowPos(hWndFrame, 0, 0L, 40L, 800L, 560L, SwpOptions);
PrfCloseProfile(hMyHini);
return;
}
VOID SaveStartupData(HWND hWndFrame)
{
HAB hab;
CHAR szIniPath[64];
HINI hMyHini;
SWP Swp;
USHORT RestoreValues[6];
WinQueryWindowPos(hWndFrame, &Swp);
RestoreValues[0] = WinQueryWindowUShort(hWndFrame, QWS_XRESTORE);
RestoreValues[1] = WinQueryWindowUShort(hWndFrame, QWS_YRESTORE);
RestoreValues[2] = WinQueryWindowUShort(hWndFrame, QWS_CXRESTORE);
RestoreValues[3] = WinQueryWindowUShort(hWndFrame, QWS_CYRESTORE);
RestoreValues[4] = WinQueryWindowUShort(hWndFrame, QWS_XMINIMIZE);
RestoreValues[5] = WinQueryWindowUShort(hWndFrame, QWS_YMINIMIZE);
hab = WinQueryAnchorBlock(hWndFrame);
WinLoadString(hab, 0, ID_INIPATH, sizeof(szIniPath), szIniPath);
if ((hMyHini = PrfOpenProfile(hab, szIniPath))!=NULLHANDLE)
{
PrfWriteProfileData(hMyHini,
"App",
"WindowSize",
(PVOID)&Swp,
sizeof(Swp));
PrfWriteProfileData(hMyHini,
"App",
"RestoreValues",
(PVOID)&RestoreValues,
sizeof(RestoreValues));
}
PrfCloseProfile(hMyHini);
return;
}
Select this to go to the next section
ΓòÉΓòÉΓòÉ 3.1.3. Documentation Chop Shop ΓòÉΓòÉΓòÉ
Our bug of the month bit me when writing a multithreaded application. It is in
reference to the DosSleep() API which does absolutely nothing when you specify
0 as its argument. According to the documentation, this should give up the
remainder of the current timeslice.
The workaround is to specify 1 as the argument.
Select this to go to the next section
ΓòÉΓòÉΓòÉ 3.1.4. Want Ads ΓòÉΓòÉΓòÉ
Below are the hot topics as of this issue's writing. Feel free to write on any
of these.
Workplace Shell Programming (hot) - lately, I have noticed two things: 1) lots
of people want to learn how to write new Workplace Shell classes and 2) no one
who knows anything about it is telling the rest of us how to do it. I'll even
stoop down to accepting an article in ASCII format on this topic! :)
Anything on Rexx/2 (hot) - many people have requested more articles on Rexx/2.
This issue sees the first article on this topic, but we can always use more.
Writing "Enhanced Editor" macros in Rexx/2 and interfacing with the Workplace
Shell from Rexx/2 are still open topics.
Using Input Hooks (hot) - this is a complicated topic which is brought up
frequently in the comp.os.os2.programmer.misc newsgroup.
Hit testing (warm) - one reader noted that the Jigsaw sample in both the IBM
and Borland toolkits (are they not the same?) perform there own correlation and
wondered why? Charles Petzold, in his OS/2 book "Programming the OS/2
Presentation Manager" briefly describes correlation and hit-testing, but does
not go into any detail nor does it describe the Gpi functions used for this
purpose.
Animation (warm) - a few readers expressed an interest in the various animation
techniques that can be applied to PM applications. The ultimate article, in my
opinion, would be one that develops a sprite library a la the Commodore 64's
(and Amiga's?) built-in routines, since this is probably the hardest component
of any good animation sequence.
Client/Server (warm) - using either named pipes (with or without a network) or
sockets, client/server programming is all-the-rage these days. Some time ago,
I started development on a post-office and a named-pipe implementation of FTP;
maybe I will get time to finish them and will write articles on them.
Select this to go to the next section
ΓòÉΓòÉΓòÉ <hidden> CONTROL.DEL Data ΓòÉΓòÉΓòÉ
Show file offset utility
Version 1.10
Copyright (c) 1992 by Larry Salomon, Jr.
All rights reserved.
Offset +-------------------------------------------------+------------------+
0x0000 | 5C 74 65 6D 70 5C 74 65 73 74 33 2E 75 6E 64 00 | \temp\test3.und. |
0x0010 | 2E 33 34 00 48 45 41 44 45 52 2E 57 4B 33 00 00 | .34.HEADER.WK3.. |
0x0020 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0030 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0040 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0050 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0060 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0070 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0080 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0090 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x00A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x00B0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x00D0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x00E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x00F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0100 | 00 00 00 00 32 36 31 32 30 35 34 35 2E 35 39 41 | ....26120545.59A |
0x0110 | 00 00 02 00 00 5C 74 65 6D 70 5C 54 45 53 54 32 | .....\temp\TEST2 |
0x0120 | 2E 55 4E 44 00 2E 33 34 00 48 45 41 44 45 52 2E | .UND..34.HEADER. |
0x0130 | 57 4B 33 00 00 00 00 00 00 00 00 00 00 00 00 00 | WK3............. |
0x0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0150 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0160 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0170 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0180 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0190 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x01A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x01B0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x01C0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x01D0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x01E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x01F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0200 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0210 | 00 00 00 00 00 00 00 00 00 32 36 31 32 30 35 34 | .........2612054 |
0x0220 | 35 2E 35 39 00 00 00 02 00 00 5C 74 65 6D 70 5C | 5.59......\temp\ |
0x0230 | 54 45 53 54 31 2E 55 4E 44 00 2E 33 34 00 48 45 | TEST1.UND..34.HE |
0x0240 | 41 44 45 52 2E 57 4B 33 00 00 00 00 00 00 00 00 | ADER.WK3........ |
0x0250 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0260 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0270 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0280 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0290 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x02A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x02B0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x02C0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x02D0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x02E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x02F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0300 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0310 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0320 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 32 36 | ..............26 |
0x0330 | 31 32 30 35 34 35 2E 35 36 00 00 00 02 00 00 5C | 120545.56......\ |
0x0340 | 74 65 6D 70 5C 65 2E 74 6D 70 00 75 6E 64 00 2E | temp\e.tmp.und.. |
0x0350 | 33 34 00 48 45 41 44 45 52 2E 57 4B 33 00 00 00 | 34.HEADER.WK3... |
0x0360 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0370 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0380 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0390 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x03A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x03B0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x03C0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x03D0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x03E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x03F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0400 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0410 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0420 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0430 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0440 | 00 00 00 32 36 31 32 30 34 30 31 2E 39 37 00 00 | ...26120401.97.. |
0x0450 | 00 02 00 00 5C 64 65 6C 65 74 65 5C 32 36 31 31 | ....\delete\2611 |
0x0460 | 35 36 34 31 2E 33 34 00 48 45 41 44 45 52 2E 57 | 5641.34.HEADER.W |
0x0470 | 4B 33 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | K3.............. |
0x0480 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0490 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x04A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x04B0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x04C0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x04D0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x04E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x04F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0500 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0510 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0520 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0530 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0540 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x0550 | 00 00 00 00 00 00 00 00 32 36 31 31 35 39 30 37 | ........26115907 |
0x0560 | 2E 32 38 00 00 00 AA 02 00 | .28...... |
+-------------------------------------------------+------------------+
ΓòÉΓòÉΓòÉ 4. Contributors to this Issue ΓòÉΓòÉΓòÉ
Are You a Potential Author?
As always, we are always looking for (new) authors. If you have a topic about
which you would like to write, send a brief description of the topic
electronically to any of the editors, whose addresses are listed below, by the
15th of the month in which your article will appear. This alerts us that you
will be sending an article so that we can plan the issue layout accordingly.
After you have done this, get the latest copy of the Article Submission
Guidelines from ftp.cdrom.com in the /pub/os2/2_x/program/newsltr directory.
(the file is artsub.zip) The completed text of your article should be sent to
us no later than the last day of the month; any articles received after that
time may be pushed to the next issue.
The editors can be reached at the following email addresses:
o Larry Salomon - os2man@panix.com (Internet).
The following people contributed to this issue in one form or another (in
alphabetical order):
o Larry Salomon, Jr.
o Gordon Zeglinski
o Network distributors
o JФrg Schwieder
ΓòÉΓòÉΓòÉ 4.1. Larry Salomon, Jr. ΓòÉΓòÉΓòÉ
Larry Salomon wrote his first Presentation Manager application for OS/2 version
1.1 in 1989. Since that time, he has written numerous VIO and PM applications,
including the Scramble applet included with OS/2 and the I-Brow/Magnify/Screen
Capture trio included with the IBM Professional Developers Kit CD-ROM currently
being distributed by IBM. Currently, he works for International Masters
Publishers in Stamford, Connecticut and resides in Bellerose, New York with his
wife Lisa.
Larry can be reached electronically via the Internet at os2man@panix.com.
ΓòÉΓòÉΓòÉ 4.2. Gordon Zeglinski ΓòÉΓòÉΓòÉ
Gordon Zeglinski is a freelance programmer/consultant who received his Master's
degree in Mechanical Engineering with a thesis on C++ sparse matrix objects.
He has been programming in C++ for 6 years and also has a strong background in
FORTRAN. He started developing OS/2 applications with version 2.0 .
His current projects include a client/server communications program that
utilitizes OS/2's features and is soon to enter beta testing. Additionally, he
is involved in the development of a "real-time" automated vehicle based on OS/2
and using C++ in which he does device driver development and designs the
applications that comprise the control logic and user interface.
He can be reached via the Internet at zeglins@cc.umanitoba.ca.
═══ 4.3. JФrg Schwieder ═══
JФrg Schwieder lives in Berlin, Germany and is a student of Economical
Engineering at the Technical University of Berlin. He is a developer for the
DOS, Windows, and OS/2 platforms and has used OS/2 since version 2.0. Some
years ago he worked for some home computer magazines writing articles about
programming tasks and columns. When time allows (and is not spent with editor
configuration), he works on PmTerm, a replacement for the system DOS and OS/2
command line windows.
JФrg can be reached at the following addresses:
email: kirk@cs.tu-berlin.de
Mail: Joerg Schwieder
Wieckerstr. 2
13051 Berlin
Germany
ΓòÉΓòÉΓòÉ 4.4. Network distributors ΓòÉΓòÉΓòÉ
These people are part of our distribution system to provide EDM/2 on networks
other than the Internet. Their help to provide access to this magazine for
others is voluntary and we appreciate them a lot!
o Paul Hethman (hethman@cs.utk.edu) - Compuserve
o David Singer (singer@almaden.ibm.com) - IBM Internal
If you would like to become a "network distributor", be sure to contact the
editors so that we can give you the credit you deserve!