home *** CD-ROM | disk | FTP | other *** search
-
- ΓòÉΓòÉΓòÉ 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!