home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 32 Periodic / 32-Periodic.zip / edmi1-6.zip / EDMI1-6.INF (.txt) < prev    next >
OS/2 Help File  |  1993-11-03  |  126KB  |  2,804 lines

  1.  
  2. ΓòÉΓòÉΓòÉ 1. Title Page ΓòÉΓòÉΓòÉ
  3.  
  4.            Welcome to EDM/2 - The Electronic OS/2 Developers Magazine!
  5.                    Portions copyright (c) by Larry Salomon Jr.
  6.                                 Volume 1, issue 6
  7.  
  8. Copyright Notice and Other Stuff 
  9.  
  10. The editor of this electronic magazine is Larry Salomon, Jr. 
  11.  
  12. Portions of EDM/2 are copyrighted by the editors.  This publication may be 
  13. freely distributed in electronic form provided that all parts are present in 
  14. their original unmodified form.  A reasonable fee may be charged for the 
  15. physical act of distribution; no fee may be charged for the publication itself. 
  16.  
  17. All articles are copyrighted by their authors. No part of any article may be 
  18. reproduced without permission from the original author. 
  19.  
  20. Neither this publication nor the editors are affiliated with International 
  21. Business Machines Corporation. 
  22.  
  23. OS/2 is a registered trademark of International Business Machines Corporation. 
  24. Other trademarks are property of their respective owners.  Any mention of a 
  25. product in this publication does not constitute an endorsement or affiliation 
  26. unless specifically stated in the text. 
  27.  
  28. Administrivia 
  29.  
  30. Volume 1, issue 5 went out with only a minor glitch, which is better than a 
  31. major glitch.  The really exciting news is that the auto-mailer worked quite 
  32. well, so we should be able to handle higher volumes of subscriptions.  My 
  33. thanks go to the person that informed me of the already-written 'majordomo' 
  34. which also does mailing lists but I found out that panix does not have it 
  35. installed and that the system administrators have said before that they would 
  36. not like mailing lists to be done from panix (and, futhermore, they would 
  37. probably charge by the KB sent per month if a mailing list ate up too much 
  38. CPU).  So, it looks like I will have to continue the mailing using my 
  39. home-grown 'submail'. 
  40.  
  41. This issue has some really good stuff in it.  The Want Ads section resulted in 
  42. a flurry of "wanna-write" notes on REXX.  While we actually received only one 
  43. article for this issue, the interest of knowledgeable people to become an 
  44. author seems high.  Also from the Want Ads, I hope that everyone will enjoy the 
  45. - not one, but - two articles on multithreading.  Finally, I must apologize to 
  46. everyone for not writing part 4 of the "Container" series, but I have this 
  47. annoying problem called "a job" and it has been eating up a lot of time lately. 
  48. I, as well as Andre Asselin ("OS/2 Installable File Systems"), will try to 
  49. finish up next month. 
  50.  
  51. EDM/2 officially welcomes IBM to the network distribution, courtesy of David 
  52. Singer who runs the IBM (internal and external) gopher servers. 
  53.  
  54. We would like to thank Gordon Zeglinski for rewriting the installation program. 
  55. It no longer depends on the system installation to work properly. 
  56.  
  57. Functions submitted for the Snippet(s) of the Month portion of Scratch Patch 
  58. will be included as snippet.zip in the distribution from this issue on. 
  59.  
  60. New look (again) 
  61.  
  62. We are still tweaking a few things regarding presentation and are interested in 
  63. your feedback.  Tell us what you like and dislike about the format of the 
  64. magazine (no promises that we will listen to you, however... :) 
  65.  
  66. Annoucement 
  67.  
  68. This is from IBM's "Software Developer Support & Services" group.  Many of you 
  69. probably saw the same in comp.os.os2.announce. 
  70.  
  71. IBM announces the PS (Personal Software, which includes DOS, OS/2 and LAN 
  72. Systems) Worldwide Developer Assistance Program mailbox for users of the 
  73. Internet. If you have a question about our program or would like to join, 
  74. please write to us at wwdap@vnet.ibm.com. 
  75.  
  76. Select this to go to the next section 
  77.  
  78.  
  79. ΓòÉΓòÉΓòÉ 2. This Issue's Features ΓòÉΓòÉΓòÉ
  80.  
  81. The following articles constitute this issue's features: 
  82.  
  83. o Customizing the Enhanced Editor 
  84. o Rexx-ercising Your Applications 
  85. o Threads in PM Applications 
  86. o Writing a C++ Thread Class 
  87.  
  88. Select this to go to the next section 
  89.  
  90.  
  91. ΓòÉΓòÉΓòÉ 2.1. Customizing the Enhanced Editor ΓòÉΓòÉΓòÉ
  92.  
  93. Written by J╨ñrg Schwieder 
  94.  
  95. Introduction 
  96.  
  97. If you talk with OS/2 developers, you will soon get the impression that most of 
  98. them are desperately looking for a good programming editor even though OS/2 
  99. includes EPM, a very capable and flexible editor.  One of the reasons for the 
  100. bad reputation EPM seems to have may be its standard configuration which makes 
  101. it resemble the system editor, OS/2's version of the Windows Notepad.  Another 
  102. reason may be that - though it is very flexible - it shares the problem of a 
  103. lot of software that was 'born to use': that is it was written to be used not 
  104. to be sold or that evolved from a line of more simple product improved over the 
  105. time.  Its user-interface clearly shows some of the author's preferences and 
  106. remainders from former stages of development.  For example, this means that EPM 
  107. is almost completely incapable of using 'hard' tabs (which doesn't matter if 
  108. you don't like them but if you're used to using such features it is quite a 
  109. drawback). 
  110.  
  111. So if you want to use EPM you may need to customize it.  To do this you need 
  112. the complete EPM toolkit available on various ftp sites such as cdrom.com.  It 
  113. includes an updated (and reconfigured) version of EPM along with a better 
  114. documentation and a compiler for the E macro language (ETPM).  This article 
  115. will cover custom configuration of your editor using this toolkit and - through 
  116. examples - provide you with some nice additional features such as file 
  117. autoloading and positioning and keyboard undo. 
  118.  
  119. Select this to go to the next section 
  120.  
  121.  
  122. ΓòÉΓòÉΓòÉ 2.1.1. Introduction ΓòÉΓòÉΓòÉ
  123.  
  124. Written by J╨ñrg Schwieder 
  125.  
  126. If you talk with OS/2 developers, you will soon get the impression that most of 
  127. them are desperately looking for a good programming editor even though OS/2 
  128. includes EPM, a very capable and flexible editor.  One of the reasons for the 
  129. bad reputation EPM seems to have may be its standard configuration which makes 
  130. it resemble the system editor, OS/2's version of the Windows Notepad.  Another 
  131. reason may be that - though it is very flexible - it shares the problem of a 
  132. lot of software that was 'born to use': that is it was written to be used not 
  133. to be sold or that evolved from a line of more simple product improved over the 
  134. time.  Its user-interface clearly shows some of the author's preferences and 
  135. remainders from former stages of development.  For example, this means that EPM 
  136. is almost completely incapable of using 'hard' tabs (which doesn't matter if 
  137. you don't like them but if you're used to using such features it is quite a 
  138. drawback). 
  139.  
  140. So if you want to use EPM you may need to customize it.  To do this you need 
  141. the complete EPM toolkit available on various ftp sites such as cdrom.com.  It 
  142. includes an updated (and reconfigured) version of EPM along with a better 
  143. documentation and a compiler for the E macro language (ETPM).  This article 
  144. will cover custom configuration of your editor using this toolkit and - through 
  145. examples - provide you with some nice additional features such as file 
  146. autoloading and positioning and keyboard undo. 
  147.  
  148. Select this to go to the next section 
  149.  
  150.  
  151. ΓòÉΓòÉΓòÉ 2.1.2. The EPM Programming Model ΓòÉΓòÉΓòÉ
  152.  
  153. Programming EPM 
  154.  
  155. There are two ways of programming EPM:  using REXX and using the E macro 
  156. language.  Using REXX you may write EPM macros that don't need to be compiled 
  157. and you don't have to learn the E macro language.  This is quite powerful and 
  158. lets you use most of EPM's features including custem menus, etc.  and so it may 
  159. be great for user macros but you cannot change the default configuration using 
  160. REXX and since its interpreted and not compiled its quite slow so we will use 
  161. the macro language here. 
  162.  
  163. What is EPM? 
  164.  
  165. EPM is not a "program" as such.  The key element of the editor is the Enhanced 
  166. Multi-Line Edit control (E-MLE) that is defined if the E-Toolkit (ETK) DLL's. 
  167. It may also be used in custom programs (see the ETK documentation).  Most of 
  168. the basic functionality as well as macro language and REXX support are provided 
  169. by the control.  It can be used to edit multiple files in a "ring" which may be 
  170. as large as system memory permits.  EPM itself is just a small program that 
  171. manages E-MLEs and the user interface (e.g. dialogs). 
  172.  
  173. Select this to go to the next section 
  174.  
  175.  
  176. ΓòÉΓòÉΓòÉ 2.1.3. The Macro Language ΓòÉΓòÉΓòÉ
  177.  
  178. Most of the features that make EPM were added using the E macro language and 
  179. therefore may be rewritten or changed as well as used within other programs. 
  180. For example, it would be possible to write applications using E-MLEs for their 
  181. own editing purpose but incorporating the user's individual EPM setup.  Given 
  182. its capability to use the REXX language this makes for a great shot in the way 
  183. of finding an individual standard user interface (a paradox within conventional 
  184. application design). 
  185.  
  186. Okay, let's get back to EPM.  Macros that were written using the macro language 
  187. (*.E) are compiled with the macro compiler ETPM and become ETPM modules (*.EX). 
  188. An E-MLE has one main module (EPM.EX for EPM) which is executed upon creation 
  189. and which contains startup and close-down code as well as basic functions and 
  190. commands, menus, key-definitions etc.  There may be additional modules that 
  191. define external commands, they can be executed at runtime or linked into a 
  192. running system meaning they're loaded permanently. 
  193.  
  194. The macro language itself closely resembles REXX with some compiler specific 
  195. differences especially regarding variable handling.  See the EPM Technical 
  196. Reference included in the complete EPM distribution. 
  197.  
  198. Select this to go to the next section 
  199.  
  200.  
  201. ΓòÉΓòÉΓòÉ 2.1.4. Changing Your Configuration ΓòÉΓòÉΓòÉ
  202.  
  203. The EPM main module is called EPM.E.  (Well, in fact EPM.E currently does 
  204. nothing else than including E.E which is the real main module but in the 
  205. documentation the main module is said to be EPM.E so we call it that.)  It 
  206. includes the other EPM modules and is compiled to EPM.EX.  Changes to the 
  207. default configuration have to be recognized by EPM.E; usually this is done by 
  208. changing a set of files that are included in EPM.E:  MYCNF.E, MYKEYS.E, 
  209. MYSTUFF.E, MYMAIN.E, MYSELECT.E and MYKEYSET.E. 
  210.  
  211. MYCNF.E is a configuration file you should use to set configuration switches 
  212. whereas the other ones are thought to include code. You may recompile your 
  213. standard configuration by just recompiling EPM.E. 
  214.  
  215. o Changing the default configuration via MYCNF.E 
  216. o Adding your own key definitions via MYKEYS.E 
  217. o Your own commands and procedures via MYSTUFF.E 
  218. o Editor startup code via MYMAIN.E 
  219. o File initializations and focus changes via MYSELECT.E 
  220.  
  221. You should use MYKEYSET.E to define a whole new keyset.  This would be a little 
  222. too much for this article so we won't cover it here. 
  223.  
  224. Select this to go to the next section 
  225.  
  226.  
  227. ΓòÉΓòÉΓòÉ 2.1.5. Changing The Default Configuration via MYCNF.E ΓòÉΓòÉΓòÉ
  228.  
  229. EPM's standard configuration is stored in a file calles STDCNF.E.  Every 
  230. definition primitive is given a default value here.  You should not change this 
  231. file since it may be changed with the next release of EPM and you had to 
  232. rewrite all your changes.  Instead you should write a file called MYCNF.E which 
  233. is included before STDCNF.E  andso settings you specify here override settings 
  234. in STDCNF.E. 
  235.  
  236. Configuration Constants 
  237.  
  238. Configuration constants are constants so you can set them using the const 
  239. statement: 
  240.  
  241. const
  242.         FOO = 'VALUE'
  243.  
  244. There are, well, quite a lot of configuration constants within EPM so we will 
  245. cover only a subset which I think are the more important ones.  See the EPM 
  246. User's Manual for the complete set of constants. Along with the constants the 
  247. default values are given: 
  248.  
  249. ALTERNATE_KEYSET=1  Defines if support for alternate keysets is included.  The 
  250.                     default is to include it.  Support for alternate keysets 
  251.                     allows you to define user keysets and switch between them. 
  252.                     It is needed for C, E, Pascal, or REXX syntax assist.  If 
  253.                     it is set to 0 these are not included.  Also MYKEYSET.E 
  254.                     would not be included. 
  255.  
  256. AUTOSAVE_PATH=''    Specifies the path where "autosave" files will be placed. 
  257.                     The default value means that they're placed in the current 
  258.                     directory. There is also an AUTOSAVE PATH setting in EPM's 
  259.                     settings notebook.  The path specified there overrides the 
  260.                     setting in MYCNF.E 
  261.  
  262. BACKUP_PATH=''      Specifies the path setting for backup files.  Same as for 
  263.                     AUTOSAVE_PATH but there is no setting in the notebook for 
  264.                     this one and it has another option:  '=' will place the 
  265.                     backup file in the same directory as the saved one (and 
  266.                     give it the extension '.BAK').  The backup directory must 
  267.                     end with a backslash. 
  268.  
  269. COMPILER_ERROR_COLOR=RED+WHITEB When used within the Workframe/2, EPM displays 
  270.                     compiler errors.  This constant is used to set the color 
  271.                     used for the errors.  Default is red on white.  See 
  272.                     Configuring Colors 
  273.  
  274. C_SYNTAX_ASSIST=1   Set this to 1 to include syntax assist for the C language. 
  275.                     Syntax assist automatically expands specific keywords into 
  276.                     the corresponding language constructions. 
  277.  
  278. C_TABS=3            Specifies the default tab size for C files. 
  279.  
  280. DEFAULT_PASTE=''    Use this setting to define the default behaviour of the 
  281.                     paste key (Shift+Insert).  '' always pastes as new lines, 
  282.                     'B' pastes as a block and 'C' pastes as a stream of 
  283.                     characters. 
  284.  
  285. DRAG_ALWAYS_MARKS=0 In advanced marking mode on dragging the mouse a new block 
  286.                     is only marked if there is no one currently marked.  This 
  287.                     can be helpful, e.g. if you want to set the cursor using 
  288.                     the mouse and don't want the current mark to be deselected. 
  289.                     It can be quite annoying if you're not used to it so you 
  290.                     can set this switch to 1 to turn it off.  DRAG_ALWAYS_MARKS 
  291.                     has no effect in CUA-marking mode. 
  292.  
  293. ENHANCED_ENTER_KEYS=0 Set this one to 1 to be able to set the action performed 
  294.                     by the Enter keys with EPMs settings notebook.  Using the 
  295.                     default value only lets you use configuration constants 
  296.                     (ENTER_ACTION and C_ENTER_ACTION, see EPM Users Guide). 
  297.  
  298. ENHANCED_PRINT_SUPPORT=0 Set this to 1 to tell EPM to bring up the "Print" 
  299.                     dialog when you specify the print command from the menu 
  300.                     instead of just printing the file. 
  301.  
  302. EPATH='EPMPATH'     Specifies the environment variable used to store EPM's 
  303.                     search path. 
  304.  
  305. EXTRA_EX=0          This must be set to 1. Code size for .EX-files is limited 
  306.                     to 64K, which is too small for the standard configuration. 
  307.                     If EXTRA_EX is set to one it is split into two files, 
  308.                     EPM.EX and EXTRA.EX both of which are loaded on startup. 
  309.  
  310. E_SYNTAX_ASSIST=1   Enables syntax assist for the E language.  See 
  311.                     C_SYNTAX_ASSIST. 
  312.  
  313. E_TABS=3            Sets the default tab setting for .E files.  See C_TABS. 
  314.  
  315. HELPFILENAME='epmhelp.qhl' Sets the filename for the "Quick Help" file. 
  316.  
  317. HIGHLIGHT_COLOR=    Sets the color that is used to draw these fancy circles 
  318.                     around found search strings.  There is no default so you 
  319.                     have to specify it.  See Configuring Colors 
  320.  
  321. INCLUDE_WORKFRAME_SUPPORT=1 Specifies if support for IBM WorkFrame/2 is 
  322.                     included in the configuration. 
  323.  
  324. MARKCOLOR=Blue+GreyB Sets the color used to mark marked marks in your file. 
  325.                     See Configuring Colors 
  326.  
  327. MENU_LIMIT=0        You can edit several files in one EPM window using the file 
  328.                     ring.  Usually these files can be accessed with the Ring 
  329.                     menu command which brings up a dialog box containing the 
  330.                     files that are currently being edited. Specifying any 
  331.                     number greater than 0 (n) for this constant tells EPM to 
  332.                     add n files to the Ring menu and bring up the dialog box 
  333.                     only if there are more than n files in the ring.  There is 
  334.                     no limit for n but 120 makes no sense. 
  335.  
  336. MESSAGECOLOR=Light_Red+WhiteB Sets the color used to display the message line 
  337.                     or error messages.  See Configuring Colors 
  338.  
  339. MY_DEFAULT_EDIT_OPTIONS='' Set default options for the edit command.  These 
  340.                     default options will be overridden by options specified on 
  341.                     each specific edit command. 
  342.  
  343. MY_DEFAULT_SAVE_OPTIONS='' Same as MY_DEFAULT_EDIT_OPTIONS for the save 
  344.                     command. 
  345.  
  346. MY_DEFAULT_SEARCH_OPTIONS='' Another one, this time for search commands! 
  347.  
  348. MY_SAVE_WITH_TABS=0 Set this to 1 to tell EPM to automatically compresses 
  349.                     spaces to tabs when saving.  This is the same as specifying 
  350.                     '/t' for MY_DEFAULT_SAVE_OPTIONS. 
  351.  
  352. NLS_LANGUAGE='english' Sets the language for National Language Support. 
  353.  
  354. P_SYNTAX_ASSIST=1   Includes Pascal syntax assist. See C_SYNTAX_ASSIST. 
  355.  
  356. P_TABS=3            Sets the tab size for Pascal files. See C_TABS. 
  357.  
  358. REXX_SYNTAX_ASSIST=1 Includes REXX syntax assist. See C_SYNTAX_ASSIST. 
  359.  
  360. REXX_TABS=3         Sets the tab size for REXX files. See C_TABS. 
  361.  
  362. SETSTAY=0           This option controls where the cursor is located after a 
  363.                     change command. 0 means it is located on the last changed 
  364.                     string, 1 means it stays where it was before the command 
  365.                     and '?' defines a STAY command to change this behavior. 
  366.  
  367. STATUS_TEMPLATE='Line %l of %s  Column %c  %i  %m  %f' This is the status 
  368.                     string used to configure the status line.  The template can 
  369.                     be up to 128 characters long and may contain characters and 
  370.                     status tags. A status tag make EPM display certain status 
  371.                     information.  The following tags are defined: 
  372.  
  373.    %A        Number of changes made since last autosave. 
  374.    %C        Current column number 
  375.    %F        Number of files in ring 
  376.    %I        Insert or replace state 
  377.    %L        Current line number 
  378.    %M        Modified status 
  379.    %S        Total number of lines in current file 
  380.    %X        Hexadecimal value of current character 
  381.    %Z        ASCII value of current character 
  382.  
  383. SUPPORT_TECHREF=0   Set this to 1 to add a 'View Technical Reference' command 
  384.                     to the help menu. 
  385.  
  386. SUPPORT_USERS_GUIDE=0 Set this to 1 to add a 'View Users Guide' command to the 
  387.                     help menu. 
  388.  
  389. SUPPORT_USER_EXITS=0 User Exits are procedures that are called by certain 
  390.                     command, e.g. load and save.  Setting SUPPORT_USER_EXITS to 
  391.                     1 enables this feature.  See Your own commands and 
  392.                     procedures via MYSTUFF.E 
  393.  
  394. SYNTAX_INDENT='3'   Specifies the number of spaces used for syntax indent when 
  395.                     syntax assist is used. 
  396.  
  397. TEXTCOLOR=Black+WhiteB Specifies the color used to display normal unmarked text 
  398.                     in the edit window.  See Configuring Colors 
  399.  
  400. TOGGLE_ESCAPE=0     Set this to 1 to define the ESCAPEKEY command. 
  401.  
  402. TOGGLE_TAB=0        Set this to 1 to define the TABKEY command. 
  403.  
  404. TRASH_TEMP_FILES=0  If set to 1 temporary files (which begin with a '.') will 
  405.                     be quit without warning even if not saved.  This may be 
  406.                     useful (e.g. if you use the ALL command and don't want to 
  407.                     be asked if you wanted to save the '.ALL' file upon exit). 
  408.  
  409. UNDERLINE_CURSOR=0  Specifies the style of the cursor used in the editor. 
  410.                     Default is a vertical bar in front of the actual character. 
  411.                     Set to 1 changes it to a horizontal bar below the actual 
  412.                     character. 
  413.  
  414. UNMARK_AFTER_MOVE=0 Use this switch if you want marked text to be unmarked 
  415.                     after a move operation.  Default is that it is not. 
  416.  
  417. USE_APPEND=0        Set this to 1 to make EPM search for files along DPATH if 
  418.                     they can't be found in the current directory. 
  419.  
  420. WANT_ALL=0          Set this to 1 to include the ALL command. 
  421.  
  422. WANT_BOOKMARKS='LINK' Specifies whether or not bookmark support is included. 
  423.                     The default is that bookmark support is linked at runtime 
  424.                     (or included in EXTRA.EX if this is used). 
  425.  
  426. WANT_CHAR_OPS=1     Set this to 0 to disable character marking mode support. 
  427.  
  428. WANT_CUA_MARKING=0  Set this to 1 to use the CUA marking style instead of the 
  429.                     EPM block marking.  A value of SWITCH adds a switch for 
  430.                     this to the Preferences menu. 
  431.  
  432. WANT_DRAW='F6'      Includes the DRAW command.  Default is to include it and 
  433.                     define the 'F6' key for it.  Other options are 1 and 0. 
  434.  
  435. WANT_DYNAMIC_PROMPTS=1 Includes dynamic prompt support.  If it is included 
  436.                     (default) there is a Prompting option in the Frame Controls 
  437.                     menu. Prompting shows a description of the currently 
  438.                     selected menu item in the message line. 
  439.  
  440. WANT_ET_COMMAND=1   Includes the ET command to issue ETPM from EPM. 
  441.  
  442. WANT_KEYBOARD_HELP=0 If keyboard help is activated the CTRL-H key is redefined 
  443.                     to issue a keyword help procedure that looks through the 
  444.                     files specified in the HELPNDX environment variable for the 
  445.                     keyword under the cursor. If it is found help for this 
  446.                     keyword is given as specified in the .NDX-file, usually by 
  447.                     invoking VIEW.EXE. 
  448.  
  449. WANT_LONGNAMES=0    Includes support for long names.  A value of SWITCH defines 
  450.                     a Longnames command. 
  451.  
  452. WANT_PROFILE=0      Includes support for a REXX profile.  If profile support is 
  453.                     included EPM looks for a file called PROFILE.ERX upon 
  454.                     startup and executes it if found. A value of SWITCH defines 
  455.                     the Profile command. 
  456.  
  457. WANT_STACK_CMDS=0   Includes a set of stack commands.  The commands are 
  458.                     PushPos, PopPos, SwapPos, PushMark, PopMark, SwapMark and 
  459.                     can be used to create a stack to store cursor positions and 
  460.                     marks.  A value of SWITCH defines an entry in the 
  461.                     Preferencs menu. 
  462.  
  463. WANT_STREAM_MODE=0  Set this to 1 to make EPM use a stream mode approach to 
  464.                     file editing that is the edited file is seen as a 
  465.                     continuing stream of characters.  In the default mode a 
  466.                     file is interpreted as a set of lines.  A value of SWITCH 
  467.                     enables you to switch between these modes at runtime using 
  468.                     the Preferences menu. 
  469. See Example MYCNF.E 
  470.  
  471. Select this to go to the next section 
  472.  
  473.  
  474. ΓòÉΓòÉΓòÉ 2.1.6. Configuring Colors ΓòÉΓòÉΓòÉ
  475.  
  476. EPM includes the file COLORS.E which defines a set of color values to be used 
  477. within EPM macros.  The available constants are: 
  478.  
  479. Foreground Colors      Background Colors
  480.  
  481. BLACK         =  0     BLACKB         =   0
  482. BLUE          =  1     BLUEB          =  16
  483. GREEN         =  2     GREENB         =  32
  484. CYAN          =  3     CYANB          =  48
  485. RED           =  4     REDB           =  64
  486. MAGENTA       =  5     MAGENTAB       =  80
  487. BROWN         =  6     BROWNB         =  96
  488. LIGHT_GRAY    =  7     LIGHT_GRAYB    = 112
  489. DARK_GREY     =  8     DARK_GREYB     = 128
  490. LIGHT_BLUE    =  9     LIGHT_BLUEB    = 144
  491. LIGHT_GREEN   = 10     LIGHT_GREENB   = 160
  492. LIGHT_CYAN    = 11     LIGHT_CYANB    = 176
  493. LIGHT_RED     = 12     LIGHT_REDB     = 192
  494. LIGHT_MAGENTA = 13     LIGHT_MAGENTAB = 208
  495. YELLOW        = 14     YELLOWB        = 224
  496. WHITE         = 15     WHITEB         = 240
  497.  
  498. These constants may be used to set the display color variables.  Note that 
  499. these constants may only be used if COLORS.E was included.  This may not be the 
  500. case for external commands that include MYCNF.E. Use the COMPILE IF statement 
  501. to include them if the color constants are defined.  Also note that the color 
  502. variables are not constants and therefore should be define'd. 
  503.  
  504. compile if defined(BLACK)
  505.   define
  506.       TEXTCOLOR       = BLACK + WHITEB
  507.       MARKCOLOR       = BLUE + GREYB
  508.       STATUSCOLOR     = BLACK + WHITEB
  509.       MESSAGECOLOR    = LIGHT_RED + WHITEB
  510.       DRAGCOLOR       = YELLOW + MAGENTAB
  511.       HIGHLIGHT_COLOR =
  512. compile endif
  513.  
  514. Select this to go to the next section 
  515.  
  516.  
  517. ΓòÉΓòÉΓòÉ 2.1.7. Example MYCNF.E ΓòÉΓòÉΓòÉ
  518.  
  519. Here's an example MYCNF.E file. 
  520.  
  521. const
  522.     NLS_LANGUAGE = 'ENGLISH'
  523.     AUTOSAVE_PATH = '\OS2\EPM\AUTO\'
  524.     BACKUP_PATH = '\OS2\EPM\BACK\'
  525.     TEMP_PATH = '\OS2\EPM\TEMP\'
  526.     EPATH = 'EPMPATH'
  527.     C_TABS = '4'
  528.     E_TABS = '4'
  529.     REXX_TABS = '4'
  530.     P_TABS = '4'
  531.     SYNTAX_INDENT = '4'
  532.     USE_APPEND = 1
  533.     SETSTAY = '?'
  534.     TRASH_TEMP_FILES = 1
  535.     WANT_ALL = 1
  536.     WANT_RETRIEVE = 1
  537.     WANT_KEYWORD_HELP = 1
  538.     WANT_LONGNAMES = 'SWITCH'
  539.     WANT_STACK_CMDS = 'SWITCH'
  540.     WANT_CUA_MARKING = 'SWITCH'
  541.     WANT_STREAM_MODE = 'SWITCH'
  542.     WANT_DYNAMIC_PROMPTS = 1
  543.     WANT_PROFILE = 'SWITCH'
  544.     MY_DEFAULT_EDIT_OPTIONS = '/t'
  545.     SUPPORT_TECHREF = 1
  546.     SUPPORT_USERS_GUIDE = 1
  547.     SUPPORT_USER_EXITS = 1
  548.     EXTRA_EX = 1
  549.     DEFAULT_PASTE = ''
  550.     TOGGLE_ESCAPE = 1
  551.     TOGGLE_TAB = 1
  552.     DRAG_ALWAYS_MARKS = 1
  553.     MENU_LIMIT = 25
  554.     SMARTFILE = 1
  555.  
  556. compile if defined(BLACK)
  557. define
  558.     HIGHLIGHT_COLOR = GREEN + GREENB
  559.     DRAGCOLOR = LIGHT_GREY + DARK_GREYB
  560. compile endif
  561.  
  562. Select this to go to the next section 
  563.  
  564.  
  565. ΓòÉΓòÉΓòÉ 2.1.8. Adding your own key definitions via MYKEYS.E ΓòÉΓòÉΓòÉ
  566.  
  567. The file MYKEYS.E should be used for your own key definitions and 
  568. redefinitions.  It is included into E.E after most of the definition stuff and 
  569. directly before MYSTUFF.E.  Currently it does not matter which of those you use 
  570. to store your key DEFs but that may change. 
  571.  
  572. Key definitions start with a DEF statement and give the key name with an 
  573. additional S_ for Shift+key, A_ for Alt+key or C_ for Ctrl+key in front of it. 
  574. Alphanumeric keys are addressed by their alphanumeric value while special keys 
  575. are listed below: 
  576.  
  577. F1-F12              The function keys 
  578. DEL                 The delete key 
  579. INS                 The insert key 
  580. UP, DOWN, LEFT, RIGHT The cursor keys 
  581. PGUP, PGDN          The paging keys 
  582. HOME, END           Home and End :) 
  583. ENTER, ESC, TAB     The respective keys. 
  584. PADENTER            The pad-enter key on AT or MFII keyboards. 
  585. PAD5                (S_ only) The pad-number 5 on AT or MFII keyboards 
  586. EQUAL               (A_ only) = 
  587. LEFTBRACKET, RIGHTBRACKET (A_ and C_ only) (,) 
  588. MINUS               (A_ and C_ only) - 
  589. BACKSPACE           Backspace 
  590. BACKSLASH           (C_ only) \ 
  591. PRTSC               (C_ only) Print Screen 
  592.  
  593. Following the DEF statement you simply start writing your key function. 
  594.  
  595. def a_s=
  596.     'saveall'
  597.  
  598. This makes ALT+S execute the SAVEALL command. 
  599.  
  600. You may, of course, also include other key definition files in MYKEYS.E, e.g. 
  601. REVERSE.E and GLOBFIND.E from the EPM distribution demos: 
  602.  
  603. include 'reverse.e'
  604. include 'globfind.e'
  605.  
  606. Select this to go to the next section 
  607.  
  608.  
  609. ΓòÉΓòÉΓòÉ 2.1.9. Example MYKEYS.E ΓòÉΓòÉΓòÉ
  610.  
  611. The following example MYKEYS.E defines an undo key for ALT+U.  It uses EPM's 
  612. undoaction command to take back changes one by one.  So you don't have to use 
  613. the UNDO-dialog if you just want to take back a few changes.  The one thing 
  614. making this a little bit complicated is the fact that the undo itself is 
  615. regarded as a change and therefore is stored by undoaction.  So if you did undo 
  616. twice you would restore the state before the undo.  However the intention of 
  617. the undo key is to be able to take back more than one step.  The solution is to 
  618. store information about which steps in the undo-chain are UNDOs itself and to 
  619. skip them. 
  620.  
  621. /* This is an example MYKEYS.E file defining an undo key
  622.    <C> 1993 Joerg Schwieder, Berlin, Germany               */
  623.  
  624. def a_u=                                    -- define ALT-U
  625.     universal undostates                    -- define an universal variable to store UNDOs taken
  626.  
  627.     undoaction 6, states                    -- get the first and the last actions stored in the UNDO-tree
  628.     parse value states with sfirst slast    -- parse the 2 values into sfirst and slast
  629.     undoakt = undostates                    -- copy our undo-states
  630.     newstate = slast                        -- save the actual state to our new UNDO-state list
  631.     slast = slast - 1                       -- last entry we could UNDO to
  632.     while slast >= sfirst do                -- loop through the UNDO-tree
  633.         parse value undoakt with thisone undoakt    -- look at our last undo
  634.         if (thisone < slast) or (not thisone) or (thisone < sfirst) then
  635.             leave                                   -- if its older than the last tree entry or if
  636.                                                     -- we never undid - leave, it's OK!
  637.         endif
  638.         if thisone then newstate = newstate' 'thisone; endif    -- add our last UNDO to our new list
  639.         if thisone = slast then slast = slast - 1; endif        -- if the actual tree entry is an old UNDO
  640.                                                                 -- look at the one before
  641.     endwhile
  642.     if slast >= sfirst then                 -- if a possible tree-entry was found,
  643.         newstate = newstate' 'slast         -- add it to the start of the new UNDO-list
  644.     endif
  645.     while undoakt and (thisone >= sfirst) do    -- add the remaining old UNDO list to the new one
  646.         newstate = newstate' 'thisone           -- but strip entries no longer in the UNDO tree
  647.         parse value undoakt with thisone undoakt
  648.     endwhile
  649.     undostates = newstate                   -- set undostates to the new list
  650.     if slast >= sfirst then                 -- if an entry was fond
  651.         undoaction 7, slast                 -- UNDO
  652.     else
  653.         parse value undostates with . undostates    -- else remove the actual entry from the list
  654.     endif
  655.  
  656. /* This is the sample ALT-S definition for SAVEALL */
  657.  
  658. def a_s=
  659.     'saveall'
  660.  
  661. /* Include REVERSE and GLOBFIND keys from the EPM distribution demos if present. */
  662.  
  663. tryinclude 'reverse.e'
  664. tryinclude 'globfind.e'
  665.  
  666. Select this to go to the next section 
  667.  
  668.  
  669. ΓòÉΓòÉΓòÉ 2.1.10. Your own commands and procedures via MYSTUFF.E ΓòÉΓòÉΓòÉ
  670.  
  671. This is the place to put your own commands and procedures.  Usually you won't 
  672. put them into the file directly but include other files. Don't be concerned if 
  673. these files contain key definitions as well.  EPM includes MYSTUFF.E directly 
  674. after MYKEYS.E.  The distinction is for organizational purposes. You might want 
  675. to put everything in MYSTUFF.E, especially if you write large extensions part 
  676. of which are key definitions and if you don't want to split them up into 
  677. several files. 
  678.  
  679. include 'saveall.e'
  680.  
  681. This would put the SAVEALL command into the default configuration. 
  682.  
  683. Commands are defined using the DEFC statement followed by the command's name, 
  684. arguments are passed through arg(1) which returns a parameter string: 
  685.  
  686. defc gocol
  687.     .col = arg(1)
  688.  
  689. This defines a GOCOL command that moves the cursor to the column passed as 
  690. parameter. 
  691.  
  692. Select this to go to the next section 
  693.  
  694.  
  695. ΓòÉΓòÉΓòÉ 2.1.11. Example MYSTUFF.E ΓòÉΓòÉΓòÉ
  696.  
  697. The following sample MYSTUFF.E file uses another EPM feature - user exits. 
  698. Several hooks are spread in the editor code which give user procedures notice 
  699. of certain events, e.g.  a file is being saved.  User exits are procedures that 
  700. are called by EPM if they are defined.  We will use them to write an autostart 
  701. feature that "reminds" of its closedown state. If EPM is closed with open 
  702. files, these filenames are stored in EPM.INI; should EPM be started later 
  703. without parameters, it will reload these files. Additionally the cursor 
  704. position in each open file is stored in extended attributes upon shutdown; 
  705. reloading this file later repositions the cursor right where it was. 
  706.  
  707. This feature is quite useful when you are editing several large files.  If you 
  708. want to leave EPM but continue the next day just close down EPM and if you 
  709. restart it the next day without parameters; you are right back where you were 
  710. without having to load all your files and having to find your way through your 
  711. code back to where you left. 
  712.  
  713. There is just one drawback:  Since defmain_exit has to be called from MAIN.E it 
  714. has to be included before it and therefore has to be included into MYCNF.E.  We 
  715. include this section as a file called MAINEXIT.E into MYCNF.E. 
  716.  
  717. MYSTUFF.E: 
  718.  
  719. include 'saveall.e'
  720.  
  721.  
  722. defc gocol
  723.     .col = arg(1)
  724.  
  725.  
  726. /* For the autoload feature we maintain a list of the currently open files in a profile entry named
  727.    STARTUP_FILES. The first one is the file actually edited to make shure it comes up on restart */
  728.  
  729. defselect                                  /* This one makes shure that the file that was on top on
  730.                                               shutdown will reappear there on startup. Defselects are
  731.                                               called whenever a file is selected */
  732.    universal appname, app_hini              /* The application name and INI-handle
  733.                                                are stored in these global variables */
  734.    psave_pos(spos)                          -- Save cursor position
  735.    filelist = queryprofile( $HINI_PARM appname, 'STARTUP_FILES')    -- get actual filelist
  736.    if wordpos(.filename, filelist) then                             -- find actual file
  737.       filelist = delword(filelist, wordpos(.filename, filelist), 1) -- and remove it
  738.    endif
  739.    if (not wordpos(.filename, filelist)) and (substr(.filename, lastpos('\', .filename) + 1, 1) <> '.') then
  740.        filelist = .filename' 'filelist                              -- add the actual filename at the beginning
  741.    endif
  742.    setprofile( $HINI_PARM appname, 'STARTUP_FILES', filelist )      -- write the new list to the INI-file
  743.    prestore_pos(spos)                       -- Restore cursor position
  744.  
  745.  
  746. defproc postsave_exit (fname)                                       -- called just after file was saved
  747.    universal appname, app_hini, oldfile                             /* oldfile is the old filename if
  748.                                                                        file was renamed */
  749.    psave_pos(spos)                          -- Save cursor position
  750.    filelist = queryprofile( $HINI_PARM appname, 'STARTUP_FILES')    -- get the old file list
  751.    if wordpos(oldfile, filelist) then                               -- if old filename is in the list
  752.        filelist = delword(filelist, wordpos(oldfile, filelist), 1)  -- delete it
  753.    endif
  754.    if (not wordpos(fname, filelist)) and (substr(fname, lastpos('\', fname) + 1, 1) <> '.') then
  755.        filelist = fname' 'filelist      -- Add actual filename if its not a tempfile ('.---')
  756.                                         -- remove the 'and (substr...' section if you want to restart temp files
  757.    endif
  758.    setprofile( $HINI_PARM appname, 'STARTUP_FILES', filelist )      -- store the new list
  759.    prestore_pos(spos)                       -- Restore cursor position
  760.  
  761.  
  762. defproc rename_exit (oldfile, fname)                                -- called if file was renamed
  763.    universal appname, app_hini
  764.    psave_pos(spos)                          -- Save cursor position
  765.    filelist = queryprofile( $HINI_PARM appname, 'STARTUP_FILES')    -- same as for postsave_exit
  766.    if wordpos(oldfile, filelist) then
  767.       filelist = delword(filelist, wordpos(oldfile, filelist), 1)
  768.    endif
  769.    if (not wordpos(fname, filelist)) and (substr(fname, lastpos('\', fname) + 1, 1) <> '.') then
  770.        filelist = fname' 'filelist
  771.    endif
  772.    setprofile( $HINI_PARM appname, 'STARTUP_FILES', filelist )
  773.    prestore_pos(spos)                       -- Restore cursor position
  774.  
  775.  
  776. defproc quit_exit (fname)                                           -- called if file is closed
  777.    universal appname, app_hini
  778.    filelist = queryprofile( $HINI_PARM appname, 'STARTUP_FILES')    -- just delete entry
  779.    if wordpos(fname, filelist) then
  780.       filelist = delword(filelist, wordpos(fname, filelist), 1)
  781.    endif
  782.    setprofile( $HINI_PARM appname, 'STARTUP_FILES', filelist )
  783.  
  784.  
  785. -- Defload functions are called whenever a file was loaded
  786.  
  787. defload                         /* same as for namefile and savefile,
  788.                                    just that there are no old names to delete */
  789.    universal appname, app_hini
  790.    filelist = queryprofile( $HINI_PARM appname, 'STARTUP_FILES')
  791.    if (not wordpos(.filename, filelist)) and (substr(.filename, lastpos('\', .filename) + 1, 1) <> '.') then
  792.        filelist = .filename' 'filelist
  793.    endif
  794.    setprofile( $HINI_PARM appname, 'STARTUP_FILES', filelist )
  795.  
  796.  
  797.  
  798. /* Here comes the Cursor position feature */
  799.  
  800. defproc presave_exit            -- issued just before a file is saved
  801.    universal oldfile            -- used to store the filename for postsave_exit
  802.    delete_ea('EPM.POS')         -- delete old 'EPM.POS' entry in the EAs
  803.    psave_pos(screenpos)         -- get cursor and screen position,...
  804.    'addea EPM.POS' screenpos    -- and store it in the EA
  805.    oldfile = .filename          -- this one's needed for the restart feature
  806.  
  807.  
  808. defload             -- This one's for setting the Cursor and window position for the loaded file
  809.     if find_ea('EPM.POS', ea_seg, ea_ofs, ea_ptr1, ea_ptr2, ea_len, ea_entrylen, ea_valuelen) then
  810.         -- if there's an EPM.POS EA...
  811.         getfileid myid          -- that's the actual file
  812.         'postme restore_ORG_pos 'myid get_EAT_ASCII_value('EPM.POS')
  813.          /* restore postions. have to post it 'cause the screen may not yet be painted.
  814.             Also I had to make this the last of my defloads to make it work correctly */
  815.     endif
  816.  
  817.  
  818. defc restore_ORG_pos    -- This sets the cursor and screen-positions. It does the same as
  819.                         -- prestore_pos except for being passed the fileid as first parameter
  820.         parse value arg(1) with myid fline fcol cx cy
  821.         myid.cursorx = cx
  822.         myid.cursory = cy
  823.         myid.line = fline
  824.         myid.col = fcol
  825.  
  826. MAINEXIT.E: 
  827.  
  828. define
  829. compile if EVERSION >= '5.20'  -- 5.20 adds a HINI to the *Profile calls.
  830.    HINI_PARM = 'app_hini,'
  831. compile else
  832.    HINI_PARM = ' '
  833. compile endif
  834.  
  835. defproc defmain_exit (var cmdline)  -- called just before files are loaded upon startup
  836.    universal appname, app_hini
  837.    if cmdline = 'e ' then            -- this is the command line + 'e '. If no parameters are specified ...
  838.        cmdline ='e 'queryprofile( $HINI_PARM appname, 'STARTUP_FILES' )
  839.                                     -- ...add the ones from 'STARTUP_FILES'
  840.    endif
  841.  
  842. Select this to go to the next section 
  843.  
  844.  
  845. ΓòÉΓòÉΓòÉ 2.1.12. Editor startup code via MYMAIN.E ΓòÉΓòÉΓòÉ
  846.  
  847. MYMAIN.E is included and executed directly after the editors own startup code 
  848. so you can put code here that has to be executed once after the editor was 
  849. initialized e.g. recognizing messages for other applications (sorry, could not 
  850. think of anything else).  With the exception of the different include position 
  851. handling of MYMAIN.E is the same as for MYSTUFF.E 
  852.  
  853. Select this to go to the next section 
  854.  
  855.  
  856. ΓòÉΓòÉΓòÉ 2.1.13. File initializations and focus changes via MYSELECT.E ΓòÉΓòÉΓòÉ
  857.  
  858. MYSELECT.E is another file that does not differ much from MYSTUFF.E.  It is 
  859. included after MYMAIN.E but before MYSTUFF.E. Theoretically, it should contain 
  860. DEFSELECTs, code that is executed whenever a file is selected, but this does 
  861. not necessarily have to be the case so we put this code into MYSTUFF.E also. 
  862. See the MYSTUFF.E example. 
  863.  
  864. Typical tasks for DEFSELECTs are cursor positioning, file-type specific changes 
  865. to the keyset or the messageline or adding of file-type specific menus etc. 
  866. Using DEFSELECTs you can change the behavior of the editor depending on the 
  867. file-type. 
  868.  
  869. Select this to go to the next section 
  870.  
  871.  
  872. ΓòÉΓòÉΓòÉ 2.1.14. Summary ΓòÉΓòÉΓòÉ
  873.  
  874. We have seen how the EPM toolkit can be used to change your EPM configuration 
  875. and how commands that you write can be integrated into the system.  A little 
  876. thought can yield a good editor for your uses at a great price.  Also, support 
  877. for EPM-related topics can already be found in comp.os.os2.programmer.misc and 
  878. is provided by Larry "Mr. Macro" Margolis, who wrote most of the standard 
  879. macros provided by EPM and is currently a member of the EPM development group 
  880. within IBM. 
  881.  
  882. Select this to go to the next section 
  883.  
  884.  
  885. ΓòÉΓòÉΓòÉ 2.2. Rexx-ercising Your Applications ΓòÉΓòÉΓòÉ
  886.  
  887. Written by Gordon W. Zeglinski 
  888.  
  889. Introduction 
  890.  
  891. Rexx/2 provides an excellent mechanism by which developers can easily add 
  892. scripting, macro, and programming abilities into their products.  Yet there is 
  893. still some cloud of mystery surrounding the use of REXX in general 
  894. applications.  It is hoped that this article will dispel this cloud and 
  895. encourage other developers to incorporate REXX abilities into their apps. 
  896.  
  897. After reading this article, you should: 
  898.  
  899.  1. Understand a bit more about how .cmd files are executed. 
  900.  2. Know how to start Rexx/2 from within an application. 
  901.  3. Be able to extend Rexx/2 by adding your own external functions to it. 
  902.  
  903. Note:  The REXXSAA.H file shipped with the 2.1 Toolkit and the C++ patched 2.0 
  904. toolkit are not C++ compatible.  A C++ compatible version is included with this 
  905. issue for use with the C-Set++ compiler. 
  906.  
  907. Select this to go to the next section 
  908.  
  909.  
  910. ΓòÉΓòÉΓòÉ 2.2.1. Introduction ΓòÉΓòÉΓòÉ
  911.  
  912. Written by Gordon W. Zeglinski 
  913.  
  914. Rexx/2 provides an excellent mechanism by which developers can easily add 
  915. scripting, macro, and programming abilities into their products.  Yet there is 
  916. still some cloud of mystery surrounding the use of REXX in general 
  917. applications.  It is hoped that this article will dispel this cloud and 
  918. encourage other developers to incorporate REXX abilities into their apps. 
  919.  
  920. After reading this article, you should: 
  921.  
  922.  1. Understand a bit more about how .cmd files are executed. 
  923.  2. Know how to start Rexx/2 from within an application. 
  924.  3. Be able to extend Rexx/2 by adding your own external functions to it. 
  925.  
  926. Note:  The REXXSAA.H file shipped with the 2.1 Toolkit and the C++ patched 2.0 
  927. toolkit are not C++ compatible.  A C++ compatible version is included with this 
  928. issue for use with the C-Set++ compiler. 
  929.  
  930. Select this to go to the next section 
  931.  
  932.  
  933. ΓòÉΓòÉΓòÉ 2.2.2. Some Basics ΓòÉΓòÉΓòÉ
  934.  
  935. In REXX, there are three different categories of executable statements: 
  936. instructions, commands, and functions.  Instructions are built into REXX and 
  937. cannot be supplemented.  Commands and functions are definable by the 
  938. application evoking the REXX interpreter.  However, functions are more flexible 
  939. in nature than commands because they can be integrated into expressions and can 
  940. be added at run time via the (built-in) RxFuncAdd() function.  This article 
  941. will focus on functions. 
  942.  
  943. Before we look closer at REXX functions, we should look a bit at Command (.CMD) 
  944. files.  Command files are REXX programs that are meant to be executed under the 
  945. "CMD" REXX environment.  The "CMD" environment is a subcommand handler 
  946. registered with REXX using the name "CMD".  It is important to note that 
  947. command files are not able to be executed unless the "CMD" command handler is 
  948. used when starting REXX if these files contain commands like "dir", "copy", 
  949. etc.  This discussion will be continued in part 2. 
  950.  
  951. More About Functions 
  952.  
  953. In REXX, functions come in two flavors:  internal and external functions.  From 
  954. the REXX programmers point of view, once the external function has been 
  955. registered, there is no difference between the two. However, xternal functions 
  956. can either be based in DLL's or within the executable itself.  Take the 
  957. following REXX program, SHOWDIR.CMD . 
  958.  
  959. /******************************************/
  960. /* Simple Rexx/2 file that opens the      */
  961. /* default view for the current directory */
  962. /******************************************/
  963.  
  964. Call RxFuncAdd 'SysLoadFuncs', 'REXXUTIL', 'SysLoadFuncs'
  965. Call SysLoadFuncs
  966.  
  967. DIR=directory();
  968. If SysSetObjectData(DIR,"OPEN=DEFAULT") Then
  969.    Say DIR "has been opened."
  970. Else
  971.    Say DIR "was not opened."
  972.  
  973. SHOWDIR.CMD illustrates several important points: 
  974.  
  975. o External functions must be registered before they can be used.  In the case 
  976.   of DLL-based external functions, they can either be registered by using a 
  977.   REXX function or by the application. 
  978.  
  979. o There are two methods to call functions, either using call or ().  Unlike in 
  980.   C or C++, if the () method is used then the line must follow the same syntax 
  981.   as as line 4 in the above example, DIR=directory();.  Strictly speaking in 
  982.   REXX terminology, if the call method is used then the procedure being called 
  983.   is referred to as a subroutine.  If the () method is used, then the procedure 
  984.   being called is referred to as a function. 
  985.  
  986. Select this to go to the next section 
  987.  
  988.  
  989. ΓòÉΓòÉΓòÉ 2.2.3. About RXSTRINGs ΓòÉΓòÉΓòÉ
  990.  
  991. The RXSTRING structure is used through out REXX and warrants a detailed 
  992. examination.  The maximum length of a RXSTRING is 4 gigabytes (the maximum 
  993. number that can be held in a ULONG variable).  Unlike C, where strings are zero 
  994. terminated, the string component of an RXSTRING is not zero-terminated. 
  995. Additional important points about RXSTRING's are listed below. 
  996.  
  997. typedef struct {
  998.    ULONG strlength;     // Length of string
  999.    PCH strptr;          // Pointer to string
  1000. } RXSTRING;
  1001.  
  1002. o An RXSTRING is said to have a value if the strptr field is not NULL. 
  1003.   Consequently, it is said to be empty if the strptr field is NULL. 
  1004.  
  1005. o An RXSTRING is said to be a null string if the strptr field points to the 
  1006.   value "" and the strlength field is zero. 
  1007.  
  1008. o The length stored in the strlength field does not include the terminating 
  1009.   null character. 
  1010.  
  1011. o Conveniently, the REXX interpreter adds the terminating zero character to the 
  1012.   strptr field when it calls external functions, subcommand handlers and exit 
  1013.   handlers.  This allows one to use the C library string functions on this 
  1014.   field.  However, there is no guarantee that this will be the only zero 
  1015.   character.  The developer will have to decide whether the data could have 
  1016.   extra zero characters. 
  1017.  
  1018. o When a return value is expected from an external function, subcommand handler 
  1019.   or exit handler, the REXX interpreter sets up a default RXSTRING of length 
  1020.   256.  If the value to be returned is requires less space than this, it is 
  1021.   simply copied into the strptr buffer and the strlength filed is set to the 
  1022.   shorter length.  If the default RXSTRING is too small, a new RXSTRING can be 
  1023.   created using DosAllocMem().  The REXX interpreter will free the memory used 
  1024.   by the new RXSTRING. 
  1025.  
  1026. Select this to go to the next section 
  1027.  
  1028.  
  1029. ΓòÉΓòÉΓòÉ 2.2.4. Starting Rexx/2 From Within an Application ΓòÉΓòÉΓòÉ
  1030.  
  1031. We will start our trek into the work of REXX by looking at how we can start the 
  1032. Rexx/2 interpreter from within our apps.  The function RexxStart() allows us to 
  1033. do this. 
  1034.  
  1035. (LONG)RexxStart(ArgCount,
  1036.                 ArgArray,
  1037.                 Name,
  1038.                 Instore,
  1039.                 Environment,
  1040.                 RXCOMMAND,
  1041.                 ExitHandler,
  1042.                 &RC,
  1043.                 &ReturnString);
  1044. (Because the RexxStart() function can do so many things, this is bound to be 
  1045. some what bewildering.  Don't panic, examples will be used to explore the 
  1046. various parameter combinations in this and future articles in the series.) 
  1047.  
  1048. ArgCount       (LONG) - input 
  1049.  
  1050.                The number of elements in the ArgList array.  This value will be 
  1051.                returned by the ARG() built-in REXX function.  ArgCount should 
  1052.                include RXSTRINGs which represent omitted arguments.  Omitted 
  1053.                arguments will be empty RXSTRINGs (strptr will be NULL).  See 
  1054.                the Discussion on RXCOMMAND for further information. 
  1055.  
  1056. ArgArray       (PRXSTRING) - input 
  1057.  
  1058.                An array of RXSTRING structures that are the REXX program 
  1059.                arguments. 
  1060.  
  1061. Name           (PSZ) - input 
  1062.  
  1063.                Points to the ASCIIZ "name" of the REXX procedure and can have 
  1064.                many different interpretations depending on the value of 
  1065.                Instore.  If Instore  is NULL, then it is the name of the file 
  1066.                containing the REXX procedure.  If Instore is not NULL, then it 
  1067.                is either the name of the subroutine or function in the 
  1068.                Macrospace. 
  1069.  
  1070.                If Name is the name of a file, a default extension of .CMD is 
  1071.                used if none is supplied.  Optionally, "name" could be the fully 
  1072.                qualified filename. 
  1073.  
  1074. Instore        (PRXSTRING) - input 
  1075.  
  1076.                An array of two RXSTRINGs that store REXX procedures in memory. 
  1077.  
  1078.                If both RXSTRINGs are empty, the interpreter searches for REXX 
  1079.                procedure Name in the macrospace.  If the procedure is not found 
  1080.                in the macrospace, the RexxStart() function returns an error 
  1081.                code. 
  1082.  
  1083.                If either RXSTRING is not empty, Instore is used to execute a 
  1084.                REXX procedure directly from memory. 
  1085.  
  1086.    Instore[0]     input 
  1087.  
  1088.                   An RXSTRING containing an REXX procedure stored in the exact 
  1089.                   same manner as a disk file.  The image must be complete with 
  1090.                   carriage returns, line feeds, and end-of-file characters. 
  1091.  
  1092.    Instore[1]     input and output 
  1093.  
  1094.                   If Instore[1] is empty, the REXX interpreter will return the 
  1095.                   tokenized image in here. 
  1096.  
  1097.                   If Instore[1] is not empty, interpreter will execute the 
  1098.                   tokenized image directly.  Instore[0] can be empty in this 
  1099.                   case, provided the REXX program does not require meaningful 
  1100.                   data from the SOURCELINE built-in REXX function.  If 
  1101.                   Instore[0] is empty and SOURCELINE  function is used, 
  1102.                   SOURCELINE  will return null strings for the REXX procedure 
  1103.                   source lines. 
  1104.  
  1105.                   If Instore[1] is not empty, but does not contain a valid REXX 
  1106.                   tokenized image, unpredictable results can occur.  The REXX 
  1107.                   interpreter might be able to determine that the tokenized 
  1108.                   image is incorrect and retokenize the source. 
  1109.  
  1110.                   If the procedure is executed from disk, the Instore pointer 
  1111.                   must be NULL.  If the first argument string in Arglist 
  1112.                   contains the string "//T" and CallType is RXCOMMAND, the 
  1113.                   interpreter will tokenize the procedure source and return the 
  1114.                   tokenized image without running the program. 
  1115.  
  1116.                   The program using the RexxStart() function must release 
  1117.                   Instore[1] using DosFreeMem() when the tokenized image is no 
  1118.                   longer needed. 
  1119.  
  1120.                   The format of the tokenized image may not be the same across 
  1121.                   interpreter versions.  Therefore, the tokenized image should 
  1122.                   not be stored to disk or transferred to other systems.  It is 
  1123.                   meant to be used multiple times with in the same application 
  1124.                   in which it was created to allow the faster execution of the 
  1125.                   REXX program during the second and subsequent executions. 
  1126.  
  1127. Environment    (PSZ) - input 
  1128.  
  1129.                Address of the ASCIIZ initial ADDRESS environment name. The 
  1130.                ADDRESS environment is a subcommand handler registered using 
  1131.                RexxRegisterSubcomExe() or RexxRegisterSubcomDll().  Environment 
  1132.                is used as the initial setting for the REXX ADDRESS instruction. 
  1133.  
  1134.                If Environment is NULL, the file extension is used as the 
  1135.                initial ADDRESS environment.  The environment name cannot be 
  1136.                longer than 250 characters. 
  1137.  
  1138.                If Environment is set to "CMD", .CMD files may be executed from 
  1139.                within the application. 
  1140.  
  1141. CallType       (LONG) - input 
  1142.  
  1143.                The type of REXX procedure execution.  Allowed execution types 
  1144.                are: 
  1145.  
  1146.    RXCOMMAND      The REXX procedure is an OS/2 operating system command or 
  1147.                   application command.  REXX commands normally have a single 
  1148.                   argument string.  The REXX PARSE SOURCE instruction will 
  1149.                   return "COMMAND" as the second token. 
  1150.  
  1151.    RXSUBROUTINE   The REXX procedure is a subroutine of another program.  The 
  1152.                   subroutine can have multiple arguments and does not need to 
  1153.                   return a result.  The REXX PARSE SOURCE instruction will 
  1154.                   return "SUBROUTINE" as the second token. 
  1155.  
  1156.    RXFUNCTION     The REXX procedure is a function called from another program. 
  1157.                   The subroutine can have multiple arguments and must return a 
  1158.                   result.  The REXX PARSE SOURCE instruction will return 
  1159.                   "FUNCTION" as the second token. 
  1160.  
  1161. ExitHandler    (PRXSYSEXIT) - input 
  1162.  
  1163.                An array of RXSYSEXIT structures defining exits the REXX 
  1164.                interpreter will use. 
  1165.  
  1166. RC             (PLONG) - output 
  1167.  
  1168.                If Result is a whole number in the range of -(2**15) to 2**15-1, 
  1169.                it will be converted to a numeric form and and returned in RC as 
  1170.                well as Result. 
  1171.  
  1172. Result         (PRXSTRING) - output 
  1173.  
  1174.                The string returned from the REXX procedure with the REXX RETURN 
  1175.                or EXIT instruction.  A default RXSTRING can be provided for the 
  1176.                returned result.  If a default RXSTRING is not provided or the 
  1177.                default is too small for the returned result, the REXX 
  1178.                interpreter will allocate an RXSTRING using DosAllocMem(). The 
  1179.                caller of the RexxStart() function must release the RXSTRING 
  1180.                storage with DosFreeMem(). 
  1181.  
  1182.                The REXX interpreter does not add a terminating zero to Result. 
  1183.  
  1184. Executing REXX Procedures From a File 
  1185.  
  1186. If we were to look at code samples to illustrate all of the various things that 
  1187. RexxStart() can do, we wouldn't be able to get to creating external functions 
  1188. in this issue.  Therefore, we will look at one of the more fundamental uses of 
  1189. RexxStart() - executing file-based REXX procedures.  The code below illustrates 
  1190. this: 
  1191.  
  1192. VOID RunScript(PSZ ScriptFile) {
  1193.    LONG      return_code;  // interpreter return code
  1194.    RXSTRING  argv[1];      // program argument string
  1195.    RXSTRING  retstr;       // program return value
  1196.    SHORT     rc;           // converted return code
  1197.  
  1198.    argv.strptr=NULL;
  1199.    argv.strlength=0;
  1200.  
  1201.    retstr.strptr=new char [1024];
  1202.    retstr.strlength=1024;
  1203.  
  1204.    return_code = RexxStart(0,               // No arguments
  1205.                            argv,            // dummy entry
  1206.                            ScriptFile,      // File name
  1207.                            NULL,            // NULL InStore
  1208.                            "CMD",           // use the "CMD" command processor
  1209.                            RXCOMMAND,       // execute as a command
  1210.                            NULL,            // No exit handlers
  1211.                            &rc,             // return code from REXX routine
  1212.                            &retstr);        // return string from REXX routine
  1213.  
  1214.    delete [] retstr.strptr;
  1215. }
  1216.  
  1217. In the above example, the return value string is set to be 1K in size. (it is 
  1218. assumed that the REXX interpreter will not require more space than this.)  It 
  1219. should also be noted that because REXX commands typically do not execute 
  1220. quickly that the above function should be called from a separate thread in PM 
  1221. programs so that it will not hang the message queue. 
  1222.  
  1223. Select this to go to the next section 
  1224.  
  1225.  
  1226. ΓòÉΓòÉΓòÉ 2.2.5. External Functions ΓòÉΓòÉΓòÉ
  1227.  
  1228. Finally, we can look at creating external functions.  There are two types of 
  1229. external functions, EXE-based and DLL-based.  The EXE-based version must be 
  1230. registered with REXX by the application in which they reside and can only be 
  1231. used by REXX programs started from within that application.  Typically, one 
  1232. would use this type of external function to support macro and scripting 
  1233. features within the application. 
  1234.  
  1235. DLL-based functions are mainly used to provide additional utility libraries for 
  1236. REXX.  Once registered, all REXX programs have access to DLL-based functions. 
  1237. Both DLL- and EXE-based functions follow the same basic declaration syntax. 
  1238. The following sample function will clear the screen if called from a VIO-based 
  1239. REXX program. 
  1240.  
  1241. ULONG _System SysCls(UCHAR *name,
  1242.                      ULONG numargs,
  1243.                      RXSTRING args[],
  1244.                      PSZ *queuename,
  1245.                      RXSTRING *retstr)
  1246. {
  1247.   BYTE bCell[2];
  1248.  
  1249.   // If arguments, return non-zero to indicate error
  1250.   if (numargs)
  1251.     return 1;
  1252.  
  1253.   bCell[0] = 0x20;
  1254.   bCell[1] = 0x07;
  1255.   VioScrollDn(0,
  1256.               0,
  1257.               (USHORT)0xFFFF,
  1258.               (USHORT)0XFFFF,
  1259.               (USHORT)0xFFFF,
  1260.               bCell,
  1261.               NULLSHANDLE);
  1262.   VioSetCurPos(0, 0, NULLSHANDLE);
  1263.  
  1264.   // return 0 to the caller
  1265.   retstr.strlength=1;
  1266.   strcpy(retstr.strptr,"0");
  1267.  
  1268.   return 0;
  1269. }
  1270.  
  1271. Things to remember 
  1272.  
  1273. External REXX functions must use the C calling convention. 
  1274.  
  1275. When creating DLL-based external functions, remember to export the function and 
  1276. use multiple data segments (DATA MULTIPLE NONSHARED). 
  1277.  
  1278. The default return string is 255 bytes long. 
  1279.  
  1280. Registering Your External Function 
  1281.  
  1282. Having previously seen how to register DLL-based external functions from within 
  1283. REXX, we will now look at way in which you can register external functions from 
  1284. within an application.  The function RexxRegisterFunctionDll() is used to 
  1285. register DLL-based external functions and RexxRegisterFunctionExe() is used to 
  1286. register external EXE-based functions. 
  1287.  
  1288. (APIRET)RexxRegisterFunctionDll(PSZ pszFunction,
  1289.                                 PSZ pszModule,
  1290.                                 PSZ pszEntryPoint);
  1291. (APIRET)RexxRegisterFunctionExe(PSZ pszFunction,
  1292.                                 PFN pfnEntryPoint);
  1293.  
  1294. For RexxRegisterFunctionDll(), the following parameters are specified: 
  1295.  
  1296. pszFunction    Points to the string specifying the name of the function as it 
  1297.                will be known by REXX. 
  1298. pszModule      Points to the string specifying the DLL containing the function 
  1299.                to be registered. 
  1300. pszEntryPoint  Points to the exported function name within pszModule which 
  1301.                specifies the function to be registered. 
  1302.  
  1303. For RexxRegisterFunctionExe(), the following parameters are specified: 
  1304.  
  1305. pszFunction    Points to the string specifying the name of the function as it 
  1306.                will be known by REXX. 
  1307. pfnEntryPoint  Points to the function to be registered. 
  1308.  
  1309. Both functions return RXFUNC_OK, RXFUNC_DEFINED, or RXFUNC_NOMEM to indicate 
  1310. successful completion, function is already defined, and out of memory, 
  1311. respectively. 
  1312.  
  1313. Additionally, there are functions to query the existance of a function and to 
  1314. deregister a function. 
  1315.  
  1316. (APIRET)RexxQueryFunction(PSZ pszFunction);
  1317. (APIRET)RexxDeregisterFunction(PSZ pszFunction);
  1318.  
  1319. For both functions, pszFunction points to the function name (as registered 
  1320. using one of the above functions) and return FXFUNC_OK or RXFUNC_NOTREG to 
  1321. indicate that the function exists and that the function does not exist, 
  1322. respectively. 
  1323.  
  1324. So now we've seen how to start Rexx/2 from within our applications, and seen 
  1325. how to register external functions.  It's time to put our newly acquired 
  1326. knowledge to use. 
  1327.  
  1328. Select this to go to the next section 
  1329.  
  1330.  
  1331. ΓòÉΓòÉΓòÉ 2.2.6. Putting it all Together ΓòÉΓòÉΓòÉ
  1332.  
  1333. In this section, we will create a simple application that will register an 
  1334. external function and execute .CMD files.  The following program (included as 
  1335. REXXSAMP.CPP) illustrates the procedure of registering an external EXE-based 
  1336. function and starting the Rexx/2 interpreter. 
  1337.  
  1338. #define INCL_RXFUNC       /* external function values */
  1339. #define INCL_VIO
  1340. #include <rexxsaa.h>
  1341. #include <iostream.h>
  1342. #include <string.h>
  1343.  
  1344. ULONG _System SysCls(UCHAR *name,
  1345.                      ULONG numargs,
  1346.                      RXSTRING args[],
  1347.                      PSZ *queuename,
  1348.                      RXSTRING *retstr)
  1349. {
  1350.   BYTE bCell[2];
  1351.  
  1352.   // If arguments, return non-zero to indicate error
  1353.   if (numargs)
  1354.     return 1;
  1355.  
  1356.   bCell[0] = 0x20;
  1357.   bCell[1] = 0x07;
  1358.   VioScrollDn(0,
  1359.               0,
  1360.               (USHORT)0xFFFF,
  1361.               (USHORT)0XFFFF,
  1362.               (USHORT)0xFFFF,
  1363.               bCell,
  1364.               NULLSHANDLE);
  1365.   VioSetCurPos(0, 0, NULLSHANDLE);
  1366.  
  1367.   // return 0 to the caller
  1368.   retstr.strlength=1;
  1369.   strcpy(retstr.strptr,"0");
  1370.  
  1371.   return 0;
  1372. }
  1373.  
  1374. INT main(VOID) {
  1375.   char      Input[CCHMAXPATH];
  1376.   LONG      return_code;  /* interpreter return code    */
  1377.   RXSTRING  argv[1];      /* program argument string    */
  1378.   RXSTRING  retstr;       /* program return value       */
  1379.   SHORT     rc;           /* converted return code      */
  1380.  
  1381.   RexxRegisterFunctionExe((PSZ)"EDM_SysCls",(PFN)&SysCls);
  1382.  
  1383.   cout << "Sample EDM/2 REXX Demonstration Program" << endl << "by Gordon Zeglinski" << endl;
  1384.   cout << "Type rexxsamp <ENTER> to execute supplied smaple" << endl;
  1385.   cin >> Input;
  1386.  
  1387.   if (!strlen(Input))
  1388.      strcpy(Input,"REXXSAMP.CMD");
  1389.  
  1390.   cout << "Executing Sample Program " << Input << endl;
  1391.   cout << "-----------------" << endl;
  1392.  
  1393.   retstr.strptr=new char [1024];
  1394.   retstr.strlength=1024;
  1395.  
  1396.   return_code = RexxStart(0,               // No arguments
  1397.                           argv,            // dummy entry
  1398.                           Input,           // File name
  1399.                           NULL,            // NULL InStore
  1400.                           "CMD",           // use the "CMD" command processor
  1401.                           RXCOMMAND,       // execute as a command
  1402.                           NULL,            // No exit handlers
  1403.                           &rc,             // return code from REXX routine
  1404.                           &retstr);        // return string from REXX routine
  1405.  
  1406.   delete [] retstr.strptr;
  1407.  
  1408.   return 0;
  1409. }
  1410.  
  1411. For those of you who can compile the sample code, feel free to experiment with 
  1412. it and a debugger. 
  1413.  
  1414. Files Included With This Issue 
  1415.  
  1416. REXXSAMP.EXE        Compiled version of REXXSAMP.CPP 
  1417. REXXSAMP.CPP        Source code to sample Rexx/2 invocation program 
  1418. REXXSAMP.MAK        Makefile for C-Set++ 
  1419. REXXSAMP.DEF        Definition file for use with linker 
  1420. REXXSAMP.CMD        Sample REXX program to be started from REXXSAMP.EXE 
  1421.  
  1422. Select this to go to the next section 
  1423.  
  1424.  
  1425. ΓòÉΓòÉΓòÉ 2.2.7. Summary ΓòÉΓòÉΓòÉ
  1426.  
  1427. This concludes our look at Rexx/2 for this issue.  You should now be able to 
  1428. start Rexx/2 from with in an application, and create external functions that 
  1429. are either DLL- or EXE-based.  Future issues will explore the other features of 
  1430. RexxStart(), macroSpaces and sub-command handlers. 
  1431.  
  1432. As usual, question and comments are welcome. 
  1433.  
  1434. Select this to go to the next section 
  1435.  
  1436.  
  1437. ΓòÉΓòÉΓòÉ 2.3. Threads in PM Applications ΓòÉΓòÉΓòÉ
  1438.  
  1439. Written by Larry Salomon, Jr. 
  1440.  
  1441. Introduction 
  1442.  
  1443. Because of what is often perceived as a design flaw in PM (but I am not passing 
  1444. judgement), tasks that require more time than is suggested by IBM's 
  1445. "well-behaved" application guideline (1/10 second) should be performed in a 
  1446. thread separate from that which contains the main dispatch loop (denoted by the 
  1447. calls to WinGetMsg() and WinDispatchMsg()).  However, the issue of 
  1448. communication between the user-interface and additional threads created by the 
  1449. user-interface arises for which there is no recommended design to follow.  This 
  1450. article will attempt to design an architecture that is easy to implement yet 
  1451. expandible and requires no global variables (always a good thing). 
  1452.  
  1453. Historical Caveat 
  1454.  
  1455. In the years that I have written applications for PM, I have tried every 
  1456. conceivable technique to accomplish mulithreading in a smooth fashion.  For 
  1457. those actions that require the user to initiate the task that requires the 
  1458. additional thread, I have found the solution that will be detailed here to be 
  1459. the best for my purposes.  It should be stressed here that your mileage may 
  1460. vary and that for your design "methodologies" this may not work as well for 
  1461. you.  It is encouraged then to use the information herein as a basis and not as 
  1462. the final result. 
  1463.  
  1464. Select this to go to the next section 
  1465.  
  1466.  
  1467. ΓòÉΓòÉΓòÉ 2.3.1. Introduction ΓòÉΓòÉΓòÉ
  1468.  
  1469. Written by Larry Salomon, Jr. 
  1470.  
  1471. Because of what is often perceived as a design flaw in PM (but I am not passing 
  1472. judgement), tasks that require more time than is suggested by IBM's 
  1473. "well-behaved" application guideline (1/10 second) should be performed in a 
  1474. thread separate from that which contains the main dispatch loop (denoted by the 
  1475. calls to WinGetMsg() and WinDispatchMsg()).  However, the issue of 
  1476. communication between the user-interface and additional threads created by the 
  1477. user-interface arises for which there is no recommended design to follow.  This 
  1478. article will attempt to design an architecture that is easy to implement yet 
  1479. expandible and requires no global variables (always a good thing). 
  1480.  
  1481. Historical Caveat 
  1482.  
  1483. In the years that I have written applications for PM, I have tried every 
  1484. conceivable technique to accomplish mulithreading in a smooth fashion.  For 
  1485. those actions that require the user to initiate the task that requires the 
  1486. additional thread, I have found the solution that will be detailed here to be 
  1487. the best for my purposes.  It should be stressed here that your mileage may 
  1488. vary and that for your design "methodologies" this may not work as well for 
  1489. you.  It is encouraged then to use the information herein as a basis and not as 
  1490. the final result. 
  1491.  
  1492. Select this to go to the next section 
  1493.  
  1494.  
  1495. ΓòÉΓòÉΓòÉ 2.3.2. From User-events to Multi-threading ΓòÉΓòÉΓòÉ
  1496.  
  1497. What happens when the user selects the "Open..." menu item from your 
  1498. application's menu?  Ignoring specifics, we can construct a timeline like the 
  1499. one below: 
  1500.  
  1501.  1. The user selects "Open..." from the menu 
  1502.  2. The application is notified of this selection 
  1503.  3. The application prompts the user for a filename 
  1504.  4. The application reads the selected file 
  1505.  5. The user is then allowed to perform operations on the file's data 
  1506.  
  1507. This is an example of a user-request that, once the relevant information is 
  1508. gathered, can be performed without any further intervention.  Because of its 
  1509. simplicity, this lends itself nicely to multithreading:  the thread is started, 
  1510. it performs its task to completion (successful or otherwise), and it notifies 
  1511. the user-interface that it is finished.  Before multithreading can be 
  1512. implemented, however, some thought to design must be given. 
  1513.  
  1514. Designing the Architecture 
  1515.  
  1516. Because the only communication required between the owner thread and the owned 
  1517. thread is upon initialization and completion, we immediately eliminate the 
  1518. system methods of IPC, such as pipes, queues, and shared memory because they 
  1519. are too cumbersome.  This does not mean that they will not work, but when you 
  1520. consider that any PM application you write that does more than draw a box or 
  1521. two will require many different types of threads, you will want a design that 
  1522. is the easiest to implement so that you can concentrate on the rest of the 
  1523. coding.  For this reason, I chose the communication path to be a structure 
  1524. whose address is passed via the single thread parameter for initialization and 
  1525. posted messages to the owner window for completion of the thread. This treats 
  1526. the thread as though it were a "black-box", i.e. you put something in one end 
  1527. and out from the other end comes a result. 
  1528.  
  1529. An Objective Approach 
  1530.  
  1531. "What is Life?  Who is God?  What is the grass green and the sky blue? What are 
  1532. the common characteristics of the threads we've been discussing?"  Although you 
  1533. could ponder the first three questions for a long time, the last one is a 
  1534. no-brainer given the power of hindsight. :) These commonalities I encapsulated 
  1535. in the THREADINFO structure, shown below: 
  1536.  
  1537. typedef struct _THREADINFO {
  1538.    //----------------------------------------------------------------------
  1539.    // Initialized by the main thread
  1540.    //----------------------------------------------------------------------
  1541.    ULONG ulSzStruct;
  1542.    HWND hwndOwner;
  1543.    BOOL bKillThread;
  1544.    //----------------------------------------------------------------------
  1545.    // Initialized by the secondary thread
  1546.    //----------------------------------------------------------------------
  1547.    HAB habThread;
  1548.    BOOL bThreadDead;
  1549.    BOOL bResult;
  1550. } THREADINFO, *PTHREADINFO;
  1551.  
  1552. ulSzStruct          This specifies the size of the structure. 
  1553. hwndOwner           This specifies the handle of the owning window. 
  1554. bKillThread         This is set to TRUE by the owner when the request is to be 
  1555.                     aborted. 
  1556. habThread           This specifies the anchor block handle of the thread. 
  1557.                     Before I start receiving hate-mail from the purists out 
  1558.                     there claiming that this isn't necessary, I answer that it 
  1559.                     is necessary and that you should keep reading to see why. 
  1560. bThreadDead         This is set to TRUE by the thread when it is dead.  Again, 
  1561.                     keep reading to see how this is possible. 
  1562. bResult             This is a blanket indicator of success or failure complete 
  1563.                     the task. 
  1564.  
  1565. From here we can add our task-specific variables in the following manner:  for 
  1566. each task type, define a structure to contain the fields specifies to that 
  1567. task, but define the first field to always be of type THREADINFO.  What does 
  1568. this buy us?  Since the THREADINFO structure contains common (that is the key 
  1569. word) fields, we can write code that initializes them regardless of the task to 
  1570. be performed by casting the task-specific structure to type THREADINFO.  Again, 
  1571. I realize that the purists are going to tell me that casting is a "bad thing", 
  1572. but I counter with the statement that casting - like goto statements - can be 
  1573. very helpful as long as it is not abused and is used in the proper way.  If you 
  1574. are the sensitive type regarding these issues, stop reading now 'cause we are 
  1575. going to "break" a few more rules along the way. 
  1576.  
  1577. In addition to defining a task-specific structure, we need to define a constant 
  1578. that uniquely describes this task.  The purpose of this will be seen later.  We 
  1579. can illustrate these things with an example. 
  1580.  
  1581. #define ASYNC_OPEN               0x00000001L
  1582.  
  1583. typedef struct _OPENTHREADINFO {
  1584.    THREADINFO tiInfo;
  1585.    CHAR achFilename[CCHMAXPATH];
  1586.    PFILEDATA pfdData;      // Make-believe data type for illustrative
  1587.                            // purposes
  1588. } OPENTHREADINFO, *POPENTHREADINFO;
  1589.  
  1590. Select this to go to the next section 
  1591.  
  1592.  
  1593. ΓòÉΓòÉΓòÉ 2.3.3. Add a Tire or Two ΓòÉΓòÉΓòÉ
  1594.  
  1595. Although a tire does not provide the impulse energy to make a car go forward, 
  1596. without it the car is not going anywhere.  Let's add a tire or two to our 
  1597. design.  Backing up a bit, we stated that a window has a menu with a menu item 
  1598. that results in a thread being created to service the request associated with 
  1599. that menu item.  Thus, the window is said to own the thread.  Since conceivably 
  1600. a window can have the need to perform the same service in different places, it 
  1601. would be nice to have a single point of entry and exit for all requests.  Enter 
  1602. "thing one" and "thing two" (for those of you who remember "The Cat in the 
  1603. Hat"). 
  1604.  
  1605. #define MYM_BASE                 (WM_USER)
  1606. #define MYM_STARTTHREAD          (MYM_BASE)
  1607. #define MYM_ENDTHREAD            (MYM_BASE+1)
  1608.  
  1609. For what are these user-messages used?  MYM_STARTTHREAD is sent by the window 
  1610. to itself to start a thread which will perform some task. MYM_ENDTHREAD is sent 
  1611. by the thread to the owning window (whose handle is in the THREADINFO 
  1612. structure, you'll remember) to indicate that processing has completed.  Both 
  1613. messages expect the thread type constant to be specified in LONGFROMMP(mpParm1) 
  1614. and a pointer to the thread-specific structure in PVOIDFROMMP(mpParm2).  The 
  1615. skeleton code for these two messages is shown below: 
  1616.  
  1617. typedef VOID (* _Optlink PFNREQ)(PVOID);
  1618.  
  1619. MRESULT EXPENTRY windowProc(HWND hwndWnd,
  1620.                             ULONG ulMsg,
  1621.                             MPARAM mpParm1,
  1622.                             MPARAM mpParm2)
  1623. {
  1624.    switch (ulMsg) {
  1625.      :
  1626.    case MYM_STARTTHREAD:
  1627.       {
  1628.          ULONG ulBit;
  1629.          PTHREADINFO ptiInput;
  1630.          PFNREQ pfnThread;
  1631.          PVOID pvParm;
  1632.  
  1633.          ulBit=LONGFROMMR(mpParm1);
  1634.          ptiInput=(PTHREADINFO)PVOIDFROMMP(mpParm2);
  1635.  
  1636.          ptiInput->hwndOwner=hwndWnd;
  1637.          ptiInput->bKillThread=FALSE;
  1638.  
  1639.          switch (ulBit) {
  1640.          case ASYNC_OPEN:
  1641.             {
  1642.                POPENTHREADINFO potiInfo;
  1643.  
  1644.                ptiInput->ulSzStruct=sizeof(OPENTHREADINFO);
  1645.  
  1646.                potiInfo=(POPENTHREADINFO)malloc(sizeof(OPENTHREADINFO));
  1647.                if (potiInfo==NULL) {
  1648.                   WinMessageBox(HWND_DESKTOP,
  1649.                                 hwndWnd,
  1650.                                 "There is not enough memory.",
  1651.                                 "Error",
  1652.                                 0,
  1653.                                 MB_OK|MB_ICONEXCLAMATION|MB_MOVEABLE);
  1654.                   return MRFROMSHORT(FALSE);
  1655.                } /* endif */
  1656.  
  1657.                memcpy(potiInfo,ptiInput,sizeof(OPENTHREADINFO));
  1658.                pfnThread=(PFNREQ)openThread;
  1659.                pvParm=(PVOID)potiInfo;
  1660.             }
  1661.             break;
  1662.          default:
  1663.             WinMessageBox(HWND_DESKTOP,
  1664.                           hwndWnd,
  1665.                           "There is an internal error.",
  1666.                           "Error",
  1667.                           0,
  1668.                           MB_OK|MB_ICONEXCLAMATION|MB_MOVEABLE);
  1669.             return MRFROMSHORT(FALSE);
  1670.          } /* endswitch */
  1671.  
  1672.          if (_beginthread(pfnThread,NULL,0x4000,pvParm)==-1) {
  1673.             free(pvParm);
  1674.             WinMessageBox(HWND_DESKTOP,
  1675.                           hwndWnd,
  1676.                           "The thread could not be created.",
  1677.                           "Error",
  1678.                           0,
  1679.                           MB_OK|MB_ICONEXCLAMATION|MB_MOVEABLE);
  1680.             return MRFROMSHORT(FALSE);
  1681.          } /* endif */
  1682.       }
  1683.       break;
  1684.    case MYM_ENDTHREAD:
  1685.       {
  1686.          ULONG ulBit;
  1687.          PTHREADINFO ptiInput;
  1688.  
  1689.          ulBit=LONGFROMMR(mpParm1);
  1690.          ptiInput=(PTHREADINFO)PVOIDFROMMP(mpParm2);
  1691.  
  1692.          //----------------------------------------------------------------
  1693.          // Wait for the thread to finish dying.  There is a bug in
  1694.          // DosSleep() such that if 0 is the argument, nothing happens.
  1695.          // Call it with 1 instead to achieve the same result.
  1696.          //----------------------------------------------------------------
  1697.          while (!ptiInput->bThreadDead) {
  1698.             DosSleep(1);
  1699.          } /* endwhile */
  1700.  
  1701.          switch (ulBit) {
  1702.          case ASYNC_OPEN:
  1703.             {
  1704.                POPENTHREADINFO potiInfo;
  1705.  
  1706.                potiInfo=(POPENTHREADINFO)ptiInput;
  1707.                free(potiInfo);
  1708.             }
  1709.             break;
  1710.          default:
  1711.             return MRFROMSHORT(FALSE);
  1712.          } /* endswitch */
  1713.       }
  1714.       break;
  1715.      :
  1716.    default:
  1717.       return WinDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
  1718.    } /* endswitch */
  1719. }
  1720.  
  1721. Since the MYM_STARTTHREAD message can be called from different place and each 
  1722. place can have different values, the thread-specific values are initialized 
  1723. before this message is sent. 
  1724.  
  1725. An important note is needed here:  some time ago I received a note from a 
  1726. columnist in a printed publication criticizing me for specifying so large a 
  1727. stack on the call to _beginthread().  What it boiled down to was a 
  1728. misunderstanding of the inner workings of OS/2 with regards to stacks and 
  1729. actual memory usage. 
  1730.  
  1731. The stack size specified in the .DEF file or the call to _beginthread() is 
  1732. allocated stack space and not committed stack space.  The distinction here is 
  1733. important. Allocated memory is memory that has been assigned to a process but 
  1734. does not consume any physical memory until it is committed.  How does this 
  1735. committment take place?  Delving into a lot of system-specific stuff here that 
  1736. you can skip if you are not interested, the system commits a small (1?) number 
  1737. of pages for the stack when each thread starts, and sets the page following the 
  1738. stack to be a guard page.  When the stack grows beyond the committed length, a 
  1739. guard page exception occurs, which is intercepted by the system's default 
  1740. exception handler.  Assuming that you have more space available, the system 
  1741. commits enough pages to satisfy the memory requirements and sets the next page 
  1742. to be the new guard page. 
  1743.  
  1744. Thus, the amount of committed memory of the stack grows (and possibly shrinks; 
  1745. here my knowledge is fuzzy at best) dynamically.  The number specified in the 
  1746. .DEF file, etc. is the maximum size that the stack should grow to.  If, 
  1747. however, you specify 32k and only use 4k, only 4k is committed. 
  1748.  
  1749. End of sermon. 
  1750.  
  1751. Select this to go to the next section 
  1752.  
  1753.  
  1754. ΓòÉΓòÉΓòÉ 2.3.4. Next Add the Engine ΓòÉΓòÉΓòÉ
  1755.  
  1756. The thread is defined like you would expect it to, but it performs a few 
  1757. important tasks. 
  1758.  
  1759. VOID openThread(POPENTHREADINFO potiInfo)
  1760. {
  1761.    HAB habAnchor;
  1762.    HMQ hmqQueue;
  1763.  
  1764.    habAnchor=WinInitialize(0);
  1765.    hmqQueue=WinCreateMsgQueue(habAnchor,0);
  1766.    WinCancelShutdown(hmqQueue,TRUE);
  1767.  
  1768.    potiInfo->tiInfo.habThread=habAnchor;
  1769.    potiInfo->tiInfo.bThreadDead=FALSE;
  1770.    potiInfo->tiInfo.bResult=FALSE;
  1771.  
  1772.    //----------------------------------------------------------------------
  1773.    // Do nothing.  This is strictly for the purposes of illustration.
  1774.    //----------------------------------------------------------------------
  1775.    WinMessageBox(HWND_DESKTOP,
  1776.                  potiInfo->tiInfo.hwndOwner,
  1777.                  "In the thread",
  1778.                  "Information",
  1779.                  0,
  1780.                  MB_OK|MB_MOVEABLE);
  1781.  
  1782.    WinPostMsg(potiInfo->tiInfo.hwndOwner,
  1783.               MYM_ENDTHREAD,
  1784.               MPFROMLONG(ASYNC_OPEN),
  1785.               MPFROMP(potiInfo));
  1786.  
  1787.    WinDestroyMsgQueue(hmqQueue);
  1788.    WinTerminate(habAnchor);
  1789.  
  1790.    DosEnterCritSec();
  1791.    potiInfo->tiInfo.bThreadDead=TRUE;
  1792. }
  1793.  
  1794. Initialization 
  1795.  
  1796. The thread initializes itself by creating a message queue and initializing the 
  1797. remainder of the common fields in the THREADINFO structure. Why do we need a 
  1798. message queue?  I claim it is by nature of the fact that this thread exists 
  1799. because of a user action, you will likely call a function that requires it 
  1800. (e.g. WinMessageBox() in the above code). 
  1801.  
  1802. Termination 
  1803.  
  1804. The thread terminates by posting a message to the owner window that it is about 
  1805. to complete, destroys the message queues, and - what's that? - enters a 
  1806. critical section to set the bThreadDead flag.  Why do we need that? 
  1807.  
  1808. We don't.  The reason for this is purely historical, as is the posting (versus 
  1809. sending) of the MYM_ENDTHREAD message.  However, this illustrates a point that 
  1810. has confused people in the past:  if a thread enters a critical section and 
  1811. then dies, the system automatically marks the critical section as having been 
  1812. exited. Thus, all suspended threads will resume execution. 
  1813.  
  1814. Although critical sections are a no-no when writing PM applications (because 
  1815. the user-interface thread is also halted), this critical section takes little 
  1816. time to execute, so we can let it be. 
  1817.  
  1818. Select this to go to the next section 
  1819.  
  1820.  
  1821. ΓòÉΓòÉΓòÉ 2.3.5. Finally Add the Rollbars and Racing Stripes ΓòÉΓòÉΓòÉ
  1822.  
  1823. User feedback becomes a big issue when you deal with multiple threads.  How do 
  1824. you indicate that something important is going on in the background?  Do you 
  1825. disable the (appropriate) menu items?  How are errors handled? etc.  The 
  1826. easiest answer is "it depends on the application". I realize that this does not 
  1827. help much, so we will now discuss techniques that can be applied in your 
  1828. applications. 
  1829.  
  1830. Mouse Pointers 
  1831.  
  1832. The most common method of indicating that something else is going on is to 
  1833. change the mouse pointer.  However, we all know what happens when you do this 
  1834. regardless of the mouse position.  The answer, then, lies in two important 
  1835. messages that the system sends regarding the mouse - WM_CONTROLPOINTER and 
  1836. WM_MOUSEMOVE.  Simply stated, when you receive those mouse messages, you set 
  1837. the mouse to an appropriate pointer (typically 
  1838. WinQuerySysPointer(HWND_DESKTOP,SPTR_WAIT,FALSE)) if another thread is in 
  1839. progress or you return WinDef*Proc() otherwise.  How do you tell if there is 
  1840. another thread?  There is no documented system API that I know of that will 
  1841. tell you this, so you will have to keep a counter in your instance data and 
  1842. increment/decrement as necessary in the MYM_STARTTHREAD/MYM_ENDTHREAD messages. 
  1843.  
  1844. Menu Items 
  1845.  
  1846. Whenever a menu is activated, the system sends the client window a WM_INITMENU 
  1847. message.  By intercepting and "switch"-ing on the value of mpParm1 (which is 
  1848. the identifier of the menu), you can enable or disable any items of interest. 
  1849.  
  1850. Error Messages 
  1851.  
  1852. Error messages, in my opinion, are best handled in the thread which detected 
  1853. the error.  You could, of course, add a numeric field in the THREADINFO 
  1854. structure in which you place an application-defined error code and then check 
  1855. that in the MYM_ENDTHREAD processing, but why clutter it up unnecessarily?  If 
  1856. you'll accept that, then the next question is what do you do when you encounter 
  1857. an error in the thread?  The strategy I follow is like this: 
  1858.  
  1859. o In the initialization of the thread, set all variables which specify 
  1860.   resources to be allocated by the thread (bitmap handles, file handles, memory 
  1861.   pointers, etc.) to the equivalent of "unallocated".  Also, set the bResult 
  1862.   field of the THREADINFO structure to FALSE. 
  1863. o Prefix the termination section of the thread with a label (i.e. EXIT_PROC). 
  1864.   Just before the label, set the bResult field of the THREADINFO structure to 
  1865.   TRUE because if you have made it there, everything must have completed 
  1866.   successfully. 
  1867. o If an error is encountered, display an error message (via WinMessageBox()) 
  1868.   and execute goto EXIT_PROC. 
  1869.  
  1870. Notes of interest: 
  1871.  
  1872.  1. You cannot name the label EXIT_THREAD, because that is a constant already 
  1873.     defined by the toolkit for the DosExit() API. Although this seems obvious, 
  1874.     unless you're already thinking about it, you will spend hours on a wild 
  1875.     goose chase through your own code looking for the "duplicate symbol 
  1876.     defined" error (like I did once). 
  1877.  2. Don't forget to check the resource handles to see if they are allocated 
  1878.     before you try to unallocate them.  This is the purpose of the first step 
  1879.     in the list above. 
  1880.  
  1881. A complete working sample application demonstrating this asynchronous design 
  1882. has been provided in the async.zip file. 
  1883.  
  1884. Select this to go to the next section 
  1885.  
  1886.  
  1887. ΓòÉΓòÉΓòÉ 2.3.6. Synchronicity ΓòÉΓòÉΓòÉ
  1888.  
  1889. As an advanced topic, another approach to this mess is to process the request 
  1890. in a synchronous fashion.  What?  Yes, you read it correctly.  What you need is 
  1891. another way of looking at synchronicity. 
  1892.  
  1893. Whoooooa Whoooooa Whooooa! 
  1894.  
  1895. (I don't know how many Police fans there are out there.) 
  1896.  
  1897. Synchronicity in the above paragraph is, as Einstein would have stated it, 
  1898. based on frame of reference.  What if we were somehow able to make the 
  1899. processing look like a simple function call to the user-interface thread, but 
  1900. in actuality process it asynchronously? 
  1901.  
  1902. Before we can do this, we have to determine what is different between a simple 
  1903. function call and what we are trying to accomplish here.  The answer is that we 
  1904. must remain responsive to the user-interface messages. This requires a little 
  1905. "hocus-pocus". 
  1906.  
  1907. Going back to "PM Programming Course 101", you'll remember that the system 
  1908. changes focus by sending a bunch of messages to the windows losing and 
  1909. receiving the focus.  If either window does not respond to these messages 
  1910. within a system-defined amount of time, you get the "The application is not 
  1911. responding to system messages..." message.  The ultimate question then becomes 
  1912. "how do you process messages?" and the answer is trivial, my dear reader. 
  1913. Simply go into a WinPeekMsg()/ WinDispatchMsg() loop. 
  1914.  
  1915. Of course, things are a little more than that, so the code is presented below. 
  1916. A complete working sample application has been provided in the sync.zip file. 
  1917.  
  1918. #define DT_NOERROR               0
  1919. #define DT_QUITRECEIVED          1
  1920. #define DT_ERROR                 2
  1921.  
  1922. typedef VOID (* _Optlink PFNREQ)(PVOID);
  1923.  
  1924. USHORT dispatchThread(HAB habAnchor,PFNREQ pfnThread,PTHREADINFO ptiInfo)
  1925. //-------------------------------------------------------------------------
  1926. // This is the thread dispatch procedure.  It calls _beginthread() and
  1927. // goes into a WinPeekMsg()/WinDispatchMsg() loop until the thread is
  1928. // finished or WM_QUIT is received.  Note the semantics of the latter event:
  1929. // if WM_QUIT is received, then it is assumed that the application will
  1930. // kill itself on return and thus any system resources will automatically
  1931. // be unallocated by the system when the application ends.  So we do not
  1932. // set bKillThread=TRUE and wait but instead call DosKillThread() and
  1933. // return.
  1934. //-------------------------------------------------------------------------
  1935. {
  1936.    TID tidThread;
  1937.    BOOL bLoop;
  1938.    QMSG qmMsg;
  1939.  
  1940.    ptiInfo->bKillThread=FALSE;
  1941.    ptiInfo->bThreadDead=FALSE;
  1942.  
  1943.    tidThread=_beginthread(pfnThread,NULL,0x4000,ptiInfo);
  1944.    if (tidThread==-1) {
  1945.       return DT_ERROR;
  1946.    } /* endif */
  1947.  
  1948.    //----------------------------------------------------------------------
  1949.    // WinGetMsg() cannot be used because it blocks if there is no message
  1950.    // waiting.  When the thread dies, therefore, the function will never
  1951.    // return if the user takes his/her hands off of the keyboard, mouse,
  1952.    // and no timers are started because we will never get a message!
  1953.    //----------------------------------------------------------------------
  1954.    WinPeekMsg(habAnchor,&qmMsg,NULLHANDLE,0,0,PM_REMOVE);
  1955.    bLoop=((qmMsg.msg!=WM_QUIT) && (!ptiInfo->bThreadDead));
  1956.  
  1957.    while (bLoop) {
  1958.       WinDispatchMsg(habAnchor,&qmMsg);
  1959.       WinPeekMsg(habAnchor,&qmMsg,NULLHANDLE,0,0,PM_REMOVE);
  1960.       bLoop=((qmMsg.msg!=WM_QUIT) && (!ptiInfo->bThreadDead));
  1961.    } /* endwhile */
  1962.  
  1963.    if (qmMsg.msg==WM_QUIT) {
  1964.       DosKillThread(tidThread);
  1965.       return DT_QUITRECEIVED;
  1966.    } /* endif */
  1967.  
  1968.    return DT_NOERROR;
  1969. }
  1970.  
  1971. Select this to go to the next section 
  1972.  
  1973.  
  1974. ΓòÉΓòÉΓòÉ 2.3.7. Summary ΓòÉΓòÉΓòÉ
  1975.  
  1976. In this article, much information was presented regarding multithreading and PM 
  1977. application development.  Even though our scope was limited to "one-shot" 
  1978. threads, we can see how a complicated matter can be simplified with a little 
  1979. thought.  This is not to belittle the task of such a complex objective, but 
  1980. instead is to illustrate the ability to implement an architecture that is 
  1981. usable in the "real-world". 
  1982.  
  1983. A summary follows: 
  1984.  
  1985. o All "one-shot" threads share a common set of data fields. 
  1986. o Exploitation of the "black-box" concept simplifies things considerably. 
  1987. o Since threads of the same type can be created from different places, a 
  1988.   single-entry and exit point is desirable. 
  1989. o User-feedback issues cannot be ignored. 
  1990. o "One-shot" threads can be implemented with the owner having a synchronous 
  1991.   perspective. 
  1992.  
  1993. All questions are welcome via email. 
  1994.  
  1995. Select this to go to the next section 
  1996.  
  1997.  
  1998. ΓòÉΓòÉΓòÉ 2.4. Writing a C++ Thread Class ΓòÉΓòÉΓòÉ
  1999.  
  2000. Written by Gordon W. Zeglinski 
  2001.  
  2002. Introduction 
  2003.  
  2004. C++ provides an ideal mechanism for encapsulating thread specific variables and 
  2005. other thread creation/maintenance related details.  This article will introduce 
  2006. a basic hierarchy which accomplishes these goals. 
  2007.  
  2008. After reading this article, you should: 
  2009.  
  2010.  1. Understand how to use the function _beginthread() with C++ code 
  2011.  2. Understand how to use the thread objects presented here 
  2012.  3. Be able to extend these basic objects to provide support for thread 
  2013.     specific variables. 
  2014.  
  2015. Select this to go to the next section 
  2016.  
  2017.  
  2018. ΓòÉΓòÉΓòÉ 2.4.1. Introduction ΓòÉΓòÉΓòÉ
  2019.  
  2020. Written by Gordon W. Zeglinski 
  2021.  
  2022. C++ provides an ideal mechanism for encapsulating thread specific variables and 
  2023. other thread creation/maintenance related details.  This article will introduce 
  2024. a basic hierarchy which accomplishes these goals. 
  2025.  
  2026. After reading this article, you should: 
  2027.  
  2028.  1. Understand how to use the function _beginthread() with C++ code 
  2029.  2. Understand how to use the thread objects presented here 
  2030.  3. Be able to extend these basic objects to provide support for thread 
  2031.     specific variables. 
  2032.  
  2033. Select this to go to the next section 
  2034.  
  2035.  
  2036. ΓòÉΓòÉΓòÉ 2.4.2. Using _beginthread ΓòÉΓòÉΓòÉ
  2037.  
  2038. The _beginthread() function is found in one of the following two forms, 
  2039. depending upon which compiler you are using. 
  2040.  
  2041. For Borland C++ for OS/2: 
  2042.  
  2043. int _beginthread(VOID (*start_address)(PVOID),
  2044.                  unsigned stack_size,
  2045.                  PVOID arglist)
  2046.  
  2047. And for C-Set++, Watcom, and GCC/EMX: 
  2048.  
  2049. int _beginthread(VOID (*start_address)(PVOID),
  2050.                  (PVOID)stack,
  2051.                  unsigned stack_size,
  2052.                  PVOID arglist);
  2053.  
  2054. The extra parameter stack is specified strictly for backwards compatibility and 
  2055. is not used by the compilers which include it.  In general, "start_address" is 
  2056. the address to a function like : 
  2057.  
  2058. VOID Foo(PVOID Ptr) {
  2059.      :
  2060.    /* Some body here */
  2061.      :
  2062. }
  2063.  
  2064. What about member functions ? 
  2065.  
  2066. Let's look at the following class foobar: 
  2067.  
  2068. class foobar {
  2069.    int i;
  2070.  
  2071. public:
  2072.    foobar();
  2073.    VOID ThreadStart(PVOID x);
  2074. };
  2075.  
  2076. Now suppose that on that one wanted to start a thread using the function 
  2077. ThreadStart() as the starting point.  This cannot be done using _beginthread() 
  2078. because of the sometimes forgotten fact that in C++, there is a hidden pointer 
  2079. that is always passed when non-static member functions are called.  The actual 
  2080. parameters passed to ThreadStart() are: 
  2081.  
  2082. foo *this      the hidden pointer 
  2083. PVOID x        the declared parameter 
  2084.  
  2085. In theory, one could use a static member function as the starting function of a 
  2086. thread, however, for some reason C-Set++ doesn't seem to accept this. 
  2087.  
  2088. Select this to go to the next section 
  2089.  
  2090.  
  2091. ΓòÉΓòÉΓòÉ 2.4.3. C++ Thread Objects ΓòÉΓòÉΓòÉ
  2092.  
  2093. So we've seen the problem when trying to use _beginthread() to start threads on 
  2094. C++ member functions.  How do we work around this?  The solution is quite 
  2095. simple, we develop a snazzy C++ object hierarchy encapsulate the thread 
  2096. creation process. 
  2097.  
  2098.                                ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
  2099.                                Γöé class Thread Γöé
  2100.                                ΓööΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÿ
  2101.               ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ    Γöé    ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
  2102.               Γöé                       Γöé                Γöé
  2103.  ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ   Γöé   ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
  2104.  Γöé template< class C,class Arg >  Γöé   Γöé   Γöé template< class C >    Γöé
  2105.  Γöé class ClassThreadwArg          Γöé   Γöé   Γöé class ClassThreadwoArg Γöé
  2106.  ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ   Γöé   ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
  2107.                                       Γöé
  2108.                       ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
  2109.                       Γöé template< class C>           Γöé
  2110.                       Γöé class StaticClassThreadwoArg Γöé
  2111.                       ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
  2112.  
  2113. Figure 1. Snazzy Thread Hierarchy 
  2114.  
  2115. Let's now look at how this hierarchy fits together by examining the classes in 
  2116. it. 
  2117.  
  2118. Thread 
  2119.  
  2120. class Thread {
  2121. protected:
  2122.    APIRET Error;
  2123.    TID tid;
  2124.    ULONG StackSize;
  2125.    USHORT Started    :1;
  2126.    USHORT Suspended  :1;
  2127.    USHORT            :(sizeof(USHORT)-2);
  2128.  
  2129.    VOID SetThreadID(TID t){tid=t;}
  2130.  
  2131. public:
  2132.    Thread(ULONG StackS=4096);
  2133.  
  2134.    TID GetThreadID() {return tid;}
  2135.    APIRET GetError() {return Error;}
  2136.  
  2137.    VOID ChangeStackSize(ULONG S) {
  2138.      if (!Started) StackSize=S;
  2139.    }
  2140.  
  2141.    virtual VOID Run(VOID)=0;
  2142.    VOID Start();
  2143.    VOID SuspendThread();
  2144.    VOID KillThread();
  2145.    VOID ResumeThread();
  2146.  
  2147. friend VOID ThreadStarter(PVOID);
  2148. };
  2149.  
  2150. The Thread class is an abstract class that forms the basis of this hierarchy. 
  2151. It contains the basic functions for manipulating threads. The advantage of 
  2152. using an abstract class is twofold:  first, it groups all of the thread 
  2153. manipulation functions into one class; and secondly, it provides an easy method 
  2154. for the end user to extend the usefulness of the hierarchy. 
  2155.  
  2156. But how does Thread work? 
  2157.  
  2158. The function friend VOID ThreadStarter(PVOID) should look familiar from our 
  2159. discussion of the _beginthread() function.  It has the exact form of the 
  2160. function that is required by the _beginthread() function and is declared as a 
  2161. friend so that it can have access to the members of the Thread class.  Ideally, 
  2162. a static member function would be used, but do to some compiler quirks, a 
  2163. friend is used instead.  No big deal - the result is the same. 
  2164.  
  2165. Using it is quite simple. To start a thread, one calls the member function 
  2166. Start(), which then calls _beginthread() and uses ThreadStarter() as the 
  2167. thread's starting point. ThreadStarter() in turn calls the member function 
  2168. Run(), which has yet to be defined.  The parameter thread starter takes is 
  2169. declared as PVOID, but it really is a Thread * pointer.  Thus, the instance of 
  2170. Thread is known by ThreadStarter(). 
  2171.  
  2172. ClassThreadwoArg 
  2173.  
  2174. template<class C>class ClassThreadwoArg:public Thread{
  2175.    C * TheClass;
  2176.    VOID (C::* TheFunc)(VOID);
  2177.  
  2178. public:
  2179.    ClassThreadwoArg(C * tc,VOID (C::* tf)(VOID)): Thread() {
  2180.       TheClass=tc;
  2181.       TheFunc=tf;
  2182.    }
  2183.  
  2184.    ClassThreadwoArg(VOID (C::* tf)(VOID)): Thread() {
  2185.       TheFunc=tf;
  2186.    }
  2187.  
  2188.    ClassThreadwoArg():Thread() {
  2189.       TheClass=NULL;
  2190.       TheFunc=NULL;
  2191.    }
  2192.  
  2193.    VOID SetInstance(C * tc) { TheClass=tc; }
  2194.    VOID SetFunc(VOID (C::* tf)(VOID)) { TheFunc=tf; }
  2195.  
  2196.    VOID Run(){
  2197.       ((TheClass)->*(TheFunc))();
  2198.    }
  2199. };
  2200.  
  2201. The ClassThreadwoArg class is the most commonly used class.  It starts a thread 
  2202. on a member function which takes no arguments.  Note that the function Run now 
  2203. has a body, the beauty of which is its simplicity.  However, its simplicity 
  2204. hides the the power of this class.  This class can be used to start threads in 
  2205. any class's instance using any member function (taking no arguments) as a 
  2206. starting point.  Also, it is completely type-safe. 
  2207.  
  2208. ClassThreadwArg 
  2209.  
  2210. template<class C,class Arg>class ClassThreadwArg:public Thread{
  2211.    C * TheClass;
  2212.    VOID (C::* TheFunc)(Arg*);
  2213.    Arg *TheArg;
  2214.  
  2215. public:
  2216.    ClassThreadwArg(C * tc,VOID (C::* tf)(Arg*), Arg* ta): Thread() {
  2217.       TheClass=tc;
  2218.       TheFunc=tf;
  2219.       TheArg=ta;
  2220.    }
  2221.  
  2222.    ClassThreadwArg(): Thread() {
  2223.       TheClass=NULL;
  2224.       TheFunc=NULL;
  2225.       TheArg=NULL;
  2226.    }
  2227.  
  2228.    VOID SetInstance(C* tc){ TheClass=tc; }
  2229.    VOID SetFunc(VOID (C::* tf)(Arg*)) { TheFunc=tf; }
  2230.    VOID SetArg(Arg* ta) { TheArg=ta; }
  2231.    VOID Run() {
  2232.       (TheClass)->*(TheFunc))((TheArg));
  2233.    }
  2234. };
  2235.  
  2236. The only difference between this class and ClassThreadwoArg, is that this class 
  2237. allows the possibility of passing an pointer argument to the started member 
  2238. function. 
  2239.  
  2240. The last class will not be discussed in depth because it is basically the same 
  2241. as ClassThreadwoArg but it is for static member functions. 
  2242.  
  2243. Overview 
  2244.  
  2245. Figure 2 shows the flow of function calls as a new thread is created. 
  2246.  
  2247.          Thread 1                                  Thread 2
  2248. ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ    ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
  2249. Γöé Start ΓöÇ> _beginthread  Γöé ΓöÇ> Γöé ThreadStarter ΓöÇ> Run ΓöÇ> (Some Member function) Γöé
  2250. ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ    ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
  2251.  
  2252. Figure 2. Function flow when creating a thread.
  2253.  
  2254. Select this to go to the next section 
  2255.  
  2256.  
  2257. ΓòÉΓòÉΓòÉ 2.4.4. Using the C++ Threading Objects ΓòÉΓòÉΓòÉ
  2258.  
  2259. Now, let's take a look at how the threading objects can be used to implement 
  2260. thread specific variables.  Keeping with tradition, let's suppose we want to 
  2261. create a bunch of foo threads.  One could create the following class: 
  2262.  
  2263. class foo {
  2264.    Thread *TheThread;
  2265.         :
  2266.       /* thread-specific variables */
  2267.         :
  2268. public:
  2269.    foo( /* list of thread-specific initial values */ ) {
  2270.       TheThread=new ClassThreadwoArg<foo>(this,
  2271.                                           &foo::ThreadEntry);
  2272.            :
  2273.          /* do some initialization */
  2274.            :
  2275.    }
  2276.    VOID ThreadEntry();
  2277.    VOID StartThread(){TheThread->Start();}
  2278.         :
  2279.       /* Add whatever other functions are needed */
  2280.         :
  2281. };
  2282.  
  2283. To create a new foo thread, one would create an instance of a foo object and 
  2284. then call the StartThread() member function.  The following code shows this 
  2285. process. 
  2286.  
  2287. INT main(VOID) {
  2288.    foo **FooThreads;
  2289.  
  2290.    FooThreads=new foo[2];
  2291.    if (FooThreads==NULL)
  2292.       exit(1);
  2293.  
  2294.    FooThreads[0]=new foo(/* some thread specific info */);
  2295.    FooThreads[1]=new foo(/* some more thread specific info */);
  2296.    FooThreads[0]->StartThread();
  2297.    FooThreads[1]->StartThread();
  2298.  
  2299.      :
  2300.    /* do something */
  2301.      :
  2302.  
  2303.    return 0;
  2304. }
  2305.  
  2306. Select this to go to the next section 
  2307.  
  2308.  
  2309. ΓòÉΓòÉΓòÉ 2.4.5. Summary ΓòÉΓòÉΓòÉ
  2310.  
  2311. This brings us to the end of our C++ threading journey.  Although, the objects 
  2312. presented here are missing some bells and whistles, adding them should be 
  2313. relatively easy.  The reason _beginthread() cannot be directly used has been 
  2314. discussed and a method to get around this limitation is presented.  The sample 
  2315. foo class illustrated how these thread objects can be used to develop thread 
  2316. specific variables. 
  2317.  
  2318. As usual, question and comments are welcome. 
  2319.  
  2320. Select this to go to the next section 
  2321.  
  2322.  
  2323. ΓòÉΓòÉΓòÉ 3. Columns ΓòÉΓòÉΓòÉ
  2324.  
  2325. The following columns can be found in this issue: 
  2326.  
  2327. o Scratch Patch 
  2328.  
  2329. Select this to go to the next section 
  2330.  
  2331.  
  2332. ΓòÉΓòÉΓòÉ 3.1. Scratch Patch ΓòÉΓòÉΓòÉ
  2333.  
  2334. Written by Larry Salomon, Jr. 
  2335.  
  2336. Welcome to this month's "Scratch Patch"!  Each month, I collect various items 
  2337. that fit into this column sent to me via email. The ones that I feel contribute 
  2338. the most to developers, whether in terms of information or as a nifty trick to 
  2339. tuck into your cap, get published in this column. 
  2340.  
  2341. To submit an item, send it via email to my address - os2man@panix.com - and be 
  2342. sure to grant permission to publish it (those that forget will not be 
  2343. considered for publication).  This month, we have the following: 
  2344.  
  2345. o Questions and Answers 
  2346. o Snippet(s) of the Month 
  2347. o Documentation Chop Shop 
  2348. o Want Ads 
  2349.  
  2350.  
  2351. ΓòÉΓòÉΓòÉ 3.1.1. Questions and Answers ΓòÉΓòÉΓòÉ
  2352.  
  2353. Before anything, a correction needs to be stated.  Last month, I stated that 
  2354. there appeared to be no difference between the WinLoadString() and 
  2355. WinLoadMessage() functions.  Unfortunately, I did not check into this other 
  2356. than a perusal of the documentation, but the documentation seems to be 
  2357. incomplete or incorrect.  When I substituted WinLoadMessage() for 
  2358. WinLoadString() in an application, incomprehensible text was returned. 
  2359.  
  2360. Considering the function of DosGetMessage(), I imagine that there might be some 
  2361. analogy between it and WinLoadMessage().  If anyone else has more information 
  2362. to clarify this, I would appreciate it if it were emailed to me. 
  2363.  
  2364. Mark Mathews (mark.mathews@channel1.com) writes: 1) What is the structure of 
  2365. the delete files in D:\DELETE? These files are created every time I delete 
  2366. files. Can you explain how these files are created and what happens when they 
  2367. are undeleted? I am writing an undelete program. (Might be an article here). 
  2368.  
  2369. To figure this out, I "installed" the undelete feature on my machine by 
  2370. un-REM-ing the SET DELDIR=... statement in my CONFIG.SYS and creating the 
  2371. directories specified.  After deleting a few test files, I ran a utility to 
  2372. dump the binary data for the control file - CONTROL.DEL.  It seems that the 
  2373. delete process works as such: 
  2374.  
  2375.  1. The user enters DEL MYFILE.DAT 
  2376.  2. If the size of the delete directory (default is \DELETE) plus the size of 
  2377.     the delete file exceeds the maximum size specified, the system removes the 
  2378.     files that have been "deleted" for the longest time, until this size meets 
  2379.     the requirements specified in CONFIG.SYS. 
  2380.  3. The system then moves the file into the delete subdirectory for that drive. 
  2381.     Since the file exists on the same drive as the delete subdirectory, a move 
  2382.     instead of a copy is executed, resulting in faster performance.  Also, the 
  2383.     file is renamed to reflect the date and time that the "delete" was 
  2384.     performed. 
  2385.  4. The control file - CONTROL.DEL - is updated. 
  2386.  5. All newly created or updated files are set to be hidden (plus any other 
  2387.     attributes; I did not check the exact match). 
  2388.  
  2389. The format of the control file, as best determined by the binary data, seems to 
  2390. be one entry per file in the following format (all offsets are relative to the 
  2391. beginning of the entry): 
  2392.  
  2393. o Bytes 0x000-0x0FF - the fully qualified filename of the file that was 
  2394.   deleted, without the drive specifier. 
  2395.  
  2396. o Byte 0x100 - trailing zero to make the filename an ASCIIZ string. 
  2397.  
  2398. o Bytes 0x101-0x103 - seem to be reserved and set to zero. 
  2399.  
  2400. o Bytes 0x104-0x10F - the new name of the deleted file as it exists in the 
  2401.   delete subdirectory 
  2402.  
  2403. o Byte 0x110 - trailing zero to make the filename an ASCIIZ string. 
  2404.  
  2405. o Bytes 0x111-0x114 - status bytes of some sort.  You will notice that in the 
  2406.   binary data, '00 00 20 00' is used when there are more entries following and 
  2407.   '00 AA 20 00' is used for the last entry. 
  2408.  
  2409. Select this to go to the next section 
  2410.  
  2411.  
  2412. ΓòÉΓòÉΓòÉ 3.1.2. Snippet(s) of the Month ΓòÉΓòÉΓòÉ
  2413.  
  2414. I received only one reader submission for this month, so there seems to be no 
  2415. competition to see which will be published this month. 
  2416.  
  2417. Remember (I cannot stress this enough), submitted functions must be modular in 
  2418. fashion.  This means that you should be able to compile them separately and 
  2419. need only link them with your applications to get them to work properly (versus 
  2420. actually including the source code as part of your application).  The functions 
  2421. below do not quite meet this criteria (they still require the application to 
  2422. define a constant), but they are close enough. 
  2423.  
  2424. The functions below are for saving and restoring window positions in an .INI 
  2425. file.  It should be noted, therefore, that OS/2 2.x provides a superset of this 
  2426. functionality in the WinStoreWindowPos() and WinRestoreWindowPos() API's. 
  2427.  
  2428. /* ========================================================================
  2429.    Submitted by:  Mark Harrison <harrison@lclark.edu>
  2430.  
  2431.    These two routines will allow you to save the startup position of a
  2432.    window in a private ini file.  There are two calls that will do this
  2433.    in the PM API ( WinStoreWindowPos() & WinRestoreWindowPos() ), but
  2434.    these save your data in the system ini files.  If you prefer to not
  2435.    clutter the system files up, you need to take care of saving the
  2436.    information in your own private file.
  2437.  
  2438.    What these two routines do is to save and restore a windows position,
  2439.    size and the previous position and size if a window is closed and
  2440.    re-opened while it is maximized or minimized.
  2441.  
  2442.    INI files have a three level heirarchy, set up as follows:
  2443.  
  2444.    Application         For example to store scores for a       Basketball
  2445.       Key Name         Basketball program, the INI file           Score
  2446.          Key Data      might look like this.                         45 51
  2447.  
  2448.    Although you can store more than one applications data in an INI file,
  2449.    this probably isn't a good practice in most cases.
  2450.  
  2451.    [ To use these functions, you need to #define the constant ID_INIPATH.
  2452.      See below for more information.  - Editor ]
  2453. ======================================================================== */
  2454.  
  2455. VOID GetStartupData(HWND hWndFrame)
  2456. {
  2457.    HAB hab;
  2458.    CHAR szIniPath[64];
  2459.    HINI hMyHini;
  2460.    SWP Swp;
  2461.    ULONG ulSwpSize = sizeof(SWP);
  2462.    ULONG SwpOptions = SWP_ACTIVATE | SWP_MOVE | SWP_SIZE | SWP_SHOW;
  2463.    USHORT RestoreValues[6];
  2464.    ULONG ulRestoreValuesSize = sizeof(RestoreValues);
  2465.  
  2466. /* ========================================================================
  2467.    Load the physical pathname from the resource file.  It should be defined
  2468.    something like this.
  2469.  
  2470.    STRINGTABLE LOADONCALL MOVEABLE
  2471.    BEGIN
  2472.       ID_INIPATH   "C:\\APP.INI"
  2473.    END
  2474. ======================================================================== */
  2475.    hab = WinQueryAnchorBlock(hWndFrame);
  2476.    WinLoadString(hab, 0, ID_INIPATH, sizeof(szIniPath), szIniPath);
  2477.  
  2478.    if ((hMyHini = PrfOpenProfile(hab, szIniPath))!=NULLHANDLE)
  2479.       {
  2480.       if (PrfQueryProfileData(hMyHini,
  2481.                               "App",
  2482.                               "WindowSize",
  2483.                               (PVOID)&Swp,
  2484.                               (PULONG)&ulSwpSize))
  2485.          {
  2486.          if (Swp.fl & SWP_MAXIMIZE)
  2487.             SwpOptions |= SWP_MAXIMIZE;
  2488.          else if (Swp.fl & SWP_MINIMIZE)
  2489.             SwpOptions |= SWP_MINIMIZE;
  2490.  
  2491.          WinSetWindowPos(hWndFrame,
  2492.                          NULLHANDLE,
  2493.                          Swp.x,
  2494.                          Swp.y,
  2495.                          Swp.cx,
  2496.                          Swp.cy,
  2497.                          SwpOptions);
  2498.  
  2499. /* ========================================================================
  2500.    Set the following flags in the frame window extra data.  This will tell
  2501.    the frame what size it should restore itself to if you restart minimized
  2502.    or maximized.
  2503. ======================================================================== */
  2504.          if (PrfQueryProfileData(hMyHini,
  2505.                                  "App",
  2506.                                  "RestoreValues",
  2507.                                  (PVOID)&RestoreValues,
  2508.                                  (PULONG)&ulRestoreValuesSize))
  2509.             {
  2510.             WinSetWindowUShort(hWndFrame, QWS_XRESTORE, RestoreValues[0]);
  2511.             WinSetWindowUShort(hWndFrame, QWS_YRESTORE, RestoreValues[1]);
  2512.             WinSetWindowUShort(hWndFrame, QWS_CXRESTORE, RestoreValues[2]);
  2513.             WinSetWindowUShort(hWndFrame, QWS_CYRESTORE, RestoreValues[3]);
  2514.             WinSetWindowUShort(hWndFrame, QWS_XMINIMIZE, RestoreValues[4]);
  2515.             WinSetWindowUShort(hWndFrame, QWS_YMINIMIZE, RestoreValues[5]);
  2516.             }
  2517.          }
  2518.       else
  2519.          WinSetWindowPos(hWndFrame, 0, 0L, 40L, 800L, 560L, SwpOptions);
  2520.       }
  2521.    else
  2522.       WinSetWindowPos(hWndFrame, 0, 0L, 40L, 800L, 560L, SwpOptions);
  2523.  
  2524.    PrfCloseProfile(hMyHini);
  2525.    return;
  2526. }
  2527.  
  2528. VOID SaveStartupData(HWND hWndFrame)
  2529. {
  2530.    HAB hab;
  2531.    CHAR szIniPath[64];
  2532.    HINI hMyHini;
  2533.    SWP Swp;
  2534.    USHORT RestoreValues[6];
  2535.  
  2536.    WinQueryWindowPos(hWndFrame, &Swp);
  2537.    RestoreValues[0] = WinQueryWindowUShort(hWndFrame, QWS_XRESTORE);
  2538.    RestoreValues[1] = WinQueryWindowUShort(hWndFrame, QWS_YRESTORE);
  2539.    RestoreValues[2] = WinQueryWindowUShort(hWndFrame, QWS_CXRESTORE);
  2540.    RestoreValues[3] = WinQueryWindowUShort(hWndFrame, QWS_CYRESTORE);
  2541.    RestoreValues[4] = WinQueryWindowUShort(hWndFrame, QWS_XMINIMIZE);
  2542.    RestoreValues[5] = WinQueryWindowUShort(hWndFrame, QWS_YMINIMIZE);
  2543.  
  2544.    hab = WinQueryAnchorBlock(hWndFrame);
  2545.    WinLoadString(hab, 0, ID_INIPATH, sizeof(szIniPath), szIniPath);
  2546.  
  2547.    if ((hMyHini = PrfOpenProfile(hab, szIniPath))!=NULLHANDLE)
  2548.       {
  2549.       PrfWriteProfileData(hMyHini,
  2550.                           "App",
  2551.                           "WindowSize",
  2552.                           (PVOID)&Swp,
  2553.                           sizeof(Swp));
  2554.       PrfWriteProfileData(hMyHini,
  2555.                           "App",
  2556.                           "RestoreValues",
  2557.                           (PVOID)&RestoreValues,
  2558.                           sizeof(RestoreValues));
  2559.       }
  2560.    PrfCloseProfile(hMyHini);
  2561.  
  2562.    return;
  2563. }
  2564.  
  2565. Select this to go to the next section 
  2566.  
  2567.  
  2568. ΓòÉΓòÉΓòÉ 3.1.3. Documentation Chop Shop ΓòÉΓòÉΓòÉ
  2569.  
  2570. Our bug of the month bit me when writing a multithreaded application.  It is in 
  2571. reference to the DosSleep() API which does absolutely nothing when you specify 
  2572. 0 as its argument.  According to the documentation, this should give up the 
  2573. remainder of the current timeslice. 
  2574.  
  2575. The workaround is to specify 1 as the argument. 
  2576.  
  2577. Select this to go to the next section 
  2578.  
  2579.  
  2580. ΓòÉΓòÉΓòÉ 3.1.4. Want Ads ΓòÉΓòÉΓòÉ
  2581.  
  2582. Below are the hot topics as of this issue's writing.  Feel free to write on any 
  2583. of these. 
  2584.  
  2585. Workplace Shell Programming (hot) - lately, I have noticed two things: 1) lots 
  2586. of people want to learn how to write new Workplace Shell classes and 2) no one 
  2587. who knows anything about it is telling the rest of us how to do it.  I'll even 
  2588. stoop down to accepting an article in ASCII format on this topic! :) 
  2589.  
  2590. Anything on Rexx/2 (hot) - many people have requested more articles on Rexx/2. 
  2591. This issue sees the first article on this topic, but we can always use more. 
  2592. Writing "Enhanced Editor" macros in Rexx/2 and interfacing with the Workplace 
  2593. Shell from Rexx/2 are still open topics. 
  2594.  
  2595. Using Input Hooks (hot) - this is a complicated topic which is brought up 
  2596. frequently in the comp.os.os2.programmer.misc newsgroup. 
  2597.  
  2598. Hit testing (warm) - one reader noted that the Jigsaw sample in both the IBM 
  2599. and Borland toolkits (are they not the same?) perform there own correlation and 
  2600. wondered why?  Charles Petzold, in his OS/2 book "Programming the OS/2 
  2601. Presentation Manager" briefly describes correlation and hit-testing, but does 
  2602. not go into any detail nor does it describe the Gpi functions used for this 
  2603. purpose. 
  2604.  
  2605. Animation (warm) - a few readers expressed an interest in the various animation 
  2606. techniques that can be applied to PM applications.  The ultimate article, in my 
  2607. opinion, would be one that develops a sprite library a la the Commodore 64's 
  2608. (and Amiga's?) built-in routines, since this is probably the hardest component 
  2609. of any good animation sequence. 
  2610.  
  2611. Client/Server (warm) - using either named pipes (with or without a network) or 
  2612. sockets, client/server programming is all-the-rage these days.  Some time ago, 
  2613. I started development on a post-office and a named-pipe implementation of FTP; 
  2614. maybe I will get time to finish them and will write articles on them. 
  2615.  
  2616. Select this to go to the next section 
  2617.  
  2618.  
  2619. ΓòÉΓòÉΓòÉ <hidden> CONTROL.DEL Data ΓòÉΓòÉΓòÉ
  2620.  
  2621. Show file offset utility
  2622. Version 1.10
  2623. Copyright (c) 1992 by Larry Salomon, Jr.
  2624. All rights reserved.
  2625.  
  2626. Offset +-------------------------------------------------+------------------+
  2627. 0x0000 | 5C 74 65 6D 70 5C 74 65 73 74 33 2E 75 6E 64 00 | \temp\test3.und. |
  2628. 0x0010 | 2E 33 34 00 48 45 41 44 45 52 2E 57 4B 33 00 00 | .34.HEADER.WK3.. |
  2629. 0x0020 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2630. 0x0030 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2631. 0x0040 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2632. 0x0050 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2633. 0x0060 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2634. 0x0070 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2635. 0x0080 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2636. 0x0090 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2637. 0x00A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2638. 0x00B0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2639. 0x00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2640. 0x00D0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2641. 0x00E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2642. 0x00F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2643. 0x0100 | 00 00 00 00 32 36 31 32 30 35 34 35 2E 35 39 41 | ....26120545.59A |
  2644. 0x0110 | 00 00 02 00 00 5C 74 65 6D 70 5C 54 45 53 54 32 | .....\temp\TEST2 |
  2645. 0x0120 | 2E 55 4E 44 00 2E 33 34 00 48 45 41 44 45 52 2E | .UND..34.HEADER. |
  2646. 0x0130 | 57 4B 33 00 00 00 00 00 00 00 00 00 00 00 00 00 | WK3............. |
  2647. 0x0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2648. 0x0150 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2649. 0x0160 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2650. 0x0170 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2651. 0x0180 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2652. 0x0190 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2653. 0x01A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2654. 0x01B0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2655. 0x01C0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2656. 0x01D0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2657. 0x01E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2658. 0x01F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2659. 0x0200 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2660. 0x0210 | 00 00 00 00 00 00 00 00 00 32 36 31 32 30 35 34 | .........2612054 |
  2661. 0x0220 | 35 2E 35 39 00 00 00 02 00 00 5C 74 65 6D 70 5C | 5.59......\temp\ |
  2662. 0x0230 | 54 45 53 54 31 2E 55 4E 44 00 2E 33 34 00 48 45 | TEST1.UND..34.HE |
  2663. 0x0240 | 41 44 45 52 2E 57 4B 33 00 00 00 00 00 00 00 00 | ADER.WK3........ |
  2664. 0x0250 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2665. 0x0260 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2666. 0x0270 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2667. 0x0280 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2668. 0x0290 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2669. 0x02A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2670. 0x02B0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2671. 0x02C0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2672. 0x02D0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2673. 0x02E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2674. 0x02F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2675. 0x0300 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2676. 0x0310 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2677. 0x0320 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 32 36 | ..............26 |
  2678. 0x0330 | 31 32 30 35 34 35 2E 35 36 00 00 00 02 00 00 5C | 120545.56......\ |
  2679. 0x0340 | 74 65 6D 70 5C 65 2E 74 6D 70 00 75 6E 64 00 2E | temp\e.tmp.und.. |
  2680. 0x0350 | 33 34 00 48 45 41 44 45 52 2E 57 4B 33 00 00 00 | 34.HEADER.WK3... |
  2681. 0x0360 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2682. 0x0370 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2683. 0x0380 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2684. 0x0390 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2685. 0x03A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2686. 0x03B0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2687. 0x03C0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2688. 0x03D0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2689. 0x03E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2690. 0x03F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2691. 0x0400 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2692. 0x0410 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2693. 0x0420 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2694. 0x0430 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2695. 0x0440 | 00 00 00 32 36 31 32 30 34 30 31 2E 39 37 00 00 | ...26120401.97.. |
  2696. 0x0450 | 00 02 00 00 5C 64 65 6C 65 74 65 5C 32 36 31 31 | ....\delete\2611 |
  2697. 0x0460 | 35 36 34 31 2E 33 34 00 48 45 41 44 45 52 2E 57 | 5641.34.HEADER.W |
  2698. 0x0470 | 4B 33 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | K3.............. |
  2699. 0x0480 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2700. 0x0490 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2701. 0x04A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2702. 0x04B0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2703. 0x04C0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2704. 0x04D0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2705. 0x04E0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2706. 0x04F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2707. 0x0500 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2708. 0x0510 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2709. 0x0520 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2710. 0x0530 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2711. 0x0540 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
  2712. 0x0550 | 00 00 00 00 00 00 00 00 32 36 31 31 35 39 30 37 | ........26115907 |
  2713. 0x0560 | 2E 32 38 00 00 00 AA 02 00                      | .28......        |
  2714.        +-------------------------------------------------+------------------+
  2715.  
  2716.  
  2717. ΓòÉΓòÉΓòÉ 4. Contributors to this Issue ΓòÉΓòÉΓòÉ
  2718.  
  2719. Are You a Potential Author? 
  2720.  
  2721. As always, we are always looking for (new) authors.  If you have a topic about 
  2722. which you would like to write, send a brief description of the topic 
  2723. electronically to any of the editors, whose addresses are listed below, by the 
  2724. 15th of the month in which your article will appear.  This alerts us that you 
  2725. will be sending an article so that we can plan the issue layout accordingly. 
  2726. After you have done this, get the latest copy of the Article Submission 
  2727. Guidelines from ftp.cdrom.com in the /pub/os2/2_x/program/newsltr directory. 
  2728. (the file is artsub.zip)  The completed text of your article should be sent to 
  2729. us no later than the last day of the month; any articles received after that 
  2730. time may be pushed to the next issue. 
  2731.  
  2732. The editors can be reached at the following email addresses: 
  2733.  
  2734. o Larry Salomon - os2man@panix.com (Internet). 
  2735.  
  2736. The following people contributed to this issue in one form or another (in 
  2737. alphabetical order): 
  2738.  
  2739. o Larry Salomon, Jr. 
  2740. o Gordon Zeglinski 
  2741. o Network distributors 
  2742. o J╨ñrg Schwieder 
  2743.  
  2744.  
  2745. ΓòÉΓòÉΓòÉ 4.1. Larry Salomon, Jr. ΓòÉΓòÉΓòÉ
  2746.  
  2747. Larry Salomon wrote his first Presentation Manager application for OS/2 version 
  2748. 1.1 in 1989.  Since that time, he has written numerous VIO and PM applications, 
  2749. including the Scramble applet included with OS/2 and the I-Brow/Magnify/Screen 
  2750. Capture trio included with the IBM Professional Developers Kit CD-ROM currently 
  2751. being distributed by IBM.  Currently, he works for International Masters 
  2752. Publishers in Stamford, Connecticut and resides in Bellerose, New York with his 
  2753. wife Lisa. 
  2754.  
  2755. Larry can be reached electronically via the Internet at os2man@panix.com. 
  2756.  
  2757.  
  2758. ΓòÉΓòÉΓòÉ 4.2. Gordon Zeglinski ΓòÉΓòÉΓòÉ
  2759.  
  2760. Gordon Zeglinski is a freelance programmer/consultant who received his Master's 
  2761. degree in Mechanical Engineering with a thesis on C++ sparse matrix objects. 
  2762. He has been programming in C++ for 6 years and also has a strong background in 
  2763. FORTRAN.  He started developing OS/2 applications with version 2.0 . 
  2764.  
  2765. His current projects include a client/server communications program that 
  2766. utilitizes OS/2's features and is soon to enter beta testing. Additionally, he 
  2767. is involved in the development of a "real-time" automated vehicle based on OS/2 
  2768. and using C++ in which he does device driver development and designs the 
  2769. applications that comprise the control logic and user interface. 
  2770.  
  2771. He can be reached via the Internet at zeglins@cc.umanitoba.ca. 
  2772.  
  2773.  
  2774. ΓòÉΓòÉΓòÉ 4.3. J╨ñrg Schwieder ΓòÉΓòÉΓòÉ
  2775.  
  2776. J╨ñrg Schwieder lives in Berlin, Germany and is a student of Economical 
  2777. Engineering at the Technical University of Berlin.  He is a developer for the 
  2778. DOS, Windows, and OS/2 platforms and has used OS/2 since version 2.0.  Some 
  2779. years ago he worked for some home computer magazines writing articles about 
  2780. programming tasks and columns.  When time allows (and is not spent with editor 
  2781. configuration), he works on PmTerm, a replacement for the system DOS and OS/2 
  2782. command line windows. 
  2783.  
  2784. J╨ñrg can be reached at the following addresses: 
  2785.  
  2786. email: kirk@cs.tu-berlin.de
  2787.  
  2788. Mail: Joerg Schwieder
  2789.       Wieckerstr. 2
  2790.       13051 Berlin
  2791.       Germany
  2792.  
  2793.  
  2794. ΓòÉΓòÉΓòÉ 4.4. Network distributors ΓòÉΓòÉΓòÉ
  2795.  
  2796. These people are part of our distribution system to provide EDM/2 on networks 
  2797. other than the Internet.  Their help to provide access to this magazine for 
  2798. others is voluntary and we appreciate them a lot! 
  2799.  
  2800. o Paul Hethman (hethman@cs.utk.edu) - Compuserve 
  2801. o David Singer (singer@almaden.ibm.com) - IBM Internal 
  2802.  
  2803. If you would like to become a "network distributor", be sure to contact the 
  2804. editors so that we can give you the credit you deserve!