home *** CD-ROM | disk | FTP | other *** search
/ Arawak OS/2 Shareware / PAKLED.ISO / docs / edm / edmi3-3.inf (.txt) < prev    next >
Encoding:
OS/2 Help File  |  1995-03-10  |  187.1 KB  |  4,935 lines

  1.  
  2. ΓòÉΓòÉΓòÉ 1. Mar 1995 Title Page ΓòÉΓòÉΓòÉ
  3.  
  4.                                       EDM/2
  5.                   The Electronic Developer's Magazine for OS/2
  6.                    Portions copyright (c) by Larry Salomon Jr.
  7.                                 Volume 3, issue 3
  8.  
  9. Administrivia 
  10.  
  11. I hate months like this one.  It's bad enough that the weather can't make up 
  12. its mind whether to be warm or cold, but my schedule can't make up its mind in 
  13. the morning whether I should have any free time that evening. 
  14.  
  15. New Beginnings 
  16.  
  17. After the fiasco surrounding my dismissal by my last employer, I managed to 
  18. quickly find new employment with a wholly-owned subsidiary of Dow Jones, 
  19. Telerate.  The job is quite interesting, and it promises to teach me much about 
  20. communications and device driver development, two areas in which I am sorely 
  21. lacking in the amount of experience I have.  Additionally, being an employee, 
  22. even indirectly, of Dow Jones has its perks; there are always free copies of 
  23. the Wall Street Journal in the lobby when I come in to work in the morning. 
  24. The only bad thing about this job is that its location is in New Jersey, which 
  25. makes for a 1 hour and 15 minute commute each way. 
  26.  
  27. Obviously, there are many other things I'd rather be doing with the three hours 
  28. I spend on the trains... 
  29.  
  30. Use (tm) Your (tm) Words (tm) Warily (tm) 
  31.  
  32. Like trying to find words that aren't listed as trademarks of major 
  33. corporations.  Microsoft apparently wasn't daunted by the removal of its 
  34. trademark on the word "windows"; they are now threatening litigation against a 
  35. small shop in upstate New York for violating their trademark on the word 
  36. "bookshelf."  You read that right.  The moral here is that you had better do 
  37. some thorough investigation into trademarks that might be a part of any product 
  38. you release commercially. 
  39.  
  40. EDM/2 probably violates a couple of hundred trademarks of this type. <grin> My 
  41. guess is that, in a year or two, someone is going to trademark individual 
  42. letters of the alphabet and will suddenly find themselves a multi- trillionaire 
  43. due to the sudden income from royalties they'll be receiving. 
  44.  
  45. In Tune With Wall Street 
  46.  
  47. Now that the New York Stock Exchange has finally broken the 4000 mark, I'm 
  48. expecting it to crash any day.  Gordon's hard drive, however, could wait, and 
  49. it decided to crash last month in anticipation; his column will be notably 
  50. absent this month.  As if that weren't enough, Carsten has too many projects 
  51. and mid-term exams to worry about for school, so his column will not appear 
  52. this month either.  He does manage to make a brief cameo appearance, though. I 
  53. didn't want to be a non-conformist and since I can use the time, my column will 
  54. not appear this month.  However, part 6 of the VIOWIN series does appear. 
  55.  
  56. Columns Are For The Weak Magazines 
  57.  
  58. But who needs columns anyway?  <grin> This month, there is the new "Letters" 
  59. section, where your email might appear.  Also, we have some great articles, and 
  60. who knows what else might turn up?  Regarding the "Letters" section, it should 
  61. be noted that email messages chosen for publication are subject to editing 
  62. changes. 
  63.  
  64. Speaking of not knowing what will turn up, I still haven't heard from my 
  65. contact at the Media Relations group regarding donations to EDM/2 for the 
  66. winners of the Reader's Choice Awards.  That is quite disappointing, but next 
  67. time I will be more cautious in whom I approach regarding this topic.  That 
  68. doesn't alleviate my current problem, though; if the winners will have some 
  69. patience, and if I ever get enough time to investigate other avenues, they will 
  70. get their well- earned stocking-stuffer even if it is too late for Christmas 
  71. 1994. 
  72.  
  73. Speaking of things turning up and then not turning up after all, a few of you 
  74. might have noticed how the article on the KEYBOARD.DCP layout was pulled out 
  75. last month after the issue had gone out.  I had to re-release the issue because 
  76. I forgot some "legal speak" that was required in addition to the OS/2 
  77. Accredited logo (but will not be forgotten again).  The article was pulled 
  78. because Martin Lafaix indicated that it was a draft only; the final version of 
  79. the article is in this issue. 
  80.  
  81.  
  82. ΓòÉΓòÉΓòÉ 2. Copyright Notice ΓòÉΓòÉΓòÉ
  83.  
  84. Copyright Notice 
  85.  
  86. EDM/2 is published by IQPac Inc.  IQPac Inc. can be reached via U.S. Mail at 
  87. the following address: 
  88.  
  89. IQPac Inc.
  90. 89-17 Moline Street
  91. Bellerose, New York 11428
  92. U.S.A.
  93.  
  94.  Editor-in-chief     Larry Salomon Jr. 
  95.  Associate editor    Carsten Whimster 
  96.  Contributing editor Gordon Zeglinski 
  97.  
  98.  CEO/President       Larry Salomon Jr. 
  99.  
  100.  All material is copyrighted by its original author.  No part of this magazine 
  101.  may be reproduced without permission from the original author. 
  102.  
  103.  This publication may be freely distributed in electronic form provided that 
  104.  all parts are present in their original unmodified form.  A reasonable fee may 
  105.  be charged for the physical act of distribution; no fee may be charged for the 
  106.  publication itself. 
  107.  
  108.  Neither IQPac Inc. nor this publication are affiliated with International 
  109.  Business Machines Corporation. 
  110.  
  111.  OS/2 is a registered trademark of International Business Machines Corporation. 
  112.  Other trademarks are property of their respective owners.  Any mention of a 
  113.  product in this publication does not constitute an endorsement or affiliation 
  114.  unless specifically stated in the text. 
  115.  
  116.  The OS/2 Accredited Logo is a trademark of International Business Machines 
  117.  Corporation and is used by IQPac Inc. under license.  This On-line Publication 
  118.  is independently produced by IQPac Inc. and IBM is not responsible in any way 
  119.  for its contents. 
  120.  
  121.  IQPac Inc. is an accredited member of the IBM Independent Vendor League. 
  122.  
  123.  Copyright Notice - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  124.  
  125.  
  126. ΓòÉΓòÉΓòÉ 3. Letters ΓòÉΓòÉΓòÉ
  127.  
  128. Letters 
  129.  
  130. To write to EDM/2, send your email to os2man@panix.com and use the Subject: 
  131. line "Letters". 
  132.  
  133. To Protect or Not (To Bother)? 
  134.  
  135. Henrik Falk (hfalk@ibm.net) writes: 
  136.  
  137. "I have a little comment on your artichle in EDM/2 2-10 on copy-protection. 
  138. Your idea about using the volume serial number for copy protection only works 
  139. for copy programs which doesn't copy the serial number.  One famous program of 
  140. that category is DISKCOPY. 
  141.  
  142. I don't know if American computer users are that naive or they just don't know 
  143. the fact that a good copy program copies the volume serial number as well. 
  144.  
  145. In Europe, we all use that kind of copy programs.  Not because we are less 
  146. software pirates than Americans but because we like powerful utilities. 
  147. Europeans don't like copy-protection.  <grin> 
  148.  
  149. I didn't mean to offend you with this letter!  It was a great article and I 
  150. think this soft-copy-protection discussion is fine.  As a developer you'll have 
  151. to protect you work in a certain degree." 
  152.  
  153. EDM/2 responds: 
  154.  
  155. A number of people have mentioned limitations with the copy-protection scheme 
  156. presented in the article, but this is one of the more serious ones because it 
  157. limits the effectiveness to preventing only those users who haven't acquired a 
  158. "good" copy protection program.  Thank you for your feedback. 
  159.  
  160. ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇ 
  161.  
  162. Ed Rozmiarek (edroz@vnet.ibm.com) writes: 
  163.  
  164. "I just downloaded EDM/2 Volume 3, Issue 1 and read that you never seem to get 
  165. responses to your magazine.  Well, I wanted to let you know that I have every 
  166. issue and have at least browsed through every one.  I have read several of the 
  167. articles and found some tricks and techniques that I can use. 
  168.  
  169. I wanted to make a comment on December's article on copy protection.  I know 
  170. there is no fool-proof and perfect form of copy protection, but there is one 
  171. problem with the method you described.  It has to do with backup copies of the 
  172. product diskettes.  The first thing I do when I get a new product is make 
  173. backup copies of the product diskettes and then install from the backups.  (I 
  174. have run into one to many program install problems that trash the install 
  175. disk).  With the method you described in the article, I would not be able to 
  176. make backup diskettes since the backups would have a different Volume Serial 
  177. Numbers and therefore would not install. 
  178.  
  179. One topic I would like to see in a future article is related to copy 
  180. protection.  It is, what is the best technique(s) for locking/unlocking 
  181. features within a shareware program without having a completely different 
  182. version.  For example, the SIO drivers by Ray Gwynn have a "nag" screen that 
  183. displays for the unregistered version.  Once you register the screen goes away. 
  184. If you download a newer version, you run a program to reregister the new 
  185. version.  Also, some programs may have limited features or options in the 
  186. unregistered version.  Once you enter some unique number, the features will 
  187. unlock." 
  188.  
  189. Larry Salomon Jr. responds: 
  190.  
  191. It should be a trivial matter to include an additional license on the disks and 
  192. write a "validator" program which, after the buyer makes the backup copy, 
  193. reduces the license count by one on the original disks and initializes the 
  194. license count to one on the backup copy. 
  195.  
  196. Disabling features and having "nag" screens are common techniques for 
  197. encouraging users to register shareware, but these non-prohibiting methods of 
  198. encouraging payment are usually too specific to each application to be dealt 
  199. with properly in an "all encompasing" article.  I'll give the matter some 
  200. thought to see if a followup should be done for this tangent. 
  201.  
  202. More Feedback Than A Biorhythm 
  203.  
  204. Juergen Wagner (hoerni@nodos.snafu.sub.org) writes: 
  205.  
  206. "In EDM/2 Jan 1995 you asked for some feedback about PM- Programing. 
  207.  
  208. Well, here it is.  I really have to say thank you for some really great hints 
  209. on all this stuff.  Since I am writing a UUCP package for OS/2 including 
  210. Mailreader and Newsreader I need every help I can get.  And you give me exactly 
  211. what I need right now. 
  212.  
  213. You also asked, if we would like to see some topics in detail.  Well, here is 
  214. something that really bothers me.  I need a dialog element, that gives me the 
  215. functionality of a spread sheet.  That is: 
  216.  
  217.    1. A field of Editlines, where the programmer can define how many they want 
  218.    2. Variable wide of each column 
  219.    3. Scrollbars to scroll through all of the Editlines 
  220.  
  221.  I think of something looking like this: 
  222.  
  223.   |---------|----------------|--------------|^
  224.   |         |                |              |
  225.   |---------|----------------|--------------|
  226.   |         |                |              |
  227.   |---------|----------------|--------------|v
  228.   <                                         >
  229.  
  230.  It should be possible, to edit each field and to put it in a dialog window. If 
  231.  you know how to do something like this, that would be an idea for EDM/2. 
  232.  
  233.  Another topic could be Drag-and-Drop, and how to print with the Printer 
  234.  Subsystem, and a closer look at the container control. 
  235.  
  236.  I hope, you can draw some ideas out of these suggestions. 
  237.  
  238.  Keep on this good work." 
  239.  
  240.  EDM/2 responds: 
  241.  
  242.  Taken under advisement.  Thank you for your feedback. 
  243.  
  244.  ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇ 
  245.  
  246.  Galen Rhodes (rhode923@uidaho.edu) writes: 
  247.  
  248.  "You asked for feedback for the PM Programming section of EDM/2. 
  249.  
  250.  First off, thanks for EDM/2!  I am new to OS/2 programming.  Having spent a 
  251.  great deal of time programming for the early 8-bit Commodore Computers (I 
  252.  wrote assembly language utilities for the C64 and C128), I have in the past 3 
  253.  years moved to DOS, Windows and now OS/2 because I enjoy it's power.  I was 
  254.  shocked to see the lack of available information on OS/2 considering it's been 
  255.  out since the late 80's.  I was also shocked bacause I was use to the 
  256.  excellent coverage the Commodore computers got.  I find EDM/2 to be a very 
  257.  helpful resource for bringing me up to speed on OS/2.  Please keep up the good 
  258.  work. 
  259.  
  260.  I would like to see a very in-depth coverage of notebooks.  This seems to be 
  261.  an area that gets very superficial, if no treatment at all.  I enjoyed Larry 
  262.  Salomon's book on OS/2 PM Programming (The Art of OS/2 2.1 C Programming) but 
  263.  even it gave a very light touch on the subject and didn't cover it in as much 
  264.  detail as I would have liked. 
  265.  
  266.  Also I would like to see the MMPM API covered as it pertains to PM 
  267.  programming.  So far I have only seen a few references on the net to MMPM 
  268.  programming and they are mostly REXX examples." 
  269.  
  270.  EDM/2 responds: 
  271.  
  272.  Also taken under advisement.  Thank you for your feedback. 
  273.  
  274.  Future Topics and More on Copy Protection 
  275.  
  276.  Brian DeWeese (71242.2616@compuserve.com) writes: 
  277.  
  278.  "In the last issue of EDM3-1 (Jan.)  you asked for ideas and comments at the 
  279.  end of "Intro to PM section."  You mentioned that you had covered the same 
  280.  subject twice and no one had commented on that fact.  Normally, I skip over 
  281.  that section since I'm a fairly experienced PM developer and that is an intro 
  282.  section.  But I have looked at some of them and they are well written. 
  283.  
  284.  Anyway, I have a couple of ideas for your column, but they may be too advanced 
  285.  of subjects for it. 
  286.  
  287.    1. Discuss and easy way to add toolbar like buttons on the title bar.  These 
  288.       buttons are similar to the buttons used in the IPMD debugger.  And they 
  289.       are simply created by retrieving the handle of the minmax buttons (they 
  290.       are actually a menu bar control) and adding some new menu item entries 
  291.       that are MIS_BITMAP. 
  292.  
  293.    2. Discuss implementing the new UCMenus toolbar available from IBM.  It's a 
  294.       very robust control and it appears that several commercial/shareware 
  295.       packages will soon be released using this control.  IBM's new EPM editor 
  296.       uses it.  It can help sharpen the image of OS/2 apps. 
  297.  
  298.    3. Subclassing.  This can be a tricky subject to tackle when you take into 
  299.       consideration that, A) you don't really what to store your "previous proc 
  300.       address" in a global variable, and B) consider the impact when your 
  301.       subclass may be one of several others, some of the others may be out of 
  302.       your control. How do you ensure that they call your subclass rather than 
  303.       the default winproc, etc.  Lots of issues there.  I've written some 
  304.       functions that store the prev proc address and private data in a link 
  305.       list per subclass and it address some of the issues but not all. 
  306.  
  307.         a. Subclassing windows that don't belong to your application.  What are 
  308.            the implications?  etc. 
  309.  
  310.         b. Superclassing, how and when should it be done. 
  311.  
  312.    4. Discuss dragging and dropping to/from the desktop (WPS).  Performing Drag 
  313.       'n Drop within your own application is one thing but I'd like to know 
  314.       more about the implications of integrating a non-WPS application with the 
  315.       WPS by accepting drag operations from it, etc. 
  316.  
  317.  These ideas are a little vague and if any interest you, I'll be happy to 
  318.  discuss them with you. 
  319.  
  320.  Also, I just now got around to reading EDM2-11 (Dec) and read your article on 
  321.  copy protection.  You asked for comments.  Copy protection is something that I 
  322.  have given a lot of thought to but have never come up with the 'ideal' 
  323.  solution.  Your idea has merits but there are a couple of things that I do not 
  324.  like about it. 
  325.  
  326.    1. The user is not allowed to make backup copies of his software, at the 
  327.       min. 1 copy should be allowed to be created. 
  328.  
  329.    2. What about shareware that is downloaded as a .zip file and installed 
  330.       after unzipping it and never sees a diskette?  Should it be crippled and 
  331.       if the user wishes to purchase it, then send him a diskette?  etc. 
  332.  
  333.  It's a controversial subject, one that I am interested in.  Perhaps I'll start 
  334.  a thread on this subject somewhere and see what gets flushed out. 
  335.  
  336.  Thank you for your work on the EDM issues.  Your efforts are appreciated." 
  337.  
  338.  EDM/2 responds: 
  339.  
  340.  Items 1-4 are taken under advisement, but you are correct in your assessment 
  341.  that the topics are a bit advanced for an introductory programming column. 
  342.  
  343.  Referring to item 1 in the section about copy protection, a suggested answer 
  344.  to this was presented earlier in this section.  Item 2 is an interesting 
  345.  notion for which the following suggested solution is presented: 
  346.  
  347.  Write a "self-extracting" installation program which has the .ZIP file as a 
  348.  user-defined resource.  The program calls DosGetResource(), writes the .ZIP 
  349.  file to disk, and then spawns the UNZIP utility available from the Info-ZIP 
  350.  package.  After the unzipping is complete, the .ZIP file is deleted. 
  351.  
  352.  Thank you for your feedback. 
  353.  
  354.  
  355. ΓòÉΓòÉΓòÉ 4. Building Smaller OS/2 Executables (Part 1) ΓòÉΓòÉΓòÉ
  356.  
  357.  
  358. ΓòÉΓòÉΓòÉ 4.1. Introduction ΓòÉΓòÉΓòÉ
  359.  
  360. Building Smaller OS/2 Executables (Part 1) 
  361.  
  362. Written by Pete Cassetta 
  363.  
  364. Introduction 
  365.  
  366. Not so long ago, programmers used to pride themselves on how tight and fast 
  367. their code was.  RAM was expensive and scarce, so it was necessary to shoehorn 
  368. as much functionality as possible into the precious memory available.  Today, 
  369. of course, all that has changed.  RAM has become cheap and plentiful, and, even 
  370. more importantly, modern PC operating systems such as OS/2 support virtual 
  371. memory.  This provides a huge address space to play in, with no more than a 
  372. fairly minor performance hit when programs need more memory than is physically 
  373. present and free in the system.  Now that memory constraints are less of a 
  374. concern, programmers naturally focus more attention on other issues such as 
  375. making deadlines, adding functionality, and getting the bugs out (often in that 
  376. order).  If code size is considered at all, it usually lags well behind these 
  377. other concerns. 
  378.  
  379. Users, on the other hand, pay a lot of attention to code size.  Their hard disk 
  380. space is always at a premium, and they complain loudly about the disk 
  381. requirements of today's multi-megabyte programs.  Also, while large programs 
  382. may still run, users notice it when programs take a long time to load or force 
  383. OS/2 to spend too much time swapping over-committed memory to disk.  Making 
  384. your program smaller will help endear it to your users and give it an edge in 
  385. competing with other programs for the limited disk space on your potential 
  386. users' computers. 
  387.  
  388. The good news is that OS/2 actually provides programmers with more facilities 
  389. for producing optimally small executables than perhaps any other PC operating 
  390. system available today.  Although these features often go unused, they are 
  391. really very quick and easy to implement.  This series of articles will detail 
  392. five simple steps you can take to get your OS/2 executables down to size. 
  393.  
  394. Executable 
  395.  
  396. I will use executable as a cover term to refer to both EXEs and DLLs. While 
  397. some of my comments also apply to device drivers, these are really beyond the 
  398. scope I wish to address, so I won't mention them specifically. 
  399.  
  400. Tradeoffs Between Size and Speed 
  401.  
  402. Before I lose the speed demons out there, I'd better reassure you that it's 
  403. possible to optimize code size without introducing too much of a speed penalty. 
  404.  
  405. Every program has bottlenecks where special attention must be given to 
  406. execution speed.  These fall into two broad categories:  I/O operations 
  407. involving the disk, screen, or to a lesser extent, the printer, and 
  408. computationally expensive operations such as searching, sorting, or updating 
  409. data structures.  I suggest code involving I/O operations is the place to 
  410. optimize for size.  The reason for this is that OS/2 programs tend to do I/O at 
  411. a fairly high level, thanks to the rich set of I/O facilities OS/2 provides via 
  412. its Dos, Gpi, Win, and other API functions.  To optimize the speed of your I/O 
  413. operations under OS/2 you usually need to improve your algorithms and possibly 
  414. also the selection and sequence of API calls you use.  Once you've done this, 
  415. you are generally free to optimize the code you've written for size, since any 
  416. speed degradation this introduces will be negligible.  After all, only a 
  417. minuscule portion of the time required by an I/O operation will be spent in 
  418. your code; the significant time is spent within OS/2's system code. 
  419.  
  420. Keeping this in mind, it is helpful to organize your source code into modules 
  421. which will be optimized for size and others where you'll go all-out for speed. 
  422. This makes it relatively easy to select different compiler options for these 
  423. two categories of code. 
  424.  
  425. Finally, while optimizing for size and speed are often mutually exclusive 
  426. goals, there are a number of things you can do that will actually improve both 
  427. size and speed.  As I discuss each step below, I'll detail the side effects on 
  428. execution speed that you're likely to encounter. 
  429.  
  430. Building Smaller OS/2 Executables (Part 1) - EDM/2 - Mar 1995 - Volume 3, Issue 
  431.  
  432.  
  433. ΓòÉΓòÉΓòÉ 4.2. Five Simple Steps ΓòÉΓòÉΓòÉ
  434.  
  435. Shown below are five steps you can take to reduce executable size.  They are 
  436. listed according to the order in which you should take them, especially if your 
  437. time is limited.  Earlier steps are both quicker to implement and more likely 
  438. to show significant results for your effort. 
  439.  
  440.    1. Compress Your Resources 
  441.    2. Put Your Linker to Work 
  442.    3. Experiment with Compiler Switches 
  443.    4. Try a Different Compiler 
  444.    5. Use API Wrapper Functions 
  445.  
  446.  I'll cover steps 1 and 2 in this article, and leave steps 3 - 5 for next time. 
  447.  
  448.  At certain points I'll need to apply my comments to specific compiler 
  449.  packages.  I own three OS/2 compilers, Borland C++ 1.5, IBM C Set++ 2.0 (CSD 
  450.  level 9), and Watcom C/C++ 10.0, so these are the ones I'll cover.  My 
  451.  apologies if your compiler isn't included. 
  452.  
  453.  Building Smaller OS/2 Executables (Part 1) - EDM/2 - Mar 1995 - Volume 3, 
  454.  Issue 3 
  455.  
  456.  
  457. ΓòÉΓòÉΓòÉ 4.3. Step 1 - Compress Your Resources ΓòÉΓòÉΓòÉ
  458.  
  459. The OS/2 resource compiler has a little-known switch, -x, which enables 
  460. compression of resources as they are being bound to the executable.  The 
  461. compression is basically run-length encoding, so it gives best results for 
  462. resource types that tend to have repeating sequences of data, e.g.  bitmaps, 
  463. icons, pointers, and fonts.  If you haven't been using this switch, I encourage 
  464. you to take a break right now and try it out on one of your programs.  Simply 
  465. re-bind the resources using a command like the following: 
  466.  
  467.      rc -x myprog.res myprog.exe
  468.  
  469. You may have noticed that this took longer than usual; resource binding is much 
  470. slower when the -x switch is used.  For this reason, you may want to reserve 
  471. this switch for making release versions of your executables. 
  472.  
  473. Even Better Under Warp 
  474.  
  475. In OS/2 Warp, IBM has made a good thing better by adding a second compression 
  476. algorithm for resources.  The Warp toolkit comes with version 2.02.001 of the 
  477. resource compiler.  This version accepts the switch -x2, which enables both 
  478. run-length encoding and the new algorithm, which I expect is a form of 
  479. Lempel-Ziv-Welch.  Note that you can still use -x, or its new synonym -x1, if 
  480. you want run-length encoding only.  The bad news is that versions of OS/2 prior 
  481. to Warp don't know how to load resources bound with - x2.  If you want your 
  482. program to run under OS/2 2.x, you'll have to stick with -x1 for now. 
  483.  
  484. Compression Schemes 
  485.  
  486. Run-length encoding is a simple scheme whereby a sequence of identical data 
  487. units is replaced by a flag, a count and then one copy of the data unit.  So a 
  488. sequence like "00 00 00 00 00 00" is compressed to "Flag 06 00".  This can be 
  489. quite effective for compressing graphical data, but rarely gives any 
  490. significant savings for text. 
  491.  
  492. Lempel-Ziv-Welch is an algorithm that replaces repeated strings by shorter 
  493. tokens.  For example, the string " rep" occurs twice in the last sentence so it 
  494. would be a good candidate for replacement by a token.  This algorithm gives 
  495. very good results for compressing a variety of data types. Variants of it are 
  496. used in commercial products like PKZIP, Stacker and many others. 
  497.  
  498. A Look at the Results 
  499.  
  500. The following chart shows the results I obtained by using the -x1 and -x2 
  501. switches while binding resources to a variety of executables: 
  502.  
  503. ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
  504. ΓöéExecutableΓöéOrigin    ΓöéOriginal  ΓöéSize with ΓöéSize with Γöé
  505. Γöé          Γöé          ΓöéSize      Γöé-x1       Γöé-x2       Γöé
  506. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  507. ΓöéI495.EXE  ΓöéEDM 2-7   Γöé129,792   Γöé68,448    Γöé50,752    Γöé
  508. Γöé          Γöé          Γöé          Γöé(53%)     Γöé(39%)     Γöé
  509. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  510. ΓöéBPMCC.DLL ΓöéBorland   Γöé99,456    Γöé84,608    Γöé64,640    Γöé
  511. Γöé          ΓöéC++ 1.5   Γöé          Γöé(85%)     Γöé(65%)     Γöé
  512. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  513. ΓöéBCRES.DLL ΓöéBorland   Γöé166,042   Γöé153,242   Γöé114,330   Γöé
  514. Γöé          ΓöéC++ 1.5   Γöé          Γöé(92%)     Γöé(69%)     Γöé
  515. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  516. ΓöéFPWCAT.DLLΓöéOS/2 Warp Γöé237,901   Γöé155,581   Γöé124,573   Γöé
  517. Γöé          Γöé          Γöé          Γöé(65%)     Γöé(52%)     Γöé
  518. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  519. ΓöéMAHJONGG.EΓöéOS/2 Warp Γöé620,731   Γöé562,139   Γöé449,199   Γöé
  520. Γöé          Γöé          Γöé          Γöé(91%)     Γöé(72%)     Γöé
  521. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  522. ΓöéOS2-CIM.EXΓöéOS/2 Warp Γöé1,275,350 Γöé1,145,962 Γöé1,023,670 Γöé
  523. Γöé          Γöé          Γöé          Γöé(90%)     Γöé(80%)     Γöé
  524. ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
  525.  
  526. Figure 1 - Effect of compressing resources on the size of various executables. 
  527.  
  528. The first executable, I495.EXE, was supplied as sample code to an accompanying 
  529. EDM/2 article, so it's not too surprising that it didn't use this switch.  But 
  530. note that all the others were taken from commercial products! I've found that 
  531. most OS/2 products are released with uncompressed resources, needlessly wasting 
  532. space on your hard disk and mine. 
  533.  
  534. As a disclaimer, I should mention that I chose these particular executables 
  535. because they contain a lot of resources.  The savings is far less spectacular 
  536. in executables which have fewer resources.  Also, you may be wondering where I 
  537. got the RES files for all these executables.  I had source code for I495.EXE, 
  538. but for each of the others, I used Borland's Resource Workshop to extract the 
  539. resources and then saved them as a RES file.  I generated new executables with 
  540. commands like the following: 
  541.  
  542.      rc -x2 bpmcc.res bpmcc.dll
  543.  
  544. Tradeoffs and Limitations 
  545.  
  546. Compressed resources do add run-time overhead, since OS/2 must decompress the 
  547. resources as they are loaded.  Keep in mind, however, that the time spent in 
  548. decompression is at least partly offset by the time savings of reading fewer 
  549. bytes from disk.  In effect, using compressed resources shifts some of the 
  550. run-time burden from I/O (reading resources from disk) to processor 
  551. (decompression).  Since processor speed is improving much more rapidly than 
  552. hard disk speed these days, this seems to be a sensible tradeoff.  In my own 
  553. informal testing, I've found that programs with compressed resources tend to 
  554. load up to 5% slower in a 386 machine, but they often load quicker in a machine 
  555. with a faster processor. 
  556.  
  557. You should be aware that compressing your resources saves nothing but disk 
  558. space; once loaded, the resources still consume the same amount of RAM as they 
  559. would otherwise.  Nevertheless, the disk space savings can be considerable, and 
  560. I see no reason why every OS/2 executable shouldn't be built with compressed 
  561. resources before release. 
  562.  
  563.  
  564. ΓòÉΓòÉΓòÉ 4.4. Step 2 - Put Your Linker to Work ΓòÉΓòÉΓòÉ
  565.  
  566. Having done all we can with resources, let's turn our attention to the link 
  567. step.  First I'll describe the four types of optimizations that can be done 
  568. during a link, then I'll look at how specific linkers support these 
  569. optimizations. 
  570.  
  571. Aligning Pages of Code and Data 
  572.  
  573. In OS/2 executables, every page of code or data begins at an offset from the 
  574. beginning of the file that is an even multiple of the page alignment.  The 
  575. alignment must be a power of two, and many linkers use 512 as the default. 512 
  576. is used because this is the size of a disk sector, and starting pages on sector 
  577. boundaries ensures that OS/2 doesn't need to read any more sectors than 
  578. necessary to load a page.  (A sector is the smallest unit of disk space OS/2 
  579. can read.)  To see how this works, consider what happens when OS/2 must load a 
  580. 32-byte page that begins at file offset 1000.  In this case, two sectors must 
  581. be read:  the one which begins at offset 512 and contains the first 24 bytes of 
  582. the page, and the one which begins at offset 1024 and contains the final 8 
  583. bytes.  Note that if this page were to begin at offset 1024, then OS/2 could 
  584. load it with a single sector read. 
  585.  
  586. The downside here is that the linker must pad pages with null bytes in order to 
  587. make their length an even multiple of the page alignment.  As a result, larger 
  588. alignment values increase executable size.  If the above example were linked 
  589. with a page alignment of 512, then the 32-byte page would need 512 - 32, or 480 
  590. bytes of padding.  So you see the tradeoff: linking with a smaller page 
  591. alignment reduces executable size, but it may also cause more pages to span 
  592. sector boundaries, increasing load time.  Also, keep in mind that using a 
  593. smaller page alignment saves disk space only; it has no effect on the amount of 
  594. memory your program will need once loaded. 
  595.  
  596. Eliminating Internal Fixups in EXEs 
  597.  
  598. For every function call made in a program, the linker must somehow supply the 
  599. function's address.  Normally, the address isn't known at link time, so the 
  600. linker creates what is known as a relocation record, or fixup for short. Fixups 
  601. come in two flavors:  internal and external.  Internal fixups reference 
  602. functions in the EXE or DLL being linked, while external fixups reference 
  603. functions in external DLLs.  Each fixup is resolved at load time, which means 
  604. that the call statement is patched with the linear run-time address of the 
  605. function being called.  You should minimize the number of fixups whenever 
  606. possible, because they are bulky and time-consuming to resolve. 
  607.  
  608. As it turns out, internal fixups aren't really necessary in EXEs.  This is 
  609. because OS/2 always loads programs at a starting address of 64K.  If you tell 
  610. the linker that your program will load at a starting address of 64K, it can 
  611. determine the run-time addresses of all functions local to that EXE, and supply 
  612. these addresses directly instead of creating fixups.  Note that this applies 
  613. only to EXEs; there is no way to reliably predict the load address of an OS/2 
  614. DLL at link time. 
  615.  
  616. I've found that when internal fixups are eliminated, most EXEs shrink by 5 to 
  617. 15% and load perceptibly quicker.  Yet a surprising number of commercial OS/2 
  618. products contain EXEs with internal fixups.  Here are a couple of examples: 
  619.  
  620.  Product             EXEs with Internal Fixups 
  621.  Borland C++ 1.5     BRCC.EXE, IMPDEF.EXE, MAKE.EXE, WORKSHOP.EXE, ... 
  622.  OS/2 Warp           FPWPIM.EXE, GOPHER.EXE, MAHJONGG.EXE, TELNETPM.EXE, ... 
  623.  
  624.  Figure 2 - Examples of OS/2 executables which contain internal fixups. 
  625.  
  626.  To check whether an EXE contains internal fixups, you can use either IBM's 
  627.  EXEHDR or Borland's TDUMP.  If a program has no internal fixups, you'll see 
  628.  the following lines near the beginning of EXEHDR's display: 
  629.  
  630.   Module type:   Program
  631.             NO internal fixups in executable image
  632.  
  633.  When internal fixups are present, the second line is omitted.  If you run 
  634.  TDUMP on a program that has no internal fixups, you'll see something similar 
  635.  to the following: 
  636.  
  637.   Module flags                       00000312
  638.        Instance init            : No
  639.        Internal fixups removed       : Yes
  640.  
  641.  When internal fixups are present, the "Yes" is changed to a "No". 
  642.  
  643.  Chaining Fixups 
  644.  
  645.  As mentioned above, every time you call a function whose address isn't known 
  646.  at link time, the linker creates a fixup.  So if your program makes 25 calls 
  647.  to WinSendMsg, the linker will create 25 fixups that all reference WinSendMsg 
  648.  in PMWIN.DLL.  This is rather redundant, since much of the information in 
  649.  these 25 fixups is identical. 
  650.  
  651.  Fixup chaining is a very elegant optimization that addresses this redundancy. 
  652.  Instead of generating 25 fixups, the linker just generates one fixup and 
  653.  creates a linked list to all the places in the executable where this fixup 
  654.  must be applied.  This reduces the size of your executable, and also speeds up 
  655.  loading.  The time savings comes about because the loader only needs to 
  656.  determine the address of the function once; it then traverses the list and 
  657.  applies this address at each node on the list. 
  658.  
  659.  Compressing Code and Data 
  660.  
  661.  Earlier we looked at resource compression.  In a similar vein, OS/2 supports 
  662.  compression of code and data, which is done during the link step. OS/2 2.x 
  663.  supports run-length encoding only, but OS/2 Warp also supports a newer scheme 
  664.  that usually yields much better results. 
  665.  
  666.  The compression tradeoffs discussed in relation to resources apply here as 
  667.  well.  Programs with compressed code and data may load a bit slower in a 386 
  668.  machine, but often load faster in machines with faster processors.  Also, 
  669.  compressing your code and data saves disk space only; it doesn't reduce your 
  670.  program's RAM requirements. 
  671.  
  672.  Building Smaller OS/2 Executables (Part 1) - EDM/2 - Mar 1995 - Volume 3, 
  673.  Issue 3 
  674.  
  675.  
  676. ΓòÉΓòÉΓòÉ 4.5. Applying Linker Optimizations ΓòÉΓòÉΓòÉ
  677.  
  678. Now I'll take a look at various linkers.  For each one, I'll mention which 
  679. optimizations are supported, and how to enable or disable these optimizations. 
  680.  
  681. TLINK (Borland C++) 
  682.  
  683. Borland's linker, TLINK, has three switches that help reduce the size of a 
  684. generated executable:  /A:dd, /B:0x10000, and /Oc. /A:dd specifies the page 
  685. alignment for code and data.  Remember that the alignment must be a power of 
  686. two, and dd specifies which power of two you wish to use.  For EXEs, /B:0x10000 
  687. specifies a load address of 64K (0x10000 in hexadecimal), eliminating all 
  688. internal fixups.  /Oc enables fixup chaining, which is disabled by default. 
  689. Unfortunately, TLINK doesn't support compression of code or data. 
  690.  
  691. To see how these options affect the size of executables, I've linked the 
  692. "Clock" sample program from Borland C++ 1.5, using a variety of switches: 
  693.  
  694. ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
  695. ΓöéAlignment ΓöéSize      ΓöéSize with ΓöéSize with Γöé
  696. Γöé          Γöé          Γöé/B:0x10000Γöé/B:0x10000Γöé
  697. Γöé          Γöé          Γöé          Γöé/Oc       Γöé
  698. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  699. Γöé/A        Γöé89,350    Γöé76,820    Γöé75,388    Γöé
  700. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  701. Γöé/A        Γöé89,356    Γöé76,824    Γöé75,392    Γöé
  702. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  703. Γöé/A        Γöé89,412    Γöé76,884    Γöé75,444    Γöé
  704. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  705. Γöé/A        Γöé92,724    Γöé79,924    Γöé78,388    Γöé
  706. ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
  707.  
  708. Figure 3 - Effect of various TLINK switches on the size of CLOCK.EXE. 
  709.  
  710. This table has a row for each alignment I tried, and columns for various 
  711. combinations of the /B:0x10000 and /Oc switches.  Recent recommendations I've 
  712. seen from IBM call for using an alignment of 2 or 4 bytes.  Since there's not 
  713. much difference between the two, I generally use 4 bytes.  I included the 16 
  714. byte row for comparison purposes, since this value is commonly used by 
  715. developers.  As you can see from the final column, linking with /B:0x10000 /Oc 
  716. gives a significant savings in size; these switches are highly recommended for 
  717. release builds. 
  718.  
  719. LINK386 (IBM C Set++) 
  720.  
  721. IBM's linker, LINK386, has four switches that can help you reduce your code 
  722. size:  /A[LIGNMENT]:n, /BAS[E]:0x10000, /E[XEPACK:{1|2}], and 
  723. /NOS[ECTORALIGNCODE].  The portion of each command which is enclosed in square 
  724. brackets is optional; I'll use the shorter forms from now on. 
  725.  
  726. The first of these, /A:n, lets you specify the alignment for code and data 
  727. pages.  The parameter n is the desired alignment in bytes, e.g. 4, 16, etc. 
  728. LINK386 uses 512 as its default alignment when /A:n isn't specified. 
  729.  
  730. I'll discuss /NOS next, since it relates to code alignment.  This switch is 
  731. new, first appearing in LINK386 version 2.02.001, which comes with the Warp 
  732. toolkit.  Previous versions of LINK386 used the /A:n switch for aligning both 
  733. code and data pages.  With version 2.02.001 (and presumably later versions) of 
  734. LINK386, /A:n affects only data pages; pages of code are always aligned on 512 
  735. byte boundaries so that they will begin on sector boundaries.  To override this 
  736. new behavior, you can supply the /NOS switch.  This forces LINK386 to use the 
  737. value given with /A:n for both code and data pages, just as earlier versions of 
  738. LINK386 did.  Note that this switch has no affect when you use an alignment of 
  739. 512. 
  740.  
  741. Since IBM has changed the default behavior of LINK386, you might imagine they 
  742. feel pretty strongly about keeping pages of code aligned on sector boundaries. 
  743. This seems to be the case; all recent build recommendations I've seen from IBM 
  744. suggest omitting the /NOS switch.  Remember that code pages are discardable, so 
  745. they may be loaded and reloaded a large number of times during a single 
  746. execution of your program.  I guess IBM feels it is very important to optimize 
  747. this load process, and keeping pages aligned on disk sectors is a help. 
  748.  
  749. For EXEs, /BAS:0x10000 specifies a load address of 64K, eliminating internal 
  750. fixups. 
  751.  
  752. Before I discuss the final switch, I should mention that fixup chaining is 
  753. always enabled in LINK386; no switches are provided to disable it. 
  754.  
  755. The final switch, /E:{1|2}, allows you to compress your code and data.  /E uses 
  756. run-length encoding for the compression, and this option has been present in 
  757. all versions of LINK386.  Version 2.02.001 of LINK386 allows you to suffix this 
  758. option with a number.  /E:1 is a synonym for /E; /E:2 enables both run-length 
  759. encoding and the new compression scheme. 
  760.  
  761. To see how these options affect the size of executables, I've linked the 
  762. "Clock" sample program from the 2.x toolkit, using a variety of switches 
  763. (/BAS:0x10000 was used for all links): 
  764.  
  765. ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
  766. ΓöéAlignment ΓöéSize      ΓöéSize with ΓöéSize with ΓöéSize with ΓöéSize with ΓöéSize with Γöé
  767. Γöé          Γöé          Γöé/NOS      Γöé/E:1      Γöé/E:1 /NOS Γöé/E:2      Γöé/E:2 /NOS Γöé
  768. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  769. Γöé/A        Γöé74,064    Γöé73,814    Γöé73,552    Γöé73,064    Γöé56,302    Γöé52,828    Γöé
  770. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  771. Γöé/A        Γöé74,068    Γöé73,820    Γöé73,556    Γöé73,072    Γöé56,308    Γöé52,848    Γöé
  772. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  773. Γöé/A        Γöé74,116    Γöé73,876    Γöé73,604    Γöé73,124    Γöé56,356    Γöé52,980    Γöé
  774. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  775. Γöé/A        Γöé76,852    Γöé76,852    Γöé76,340    Γöé76,340    Γöé59,444    Γöé59,444    Γöé
  776. ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
  777.  
  778. Figure 4 - Effect of LINK386 switches on the size of CLOCK.EXE. 
  779.  
  780. This table has a row for each alignment I tried, and columns which show various 
  781. combinations of the /E:{1|2} and /NOS switches.  By scanning across the bottom 
  782. row, you'll see that /NOS indeed has no effect with an alignment of 512. 
  783. Notice that /NOS makes the most difference when used with the /E:2 switch; it 
  784. has a much smaller impact elsewhere. 
  785.  
  786. WLINK (Watcom C/C++) 
  787.  
  788. Watcom's linker, WLINK, provides two options that affect executable size. The 
  789. Alignment option lets you specify page alignment, and the Offset option lets 
  790. you specify the load address for EXEs.  Unlike the other linkers I've 
  791. discussed, WLINK's defaults produce optimally small executables.  You won't be 
  792. able to reduce executable size any further by overriding these defaults. 
  793. Unfortunately, WLINK doesn't support fixup chaining or compression of code and 
  794. data. 
  795.  
  796. Building Smaller OS/2 Executables (Part 1) - EDM/2 - Mar 1995 - Volume 3, Issue 
  797.  
  798.  
  799. ΓòÉΓòÉΓòÉ 4.6. To Be Continued... ΓòÉΓòÉΓòÉ
  800.  
  801. So far I've shown that judicious selection of switches during linking and 
  802. resource binding can reduce executable size significantly.  Next time I'll look 
  803. at the compile step and also discuss a simple source code modification that can 
  804. produce further savings. 
  805.  
  806. In the meantime, if you haven't been using the techniques I've discussed in 
  807. this article, I encourage you to give them a try.  I'd enjoy hearing how much 
  808. savings they give you.  I'll also welcome any other comments, questions, or 
  809. suggestions you might have on this topic. 
  810.  
  811. Building Smaller OS/2 Executables (Part 1) - EDM/2 - Mar 1995 - Volume 3, Issue 
  812.  
  813.  
  814. ΓòÉΓòÉΓòÉ 5. The Design and Implementation of VIOWIN:  Part 6 ΓòÉΓòÉΓòÉ
  815.  
  816.  
  817. ΓòÉΓòÉΓòÉ 5.1. Introduction ΓòÉΓòÉΓòÉ
  818.  
  819. The Design and Implementation of VIOWIN:  Part 5 
  820.  
  821. Written by Larry Salomon, Jr. 
  822.  
  823. Introduction 
  824.  
  825. For my job, I once had to write an application that ran only when OS/2 booted 
  826. from the floppy diskettes.  Because I had no access to the functionality PM 
  827. provides, I resorted to a line-oriented interface, where messages were 
  828. displayed on the screen and scrolled up when necessary.  It was a good 
  829. interface, I thought; it was fully NLS enabled and had intelligent defaults so 
  830. the user basically only had to type in the name of the application. 
  831. Unfortunately, the Quality Assurance team didn't concur with my opinion.  "We 
  832. want a nice interface!"  one exclaimed.  "Yeah, one with different windows and 
  833. such!"  another shouted. 
  834.  
  835. I was backed into a corner that I could only get out of one way. 
  836.  
  837. This series describes the design and implementation of VIOWIN, a library that 
  838. implements a subset of the Win APIs provided by PM for fullscreen sessions. 
  839. The reasoning behind writing this series is that it provided me and will 
  840. hopefully provide you with some unique insights into how a windowing system is 
  841. developed; and since it is based on PM, your familiarity with the already 
  842. defined interface will increase your capability to fully understand what is 
  843. being described. 
  844.  
  845. Obviously, this series assumes you have PM application development experience, 
  846. but it isn't required. 
  847.  
  848. This Month 
  849.  
  850. This month, we will begin our look at the windows classes that are implemented 
  851. in VIOWIN. 
  852.  
  853. The Design and Implementation of VIOWIN:  Part 6 - EDM/2 - Mar 1995 - Volume 3, 
  854. Issue 3 
  855.  
  856.  
  857. ΓòÉΓòÉΓòÉ 5.2. Scaffolding ΓòÉΓòÉΓòÉ
  858.  
  859. Scaffolding 
  860.  
  861. First, we must look at how and where the classes are registered.  In issue 2-9, 
  862. we briefly looked at how ordinal 1 was loaded by vwInitialize() and called to 
  863. give the DLL an opportunity to perform any initialization it needed.  We could 
  864. have just as easily used the DLL initialization routine, which IBM's C-Set++ 
  865. product allows you to redefine. 
  866.  
  867. BOOL EXPENTRY vwInitDll(VOID)
  868. //-------------------------------------------------------------------------
  869. // This function registers the classes used by VIOWIN.
  870. //
  871. // Returns:  TRUE if successful, FALSE otherwise
  872. //-------------------------------------------------------------------------
  873. {
  874.    vwRegisterClass(VWWC_BUTTON,VwButtonClassProc);
  875.    vwRegisterClass(VWWC_ENTRYFIELD,VwEntryfieldClassProc);
  876.    vwRegisterClass(VWWC_LISTBOX,VwListboxClassProc);
  877.    vwRegisterClass(VWWC_SCROLLBAR,VwScrollbarClassProc);
  878.    vwRegisterClass(VWWC_STATIC,VwStaticClassProc);
  879.  
  880.    return TRUE;
  881. }
  882.  
  883. It needs to be mentioned that the various window procedures are exported in the 
  884. .DEF file, but this is probably unnecessary since the vwSendMsg() and 
  885. vwPostMsg() routines simply call the procedure directly using the function 
  886. address that was passed to vwRegisterClass(). 
  887.  
  888. The Design and Implementation of VIOWIN:  Part 6 - EDM/2 - Mar 1995 - Volume 3, 
  889. Issue 3 
  890.  
  891.  
  892. ΓòÉΓòÉΓòÉ 5.3. Entryfields ΓòÉΓòÉΓòÉ
  893.  
  894. Entryfields 
  895.  
  896. Let us now begin to look at the VWWC_ENTRYFIELD class.  Since we should all be 
  897. familiar with its PM counterpart, we will skip the introduction to the class 
  898. and will instead dive into the code itself.  The first thing that should be 
  899. discovered is the instance data used by the instantiations of the class. 
  900.  
  901. typedef struct _INSTDATA {
  902.    ULONG ulSzStruct;
  903.    BOOL bChanged;
  904.    BOOL bDirty;
  905.    BOOL bSetParms;
  906.    SHORT sAnchor;
  907.    SHORT sCursor;
  908.    SHORT sFirstChar;
  909.    PCHAR pchBuf;
  910.    USHORT usSzBuf;
  911. } INSTDATA, *PINSTDATA;
  912.  
  913. ulSzStruct is the size of the structure. 
  914.  
  915. bChanged is TRUE when the entryfield's contents have changed since the last 
  916. EM_QUERYCHANGED message. 
  917.  
  918. bDirty is TRUE when a call to vwSetWindowText() exceeded the text limit and, 
  919. thus, the text the VIOWIN system thinks the entryfield has is different from 
  920. what the entryfield really has. 
  921.  
  922. bSetParms is used in conjunction with bDirty to synchronize the differences in 
  923. window text. 
  924.  
  925. sAnchor is the anchor point. 
  926.  
  927. sCursor is the cursor point. 
  928.  
  929. sFirstChar is the first visible character index. 
  930.  
  931. pchBuf points to the buffer containing the text. 
  932.  
  933. usSzBuf is the size of the buffer. 
  934.  
  935. Utility Functions 
  936.  
  937. As you can well imagine, there are a number of tasks which are used frequently 
  938. or are complex enough to warrant a separate function.  These functions are 
  939. listed below: 
  940.  
  941. scrollText() - scrolls the text of the window by one-half of the window's width 
  942. either left or right. 
  943.  
  944. deltaCursor() - moves the cursor one position to the left or right. 
  945.  
  946. textChanged() - sets bChanged and bDirty and sends an EN_CHANGED notification 
  947. to the owner. 
  948.  
  949. handleVkey() - processes virtual keystrokes. 
  950.  
  951. handleChr() - processes non-virtual keystrokes. 
  952.  
  953. The Design and Implementation of VIOWIN:  Part 6 - EDM/2 - Mar 1995 - Volume 3, 
  954. Issue 3 
  955.  
  956.  
  957. ΓòÉΓòÉΓòÉ 5.4. The scrollText() Function ΓòÉΓòÉΓòÉ
  958.  
  959. The scrollText() Function 
  960.  
  961. The code for the function is presented below. 
  962.  
  963. BOOL scrollText(HVWWND hwndWnd,BOOL bLeft)
  964. //-------------------------------------------------------------------------
  965. // This function scrolls the text left or right by half the window's
  966. // width.
  967. //
  968. // Input:  hwndWnd - handle to the window
  969. //         bLeft - TRUE if scroll goes to the left, FALSE if to the right
  970. // Returns:  TRUE if successful, FALSE otherwise
  971. //-------------------------------------------------------------------------
  972. {
  973.    PINSTDATA pidData;
  974.    RECTL rclWnd;
  975.  
  976.    //----------------------------------------------------------------------
  977.    // Get the instance data and the window rectangle
  978.    //----------------------------------------------------------------------
  979.    pidData=vwQueryWindowPtr(hwndWnd,1);
  980.  
  981.    vwQueryWindowRect(hwndWnd,&rclWnd);
  982.    rclWnd.xRight--;
  983.    rclWnd.yTop--;
  984.  
  985.    //----------------------------------------------------------------------
  986.    // Calculate the new first character
  987.    //----------------------------------------------------------------------
  988.    if (bLeft) {
  989.       pidData->sFirstChar-=(rclWnd.xRight-rclWnd.xLeft)/2;
  990.    } else {
  991.       pidData->sFirstChar+=(rclWnd.xRight-rclWnd.xLeft)/2;
  992.    } /* endif */
  993.  
  994.    //----------------------------------------------------------------------
  995.    // Do boundary checks
  996.    //----------------------------------------------------------------------
  997.    if (pidData->sFirstChar<0) {
  998.       pidData->sFirstChar=0;
  999.    } /* endif */
  1000.  
  1001.    if (pidData->sFirstChar>strlen(pidData->pchBuf)-1) {
  1002.       pidData->sFirstChar=strlen(pidData->pchBuf)-1;
  1003.    } /* endif */
  1004.  
  1005.    //----------------------------------------------------------------------
  1006.    // Paint and notify the owner
  1007.    //----------------------------------------------------------------------
  1008.    vwSendMsg(hwndWnd,WM_PAINT,0,0);
  1009.  
  1010.    vwSendMsg(VWHWND_DESKTOP,
  1011.              WM_CONTROL,
  1012.              MPFROM2SHORT(vwQueryWindowUShort(hwndWnd,QWS_ID),
  1013.                           EN_SCROLL),
  1014.              MPFROMHWND(hwndWnd));
  1015.  
  1016.    return TRUE;
  1017. }
  1018.  
  1019. This function is fairly straightforward; it calculates the new "first visible 
  1020. character", performs a bounds check, sends the EN_SCROLL notification, and 
  1021. repaints the window. 
  1022.  
  1023. The Design and Implementation of VIOWIN:  Part 6 - EDM/2 - Mar 1995 - Volume 3, 
  1024. Issue 3 
  1025.  
  1026.  
  1027. ΓòÉΓòÉΓòÉ 5.5. The deltaCursor() Function ΓòÉΓòÉΓòÉ
  1028.  
  1029. The deltaCursor() Function 
  1030.  
  1031. The code for the function is presented below. 
  1032.  
  1033. BOOL deltaCursor(HVWWND hwndWnd,BOOL bLeft)
  1034. //-------------------------------------------------------------------------
  1035. // This function moves the cursor one character to the left or right
  1036. //
  1037. // Input:  hwndWnd - handle to the window
  1038. //         bLeft - TRUE if cursor goes to the left, FALSE if to the right
  1039. // Returns:  TRUE if successful, FALSE otherwise
  1040. //-------------------------------------------------------------------------
  1041. {
  1042.    PINSTDATA pidData;
  1043.    RECTL rclWnd;
  1044.  
  1045.    //----------------------------------------------------------------------
  1046.    // Get the instance data and the window rectangle
  1047.    //----------------------------------------------------------------------
  1048.    pidData=vwQueryWindowPtr(hwndWnd,1);
  1049.  
  1050.    vwQueryWindowRect(hwndWnd,&rclWnd);
  1051.    rclWnd.xRight--;
  1052.    rclWnd.yTop--;
  1053.  
  1054.    if (bLeft) {
  1055.       //-------------------------------------------------------------------
  1056.       // Check to see if we're already at the beginning
  1057.       //-------------------------------------------------------------------
  1058.       if (pidData->sCursor>0) {
  1059.          pidData->sCursor--;
  1060.  
  1061.          //----------------------------------------------------------------
  1062.          // If the cursor went beyond the left edge, scroll the window
  1063.          //----------------------------------------------------------------
  1064.          if (pidData->sCursor<pidData->sFirstChar) {
  1065.             scrollText(hwndWnd,TRUE);
  1066.          } /* endif */
  1067.  
  1068.          //----------------------------------------------------------------
  1069.          // Update the cursor position
  1070.          //----------------------------------------------------------------
  1071.          vwCreateCursor(hwndWnd,
  1072.                         pidData->sCursor-pidData->sFirstChar+1,
  1073.                         0,
  1074.                         1,
  1075.                         1,
  1076.                         CURSOR_SETPOS);
  1077.  
  1078.          return TRUE;
  1079.       } else {
  1080.          vwAlarm(WA_ERROR);
  1081.          return FALSE;
  1082.       } /* endif */
  1083.    } else {
  1084.       //-------------------------------------------------------------------
  1085.       // Check to see if we're already at the end
  1086.       //-------------------------------------------------------------------
  1087.       if (pidData->sCursor<strlen(pidData->pchBuf)) {
  1088.          pidData->sCursor++;
  1089.  
  1090.          //----------------------------------------------------------------
  1091.          // If the cursor went beyond the right edge, scroll the window
  1092.          //----------------------------------------------------------------
  1093.          if (pidData->sCursor>pidData->sFirstChar+rclWnd.xRight-2) {
  1094.             scrollText(hwndWnd,FALSE);
  1095.          } /* endif */
  1096.  
  1097.          //----------------------------------------------------------------
  1098.          // Update the cursor position
  1099.          //----------------------------------------------------------------
  1100.          vwCreateCursor(hwndWnd,
  1101.                         pidData->sCursor-pidData->sFirstChar+1,
  1102.                         0,
  1103.                         1,
  1104.                         1,
  1105.                         CURSOR_SETPOS);
  1106.  
  1107.          return TRUE;
  1108.       } else {
  1109.          vwAlarm(WA_ERROR);
  1110.          return FALSE;
  1111.       } /* endif */
  1112.    } /* endif */
  1113. }
  1114.  
  1115. This function is a bit more complication, but should still be easy to 
  1116. understand.  It calculates the new cursor position and calls scrollText() if 
  1117. the cursor goes off-screen. 
  1118.  
  1119. The Design and Implementation of VIOWIN:  Part 6 - EDM/2 - Mar 1995 - Volume 3, 
  1120. Issue 3 
  1121.  
  1122.  
  1123. ΓòÉΓòÉΓòÉ 5.6. The textChanged() Function ΓòÉΓòÉΓòÉ
  1124.  
  1125. The textChanged() Function 
  1126.  
  1127. The code for the function is presented below. 
  1128.  
  1129. VOID textChanged(HWND hwndWnd)
  1130. //-------------------------------------------------------------------------
  1131. // This function simply performs a common task of setting the changed
  1132. // and dirty flags and sending the owner a changed notification.
  1133. //
  1134. // Input:  hwndWnd - handle to the window
  1135. //-------------------------------------------------------------------------
  1136. {
  1137.    PINSTDATA pidData;
  1138.  
  1139.    pidData=vwQueryWindowPtr(hwndWnd,1);
  1140.  
  1141.    pidData->bChanged=TRUE;
  1142.    pidData->bDirty=TRUE;
  1143.  
  1144.    vwSendMsg(VWHWND_DESKTOP,
  1145.              WM_CONTROL,
  1146.              MPFROM2SHORT(vwQueryWindowUShort(hwndWnd,QWS_ID),EN_CHANGE),
  1147.              MPFROMHWND(hwndWnd));
  1148. }
  1149.  
  1150. This code is trivial. 
  1151.  
  1152. The Design and Implementation of VIOWIN:  Part 6 - EDM/2 - Mar 1995 - Volume 3, 
  1153. Issue 3 
  1154.  
  1155.  
  1156. ΓòÉΓòÉΓòÉ 5.7. The handleVkey() Function ΓòÉΓòÉΓòÉ
  1157.  
  1158. The handleVkey() Function 
  1159.  
  1160. The code for the function is presented below. 
  1161.  
  1162. BOOL handleVkey(struct _CHARMSG *pcmMsg,HVWWND hwndWnd)
  1163. //-------------------------------------------------------------------------
  1164. // This function processes the virtual keystrokes.
  1165. //
  1166. // Input:  pcmMsg - points to the CHARMSG structure
  1167. //         hwndWnd - handle to the window
  1168. //
  1169. // Returns:  TRUE if successful, FALSE otherwise
  1170. //-------------------------------------------------------------------------
  1171. {
  1172.    PINSTDATA pidData;
  1173.    RECTL rclWnd;
  1174.    ULONG ulMods;
  1175.    ULONG ulIndex;
  1176.  
  1177.    //----------------------------------------------------------------------
  1178.    // Get the instance data and the window rectangle
  1179.    //----------------------------------------------------------------------
  1180.    pidData=vwQueryWindowPtr(hwndWnd,1);
  1181.  
  1182.    vwQueryWindowRect(hwndWnd,&rclWnd);
  1183.    rclWnd.xRight--;
  1184.    rclWnd.yTop--;
  1185.  
  1186.    ulMods=KC_CTRL | KC_ALT | KC_SHIFT;
  1187.  
  1188.    //----------------------------------------------------------------------
  1189.    // Check for Control or Alt or Shift key down
  1190.    //----------------------------------------------------------------------
  1191.    switch (pcmMsg->fs & ulMods) {
  1192.    case 0:
  1193.       //-------------------------------------------------------------------
  1194.       // None of them were down
  1195.       //-------------------------------------------------------------------
  1196.       switch (pcmMsg->vkey) {
  1197.       case VK_LEFT:
  1198.          //----------------------------------------------------------------
  1199.          // Move the cursor
  1200.          //----------------------------------------------------------------
  1201.          if (!deltaCursor(hwndWnd,TRUE)) {
  1202.             return FALSE;
  1203.          } /* endif */
  1204.          break;
  1205.       case VK_RIGHT:
  1206.          //----------------------------------------------------------------
  1207.          // Move the cursor
  1208.          //----------------------------------------------------------------
  1209.          if (!deltaCursor(hwndWnd,FALSE)) {
  1210.             return FALSE;
  1211.          } /* endif */
  1212.          break;
  1213.       case VK_BACKSPACE:
  1214.          {
  1215.             PCHAR pchFirst;
  1216.  
  1217.             //-------------------------------------------------------------
  1218.             // Move the cursor and move the text in the buffer to the
  1219.             // left one character, effectively erasing the current
  1220.             // character
  1221.             //-------------------------------------------------------------
  1222.             if (deltaCursor(hwndWnd,TRUE)) {
  1223.                pchFirst=&pidData->pchBuf[pidData->sCursor];
  1224.                strcpy(pchFirst,pchFirst+1);
  1225.  
  1226.                vwSendMsg(hwndWnd,WM_PAINT,0,0);
  1227.                textChanged(hwndWnd);
  1228.             } else {
  1229.                return FALSE;
  1230.             } /* endif */
  1231.          }
  1232.          break;
  1233.       case VK_INSERT:
  1234.          //----------------------------------------------------------------
  1235.          // Toggle the insertion mode
  1236.          //----------------------------------------------------------------
  1237.          vwSendMsg(hwndWnd,
  1238.                    EM_SETINSERTMODE,
  1239.                    MPFROMLONG(!vwQuerySysValue(VWSV_INSERTMODE)),
  1240.                    0);
  1241.          break;
  1242.       case VK_DELETE:
  1243.          {
  1244.             PCHAR pchFirst;
  1245.  
  1246.             //-------------------------------------------------------------
  1247.             // Move the cursor and move the text in the buffer to the
  1248.             // left one character, effectively erasing the character
  1249.             // to the right of the cursor
  1250.             //-------------------------------------------------------------
  1251.             if (pidData->sCursor<strlen(pidData->pchBuf)) {
  1252.                pchFirst=&pidData->pchBuf[pidData->sCursor];
  1253.                strcpy(pchFirst,pchFirst+1);
  1254.  
  1255.                vwSendMsg(hwndWnd,WM_PAINT,0,0);
  1256.                textChanged(hwndWnd);
  1257.             } else {
  1258.                vwAlarm(WA_ERROR);
  1259.                return FALSE;
  1260.             } /* endif */
  1261.          }
  1262.          break;
  1263.       case VK_SPACE:
  1264.          if (pidData->sCursor<pidData->usSzBuf) {
  1265.             //-------------------------------------------------------------
  1266.             // Insert a space or overwrite the current character with a
  1267.             // space
  1268.             //-------------------------------------------------------------
  1269.             if (vwQuerySysValue(VWSV_INSERTMODE)) {
  1270.                if (strlen(pidData->pchBuf)<pidData->usSzBuf) {
  1271.                   for (ulIndex=strlen(pidData->pchBuf)+1;
  1272.                        ulIndex>pidData->sCursor;
  1273.                        ulIndex--) {
  1274.                       pidData->pchBuf[ulIndex]=pidData->pchBuf[ulIndex-1];
  1275.                   } /* endfor */
  1276.                } else {
  1277.                   vwAlarm(WA_ERROR);
  1278.                   return FALSE;
  1279.                } /* endif */
  1280.             } /* endif */
  1281.  
  1282.             pidData->pchBuf[pidData->sCursor]=' ';
  1283.             deltaCursor(hwndWnd,FALSE);
  1284.  
  1285.             vwSendMsg(hwndWnd,WM_PAINT,0,0);
  1286.             textChanged(hwndWnd);
  1287.          } else {
  1288.             vwAlarm(WA_ERROR);
  1289.             return FALSE;
  1290.          } /* endif */
  1291.          break;
  1292.       case VK_HOME:
  1293.          //----------------------------------------------------------------
  1294.          // Move the cursor to the first position
  1295.          //----------------------------------------------------------------
  1296.          pidData->sFirstChar=0;
  1297.          pidData->sCursor=pidData->sFirstChar;
  1298.  
  1299.          vwSendMsg(hwndWnd,WM_PAINT,0,0);
  1300.  
  1301.          vwCreateCursor(hwndWnd,
  1302.                         pidData->sCursor-pidData->sFirstChar+1,
  1303.                         0,
  1304.                         1,
  1305.                         1,
  1306.                         CURSOR_SETPOS);
  1307.          break;
  1308.       case VK_END:
  1309.          //----------------------------------------------------------------
  1310.          // Move the cursor to the last position
  1311.          //----------------------------------------------------------------
  1312.          pidData->sCursor=strlen(pidData->pchBuf)-1;
  1313.          pidData->sFirstChar=pidData->sCursor-(rclWnd.xRight-rclWnd.xLeft)/2;
  1314.  
  1315.          if (pidData->sFirstChar<0) {
  1316.             pidData->sFirstChar=0;
  1317.          } /* endif */
  1318.  
  1319.          vwSendMsg(hwndWnd,WM_PAINT,0,0);
  1320.  
  1321.          vwCreateCursor(hwndWnd,
  1322.                         pidData->sCursor-pidData->sFirstChar+1,
  1323.                         0,
  1324.                         1,
  1325.                         1,
  1326.                         CURSOR_SETPOS);
  1327.          break;
  1328.       default:
  1329.          return FALSE;
  1330.       } /* endswitch */
  1331.       break;
  1332.    case KC_CTRL:
  1333.       //-------------------------------------------------------------------
  1334.       // Control key was down
  1335.       //-------------------------------------------------------------------
  1336.       switch (pcmMsg->vkey) {
  1337.       case VK_LEFT:
  1338.          //----------------------------------------------------------------
  1339.          // Ctrl-Left is the same as "home"
  1340.          //----------------------------------------------------------------
  1341.          pidData->sCursor=0;
  1342.          pidData->sFirstChar=pidData->sCursor;
  1343.  
  1344.          vwSendMsg(hwndWnd,WM_PAINT,0,0);
  1345.  
  1346.          vwCreateCursor(hwndWnd,
  1347.                         pidData->sCursor-pidData->sFirstChar+1,
  1348.                         0,
  1349.                         1,
  1350.                         1,
  1351.                         CURSOR_SETPOS);
  1352.          break;
  1353.       case VK_RIGHT:
  1354.          //----------------------------------------------------------------
  1355.          // Ctrl-Right is the same as "end"
  1356.          //----------------------------------------------------------------
  1357.          pidData->sFirstChar=strlen(pidData->pchBuf)-1;
  1358.          pidData->sCursor=pidData->sFirstChar;
  1359.  
  1360.          vwSendMsg(hwndWnd,WM_PAINT,0,0);
  1361.  
  1362.          vwCreateCursor(hwndWnd,
  1363.                         pidData->sCursor-pidData->sFirstChar+1,
  1364.                         0,
  1365.                         1,
  1366.                         1,
  1367.                         CURSOR_SETPOS);
  1368.          break;
  1369.       default:
  1370.          return FALSE;
  1371.       } /* endswitch */
  1372.       break;
  1373.    default:
  1374.       return FALSE;
  1375.    } /* endswitch */
  1376.  
  1377.    return TRUE;
  1378. }
  1379.  
  1380. This function is one of the workhorses (the other being the handleChr() 
  1381. function).  It classifies the keystroke according to the modifiers (Ctrl, Alt, 
  1382. or Shift) and processes the keys individually.  It relies heavily on 
  1383. deltaCursor(). 
  1384.  
  1385. The Design and Implementation of VIOWIN:  Part 6 - EDM/2 - Mar 1995 - Volume 3, 
  1386. Issue 3 
  1387.  
  1388.  
  1389. ΓòÉΓòÉΓòÉ 5.8. The handleChr() Function ΓòÉΓòÉΓòÉ
  1390.  
  1391. The handleChr() Function 
  1392.  
  1393. The code for the function is presented below. 
  1394.  
  1395. BOOL handleChr(struct _CHARMSG *pcmMsg,HVWWND hwndWnd)
  1396. //-------------------------------------------------------------------------
  1397. // This function processes the non-virtual keystrokes.
  1398. //
  1399. // Input:  pcmMsg - points to the CHARMSG structure
  1400. //         hwndWnd - handle to the window
  1401. //
  1402. // Returns:  TRUE if successful, FALSE otherwise
  1403. //-------------------------------------------------------------------------
  1404. {
  1405.    PINSTDATA pidData;
  1406.    ULONG ulMods;
  1407.    ULONG ulIndex;
  1408.  
  1409.    pidData=vwQueryWindowPtr(hwndWnd,1);
  1410.    ulMods=KC_CTRL | KC_ALT | KC_SHIFT;
  1411.  
  1412.    switch (pcmMsg->fs & ulMods) {
  1413.    case 0:
  1414.    case KC_SHIFT:
  1415.       if (pidData->sCursor<pidData->usSzBuf) {
  1416.          //----------------------------------------------------------------
  1417.          // Insert the character or overwrite the current character with
  1418.          // the character
  1419.          //----------------------------------------------------------------
  1420.          if (vwQuerySysValue(VWSV_INSERTMODE)) {
  1421.             if (strlen(pidData->pchBuf)<pidData->usSzBuf) {
  1422.                for (ulIndex=strlen(pidData->pchBuf)+1;
  1423.                     ulIndex>pidData->sCursor;
  1424.                     ulIndex--) {
  1425.                    pidData->pchBuf[ulIndex]=pidData->pchBuf[ulIndex-1];
  1426.                } /* endfor */
  1427.             } else {
  1428.                vwAlarm(WA_ERROR);
  1429.                return FALSE;
  1430.             } /* endif */
  1431.          } /* endif */
  1432.  
  1433.          pidData->pchBuf[pidData->sCursor]=pcmMsg->chr;
  1434.          deltaCursor(hwndWnd,FALSE);
  1435.          vwSendMsg(hwndWnd,WM_PAINT,0,0);
  1436.          textChanged(hwndWnd);
  1437.  
  1438.          vwCreateCursor(hwndWnd,
  1439.                         pidData->sCursor-pidData->sFirstChar+1,
  1440.                         0,
  1441.                         1,
  1442.                         1,
  1443.                         CURSOR_SETPOS);
  1444.       } else {
  1445.          vwAlarm(WA_ERROR);
  1446.          return FALSE;
  1447.       } /* endif */
  1448.       break;
  1449.    default:
  1450.       return FALSE;
  1451.    } /* endswitch */
  1452.  
  1453.    return TRUE;
  1454. }
  1455.  
  1456. This function inserts the character pressed into the buffer or overwrites the 
  1457. character at the current cursor position with the character pressed. 
  1458.  
  1459. The Design and Implementation of VIOWIN:  Part 6 - EDM/2 - Mar 1995 - Volume 3, 
  1460. Issue 3 
  1461.  
  1462.  
  1463. ΓòÉΓòÉΓòÉ 5.9. The Window Procedure ΓòÉΓòÉΓòÉ
  1464.  
  1465. The Window Procedure 
  1466.  
  1467. The window procedure is shown below. 
  1468.  
  1469. MRESULT EXPENTRY VwEntryfieldClassProc(HVWWND hwndWnd,
  1470.                                        ULONG ulMsg,
  1471.                                        MPARAM mpParm1,
  1472.                                        MPARAM mpParm2)
  1473. {
  1474.    PINSTDATA pidData;
  1475.  
  1476.    pidData=vwQueryWindowPtr(hwndWnd,1);
  1477.  
  1478.    switch (ulMsg) {
  1479.    case WM_CREATE:
  1480.       //-------------------------------------------------------------------
  1481.       // Allocate and initialize the instance data
  1482.       //-------------------------------------------------------------------
  1483.       pidData=calloc(1,sizeof(INSTDATA));
  1484.       if (pidData==NULL) {
  1485.          return MRFROMSHORT(TRUE);
  1486.       } /* endif */
  1487.  
  1488.       vwSetWindowPtr(hwndWnd,1,pidData);
  1489.  
  1490.       pidData->ulSzStruct=sizeof(pidData);
  1491.  
  1492.       pidData->bChanged=FALSE;
  1493.       pidData->bDirty=FALSE;
  1494.       pidData->bSetParms=FALSE;
  1495.       pidData->sAnchor=0;
  1496.       pidData->sCursor=0;
  1497.       pidData->sFirstChar=0;
  1498.       pidData->pchBuf=NULL;
  1499.       pidData->usSzBuf=0;
  1500.  
  1501.       vwSendMsg(hwndWnd,EM_SETTEXTLIMIT,MPFROMSHORT(32),0);
  1502.       break;
  1503.    case WM_DESTROY:
  1504.       free(pidData);
  1505.       break;
  1506.    case WM_PAINT:
  1507.       {
  1508.          RECTL rclWnd;
  1509.          CHAR achText[256];
  1510.          ULONG ulFore;
  1511.          ULONG ulBack;
  1512.          PCHAR pchFirst;
  1513.          USHORT usLenVisible;
  1514.          ULONG ulStyle;
  1515.  
  1516.          //----------------------------------------------------------------
  1517.          // Get the window rectangle
  1518.          //----------------------------------------------------------------
  1519.          vwQueryWindowRect(hwndWnd,&rclWnd);
  1520.          rclWnd.xRight--;
  1521.          rclWnd.yTop--;
  1522.  
  1523.          //----------------------------------------------------------------
  1524.          // Initialize the buffer that we will draw to "show" ourselves
  1525.          //----------------------------------------------------------------
  1526.          memset(achText,' ',sizeof(achText));
  1527.          achText[sizeof(achText)-1]=0;
  1528.  
  1529.          //----------------------------------------------------------------
  1530.          // Get the colors and toggle them if we have the focus
  1531.          //----------------------------------------------------------------
  1532.          ulFore=vwQueryForeColor(hwndWnd);
  1533.          ulBack=vwQueryBackColor(hwndWnd);
  1534.  
  1535.          if (vwQueryFocus()==hwndWnd) {
  1536.             ulFore^=0x000000FF;
  1537.             ulBack^=0x000000FF;
  1538.          } /* endif */
  1539.  
  1540.          //----------------------------------------------------------------
  1541.          // Put the brackets in the buffer
  1542.          //----------------------------------------------------------------
  1543.          achText[0]='[';
  1544.          achText[rclWnd.xRight]=']';
  1545.  
  1546.          //----------------------------------------------------------------
  1547.          // Copy the text to the buffer.  If unreadable, set the
  1548.          // text in the buffer to '*'.
  1549.          //----------------------------------------------------------------
  1550.          pchFirst=&pidData->pchBuf[pidData->sFirstChar];
  1551.          usLenVisible=min(rclWnd.xRight-2,strlen(pchFirst));
  1552.  
  1553.          ulStyle=vwQueryWindowULong(hwndWnd,QWL_STYLE);
  1554.  
  1555.          if ((ulStyle & ES_UNREADABLE)!=0) {
  1556.             memset(&achText[1],'*',usLenVisible);
  1557.          } else {
  1558.             memcpy(&achText[1],pchFirst,usLenVisible);
  1559.          } /* endif */
  1560.  
  1561.          vwDrawText(hwndWnd,
  1562.                     -1,
  1563.                     achText,
  1564.                     NULL,
  1565.                     ulFore,
  1566.                     ulBack,
  1567.                     DT_LEFT|DT_TOP|DT_ERASERECT);
  1568.       }
  1569.       break;
  1570.    case WM_CHAR:
  1571.       {
  1572.          ULONG ulFlags;
  1573.  
  1574.          ulFlags=KC_KEYUP;
  1575.  
  1576.          if ((CHARMSG(&ulMsg)->fs & ulFlags)!=0) {
  1577.             return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
  1578.          } /* endif */
  1579.  
  1580.          if ((CHARMSG(&ulMsg)->fs & KC_VIRTUALKEY)!=0) {
  1581.             if (!handleVkey(CHARMSG(&ulMsg),hwndWnd)) {
  1582.                return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
  1583.             } /* endif */
  1584.          } else {
  1585.             if (!handleChr(CHARMSG(&ulMsg),hwndWnd)) {
  1586.                return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
  1587.             } /* endif */
  1588.          } /* endif */
  1589.       }
  1590.       return MRFROMSHORT(TRUE);
  1591.    case WM_QUERYDLGCODE:
  1592.       return MRFROMLONG(DLGC_ENTRYFIELD);
  1593.    case WM_SETFOCUS:
  1594.       if (SHORT1FROMMP(mpParm2)) {
  1595.          vwSendMsg(VWHWND_DESKTOP,
  1596.                    WM_CONTROL,
  1597.                    MPFROM2SHORT(vwQueryWindowUShort(hwndWnd,QWS_ID),
  1598.                                 EN_SETFOCUS),
  1599.                    MPFROMHWND(hwndWnd));
  1600.  
  1601.          vwCreateCursor(hwndWnd,
  1602.                         pidData->sCursor-pidData->sFirstChar+1,
  1603.                         0,
  1604.                         1,
  1605.                         1,
  1606.                         0);
  1607.          vwShowCursor(hwndWnd,TRUE);
  1608.       } else {
  1609.          vwSendMsg(VWHWND_DESKTOP,
  1610.                    WM_CONTROL,
  1611.                    MPFROM2SHORT(vwQueryWindowUShort(hwndWnd,QWS_ID),
  1612.                                 EN_KILLFOCUS),
  1613.                    MPFROMHWND(hwndWnd));
  1614.  
  1615.          vwShowCursor(hwndWnd,FALSE);
  1616.          vwDestroyCursor(hwndWnd);
  1617.       } /* endif */
  1618.       break;
  1619.    case WM_SETWINDOWPARAMS:
  1620.       {
  1621.          PWNDPARAMS pwpParms;
  1622.          MRESULT mrRc;
  1623.  
  1624.          pwpParms=(PWNDPARAMS)PVOIDFROMMP(mpParm1);
  1625.  
  1626.          mrRc=vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
  1627.  
  1628.          //----------------------------------------------------------------
  1629.          // This is the tricky part:
  1630.          //
  1631.          // If the application calls vwSetWindowText(), then we want to
  1632.          // execute normally.  However, WM_QUERYWINDOWPARAMS calls
  1633.          // vwSetWindowText() whenever the "dirty" flag is set, so
  1634.          // we don't want to do this processing in that case.
  1635.          //----------------------------------------------------------------
  1636.          if ((pwpParms->fsStatus==WPM_TEXT) && (!pidData->bSetParms)) {
  1637.             *pidData->pchBuf=0;
  1638.             strncat(pidData->pchBuf,pwpParms->pszText,pidData->usSzBuf-1);
  1639.  
  1640.             if (pidData->usSzBuf<pwpParms->cchText) {
  1641.                pidData->bDirty=TRUE;
  1642.             } else {
  1643.                pidData->bDirty=FALSE;
  1644.             } /* endif */
  1645.  
  1646.             pidData->bChanged=TRUE;
  1647.             pidData->sCursor=0;
  1648.             pidData->sFirstChar=0;
  1649.  
  1650.             vwSendMsg(hwndWnd,WM_PAINT,0,0);
  1651.          } /* endif */
  1652.  
  1653.          return mrRc;
  1654.       }
  1655.    case WM_QUERYWINDOWPARAMS:
  1656.       {
  1657.          PWNDPARAMS pwpParms;
  1658.  
  1659.          pwpParms=(PWNDPARAMS)PVOIDFROMMP(mpParm1);
  1660.  
  1661.          switch (pwpParms->fsStatus) {
  1662.          case WPM_CCHTEXT:
  1663.          case WPM_TEXT:
  1664.             //-------------------------------------------------------------
  1665.             // If we're "dirty", set bSetParms to TRUE and call
  1666.             // vwSetWindowText() to synch VIOWIN's version of our
  1667.             // text with our own copy.
  1668.             //-------------------------------------------------------------
  1669.             if (pidData->bDirty) {
  1670.                pidData->bSetParms=TRUE;
  1671.                vwSetWindowText(hwndWnd,pidData->pchBuf);
  1672.                pidData->bSetParms=FALSE;
  1673.  
  1674.                pidData->bDirty=FALSE;
  1675.             } /* endif */
  1676.             break;
  1677.          default:
  1678.             break;
  1679.          } /* endswitch */
  1680.  
  1681.          return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
  1682.       }
  1683.    case EM_QUERYCHANGED:
  1684.       {
  1685.          BOOL bChanged;
  1686.  
  1687.          bChanged=pidData->bChanged;
  1688.          pidData->bChanged=FALSE;
  1689.          return MRFROMSHORT(bChanged);
  1690.       }
  1691.    case EM_QUERYSEL:
  1692.       break;
  1693.    case EM_SETSEL:
  1694.       break;
  1695.    case EM_SETTEXTLIMIT:
  1696.       {
  1697.          PCHAR pchBuf;
  1698.  
  1699.          //----------------------------------------------------------------
  1700.          // Allocate a new buffer, free the current one, and set
  1701.          // pidData->pchBuf to the new buffer
  1702.          //----------------------------------------------------------------
  1703.          pchBuf=calloc(1,SHORT1FROMMP(mpParm1)+1);
  1704.          if (pchBuf==NULL) {
  1705.             return MRFROMSHORT(FALSE);
  1706.          } /* endif */
  1707.  
  1708.          if (pidData->pchBuf!=NULL) {
  1709.             *pchBuf=0;
  1710.             strncat(pchBuf,pidData->pchBuf,SHORT1FROMMP(mpParm1));
  1711.  
  1712.             free(pidData->pchBuf);
  1713.          } /* endif */
  1714.  
  1715.          pidData->pchBuf=pchBuf;
  1716.          pidData->usSzBuf=SHORT1FROMMP(mpParm1);
  1717.          return MRFROMSHORT(TRUE);
  1718.       }
  1719.    case EM_CUT:
  1720.       break;
  1721.    case EM_COPY:
  1722.       break;
  1723.    case EM_PASTE:
  1724.       break;
  1725.    case EM_QUERYFIRSTCHAR:
  1726.       return MRFROMSHORT(pidData->sFirstChar);
  1727.    case EM_SETFIRSTCHAR:
  1728.       {
  1729.          SHORT sFirstChar;
  1730.  
  1731.          sFirstChar=SHORT1FROMMP(mpParm1);
  1732.          if ((sFirstChar>0) && (sFirstChar<strlen(pidData->pchBuf))) {
  1733.             pidData->sFirstChar=sFirstChar;
  1734.             return MRFROMSHORT(TRUE);
  1735.          } else {
  1736.             return MRFROMSHORT(FALSE);
  1737.          } /* endif */
  1738.       }
  1739.    case EM_QUERYREADONLY:
  1740.       {
  1741.          ULONG ulStyle;
  1742.  
  1743.          ulStyle=vwQueryWindowULong(hwndWnd,QWL_STYLE);
  1744.          return MRFROMSHORT((ulStyle & ES_READONLY)!=0);
  1745.       }
  1746.    case EM_SETREADONLY:
  1747.       {
  1748.          ULONG ulStyle;
  1749.  
  1750.          ulStyle=vwQueryWindowULong(hwndWnd,QWL_STYLE);
  1751.          ulStyle=ulStyle & (~ES_READONLY) |
  1752.                     (SHORT1FROMMP(mpParm1) ? ES_READONLY : 0);
  1753.          vwSetWindowULong(hwndWnd,QWL_STYLE,ulStyle);
  1754.          return MRFROMSHORT(TRUE);
  1755.       }
  1756.    case EM_SETINSERTMODE:
  1757.       {
  1758.          BOOL bInsert;
  1759.  
  1760.          bInsert=(SHORT1FROMMP(mpParm1)!=0);
  1761.          vwSetSysValue(VWSV_INSERTMODE,bInsert);
  1762.       }
  1763.       break;
  1764.    default:
  1765.       return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
  1766.    } /* endswitch */
  1767.  
  1768.    return MRFROMLONG(FALSE);
  1769. }
  1770.  
  1771. The window procedure, too, is fairly trivial, but there are two noteworthy 
  1772. pieces:  the processing for the WM_SETWINDOWPARAMS and WM_QUERYWINDOWPARAMS 
  1773. messages.  Here is where the bDirty and bSetParms flags are used.  Since it 
  1774. would be costly, in terms of performance, to call vwSetWindowText() everytime a 
  1775. character is pressed, the entryfield maintains its own copy of the text and 
  1776. only updates the system's version of the text when someone requests it via 
  1777. vwQueryWindowText().  When this function is called, the system sends us a 
  1778. WM_QUERYWINDOWPARAMS message, during which we check bDirty.  If it is TRUE, we 
  1779. set bSetParms and call vwSetWindowText().  This results in us receiving a 
  1780. WM_SETWINDOWPARAMS message, but we don't want to update ourselves because of 
  1781. the annoying flicker that will take place.  So, only if bSetParms is not set do 
  1782. we update the internal buffer. 
  1783.  
  1784. The Design and Implementation of VIOWIN:  Part 6 - EDM/2 - Mar 1995 - Volume 3, 
  1785. Issue 3 
  1786.  
  1787.  
  1788. ΓòÉΓòÉΓòÉ 5.10. Conclusion ΓòÉΓòÉΓòÉ
  1789.  
  1790. Conclusion 
  1791.  
  1792. This month, we looked at the code for the entryfield.  Next month, we will look 
  1793. at the next window class, VWWC_STATIC.  Please send me your thoughts on the 
  1794. approach I've taken this month, so that I can modify it as necessary. 
  1795.  
  1796. Please note that the source is not included in any of the .ZIP files that 
  1797. accompany this issue.  When all of the window classes have been discussed, the 
  1798. source for the entire class library will be given as well as the source for the 
  1799. VIOWIN library. 
  1800.  
  1801. The Design and Implementation of VIOWIN:  Part 6 - EDM/2 - Mar 1995 - Volume 3, 
  1802. Issue 3 
  1803.  
  1804.  
  1805. ΓòÉΓòÉΓòÉ 6. KEYBOARD.DCP File Format ΓòÉΓòÉΓòÉ
  1806.  
  1807.  
  1808. ΓòÉΓòÉΓòÉ 6.1. Introduction ΓòÉΓòÉΓòÉ
  1809.  
  1810. KEYBOARD.DCP File Format 
  1811.  
  1812. Written by Martin Lafaix 
  1813.  
  1814. Introduction 
  1815.  
  1816. What's That? 
  1817.  
  1818. Keyboard layouts are stored in KEYBOARD.DCP  These layouts are used when 
  1819. converting a raw scancode (that is, the value sent by the keyboard controller) 
  1820. to its corresponding value (that is, a symbol, like "A", or a virtual key code, 
  1821. like F1 or CapsLock). 
  1822.  
  1823. Knowing this file format would allowed us to create customized layouts, or new 
  1824. layouts for specific keyboards, or whatever.  Wouldn't it be nice? 
  1825.  
  1826. Another advantage would be the possibility for us to write our own keyboard 
  1827. handler; that is, something which converts a scancode to a key value. 
  1828. Naturally, we can already do that, but we have to define the keyboard layout, 
  1829. which is, er, boring ?  Reinventing the wheel is not always funny! 
  1830.  
  1831. Why? 
  1832.  
  1833. Well, to please our beloved editor. <grin> 
  1834.  
  1835. Contents 
  1836.  
  1837. This article describes the OS/2 2.x KEYBOARD.DCP file format. It contains three 
  1838. main parts:  the first one being the description properly-speaking, the second 
  1839. one explaining how to translate a raw scancode to an OS/2 key code and the 
  1840. third one describing a keyboard layout manipulation tool. 
  1841.  
  1842. Credit 
  1843.  
  1844. The first part of this paper is mainly based upon Ned Konz's SWAPDCP tool, 
  1845. available from your favorite ftp site. 
  1846.  
  1847. KEYBOARD.DCP File Format - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  1848.  
  1849.  
  1850. ΓòÉΓòÉΓòÉ 6.2. KEYBOARD.DCP File Format ΓòÉΓòÉΓòÉ
  1851.  
  1852. KEYBOARD.DCP File Format 
  1853.  
  1854. How is KEYBOARD.DCP Organized? 
  1855.  
  1856. The first four bytes of KEYBOARD.DCP contain the index table offset (0-based), 
  1857. ito. 
  1858.  
  1859. The first two bytes of the index table contain the index entry count, iec. 
  1860. Following this index entry count are iec Index Entries. Each index entry is as 
  1861. follow: 
  1862.  
  1863. typedef struct
  1864. {
  1865.   WORD    word1;
  1866.   BYTE    Country[2];       /* i.e. "US" */
  1867.   BYTE    SubCountryID[4];  /* i.e. "153 " */
  1868.   WORD    word2;
  1869.   WORD    XTableID;         /* i.e. 0x1b5 (437) */
  1870.   WORD    KbdType;
  1871.   ULONG   HeaderLocation;   /* of beginning of table (header)
  1872. */
  1873. } IndexEntry;
  1874.  
  1875. Figure 1. The Index Entry structure. 
  1876.  
  1877.  Field               Description 
  1878.  word1               unknown 
  1879.  Country             The country name abbreviation ("US", "FR", ...).  Note: 
  1880.                      The byte ordering is reversed.  That is, the first 
  1881.                      character of the abbreviation is in Country[1], and the 
  1882.                      second character is in Country[0]. 
  1883.  SubCountryID        The country's keyboard layout ID ("153 ", "189 ", "120 ", 
  1884.                      ...).  In some country (UK, France, Italy, ...)  there 
  1885.                      exists different "main keyboard" layouts.  This field 
  1886.                      reflects this information. 
  1887.  word2               unknown 
  1888.  XTableID            The keyboard's layout codepage (437, 850, ...). 
  1889.  KbdType             The keyboard type (0 for a 89 keys keyboard, 1 for a 
  1890.                      101/102 keys keyboard). 
  1891.  HeaderLocation      The corresponding layout table offset (0-based). 
  1892.  
  1893.  So, to find a specific keyboard layout, we have to (1) read the first four 
  1894.  bytes to find the index table and (2) locate the specified index entry 
  1895.  (Country, SubCountryID, XTableID and keyboard type).  If such an entry exists, 
  1896.  its HeaderLocation field contains the Keyboard Layout Table Entry offset. 
  1897.  
  1898.  The Keyboard Layout Table Entry 
  1899.  
  1900.  Each keyboard layout table entry contains a header, followed by key and accent 
  1901.  definitions.  The header is as follows: 
  1902.  
  1903.   /* code page header */
  1904.   typedef struct XHeader
  1905.   {
  1906.     WORD    XTableID;       /* code page number */
  1907.  
  1908.     /* note: 32-bit wide field */
  1909.     struct
  1910.     {
  1911.       /* which shift key or key combo affects Char3 of each KeyDef */
  1912.       BITFIELD    ShiftAlt     :1; /* use shift-alt instead of ctrl-alt */
  1913.       BITFIELD    AltGrafL     :1; /* use left alt key as alt- graphics */
  1914.       BITFIELD    AltGrafR     :1; /* use right alt key as alt- graphics */
  1915.       /* other modifiers */
  1916.       BITFIELD    ShiftLock    :1; /* treat caps lock as shift lock */
  1917.       BITFIELD    DefaultTable :1; /* default table for the language */
  1918.       BITFIELD    ShiftToggle  :1; /* TRUE:. toggle, FALSE:. latch shiftlock */
  1919.       BITFIELD    AccentPass   :1; /* TRUE:. pass on accent keys and beep,
  1920.                                       FALSE:. just beep */
  1921.       BITFIELD    CapsShift    :1; /* caps-shift uses Char5 */
  1922.       BITFIELD    MachDep      :1; /* machine-dependent table */
  1923.       /* Bidirectional modifiers */
  1924.       BITFIELD    RTL          :1; /* Right-To-Left orientation */
  1925.       BITFIELD    LangSel      :1; /* TRUE:. National language layout
  1926.                                       FALSE:. English language layout */
  1927.       /* default layout indicator */
  1928.       BITFIELD    DefaultLayout:1; /* default layout for the country */
  1929.     } XTableFlags1;
  1930.  
  1931.     WORD    KbdType;            /* keyboard type */
  1932.     WORD    KbdSubType;         /* keyboard sub-type */
  1933.     WORD    XtableLen;          /* length of table */
  1934.     WORD    EntryCount;         /* number of KeyDef entries */
  1935.     WORD    EntryWidth;         /* width in bytes of KeyDef entries */
  1936.     BYTE    Country[2];         /* country ID, i.e. "US" */
  1937.     WORD    TableTypeID;        /* Table type, 0001=OS/2 */
  1938.     BYTE    SubCountryID[4];    /* sub-country ID, ASCII, i.e. "153 " */
  1939.     WORD    Reserved[8];
  1940.   } XHeader;
  1941.  
  1942.  Figure 2. The Table header structure. 
  1943.  
  1944.  Field               Description 
  1945.  XTableID            The keyboard layout codepage. 
  1946.  XTableFlags         Layout's flags (see the Layout Flags subsection below). 
  1947.  KbdType             The keyboard type (0 = 89 keys, 1 = 101/102 keys). 
  1948.  KbdSubType          The keyboard subtype (???  0). 
  1949.  XtableLen           The table length.  The length (in bytes) includes this 
  1950.                      header. 
  1951.  EntryCount          The number of KeyDef entries. 
  1952.  EntryWidth          The width in bytes of KeyDef entries. 
  1953.  Country             The country ID (bytes reversed, that is, you got " SU" for 
  1954.                      US). 
  1955.  TableTypeID         The table type (1 for OS/2). 
  1956.  SubCountryID        The subcountry ID ("153 ", "189 ", "120 ", ...). 
  1957.  Reserved            Unknown. 
  1958.  
  1959.  This table header is followed by EntryCount KeyDef entries. Each KeyDef entry 
  1960.  is as follows: 
  1961.  
  1962.   /* Key definition, one per scan code in table */
  1963.   typedef struct
  1964.   {
  1965.     WORD    XlateOp;
  1966.  
  1967.     BYTE    Char1;
  1968.     BYTE    Char2;
  1969.     BYTE    Char3;
  1970.     BYTE    Char4;
  1971.     BYTE    Char5;
  1972.   } KeyDef;
  1973.  
  1974.  Figure 3. The KeyDef structure. 
  1975.  
  1976.  Field               Description 
  1977.  XlateOP             The 9 lower bits specify the key type (see the key type 
  1978.                      subsection below). 
  1979.  
  1980.                      The high 7 bits specify which "accent key" is allowed. 
  1981.  
  1982.                      Note:  if there's more than seven accent keys, and if an 
  1983.                      accent key with an ID greater than 7 is allowed, the 
  1984.                      seventh bit of the high 7 bits will be set and we will 
  1985.                      have to check the corresponding Accent Table Entry to find 
  1986.                      out the validity of the combination. 
  1987.  char1               The "standard" value 
  1988.  char2               The "shifted" value 
  1989.  char3               The "Alted" value 
  1990.  char4 
  1991.  char5 
  1992.  
  1993.  The specific meaning of the charx fields depends on the XlateOP value, as 
  1994.  explained in the key type subsection.  The default value of the EntryWidth 
  1995.  field (in the header) is 7, but, if this value is bigger, then, there are 
  1996.  additional charx fields in the KeyDef structure.  (Namely, you have EntryWidth 
  1997.  - sizeof(XlateOP) charx fields, with sizeof(XlateOP) being 2.) 
  1998.  
  1999.  These EntryCount KeyDef entries are then followed by the Accent Table, which 
  2000.  contains the seven Accent Table Entries (one per possible accent -- if there's 
  2001.  more than seven accents, the seventh entry contains the additional entries). 
  2002.  
  2003.  Each Accent Table Entry is as follows: 
  2004.  
  2005.   /*  Accent Table Entry, one per accent, up to seven accent */
  2006.   typedef struct
  2007.   {
  2008.     CHAR charOrg;               /* The key's ASCII value, i.e. "a" */
  2009.     CHAR charRes;               /* The resulting ASCII value, i.e. "╨ò" */
  2010.   } TRANS;
  2011.  
  2012.   typedef struct
  2013.   {
  2014.     BYTE AccentGlyph;           /* What to show while waiting
  2015.   for a key */
  2016.     BYTE byte1;
  2017.     BYTE byte2;
  2018.     BYTE byte3;
  2019.     BYTE byte4;
  2020.     BYTE byte5;
  2021.     TRANS aTrans[20];           /* The allowed substitutions */
  2022.   } AccentTableEntry;
  2023.  
  2024.  Figure 4. The AccentTableEntry structure. 
  2025.  
  2026.  The seventh entry has a slightly different format.  If there's more than 6 
  2027.  accents, its first byte contains the length of the seventh Accent Table Entry. 
  2028.  This entry is then followed by a byte whose contents is the length of the 
  2029.  eighth entry, and so on: 
  2030.  
  2031.   +--+-------------------------+--+-----------+--+-------------
  2032.   |l7|        entry #7         |l8| entry #8  |l9|  entry #9
  2033.     :
  2034.     :
  2035.   +--+-------------------------+--+-----------+--+-------------
  2036.    <-       l7 bytes         -> <- l8 bytes -> <-  l9 bytes  ->
  2037.  
  2038.  Figure 5. The seventh Accent Table Entry structure. 
  2039.  
  2040.  There's no "end of entry" indicator.  Use the XTableLen field to check it: 
  2041.  AccentTableEntryLen = XTableLen - sizeof(XHeader) - EntryCount * EntryWidth. 
  2042.  
  2043.  The first six entries take 6*sizeof(AccentTableEntry) = 276 bytes.  The 
  2044.  remaining Accent Table entries fit in AccentTableEntryLen-276 bytes.  When the 
  2045.  sum of the size of the additional entries (that is, l7 + l8 + ...)  reaches 
  2046.  this value, it's done. 
  2047.  
  2048.  If there's less than seven accents, the first byte of the seventh entry is 
  2049.  0x00. 
  2050.  
  2051.  So, the accent-key+ASCII-key translation process is quite easy:  when an 
  2052.  accent key is pressed, just remember the accent code, and optionally display 
  2053.  the corresponding glyph (AccentGlyph).  Then, wait for another key to be 
  2054.  pressed.  If this key accepts the remembered accent (that is, the 
  2055.  corresponding bit in the 7 high bits of XlateOP is set), locate the 
  2056.  corresponding charRes in the aTrans array of the Accent Table Entry (yes, 
  2057.  you'll have to browse this array until you find the right charOrg!).  If the 
  2058.  pressed key does not accept the remembered accent (or if you can't find the 
  2059.  corresponding charOrg in aTrans), just beep.  You're done! 
  2060.  
  2061.  Layout Flags 
  2062.  
  2063.  Various flags describe the layout's behavior. 
  2064.  
  2065.  Flag                Description 
  2066.  ShiftAlt            When this flag is 1, it allows you to use "Shift+Alt" 
  2067.                      instead of "Ctrl+Alt" when accessing the third glyph of a 
  2068.                      key.  (With 89-keys keyboards.) 
  2069.  AltGrL              When this flag is 1, the left "Alt" key is used for 
  2070.                      "AltGr".  (With 101/102-keys keyboards.) 
  2071.  AltGrR              When this flag is 1, the right "Alt" key is used for 
  2072.                      "AltGr".  (With 101/102-keys keyboards.) 
  2073.  ShiftLock           When this flag is 1, "CapsLock" acts as a "ShiftLock" key. 
  2074.                      That is, when CapsLock is ON, pressing a "Shift" key unset 
  2075.                      it.  When this flag is 0, pressing a "Shift" key 
  2076.                      temporarily toggle the CapsLock state, but it is restored 
  2077.                      when releasing the "Shift" key. 
  2078.  DefaultTable        When this flag is 1, the layout uses the default country 
  2079.                      codepage. 
  2080.  ShiftToggle         With 89-keys keyboards, set this flag in conjunction with 
  2081.                      ShiftLock.  That is, when ShiftLock is 1, set ShiftToggle 
  2082.                      to 1, and when ShiftLock is 0, set ShiftToggle to 0. On 
  2083.                      101/102-keys keyboards, set this flag to 0. 
  2084.  AccentPass          When this flag is set to 1, accents keys (aka.  dead keys) 
  2085.                      are allowed. 
  2086.  CapsShift           Unknown.  It's 1 for all Swiss keyboards, 0 otherwise. 
  2087.  MachDep             Set this flag to 1 when there's more than one physical 
  2088.                      layout sharing the same country code.  See the 
  2089.                      DefaultLayout flag below. 
  2090.  RTL                 When this flag is 1, the layout use the Bidirectional 
  2091.                      Languages support. (RTL stands for Right-to-Left.) 
  2092.  LangSel             When this flag is 1, the layout is a National one (Arabic 
  2093.                      or Hebrew, usually).  When this flag is 0, the layout is 
  2094.                      an English one. Note: When RTL is 0, this flag is not used 
  2095.                      - set it to 0. 
  2096.  DefaultLayout       When MachDep is 1, set this flag to 1 to denote the fact 
  2097.                      that this layout is the default one.  Otherwise, set it to 
  2098.                      0. 
  2099.  
  2100.  Key Type 
  2101.  
  2102.  The "Key type" value specifies the meaning of the charx fields in the KeyDef 
  2103.  entries. 
  2104.  
  2105.  Note:  In the following table, "xxx'ed" means holding down xxx while pressing 
  2106.  the key.  And, if an entry contains "???", well, its meaning is not completely 
  2107.  known... 
  2108.  
  2109.  Value               Signification 
  2110.  0                   An empty entry.  That is, no key produces this scan code. 
  2111.  0x01                AlphaKey.  This is an alphabetical key.  The char1 field 
  2112.                      contains the unshifted key value.  The char2 field 
  2113.                      contains the shifted key value.  If the "accent" bits are 
  2114.                      not null, they specify the allowed accents. 
  2115.  
  2116.                      Each AlphaKey can generates a "Ctrl'ed" value, when used 
  2117.                      in conjunction with a "Ctrl" key.  In this case, the 
  2118.                      generated value is char1-96. 
  2119.  0x02                SpecKey.  This key generates an unshifted value (char1) 
  2120.                      and a shifted value (char2).  It does not generates an 
  2121.                      "Alted", "AltGr'ed" or "Ctrl'ed" value. 
  2122.  0x03                SpecKeyC.  This key generates can generate a value when 
  2123.                      "AltGr'ed". char3 contains this (optional) value.  If 
  2124.                      char3 is non null, then, it's the value.  If char3 is less 
  2125.                      than 32, the value is an accent. Otherwise, it's a 
  2126.                      "normal" symbol. 
  2127.  
  2128.                      char1 and char2 contain the unshifted and shifted key 
  2129.                      value.  When CapsLock is ON, the order is reversed.  That 
  2130.                      is, the unshifted value is char2 while the shifted value 
  2131.                      is char1. 
  2132.  0x04                SpecKeyA.  This key can generate a value (char3) when 
  2133.                      "AltGr'ed".  char1 and char2 contain the unshifted and the 
  2134.                      shifted key value, respectively.  It does not depend on 
  2135.                      the CapsLock value (that is, char1 is always the unshifted 
  2136.                      value while char2 is always the shifted value, whether 
  2137.                      CapsLock is ON or not). 
  2138.  
  2139.                      If char3 is less than 32, the value is a "control" code. 
  2140.                      It's not an accent (compare with the previous key type, 
  2141.                      SpecKeyC). 
  2142.  0x05                SpecKeyCA ??? 
  2143.  0x06                FuncKey.  The char1 field contains the function key 
  2144.                      number. All other fields contain 0. 
  2145.  0x07                PadKey.  This is a "NumPad" key.  The char1 field contains 
  2146.                      the padkey indices (0 = "7", 1 = "8", 2 = "9", 3 = "-", 4 
  2147.                      = "4", 5="5", 6="6", 7="+", 8="1", 9="2", 10="3", 11="0" 
  2148.                      and 12 = "."). 
  2149.  
  2150.                      Note:  This follows the "old" keyboard (89 keys) layout. 
  2151.  
  2152.                      The char2 field contains the ASCII character.  All other 
  2153.                      fields contains 0. 
  2154.  0x08                SpecCtlKey.  This keys generates "control" code (that is, 
  2155.                      ASCII code in range 0..31).  char1 contains the unshifted 
  2156.                      control code, while char2 contains the shifted control 
  2157.                      code.  All other fields contain 0. 
  2158.  0x09                The PrtSc key. 
  2159.  0x0a                The SysReq key. 
  2160.  0x0b                AccentKey.  This key generates "accent" code.  char1 is 
  2161.                      the unshifted accent (in range 1..7) and char2 is the 
  2162.                      shifted accent (also in range 1..7).  char5 has the value 
  2163.                      of char1. If char3 is not null, it's the value generated 
  2164.                      when "alted". char4 is 0. 
  2165.  0x0c                ShiftKey.  A shift or control key.  If char1 is 0x1 it's 
  2166.                      the right "Shift" key.  It's the left one if char1 is 0x2. 
  2167.                      If char1 is 0x4, it's a "Ctrl" key.  In this case, char2 
  2168.                      is 0x1 and char3 is 0x4, and char4  & char5 are 0. 
  2169.                      Otherwise, char2..char5 are 0. 
  2170.  0x0d                ToggleKey ??? 
  2171.  0x0e                The Alt key . 
  2172.  0x0f                The NumLock key. 
  2173.  0x10                The CapsLock key. 
  2174.  0x11                The ScrollLock key. 
  2175.  0x12                XShiftKey ??? 
  2176.  0x13                XToggleKey ??? 
  2177.  0x14                SpecKeyCS.  When CapsLock is OFF, this key generates char1 
  2178.                      when unshifted and char2 when shifted.  When CapsLock is 
  2179.                      ON, this key generates char4 when unshifted and char5 when 
  2180.                      shifted. When used in conjunction with the "AltGr" key, 
  2181.                      this key generates char3 (whether CapsLock is ON or not). 
  2182.                      If char3 is less than 32, it's an accent. 
  2183.  
  2184.                      Otherwise, and if char3 is not null (in which case nothing 
  2185.                      is produced), it's a "normal" symbol. 
  2186.  0x15                SpecKeyAS ???  (my guess:  it's like SpecKeyCS, except 
  2187.                      that char3 is a "control" code (that is, an ASCII value in 
  2188.                      range 1..31). 
  2189.  0x1a                ExtExtKey.  The new "cursor" keys.  That is, the keys 
  2190.                      which are missing in a 89-keys keyboard. 
  2191.  <other>             unknown 
  2192.  
  2193.  KEYBOARD.DCP File Format - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  2194.  
  2195.  
  2196. ΓòÉΓòÉΓòÉ 6.3. ScanCode to Key Value Conversion ΓòÉΓòÉΓòÉ
  2197.  
  2198. ScanCode to Key Value Conversion 
  2199.  
  2200. In this section, we'll describe the keyboard scancode to ASCII char conversion 
  2201. scheme.  Doing such a conversion is required when you want to write your own 
  2202. keyboard handler, or when, for any other reason, you have to deal directly with 
  2203. scancodes. 
  2204.  
  2205. Portion of code will be given in REXX.  Please refer to SHOWDCP.CMD for missing 
  2206. functions. 
  2207.  
  2208. And, it's just a scheme, it's not a complete and fully-functional scancode to 
  2209. key value converter. <grin> 
  2210.  
  2211. Determining the Required Keyboard Layout 
  2212.  
  2213. The first thing to do is to load the correct keyboard layout.  We first have to 
  2214. find the current Country/CodePage value by using the 
  2215. DosQueryCp/DosQueryCtryInfo functions (Refer to Control Program Guide and 
  2216. Reference for more information on those two functions). 
  2217.  
  2218. We then have to find the current keyboard type - that is, an old (89 keys) one 
  2219. or a "new" one (101/102 keys).  If you know how to do this, please, let me 
  2220. know! 
  2221.  
  2222. The last step is to find the user's desired keyboard layout.  The easiest way 
  2223. to do this is probably to scan the CONFIG.SYS, or to provide a command 
  2224. parameter.  (We need both country abbrev and subcountry code.) 
  2225.  
  2226. We could then load the corresponding keyboard layout. 
  2227.  
  2228. /* Loading the keyboard layout
  2229. **
  2230. ** rcp is current codepage
  2231. ** rcn is current country abbrev (US, FR, ...)
  2232. ** rss is current subcountry (153, 189, 120)
  2233. ** rty is current keyboard type
  2234. */
  2235. ito = readl()
  2236. call charin infile,ito
  2237. iec = readw()
  2238. do iec
  2239.    call getindex
  2240.    if (country = rcn) & (rss = subcntr) & (rcp = cp) & (rty =
  2241. type) then
  2242.       leave
  2243. end /* do */
  2244.  
  2245. if (country \= rcn) | (rss \= subcntr) | (rcp \= cp) | (rty \=
  2246. type) then
  2247.    do
  2248.    say "Keyboard layout not found!"
  2249.    exit
  2250.    end
  2251.  
  2252. call getentry offset
  2253.  
  2254. Having read the layout header, we then have to read the corresponding KeyDefs: 
  2255.  
  2256. do i = 1 to EntryCount
  2257.    call getkeydef
  2258.    /* here, we have to store is somewhere... */
  2259.    ...
  2260. end
  2261.  
  2262. And then come the last initialization step: 
  2263.  
  2264. Determining the Accent Key Conversion Table 
  2265.  
  2266. free = tablelen - 40 - entrycount * entrywidth
  2267. j = 1
  2268. empty = 1
  2269. do while free > 0
  2270.    call getaccententry j
  2271.    /* We here have to store it somewhere ... */
  2272.    ...
  2273.    j = j + 1
  2274.    free = free - len
  2275. end /* do */
  2276.  
  2277. We are now ready... 
  2278.  
  2279. Converting a Scancode to a Key Value 
  2280.  
  2281. The first thing to do is to maintain some Boolean values containing various 
  2282. special keys status (CapsLock, NumLock, ScrollLock and Alt/Shift/Ctrl).  We 
  2283. have to remember the last accent key pressed, too. 
  2284.  
  2285. We then have to handle each key type. 
  2286.  
  2287. /* scan is the current key scancode */
  2288. type = key.scan.keytype
  2289. select
  2290.    /* One of the many "toggle" key */
  2291.    when type = 'CAPSLOCK' then CapsLock = \CapsLock
  2292.    ...
  2293.    when type = 'ALPHAKEY' then do
  2294.       if \PendingAccent then
  2295.          select
  2296.             when CtrlPressed then code = key.scan.char1 - 96
  2297.             when AltPressed  then code = '00'x||scan
  2298.             when ShiftPressed & \CapsLock then code = key.scan.char2
  2299.             when ShiftPressed then code = key.scan.char1
  2300.             when CapsLock then code = key.scan.char2
  2301.          otherwise
  2302.             code = key.scan.char1
  2303.          end /* select */
  2304.       else
  2305.          select
  2306.             when \allowedAccent() | CtrlPressed | AltPressed then
  2307.                call BEEP
  2308.             when ShiftPressed & \CapsLock then
  2309.                code = addAccent(key.scan.char2)
  2310.             when ShiftPressed then
  2311.                code = addAccent(key.scan.char1)
  2312.             when CapsLock then
  2313.                code = addAccent(key.scan.char2)
  2314.          otherwise
  2315.             code = addAccent(key.scan.char1)
  2316.          end
  2317.       end
  2318.    ...
  2319. otherwise
  2320.    /* Not a known type! */
  2321.    say 'Type 'type' unknown!'
  2322. end
  2323.  
  2324. The complete implementation is, err, left as an exercise. <grin> 
  2325.  
  2326. KEYBOARD.DCP File Format - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  2327.  
  2328.  
  2329. ΓòÉΓòÉΓòÉ 6.4. Using Keyboard Layouts ΓòÉΓòÉΓòÉ
  2330.  
  2331. Using Keyboard Layouts 
  2332.  
  2333. A very important note:  be sure to have a safe copy of your original 
  2334. KEYBOARD.DCP before any experimentation!  You've been warned. <grin> 
  2335.  
  2336. In SHOWDCP.ZIP is included a REXX script which allows you to explores/modify 
  2337. keyboard layouts.  It's usage is as follow: 
  2338.  
  2339. showdcp usage
  2340.  
  2341. Usage:  showdcp [param] file [country [subcountry [cp [type]]]] [file2]
  2342.  
  2343.         -h         - Access Help ;
  2344.         -v[n]      - View matching layouts.  n is the detail level ;
  2345.         -x         - Extract matching layouts ;
  2346.         -a         - Add layout to file ;
  2347.         -ds,t,c    - Define a key ;
  2348.         -sk1,k2    - Swap key k1 and key k2.
  2349.  
  2350.         country    = country code (US, FR, ...) or *
  2351.         subcountry = subcountry code (189, 120, ...) or *
  2352.         cp         = code page (437, 850, ...) or *
  2353.         type       = keyboard type (0 = 89 keys, 1 = 101/102 keys) or *
  2354.  
  2355. Figure 6. The showdcp command usage 
  2356.  
  2357.  Param               Description 
  2358.  -h                  Shows usage information (see Figure 6); 
  2359.  -v[n]               Displays keyboard layouts which match the given 
  2360.                      specification.  n is the detail level, in range 0- 4 (1 is 
  2361.                      the default): 
  2362.  
  2363.                      0 displays matching entry count, 
  2364.  
  2365.                      1 adds Index Entry information, 
  2366.  
  2367.                      2 adds Table Entry information, 
  2368.  
  2369.                      3 adds KeyDefs definitions, 
  2370.  
  2371.                      4 adds AccentTable definitions; 
  2372.  -x                  Extracts the matching layouts in file2; 
  2373.  -a                  Adds the layouts contained in file2 to file (if file does 
  2374.                      not exist, it's created); 
  2375.  -ds,t,d             Defines the key associated with scancode s. t is the key 
  2376.                      type and d is the definition.  It's an hexadecimal string 
  2377.                      (see Example 3 below); 
  2378.  -sk1,k2             Swaps keys definition.  k1 and k2 are the scancode to 
  2379.                      swap. 
  2380.  
  2381.  Example 1 
  2382.  
  2383.  If you want to create a restricted KEYBOARD.DCP file which contains all US 
  2384.  layouts, but nothing else, enter the following commands: 
  2385.  
  2386.   showdcp -x c:\os2\keyboard.dcp US * * * dummy
  2387.   showdcp -a mylayout.dcp dummy
  2388.  
  2389.  And then, replace the DEVINFO=KBD...  line in your CONFIG.SYS with: 
  2390.  
  2391.   DEVINFO=KBD,US,D:\TMP\MYLAYOUT.DCP
  2392.  
  2393.  Example 2 
  2394.  
  2395.  If you want to find all layouts which use the 863 (Canadian French) codepage, 
  2396.  enter: 
  2397.  
  2398.   showdcp -v c:\os2\keyboard.dcp * * 863
  2399.  
  2400.  You'll get something like: 
  2401.  
  2402.   Operating System/2  Keyboard.dcp file viewer
  2403.   Version 1.05.000 Jan 25 1995
  2404.   (C) Copyright Martin Lafaix 1994, 1995
  2405.   All rights reserved.
  2406.  
  2407.   Index Country SubCountry CodePage Offset Type ...
  2408.   --------------------------------------------------------
  2409.       1 CF      058             863  17611    0 0 0
  2410.       2 CF      058             863  18862    1 1 0
  2411.  
  2412.  Example 3 
  2413.  
  2414.  If you want to change the definition of the "A" key in the standard French 
  2415.  layout so that the key caps are reversed, enter: 
  2416.  
  2417.   copy c:\os2\keyboard.dcp mykbd.dcp
  2418.   showdcp -d16,1E05,4161000000 MYKBD.DCP FR 189 * 1
  2419.  
  2420.  If you want to try the newly defined layout, and assuming your boot drive is 
  2421.  "C:", enter: 
  2422.  
  2423.   copy c:\os2\keyboard.dcp keyboard.org
  2424.   copy mybkd.dcp c:\os2\keyboard.dcp
  2425.   keyb fr189
  2426.  
  2427.  Then, experiment with it (with the French layout, the "A" key is on the US "Q" 
  2428.  key).  And, after that, restore your initial configuration: 
  2429.  
  2430.   keyb us
  2431.   copy keyboard.org c:\os2\keyboard.dcp
  2432.  
  2433.  The "-d" parameter revisited 
  2434.  
  2435.  The "-d" parameter is immediately followed by the key scancode.  It's a 
  2436.  decimal number.  It's then followed by a comma.  The key type comes next. It's 
  2437.  a 16bits hexadecimal value.  Its 9 low bits contains the key type properly 
  2438.  speaking, while the 7 high bits contain the allowed accents.  The key type is 
  2439.  followed by another comma, which is followed by the key definition. It's an 
  2440.  hexadecimal string.  The first two hexadecimal digits corresponds to the char1 
  2441.  field, and so on.  In the previous example, we are assigning 0x41 to the char1 
  2442.  field ("A"), 0x61 ("a") to char2, and 0x00 to all remaining fields (char3, 
  2443.  char4 and char5).  If the key definition string does not defines all fields, 
  2444.  the value of the non-specified fields is not modified. In the previous 
  2445.  example, we could have used "4161" instead of "4161000000". 
  2446.  
  2447.  Be really careful when using the "-d" parameter. 
  2448.  
  2449.  Summary 
  2450.  
  2451.  Please tell me what you think! 
  2452.  
  2453.  I hope you find this article useful and informative.  If you like what I have 
  2454.  done, please let me know; if not, please tell me why.  I will use your 
  2455.  comments to make upcoming papers better. 
  2456.  
  2457.  Thank you! 
  2458.  
  2459.  KEYBOARD.DCP File Format - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  2460.  
  2461.  
  2462. ΓòÉΓòÉΓòÉ 7. REXX, The Developer's Best Friend ΓòÉΓòÉΓòÉ
  2463.  
  2464.  
  2465. ΓòÉΓòÉΓòÉ 7.1. Introduction ΓòÉΓòÉΓòÉ
  2466.  
  2467. REXX, The Developer's Best Friend 
  2468.  
  2469. Written by Paul Gallagher 
  2470.  
  2471. Introduction 
  2472.  
  2473. Whenever I think of REXX, I picture a golden brown Bassett hound.  He's always 
  2474. by my side, panting and slobbering a bit, eager to run around and do some 
  2475. tricks for me.  When I throw a ball, off he scampers, eager to please. His legs 
  2476. are a bit short, but boy do they pump him along. 
  2477.  
  2478. Yep, that's m' boy, good ol' REXX.  A developer couldn't have a more faithful 
  2479. companion. 
  2480.  
  2481. OS/2 provides a tremendous environment for the software developer.  I'm sure we 
  2482. all know that by now!  As the toothpaste advertisements go, "OS/2. Recommended 
  2483. by more software developers than any other operating system". 
  2484.  
  2485. This article will focus on one feature of OS/2 that can really help improve the 
  2486. way you build software, perhaps more than any other - REXX.  Now, I'm not so 
  2487. much talking about developing in REXX as using REXX to assist your development 
  2488. in some other language (which could be REXX however, but could equally be C or 
  2489. C++). 
  2490.  
  2491. We're going to do some basic to intermediate REXX scripting which I'll present 
  2492. in tutorial format.  At the end we'll have a few fully functional developer's 
  2493. tools.  That's only half the story though; I intend to take a few detours along 
  2494. the way and get philosophical about the software development process and 
  2495. developer tools. 
  2496.  
  2497. I guess I have two audiences in mind.  If you are fairly new to REXX, or have 
  2498. never used it before, then I hope the tutorial nature of the presentation will 
  2499. help you along the learning curve.  Many developers could be reasonably 
  2500. familiar with REXX though.  If you are in this camp, then (if nothing else) I 
  2501. hope I can stimulate your imagination somewhat, and perhaps encourage you to 
  2502. use REXX for tasks that you wouldn't have otherwise considered. 
  2503.  
  2504. REXX, The Developer's Best Friend - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  2505.  
  2506.  
  2507. ΓòÉΓòÉΓòÉ 7.2. The REXX Renaissance ΓòÉΓòÉΓòÉ
  2508.  
  2509. The REXX Renaissance 
  2510.  
  2511. Some Background for Beginners 
  2512.  
  2513. It seems interest in REXX has never been stronger, and I suspect that's largely 
  2514. thanks to OS/2 (remember, REXX is available on a wide range of platforms).  The 
  2515. Internet newsgroup "comp.lang.rexx" is lively and EDM/2 is full of reviews of 
  2516. REXX-based tools.  The IBM Employee Written Software (EWS) scheme has provided 
  2517. some great REXX support; there are now libraries that enable REXX to access a 
  2518. large portion of the OS/2 API, including networking features. 
  2519.  
  2520. The key to REXX is its refreshing combination of simplicity and power.  It is 
  2521. particularly strong in handling text strings and patterns.  For example, the 
  2522. simple REXX statement 
  2523.  
  2524.    Parse Var tempstring word1 word2 therest
  2525.  
  2526. will split the contents of the variable "tempstring" into three variables 
  2527. (stored in "word1", "word2" and "therest").  Everything in the source string up 
  2528. until the first space character goes into "word1", the text between the first 
  2529. and second space goes into "word2" and the remaining characters (if any) get 
  2530. put into "therest".  Note also that our use of the space character as the 
  2531. delimiter is purely arbitrary - it could have been '#', '^' or any other 
  2532. character (or combinations of characters). 
  2533.  
  2534. REXX is arguably one of the best text processing languages around.  It is 
  2535. easier and more flexible than AWK, quicker and easier to develop than C and 
  2536. other 3GLs, and more widely supported than some specialist text processing 
  2537. engines (e.g. the DOS Shareware program "Parse-O-Matic"). 
  2538.  
  2539. Developing software is a complex activity with many aspects.  However, when you 
  2540. consider that producing code, particularly for 3GL languages, is largely a 
  2541. "text processing" activity, you perhaps get an inkling that REXX may have 
  2542. something to offer when it comes to helping your software development 
  2543. activities. 
  2544.  
  2545. REXX, The Developer's Best Friend - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  2546.  
  2547.  
  2548. ΓòÉΓòÉΓòÉ 7.3. Developer Productivity?  Me? ΓòÉΓòÉΓòÉ
  2549.  
  2550. Developer Productivity? Me? 
  2551.  
  2552. Developer productivity - A Personal Issue 
  2553.  
  2554. Most people reading this article would call themselves software developers, at 
  2555. least to some extent.  Perhaps you are paid to write software; I'm sure many 
  2556. more wish they were, but only get to write code at home while attending to 
  2557. their other responsibilities at work. 
  2558.  
  2559. Any developer, whether working for profit or just for fun, must surely be 
  2560. interested in being as productive as possible.  After all, the more time you 
  2561. save, the more work you can do (and as we all know, developing in GUI, 
  2562. networked environments usually takes a lot of work - in terms of lines of 
  2563. code). 
  2564.  
  2565. Have you ever sat back and made an objective assessment of your 'productivity'? 
  2566. If you're paid to program, then I'm sure your managers make sure that the 
  2567. productivity issue is always in your face.  But are you like me? You have good 
  2568. habits at work and then everything is out the window when you're sitting at 
  2569. your home computer? 
  2570.  
  2571. Anyone answer in the affirmative?  <grin> 
  2572.  
  2573. Well, perhaps I'm not that bad (I hope <grin>), but I recognize that I tend to 
  2574. suffer from another ailment - "DOS-upsized-tunnel-vision".  Like many of you, 
  2575. my first programming experience was under DOS.  I am used to having low 
  2576. expectations of the operating environment.  DOS .BAT files can only do so much. 
  2577. To do anything beyond copying and concatenating files etc, you need to turn to 
  2578. additional tools - tools you have to find (or buy) yourself.  I guess partly in 
  2579. response to the paucity of standard tools, compiler products tended to come 
  2580. with a lot of functionality built-in (like Borland's IDE).  Even adding Windows 
  2581. to the equation didn't help:  it was a boon to the functionality that the 
  2582. average programmer could build into a program, but Windows itself didn't help 
  2583. us develop any better (far from it - productivity plummeted). 
  2584.  
  2585. Now that I've "upsized" to a decent operating environment - OS/2 - I tend to 
  2586. forget that my development environment doesn't end at the edge of the IDE. 
  2587.  
  2588. I would suggest that two features of OS/2 - the Workplace shell and REXX - set 
  2589. it apart from most other operating systems/environments when it comes to the 
  2590. software development process.  The Workplace Shell gives us a powerful space in 
  2591. which to organize the files and programs that "belong" to a project, and REXX 
  2592. gives us a supremely powerful scripting tool to automate many of the basic 
  2593. activities involved in setting up and working on a project. 
  2594.  
  2595. REXX, The Developer's Best Friend - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  2596.  
  2597.  
  2598. ΓòÉΓòÉΓòÉ 7.4. My Scripting Style ΓòÉΓòÉΓòÉ
  2599.  
  2600. My Scripting Style 
  2601.  
  2602. This article includes a good half-dozen scripts, but they are all based on the 
  2603. same basic template.  Rather than tediously describe the scripts in full, over 
  2604. and over, I'll take this opportunity to introduce you REXX scripts a la Paul 
  2605. Gallagher. 
  2606.  
  2607. Here's the 8-point blueprint I use: 
  2608.  
  2609. 1. Header 
  2610.  
  2611. This is basically a non-executable section containing comments, description and 
  2612. version history notes.  I generally define three global variables here: 
  2613. programNameStr (a text description of the script), copyrightStr (appropriate 
  2614. copyright message) and versionStr (version message).  These variables are 
  2615. mainly used in banner messages and help screens. 
  2616.  
  2617. You will note a number of cryptic tags (beginning with "***" at around column 
  2618. 35) in the header - these are actually instructions for the version control 
  2619. software I use (TLib).  They only pop up in a few places so I have not removed 
  2620. them for publication.  (They also let you know that these are real scripts!) 
  2621.  
  2622. Example: 
  2623.  
  2624. /*-------------------------------------------------------------------------*/
  2625.   '@echo off'
  2626.   programNameStr=   "Create new project (example only)"
  2627.   copyrightStr=     "Copyright (c) Paul Gallagher 1995"
  2628.  
  2629. /*                                ***keywords*** "Version: %v  Date: %d %t"*/
  2630.   versionStr=       ""
  2631. /*
  2632. ;                                 ***keywords*** "%l"
  2633. ; LOCK STATUS       ""
  2634. ;
  2635. ;                                 ***keywords*** "%n"
  2636. ; Filename          "newproj.cmd"
  2637. ; Platform          OS/2 (REXX)
  2638. ;
  2639. ; Authors           Paul Gallagher (paulg@resmel.bhp.com.au)
  2640. ;
  2641. ; Description
  2642. ;
  2643. ; Revision History
  2644. ;                                 ***revision-history***
  2645. ;                                 ***revision-history***
  2646. ;--------------------------------------------------------------------------*/
  2647.  
  2648. 2. Load REXX utility functions [optional] 
  2649.  
  2650. Next, I load the REXX utility package, if any of the functions will be required 
  2651. by the script.  There tend to be a few schools of thought in relation to 
  2652. loading/unloading external functions.  I prefer to load the entire standard 
  2653. utility package - and not unload functions at the end of the script.  I do this 
  2654. since the load/unload is a global operation - not limited to the one process. 
  2655.  
  2656. Example: 
  2657.  
  2658. /*---------------------------------------------------------------------------
  2659. ; Load REXXUTIL
  2660. ;--------------------------------------------------------------------------*/
  2661. If RxFuncQuery('SysLoadFuncs') <> 0 Then
  2662.   If RxFuncAdd('SysLoadFuncs','RexxUtil','SysLoadFuncs') <>0 Then Do
  2663.     Say 'Unable to init REXX Utility function loader.'
  2664.     Exit
  2665.   End
  2666. Call SysLoadFuncs
  2667.  
  2668. 3. Install selected error traps 
  2669.  
  2670. As with any other programming language, it is important to ensure that programs 
  2671. fail as gracefully as possible as possible when severe errors are encountered. 
  2672. All error conditions cause program flow to jump to ExitProc - the point from 
  2673. which a reasonably clean exit can occur. 
  2674.  
  2675. Example: 
  2676.  
  2677. /*---------------------------------------------------------------------------
  2678. ; Set error traps
  2679. ;--------------------------------------------------------------------------*/
  2680. signal on failure name ExitProc
  2681. signal on halt name ExitProc
  2682. signal on syntax name ExitProc
  2683.  
  2684. 4. Initial parse of command line [optional] 
  2685.  
  2686. If the command arguments are of any interest at all, they are initially loaded 
  2687. into a variable called params.  The code that follows does a quick check for 
  2688. anything that looks like the user is after help - if that is the case, then the 
  2689. HelpInfo procedure is called prior to jumping to the script exit point. 
  2690.  
  2691. Example: 
  2692.  
  2693. /*---------------------------------------------------------------------------
  2694. ; Do initial parse of command line and call help message if
  2695. required
  2696. ;--------------------------------------------------------------------------*/
  2697.                                   /* get the command line arguments */
  2698. Parse Arg params
  2699.                                   /* call help routine if required */
  2700. If POS(TRANSLATE(params),"-?"'00'x"/?"'00'x"-HELP"'00'x"/HELP") > 0 Then Do
  2701.   Call HelpInfo
  2702.   Signal ExitProc
  2703. End
  2704.  
  2705. The check for a "help" parameter illustrates a neat trick that is handy for 
  2706. command parsing.  TRANSLATE(params) simply does an uppercase conversion.  All 
  2707. of the valid "help" commands are concatenated as a string with intervening null 
  2708. characters.  The POS function is then used to test whether the parameters 
  2709. passed to the program match any of the "help" commands.  Thus, the parameters 
  2710. "-?", "/?", "/help", "-H", etc. will all be recognized as calls for help. 
  2711.  
  2712. 5. Main program 
  2713.  
  2714. This is where the "generic" stuff ends, and the real program starts. 
  2715.  
  2716. 6. General Exit Procedure 
  2717.  
  2718. The main exit point of the script is named ExitProc.  Normal execution of the 
  2719. main program will simply see control flow to this point, but error conditions 
  2720. will generally see execution jump to this point.  Prior to exiting completely, 
  2721. variables are dropped (mainly to be neat than for any other reason) - but other 
  2722. tasks could be included here if required. 
  2723.  
  2724. Example: 
  2725.  
  2726. /*---------------------------------------------------------------------------
  2727. ; General exit procedure
  2728. ;--------------------------------------------------------------------------*/
  2729. ExitProc:
  2730.   Drop params programNameStr copyrightStr versionStr
  2731. Exit
  2732.  
  2733. 7. Standard help procedure 
  2734.  
  2735. A standard help procedure is provided, and usually enhanced for each individual 
  2736. script.  It is intended to simply display an appropriate message, but other 
  2737. features could be added if required. 
  2738.  
  2739. Example: 
  2740.  
  2741. /*---------------------------------------------------------------------------
  2742. ; routine to display help message
  2743. ;--------------------------------------------------------------------------*/
  2744. HelpInfo: Procedure Expose programNameStr copyrightStr
  2745. versionStr
  2746.   Say
  2747.   Say "*===================================================================*"
  2748.   Say "   "programNameStr
  2749.   Say "   "versionStr
  2750.   Say "   "copyrightStr
  2751.   Say
  2752.   Say "*===================================================================*"
  2753.   Return
  2754.  
  2755. 8. Additional functions/procedures 
  2756.  
  2757. The standard template ended at point 7.  What follows are all the functions and 
  2758. procedures written for a specific application. 
  2759.  
  2760. Coding and documentation standards 
  2761.  
  2762. In addition to following the blueprint outlined above, the code adheres to some 
  2763. basic presentation guidelines: 
  2764.  
  2765.      Program blocks indented by 2 characters 
  2766.      Keywords typed in proper case (e.g. "If" or "Select") 
  2767.      Function names typed in uppercase (e.g. "TRANSLATE") 
  2768.      Variables name components in proper case, but name started with lower 
  2769.       case (e.g. "programName") 
  2770.      All comments that document algorithms are indented by 35 characters (i.e. 
  2771.       comments appear to the right of the page).  Comments that precede a 
  2772.       procedure or block of code begin at column 1. 
  2773.  
  2774.  REXX, The Developer's Best Friend - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  2775.  
  2776.  
  2777. ΓòÉΓòÉΓòÉ 7.5. Setting Up Your Development Environment ΓòÉΓòÉΓòÉ
  2778.  
  2779. Setting Up Your Development Environment 
  2780.  
  2781. In this section I'll talk about a selection of REXX scripts that can help you 
  2782. setup and maintain a development environment. 
  2783.  
  2784. INFICONS.CMD 
  2785.  
  2786. A great deal of developer information comes packaged as INF files (just like 
  2787. EDM/2).  As always, INF files tend to get scattered all over your disk - and 
  2788. searching for the right one can be a difficult task since they invariably have 
  2789. 8 character filenames (and if they come from IBM, the name will probably be 
  2790. particularly cryptic.  <grin>) 
  2791.  
  2792. This is a fairly simple script searches for INF files on your disk.  The script 
  2793. takes an optional parameter to specify the path and/or filemask used in the 
  2794. search for INF files.  By default it will search C: drive, examining all 
  2795. subdirectories for *.INF files.  I must admit that this is not the first script 
  2796. to be written with this purpose in mind - but it's the only one I know of that 
  2797. assigns the INF's true title to the icon created. 
  2798.  
  2799. After parsing the command line to determine the appropriate search mask 
  2800. (variable mask), the main algorithm to search the disk and process files is as 
  2801. follows: 
  2802.  
  2803. If SysFileTree(mask, 'file', 'FSO') > 0 then
  2804.                                   /* out of memory message */
  2805.   Say SysGetMessage(8)
  2806. Else Do
  2807.                                   /* if files found then process */
  2808.   if file.0>0 Then Do
  2809.  
  2810.     /* ... create folder for all INFs if it doesn't already exist */
  2811.  
  2812.                                   /* loop through all INFs found */
  2813.     Do i=1 to file.0
  2814.  
  2815.       /* ... for each INF, get its title & create icon */
  2816.  
  2817.     End
  2818.   End
  2819.   Else
  2820.                                   /* file not found message */
  2821.     Say SysGetMessage(2)
  2822. End
  2823.  
  2824. This is a fairly generic algorithm to search-and-process files that could be 
  2825. used in other situations. 
  2826.  
  2827. Workplace Shell icons are created for all INFs found using the SysCreateObject 
  2828. function.  Icons are created in a folder called "INF Files" on the desktop. 
  2829. The enclosing folder is created with the following command: 
  2830.  
  2831.   Call SysCreateObject "WPFolder", "INF Files", "<WP_DESKTOP>",,
  2832.                        "OBJECTID=<INF_FILES>"
  2833.  
  2834. Translated this means that this command creates a WPFolder object on the 
  2835. desktop called "INF Files" and assigns to it the object ID <INF_FILES>.  Note 
  2836. the extra comma on the first line - this is the method REXX uses to indicate 
  2837. the command continues on the next line. 
  2838.  
  2839. INF icons are created with the command: 
  2840.  
  2841.       Call SysCreateObject "WPProgram", title' ['path']', "<INF_FILES>",,
  2842.          "EXENAME=VIEW.EXE;PARAMETERS="name";STARTUPDIR="path";",, "UPDATE"
  2843.  
  2844. where path is the fully qualified path of the INF file, name is the filename, 
  2845. and title is its true title. Basically, an icon has been created for the 
  2846. VIEW.EXE program which is passed the INF filename as a parameter and is 
  2847. executed in the INF file's directory. 
  2848.  
  2849. The UPDATE parameter to the SysCreateObject function requests that object 
  2850. attributes be updated, rather than additional objects created if a duplicate is 
  2851. encountered.  In writing this script I discovered that if a WPProgram object 
  2852. had a multi-line name, then an object match was never detected and additional 
  2853. objects always created - something to watch out for. 
  2854.  
  2855. The INF file's true title is obtained by peeking inside the INF file.  The 
  2856. title is a null terminated string up to 48 characters long, starting at offset 
  2857. 6B(hex).  The "peek" is easily achieved with two lines of code: 
  2858.  
  2859.   Call CHARIN file.i,1,X2D('6B')
  2860.   Parse Value CHARIN(file.i,,48) with title'00'x
  2861.  
  2862. The first line "gobbles" the first 6B characters (Note: file.i is the INF file 
  2863. name).  The next line reads the 48 characters that constitute the title field, 
  2864. and uses the Parse command to assign all characters up to the first null to the 
  2865. variable "title". 
  2866.  
  2867. Once we have the INF filename ("name", located in directory "path") and true 
  2868. title ("title"), creating an icon for it requires a simple variation on the 
  2869. SysCreateObject we have seen already: 
  2870.  
  2871.       Call SysCreateObject "WPProgram", title' ['path']', "<INF_FILES>",,
  2872.          "EXENAME=VIEW.EXE;PARAMETERS="name";STARTUPDIR="path";",, "UPDATE"
  2873.  
  2874. NEWPROJ.CMD 
  2875.  
  2876. Starting a new programming task or project involves a bit of administration to 
  2877. get you going.  Aside from perhaps cleaning up your desk a bit, emptying the 
  2878. ashtray and so on, you need to set up an appropriate computing environment (set 
  2879. up a project directory, check-out some standard libraries etc).  I'm here to 
  2880. tell you that REXX can't help with the first tasks (unfortunately) - but it can 
  2881. certainly help with the later. 
  2882.  
  2883. As an example, here's what I'll typically do when starting a new C or C++ 
  2884. project: 
  2885.  
  2886.    1. Create a project directory (I'll also create directories for the 
  2887.       different target platforms - DOS, OS/2 etc) 
  2888.    2. I use TLib version control software, so I'll create a project-specific 
  2889.       TLib configuration file, and create a project directory in my TLib 
  2890.       archive area. 
  2891.    3. I may use TLib to check out a copy of my (personal) standard library for 
  2892.       use in the project 
  2893.    4. Create a Workplace Shell folder for the project.  I'll make this folder a 
  2894.       "work area", and fill it up with some objects:  a shadow of the project 
  2895.       directory (from 1); an OS/2 Command Line icon with startup directory set 
  2896.       to the project directory; icons for any compilers or tools I intend on 
  2897.       using - with startup directories set to the project directory where 
  2898.       appropriate. 
  2899.    5. Copy some program stubs as a starting point for coding. 
  2900.  
  2901.  Phew!  I haven't even got started yet, but if I had to do this manually I 
  2902.  would already have wasted a good half an hour.  Fortunately it's all pretty 
  2903.  mundane stuff - easy to automate with REXX.  Unfortunately, it's also all very 
  2904.  idiosyncratic - chances are, what I've described is similar to what you do, 
  2905.  but we'll always have our differences.  For that reason, the NEWPROJ.CMD 
  2906.  script I describe here is only an example - it will need some heavy 
  2907.  customization for it to meet you own requirements. 
  2908.  
  2909.  After asking for the name of the new project, NEWPROJ.CMD does two things: 
  2910.  creates some directories (the project's "home" directories); and then some 
  2911.  Workplace Shell objects. 
  2912.  
  2913.  Directories are created using the utility function SysMkDir: 
  2914.  
  2915.     Call SysMkDir prjDir
  2916.  
  2917.  We could just have easily (but with a speed penalty) invoked the built-in 
  2918.  command 'md' (i.e. 'mkdir'): 
  2919.  
  2920.     'md' prjDir
  2921.  
  2922.  To "personalize" this script, you may wish to create some basic project files 
  2923.  at this stage:  customized software configuration files; initialize some 
  2924.  project log files etc etc. 
  2925.  
  2926.  The Workplace Shell objects are a bit more interesting.  Firstly, the script 
  2927.  creates a folder on the desktop called "REXX - The developer's best friend"; 
  2928.  this folder will be a container for all the individual project folders. 
  2929.  
  2930.     Call SysCreateObject "WPFolder", "REXX - The developer's best friend",,
  2931.                          "<WP_DESKTOP>","OBJECTID=<REXX-TDBF>;"
  2932.  
  2933.  After creating a project folder (as a workarea, i.e. when this folder is 
  2934.  closed, all child objects are also closed)... 
  2935.  
  2936.     SysCreateObject("WPFolder", n "Project", "<REXX-TDBF>",,
  2937.                      "OBJECTID=<SPRJ-"n">;WORKAREA=YES;","UPDATE")
  2938.  
  2939.  it is populated with a selection of objects.  Firstly, it creates icons for 
  2940.  the Borland C++ compilers - OS/2, DOS and Windows version (if you use these 
  2941.  products you should examine the NEWPROJ script for details of the required 
  2942.  SysCreateObject commands).  An OS/2 Command Prompt icon is also created, as 
  2943.  well as a shadow of the project directory ("d"; "n" is the project name). 
  2944.  
  2945.     Call SysCreateObject "WPProgram", "OS/2 Command Prompt", "<SPRJ-"n">",,
  2946.                      "EXENAME=*;STARTUPDIR="d";","UPDATE"
  2947.     Call SysCreateShadow d, "<SPRJ-"n">"
  2948.  
  2949.  As I have already mentioned, NEWPROJ.CMD is probably of little use as it 
  2950.  stands, but is a good basis on which to build your own "New Project 
  2951.  Initiation" script. 
  2952.  
  2953.  STUBS.CMD 
  2954.  
  2955.  Most developers have a pretty clear idea about how they like their source 
  2956.  files organized.  You may follow the more common conventions, or use a style 
  2957.  of your own.  Either way, you don't want to be typing all the same fluff for 
  2958.  each new file created. 
  2959.  
  2960.  The most common solution to this problem is to keep a selection of templates. 
  2961.  Rather than type a header, copyright info and so on - just copy the template 
  2962.  and away you go.  Two things could be improved though:  you may end up with a 
  2963.  clutter of templates, and the template may still need a bit of tweaking on a 
  2964.  case-by-case basis. 
  2965.  
  2966.  STUBS.CMD allows you to automate your use of templates ("stubs").  Running 
  2967.  STUBS first presents the user with a menu from which they can select the 
  2968.  template to be generated.  After providing a new filename, the script 
  2969.  generates a file based on the selected template.  Of course, being a REXX 
  2970.  script it can include all kinds of smarts when generating the template - such 
  2971.  as including a copyright notice with this years date (instead of the date from 
  2972.  when you created the template.  <grin> 
  2973.  
  2974.  So far its a good concept - but you may ask how I (as the guy who wrote 
  2975.  STUBS.CMD) know how you want your templates produced.  The answer is - I 
  2976.  don't!  In fact, STUBS.CMD comes as a basic menu shell but contains no actual 
  2977.  templates.  The one useful command it provides you (other than "exit") is 
  2978.  "modify".  Here is the basic menu structure: 
  2979.  
  2980.   Do Forever
  2981.                                     /* display menu */
  2982.     Say
  2983.     Say "Select from the following commands:"
  2984.   /*FLAG1* DO NOT DELETE THIS LINE - New menu items inserted above*/
  2985.     Say "   -----------------------------------------------------"
  2986.     Say "   MODIFY: add a new template to this file"
  2987.     Say "   EXIT: end processing"
  2988.     Call CHAROUT ,'> '
  2989.     Pull Cmd
  2990.                                     /* process menu option */
  2991.     Select
  2992.     When ABBREV("EXIT",Cmd) Then
  2993.       Signal ExitProc
  2994.     When ABBREV("MODIFY",Cmd) Then
  2995.       Call AdminMODIFY
  2996.   /*FLAG2* DO NOT DELETE THIS LINE - New menu items inserted below*/
  2997.     Otherwise
  2998.       Nop
  2999.     End
  3000.   End
  3001.  
  3002.  As you can see, very simple.  A menu is printed, acommand is accepted, and 
  3003.  then the command is processed.  Using the ABBREV function to examine a command 
  3004.  makes for a very user-friendly interface reminiscent of VMS. (Isn't that an 
  3005.  oxymoron?)  Users can type as few or as many letters of a command as they 
  3006.  like; insofar as the letters they have typed match the actual keyword, then 
  3007.  the command is recognized. 
  3008.  
  3009.  The MODIFY command allows you to add a new template to STUBS.CMD.  You provide 
  3010.  an example file, and the script reads it in, creating the necessary procedures 
  3011.  and code modifications in STUBS.CMD to support the new template type.  You 
  3012.  will notice the hard-coded "tags" in the preceding code - these help the 
  3013.  script locate the menu code that requires extending.  The code to write the 
  3014.  new templates is encapsulated in procedures which are simply appended to the 
  3015.  file. 
  3016.  
  3017.  When adding a new template, you are asked for three bits of information:  the 
  3018.  filename of the file to be used as the example template; the keyword to be 
  3019.  used as the menu command; and a description of the template - this is used in 
  3020.  the menu display. 
  3021.  
  3022.  To modify STUBS.CMD, you first need to locate the scripts true file name and 
  3023.  path.  This is easily done using the "Source" variant of the "Parse" command: 
  3024.  
  3025.     Parse Source . . SourceFile
  3026.  
  3027.  Next, the SourceFile ("STUBS.CMD") is read into a queue.  The queue provides a 
  3028.  temporary storage area prior to writing back a modified STUBS.CMD, and is much 
  3029.  more convenient than messing about with temporary files and so on. 
  3030.  
  3031.                                     /* read stubs.cmd to queue */
  3032.     Do While LINES(SourceFile) > 0
  3033.       line = LINEIN(SourceFile)
  3034.       queue line
  3035.     End
  3036.  
  3037.  Once the file has been fully enqueued, write-back begins by first 
  3038.  repositioning the file pointer to the start of the file... 
  3039.  
  3040.     Call LINEOUT SourceFile,,1
  3041.  
  3042.  and then pulling the the source from the queue, writing each line back to file 
  3043.  - inserting the new menu display and processing commands as we go: 
  3044.  
  3045.     Do While QUEUED() > 0
  3046.       Parse Pull line
  3047.  
  3048.                                     /* insert new menu item */
  3049.       if (POS("/*FLAG1",line)=1) Then Do
  3050.         Call LINEOUT SourceFile,'  Say "   'key': 'description'"'
  3051.       End
  3052.                                     /* write current line */
  3053.       Call LINEOUT SourceFile,line
  3054.  
  3055.                                     /* insert new menu- processing commands */
  3056.       if (POS("/*FLAG2",line)=1) Then Do
  3057.         Call LINEOUT SourceFile,'  When ABBREV("'key'",Cmd) Then'
  3058.         Call LINEOUT SourceFile,'    Call Create'key
  3059.       End
  3060.     End
  3061.  
  3062.  After that, the new template-writing procedure is appended to the source file. 
  3063.  At its core, this is a simplified variation of the procedure used in the 
  3064.  QUOTE.CMD script to read the example file and convert it to commands that can 
  3065.  re- write the example (see the section on text filters for more details).  In 
  3066.  part: 
  3067.  
  3068.                                     /* insert sample script */
  3069.     pre="Call LINEOUT f,'"
  3070.     post="'"
  3071.     Do While LINES(sample) > 0
  3072.       line = LINEIN(sample)
  3073.       new=""
  3074.                                     /* replace conflicting quote chars in
  3075.                                        source line */
  3076.       Do While POS("'",line)>0
  3077.         Parse Var line frag"'"line
  3078.         new=new''frag"''"
  3079.       End
  3080.       new=new''line
  3081.                                     /* write command to write 'clean' line */
  3082.       Call LINEOUT SourceFile,'  'pre''new''post
  3083.     End
  3084.  
  3085.  In my experience, such self-modifying scripts appear to be quite safe. 
  3086.  Although I don't know the details of the process, I suspect that the REXX 
  3087.  command processor reads and semi-compiles a script before beginning execution 
  3088.  (the compiled form of the script is also stored in extended attributes for 
  3089.  re-use).  This means a script can safely read and modify itself without 
  3090.  altering the execution path of the current program instance. 
  3091.  
  3092.  How to best use STUBS?  I suggest that you marshal your most commonly used 
  3093.  templates and put a bit of effort into cleaning them up.  Once they've checked 
  3094.  out OK, use the MODIFY command to add them to STUBS. 
  3095.  
  3096.  At this stage, STUBS.CMD will be a self-contained script that can re-create 
  3097.  your original templates on command.  You can stop right there, however you may 
  3098.  wish to delve in and customize the template-writing procedures to add extra 
  3099.  capabilities. 
  3100.  
  3101.  For example, my personal STUBS.CMD can create C, C++ and REXX templates.  I 
  3102.  have converted all hard-coded dates (in the copyright legends for example) to 
  3103.  calculated values so that the templates never go out of date. The C/C++ 
  3104.  template procedures contain other enhancements.  For example, after asking for 
  3105.  a name stem, they produce both an include (*.h) and associated source files 
  3106.  (*.c or *.cpp).  The source files #include the matching header file. 
  3107.  
  3108.  More Ideas 
  3109.  
  3110.  I've already hinted that I use REXX to assist with version and project control 
  3111.  activities (to great effect).  In fact, my own variant of the NEWPROJ.CMD 
  3112.  script is growing in scope and capabilities almost daily - it maintains 
  3113.  version control configurations, checks-in/out and updates standard libraries, 
  3114.  packages software for distribution and so on.  As you would appreciate, I 
  3115.  cannot really present these systems in this article because they are too 
  3116.  specialized and peculiar to my personal development environment.  But there's 
  3117.  a lesson in there somewhere:  I'm sure that most of you would be in a similar 
  3118.  situation of having unique requirements, and I can only encourage you to 
  3119.  examine what you do and consider whether REXX can give you a hand! 
  3120.  
  3121.  REXX, The Developer's Best Friend - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  3122.  
  3123.  
  3124. ΓòÉΓòÉΓòÉ 7.6. Text Filters ΓòÉΓòÉΓòÉ
  3125.  
  3126. Text Filters 
  3127.  
  3128. I made the point that REXX is particularly good at text processing, and indeed, 
  3129. it is a natural choice for implementing a whole raft of "text filter" type 
  3130. utilities. 
  3131.  
  3132. Text filters are basically programs that read a text file from the standard 
  3133. input stream, modify or process it in some way (line by line), and write the 
  3134. resultant file to the standard output stream.  This means that the standard 
  3135. operating system pipe and redirection functions can be used to select input and 
  3136. output files or device handles.  Typical text filter usage is as follows, using 
  3137. the OS/2 SORT.EXE filter as an example: 
  3138.  
  3139.   type c:\config.sys | sort > lpt1:
  3140.  
  3141. The basic filter structure in REXX is a very simple 'Do' loop which reads from 
  3142. the standard input (using the LINEIN function) until no more lines are 
  3143. available (i.e. LINES() = 0). 
  3144.  
  3145.                                   /* loop until no lines available at
  3146.                                      standard input */
  3147.   Do While LINES() > 0
  3148.                                   /* read current line */
  3149.     line = LINEIN()
  3150.  
  3151.     /* ... perform some processing */
  3152.  
  3153.   End
  3154.  
  3155. LINE.CMD 
  3156.  
  3157. This is the good old "print line number x" program (it actually prints line "x" 
  3158. and the 3 lines before and after).  Implementing this requires only a few extra 
  3159. lines added to the basic filter script.  Basically, we count lines and only 
  3160. print the ones we want. 
  3161.  
  3162. TAIL.CMD 
  3163.  
  3164. Again, an "oldie but a goodie" - print the last x lines of a file.  When 
  3165. setting out to implement this specification, there are two obvious problems: 
  3166. we don't know how many lines are in the file - until after we've read it; and 
  3167. what's the best way of buffering the file so that you can come back and process 
  3168. it after you've worked out which lines to print. 
  3169.  
  3170. The queue interface in REXX gives us an elegant way of solving these problems. 
  3171. As we read the source file, we write the lines to a first-in, first-out (FIFO) 
  3172. queue using the QUEUE keyword.  Once we've read x lines, we PULL (discard) a 
  3173. line from the front of the queue for each new one we add - this way the queue 
  3174. becomes a sliding window containing the most recently read x lines. 
  3175.  
  3176.   Do While LINES() > 0
  3177.     line = LINEIN()
  3178.     lc=lc+1
  3179.                                   /* enqueue the new line */
  3180.     Queue line
  3181.                                   /* if we already have our quota, also
  3182.                                      discard a line from the front of
  3183.                                      the queue */
  3184.     If (lc>params) Then
  3185.       Pull line
  3186.   End
  3187.  
  3188. By the time we get to the end of the file, the queue contains exactly what we 
  3189. need to print.  A simple process: 
  3190.  
  3191.   Do While QUEUED() > 0
  3192.     Parse Pull line
  3193.     Say line
  3194.   End
  3195.  
  3196. QUOTE.CMD 
  3197.  
  3198. This script helps you prepare text for inclusion as print statements in C/C++ 
  3199. or REXX programs.  For example, the line 
  3200.  
  3201.   I said "I'm here!"
  3202.  
  3203. may be converted to the C statement 
  3204.  
  3205.   printf("I said \"I'm here!\"");
  3206.  
  3207. In fact there are four output formats supported: 
  3208.  
  3209.    1. C stdio 
  3210.  
  3211.               printf("I said \"I'm here!\"");
  3212.  
  3213.    2. C++ iostreams 
  3214.  
  3215.               cout << "I said \"I'm here!\"" << endl;
  3216.  
  3217.    3. REXX - Say keyword 
  3218.  
  3219.               Say 'I said "I''m here!"'
  3220.  
  3221.    4. REXX - CHAROUT function 
  3222.  
  3223.               Call LINEOUT f,'I said "I''m here!"'
  3224.  
  3225.  The QUOTE.CMD script uses the basic text filter model to process the input 
  3226.  stream.  Lines are prepared for output using five variables: 
  3227.  
  3228.       1.  ("pre") a prefix is prepended to the line 
  3229.       2.  ("post") a suffix is appended to the line 
  3230.       3 & 4.  Nominated quote characters ("qchar") that appear within the text 
  3231.       itself are substituted by a quote character replacement string 
  3232.       ("reqchar"). 
  3233.       5.  The process of replacing quote characters requires another variable 
  3234.       ("qqchar"), which takes the value of "qchar" in REXX-quoted format (more 
  3235.       on that later!) 
  3236.  
  3237.  So, to produce a C++ iostream compatible output, we define these 5 quantities 
  3238.  as follows: 
  3239.  
  3240.     pre='cout << \"'
  3241.     post='\" << endl;'
  3242.     qchar='"'
  3243.     qqchar='''"'''          /* the value of qqchar is '"' i.e. qchar */
  3244.     reqchar='\"'
  3245.  
  3246.  By using this generic "quoting" model, it is easy to add new variations. 
  3247.  
  3248.  The trickiest part of the procedure (shown below) is the replacement of 
  3249.  embedded quote characters ("qchar") with a substitute string ("reqchar").  If 
  3250.  it was to be a simple character for character replacement, then the TRANSLATE 
  3251.  function could have come in handy, however there are situations where the 
  3252.  quote character needs to be replaced with more than one character.  The 
  3253.  solution I have chosen is to repeatedly parse the input line into left and 
  3254.  right fragments separated by the first occurrence of the quote character.  The 
  3255.  right-hand fragment becomes the subject of the next iteration - this process 
  3256.  continues until the quote character can no longer be found in the remaining 
  3257.  fragment.  Because the delimiter between left and right portions is a variable 
  3258.  quantity, we need to construct a command and request REXX to interpret it on 
  3259.  the fly - this allows us to make the delimiter appear as a constant in the 
  3260.  Parse template. 
  3261.  
  3262.     Do While LINES() > 0
  3263.       line = LINEIN()
  3264.       new=""
  3265.  
  3266.                                     /* replace qchar occurrences in source
  3267.                                        text with reqchar */
  3268.       Do While POS(qchar,line)>0
  3269.         cmd = "Parse Var line frag"qqchar"line"
  3270.         interpret cmd
  3271.         new=new''frag''reqchar
  3272.       End
  3273.  
  3274.                                     /* tack on any remaining line to new */
  3275.       new=new''line
  3276.                                     /* print the quoted line */
  3277.       say pre''new''post
  3278.     End
  3279.  
  3280.  You may not need the QUOTE.CMD itself - but this example of string 
  3281.  substitution may be of assistance in other scripts you write. Note:  I'm not 
  3282.  altogether happy with this approach to string substitution; if anyone has a 
  3283.  better suggestion I'd be glad to hear from them! 
  3284.  
  3285.  More Ideas 
  3286.  
  3287.  I've presented a few generic text filters.  It's pretty obvious though that 
  3288.  the sky's the limit when you start considering special purpose filters.  Here 
  3289.  are some suggestions. 
  3290.  
  3291.  There are quite a few code formatters already available (mostly free) but 
  3292.  these may not do exactly what you want.  Perhaps you or the companies you work 
  3293.  for have some special coding requirements.  Developing a code formatter can be 
  3294.  a fairly involved process (depending on how deep into the syntax you needed to 
  3295.  peek to make the required modifications), but it would be easier in REXX than 
  3296.  C! 
  3297.  
  3298.  Do you need to update copyright notices embedded in source files?  If you use 
  3299.  a consistent format, then a REXX script to do the update for you would be 
  3300.  trivial. 
  3301.  
  3302.  Are you half way through a project you decide to change your variable or 
  3303.  function naming conventions.  If you have many source modules, a quick REXX 
  3304.  script may be the easiest way of implementing the change. 
  3305.  
  3306.  REXX, The Developer's Best Friend - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  3307.  
  3308.  
  3309. ΓòÉΓòÉΓòÉ 7.7. Conclusion ΓòÉΓòÉΓòÉ
  3310.  
  3311. Conclusion 
  3312.  
  3313. REXX is a fantastic language for text processing - enormously powerful (yet 
  3314. simple) features such as the Parse command set it apart from its competitors. 
  3315.  
  3316. Coupled with the ability to manipulate Workplace Shell objects, REXX clearly 
  3317. has a lot to offer the developer - not only as a language for coding the end 
  3318. product, but as I've explored in this article:  as a tool for assisting 
  3319. development. 
  3320.  
  3321. Hopefully the scripts presented - if not immediately useful to you - will give 
  3322. you ideas for other applications. 
  3323.  
  3324. REXX, The Developer's Best Friend - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  3325.  
  3326.  
  3327. ΓòÉΓòÉΓòÉ 7.8. APPENDIX: Summary of REXX Scripts Presented ΓòÉΓòÉΓòÉ
  3328.  
  3329. APPENDIX:  Summary of REXX Scripts Presented 
  3330.  
  3331. INFICONS.CMD 
  3332.  
  3333. Invocation: 
  3334.  
  3335.    INFICONS [path][filemask]
  3336.  
  3337. Example usage: 
  3338.  
  3339.    INFICONS
  3340.    INFICONS C:\OS2\BOOK
  3341.  
  3342. This script will search for INF files on disk, and create icons for them in a 
  3343. folder called "INF Files".  The icons will be named according to the INF file's 
  3344. true title (not its filename). 
  3345.  
  3346. LINE.CMD 
  3347.  
  3348. Invocation: 
  3349.  
  3350.    LINE x
  3351.  
  3352. Example usage: 
  3353.  
  3354.    type file.txt | LINE 50
  3355.  
  3356. This script prints the specified line number of a file to the screen, along 
  3357. with the 3 lines immediately before and after. 
  3358.  
  3359. NEWPROJ.CMD 
  3360.  
  3361. Invocation: 
  3362.  
  3363.    NEWPROJ
  3364.  
  3365. QUOTE.CMD 
  3366.  
  3367. Invocation: 
  3368.  
  3369.    QUOTE [c|c++|rexx|rexxf]
  3370.  
  3371. Example usage: 
  3372.  
  3373.    type file.txt | LINE 50
  3374.  
  3375. This script formats lines for inclusion in C/C++ or REXX procedures.  The 
  3376. parameter indicates the formatting system used. 
  3377.  
  3378. STUBS.CMD 
  3379.  
  3380. Invocation: 
  3381.  
  3382.    STUBS
  3383.  
  3384. TAIL.CMD 
  3385.  
  3386. Invocation: 
  3387.  
  3388.    TAIL x
  3389.  
  3390. Example usage: 
  3391.  
  3392.    type file.txt | TAIL 5
  3393.  
  3394. This script prints the last 'x' lines of a text file to the screen. 
  3395.  
  3396. REXX, The Developer's Best Friend - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  3397.  
  3398.  
  3399. ΓòÉΓòÉΓòÉ 8. RMX-OS2:  An In-Depth View (Part 3) ΓòÉΓòÉΓòÉ
  3400.  
  3401.  
  3402. ΓòÉΓòÉΓòÉ 8.1. Introduction ΓòÉΓòÉΓòÉ
  3403.  
  3404. RMX-OS2:  An In-Depth View (Part 3) 
  3405.  
  3406. Written by Johan Wikman 
  3407.  
  3408. Introduction 
  3409.  
  3410. This is the third article in my series of articles about RMX.  RMX is a system 
  3411. that allows OS/2 PM applications to run remotely, i.e., to run on one computer 
  3412. and be used on another.  A somewhat more thorough overview of RMX is available 
  3413. in EDM/2 Volume 3 Issue 1. 
  3414.  
  3415. One central feature of RMX is the replacement of the local procedure calls 
  3416. (LPCs) an application makes to PM with equivalent remote procedure calls (RPCs) 
  3417. over a network to a remote computer.  In this article I will describe how that 
  3418. replacement is done. 
  3419.  
  3420. RMX-OS2:  An In-Depth View (Part 3) - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  3421.  
  3422.  
  3423. ΓòÉΓòÉΓòÉ 8.2. Dynamic Link Libraries ΓòÉΓòÉΓòÉ
  3424.  
  3425. Dynamic Link Libraries 
  3426.  
  3427. Anybody who has programmed for OS/2 must have - at some point - come across 
  3428. dynamic link libraries or DLLs.  But let us, nevertheless, take a closer look 
  3429. at the differences between static and dynamic linking. 
  3430.  
  3431. Static Linking 
  3432.  
  3433. When an application is statically linked, all code the application needs is 
  3434. collected into the application itself.  Source files are first compiled into 
  3435. object modules that are combined - if of more general use - into a library. The 
  3436. executable is built by linking object modules and libraries as shown in the 
  3437. following figure. 
  3438.  
  3439. There are obvious drawbacks with this approach.  If several applications use 
  3440. the same routine from the library, it effectively means that there are N copies 
  3441. of that routine on disk and potentially in memory.  Also, if a bug is found in 
  3442. the routine, then all applications using that routine must be rebuilt. 
  3443. However, as the application contains all code it needs there is never the risk 
  3444. that it stops working because some DLL is missing.  This may be a highly 
  3445. desirable with certain programs. 
  3446.  
  3447. Dynamic Linking 
  3448.  
  3449. When DLLs are used the build process is slightly different.  Object modules of 
  3450. general use are linked into a DLL instead of being combined into a library. 
  3451. From the DLL (or alternatively from a definitions file) an import library is 
  3452. generated that is used when the application is linked.  When the application is 
  3453. run the DLL is dynamically linked and the application uses the code in the DLL 
  3454. as if it would be a part of the application itself. 
  3455.  
  3456. Back in 1989 Ray Duncan listed in his book Advanced OS/2 Programming the 
  3457. following benefits of dynamic linking: 
  3458.  
  3459.      Code and read-only data in DLLs is shared invisibly between all processes 
  3460.       or clients that use the libraries. 
  3461.      Code and data in DLLs can be loaded on demand, so it does not occupy 
  3462.       physical memory until needed. 
  3463.      DLL routines can be referenced dynamically, i.e., by name, so a library 
  3464.       and the applications that use it can be independently maintained and 
  3465.       improved. 
  3466.      Centralization of frequently used routines in DLLs reduces the size of 
  3467.       each application program file and conserves disk space. 
  3468.  
  3469.  So what has this got to do with RMX? 
  3470.  
  3471.  RMX-OS2:  An In-Depth View (Part 3) - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  3472.  
  3473.  
  3474. ΓòÉΓòÉΓòÉ 8.3. RMX and DLLs ΓòÉΓòÉΓòÉ
  3475.  
  3476. RMX and DLLs 
  3477.  
  3478. Although Ray Duncan's list of DLL benefits is valid, it is not complete. One 
  3479. important point that must be added is: 
  3480.  
  3481.      Provided the prototypes of the exported functions of a DLL are known, a 
  3482.       third party can provide a replacement DLL - with added value - to be used 
  3483.       instead of the original DLL. 
  3484.  
  3485.  This is in essence precisely what RMX is all about.  RMX provides replacement 
  3486.  DLLs that offer remote execution capability.  That is, for each PM DLL to be 
  3487.  replaced there is a corresponding RMX DLL that contains the same functions 
  3488.  with the same prototypes and where the functions are exported using the same 
  3489.  ordinals.  So, an application can not detect any difference between the PM 
  3490.  DLLs and the RMX DLLs.  The user suddenly just can use the application 
  3491.  remotely. 
  3492.  
  3493.  Replacing DLLs 
  3494.  
  3495.  Ok, so we want to replace a DLL an application is using (or actually would be 
  3496.  using if it was started).  How should we do that?  There are at least three 
  3497.  ways: 
  3498.  
  3499.    1. Simply replace the original DLL with the replacement DLL. 
  3500.    2. Place the replacement DLL - with the same file and module name - earlier 
  3501.       in the LIBPATH than the original DLL. 
  3502.    3. Patch the executable so that it uses the replacement DLL instead of the 
  3503.       original DLL. 
  3504.  
  3505.  With ordinary DLLs - i.e., DLLs that are only used by applications and not by 
  3506.  the system - all alternatives are usable, although the first one obviously is 
  3507.  the easiest. 
  3508.  
  3509.  With system DLLs - e.g.  PMWIN.DLL and PMGPI.DLL - the situation is more 
  3510.  complicated.  First of all they can't simply be replaced as they are always in 
  3511.  use, if not by an application then by WPS and the system itself.  Also, the 
  3512.  idea of replacing the DLLs of the system is not particularly appealing. 
  3513.  
  3514.  It is also not possible to allow the system to use the original PM DLLs and 
  3515.  force applications to use the replacement DLLs.  The reason is that once a DLL 
  3516.  is loaded OS/2 does no longer look for it from the LIBPATH.  So, even if a DLL 
  3517.  with the name PMWIN.DLL is in the current directory and the value of LIBPATH 
  3518.  is .;C:\OS2\DLL;...  the original PMWIN.DLL which already is in memory will be 
  3519.  used when an application is started. 
  3520.  
  3521.  That leaves the third alternative.  Patching an application is not all that 
  3522.  nice, but it is actually quite trivial to do and, above all, it works.  But 
  3523.  before I continue with the actual process of patching, I'll mention a fourth 
  3524.  way which actually would be the nicest. 
  3525.  
  3526.  I'm convinced it would be possible to write a loader application that when 
  3527.  started would explicitly read the code and data pages of another executable, 
  3528.  patch the code in memory and execute it.  Hence, no patching of the actual 
  3529.  executable files would be needed. 
  3530.  
  3531.  RMX-OS2:  An In-Depth View (Part 3) - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  3532.  
  3533.  
  3534. ΓòÉΓòÉΓòÉ 8.4. RMX DLLs ΓòÉΓòÉΓòÉ
  3535.  
  3536. RMX DLLs 
  3537.  
  3538. RMX does not provide replacement DLLs for all DLLs of OS/2.  Only some of the 
  3539. ones that deal with PM have to be replaced.  Currently rmxpatch.exe - which is 
  3540. the application that does the actual patching - replaces the following DLLs: 
  3541.  
  3542.  Original            Replaced with 
  3543.  PMWIN.DLL           RXWIN.DLL 
  3544.  PMGPI.DLL           RXGPI.DLL 
  3545.  PMSHAPI.DLL         RXSHAPI.DLL 
  3546.  PMCTLS.DLL          RXCTLS.DLL 
  3547.  HELPMGR.DLL         RXLPMGR.DLL 
  3548.  
  3549.  Replacing these DLLs is enough to get most PM programs up and running.  It 
  3550.  turned out to be necessary to replace HELPMGR.DLL because amazingly many 
  3551.  programs refuse to start if they fail to initialise the help system. Obviously 
  3552.  some functionality - e.g. drag and drop as PMDRAG.DLL is not on the list - is 
  3553.  missing but more often than not it doesn't significantly influence the 
  3554.  usability of the application. 
  3555.  
  3556.  RMX-OS2:  An In-Depth View (Part 3) - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  3557.  
  3558.  
  3559. ΓòÉΓòÉΓòÉ 8.5. Reading Executable Info ΓòÉΓòÉΓòÉ
  3560.  
  3561. Reading Executable Info 
  3562.  
  3563. Ok, let's get on with the real stuff. 
  3564.  
  3565. Executable Header 
  3566.  
  3567. Before anything can be done, we need to find out exactly which DLLs the 
  3568. executable (application or DLL) uses.  In order to do that we must read the 
  3569. header of the executable.  Although RMX is targeted only towards 32-bit 
  3570. applications we have to, in order to find the 32-bit header, read the old DOS 
  3571. executable header first.  The layout of a 32-bit application looks like 
  3572.  
  3573.        00h +------------------+  <--+
  3574.            | DOS 2 Compatible |     |
  3575.            |    EXE Header    |     |
  3576.        1Ch +------------------+     |
  3577.            |      unused      |     |
  3578.            +------------------+     |
  3579.        24h |  OEM Identifier  |     |
  3580.        26h |  OEM Info        |     |
  3581.            |                  |     |-- DOS 2.0 Section
  3582.        3Ch |  Offset to       |     |   (Discarded)
  3583.            |  Linear EXE      |     |
  3584.            |  Header          |     |
  3585.        40h +------------------+     |
  3586.            |   DOS 2.0 Stub   |     |
  3587.            |   Program &      |     |
  3588.            |   Reloc. Table   |     |
  3589.            +------------------+  <--+
  3590.            |                  |
  3591.        xxh +------------------+  <--+
  3592.            |    Executable    |     |
  3593.            |       Info       |     |
  3594.            +------------------+     |
  3595.            |      Module      |     |
  3596.            |       Info       |     |
  3597.            +------------------+     |-- Linear Executable
  3598.            |  Loader Section  |     |   Module Header
  3599.            |       Info       |     |   (Resident)
  3600.            +------------------+     |
  3601.            |   Table Offset   |     |
  3602.            |       Info       |     |
  3603.            +------------------+  <--+
  3604.            |   Object Table   |     |
  3605.            +------------------+     |
  3606.            | Object Page Table|     |
  3607.            +------------------+     |
  3608.            |  Resource Table  |     |
  3609.            +------------------+     |
  3610.            |  Resident Name   |     |
  3611.            |      Table       |     |
  3612.            +------------------+     |-- Loader Section
  3613.            |   Entry Table    |     |   (Resident)
  3614.            +------------------+     |
  3615.            |   Module Format  |     |
  3616.            | Directives Table |     |
  3617.            |    (Optional)    |     |
  3618.            +------------------+     |
  3619.            |     Resident     |     |
  3620.            | Directives Data  |     |
  3621.            |    (Optional)    |     |
  3622.            |                  |     |
  3623.            |  (Verify Record) |     |
  3624.            +------------------+     |
  3625.            |     Per-Page     |     |
  3626.            |     Checksum     |     |
  3627.            +------------------+  <--+
  3628.            | Fixup Page Table |     |
  3629.            +------------------+     |
  3630.            |   Fixup Record   |     |
  3631.            |       Table      |     |
  3632.            +------------------+     |-- Fixup Section
  3633.            |   Import Module  |     |   (Optionally Resident)
  3634.            |    Name Table    |     |
  3635.            +------------------+     |
  3636.            | Import Procedure |     |
  3637.            |    Name Table    |     |
  3638.            +------------------+  <--+
  3639.            |   Preload Pages  |     |
  3640.            +------------------+     |
  3641.            |    Demand Load   |     |
  3642.            |       Pages      |     |
  3643.            +------------------+     |
  3644.            |  Iterated Pages  |     |
  3645.            +------------------+     |
  3646.            |   Non-Resident   |     |-- (Non-Resident)
  3647.            |    Name Table    |     |
  3648.            +------------------+     |
  3649.            |   Non-Resident   |     |
  3650.            | Directives Data  |     |
  3651.            |    (Optional)    |     |
  3652.            |                  |     |
  3653.            |  (To be Defined) |     |
  3654.            +------------------+  <--+
  3655.            |    Debug Info    |     |-- (Not used by Loader)
  3656.            +------------------+  <--+
  3657. where  the  actual DOS 2 compatible header  contains  the following fields: 
  3658.  
  3659. struct exe {
  3660.         WORD eid;
  3661.         ...
  3662.         WORD ereloff;
  3663.         ...
  3664. };
  3665.  
  3666. If we read a proper old header then at offset 0x3C we find the offset of the 
  3667. 32-bit header.  But only if the value of the eid field is EXEID (defined in 
  3668. exe.h) and the value of the erelof field is 0x40. 
  3669.  
  3670. Once we have got that offset (which is from the beginning of the file) we can 
  3671. read the 32-bit executable header. 
  3672.  
  3673. Modules 
  3674.  
  3675. Our target is to find the names of all DLLs the executable implicitly uses but 
  3676. unfortunately that is not quite enough.  There are in practice three kinds of 
  3677. PM applications: 
  3678.  
  3679.    1. 16-bit applications that uses the 16-bit PM functions. 
  3680.    2. 32-bit applications that uses the 16-bit PM functions. 
  3681.    3. 32-bit applications that uses the 32-bit PM functions. 
  3682.  
  3683.  E.g., the Warp applications mahjongg.exe belongs to the third cathegory while 
  3684.  klondike.exe belongs to the second.  As RMX only supports pure 32-bit 
  3685.  applications we have to find out which interface of PM the application uses 
  3686.  before we can patch it.  The required information can be find out by looking 
  3687.  at the fixups that have to be applied when the application is loaded by OS/2. 
  3688.  All needed information is in the fixup section of the executable (copied here 
  3689.  for ease of reference). 
  3690.  
  3691.              +------------------+  <--+
  3692.              | Fixup Page Table |     |
  3693.              +------------------+     |
  3694.              |   Fixup Record   |     |
  3695.              |       Table      |     |
  3696.              +------------------+     |-- Fixup Section
  3697.              |   Import Module  |     |   (Optionally Resident)
  3698.              |    Name Table    |     |
  3699.              +------------------+     |
  3700.              | Import Procedure |     |
  3701.              |    Name Table    |     |
  3702.              +------------------+  <--+
  3703.  
  3704.  The fixup page table provides a mapping from logical page number to an offset 
  3705.  into the fixup record table for that page. 
  3706.  
  3707.  The fixup record table contains entries for all fixups in the executable.  To 
  3708.  make things more complicated there are four types of fixups: an internal 
  3709.  reference, imported by ordinal, imported by name or an internal reference via 
  3710.  an entry table.  So, the fixup record table has to be scanned and for each 
  3711.  reference that refers to a PM DLL we must establish whether it is a pure 
  3712.  32-bit call. 
  3713.  
  3714.  The import module name table is a list of all modules the executable imports 
  3715.  through dynamic link references. 
  3716.  
  3717.  The import procedure name table contains the procedures the executable imports 
  3718.  by name. 
  3719.  
  3720.  Everything we need for extracting this information is in the 32-bit header: 
  3721.  
  3722.   struct e32_exe
  3723.   {
  3724.       ...
  3725.       unsigned long       e32_fixupsize; // Fixup section size
  3726.       ...
  3727.       unsigned long       e32_fpagetab;  // Offset of Fixup Page Table
  3728.       unsigned long       e32_frectab;   // Offset of Fixup Record Table
  3729.       unsigned long       e32_impmod;    // Offset of Import Module Name Table
  3730.       unsigned long       e32_impmodcnt; // Number of entries in Import Module
  3731.                                          // Name Table
  3732.       unsigned long       e32_impproc;   // Offset of Import Proc. Name Table
  3733.       unsigned long       e32_pagesum;
  3734.       ...
  3735.     };
  3736.  
  3737.  Before we begin we define a structure where the data of interest is collected: 
  3738.  
  3739.   #define RMX_MAXDLLNAME 128
  3740.  
  3741.   typedef struct RMXMODULE_
  3742.   {
  3743.     CHAR  achName[RMX_MAXDLLNAME];
  3744.     ULONG ulOffset;
  3745.     BOOL  fixup8;
  3746.     BOOL  fixup16Selector;
  3747.     BOOL  fixup1616Pointer;
  3748.     BOOL  fixup16Offset;
  3749.     BOOL  fixup1632Pointer;
  3750.     BOOL  fixup32Offset;
  3751.     BOOL  fixup32SROffset;
  3752.   } RMXMODULE;
  3753.  
  3754.  achName is a buffer where the module name is stored. 
  3755.  
  3756.  ulOffset is the position - from the beginning of the executable - where the 
  3757.  name of this module is.  This information will be used when the exe is 
  3758.  patched. 
  3759.  
  3760.  fixup8, fixup16Selector, fixup1616Pointer, fixup16Offset, fixup1632Pointer, 
  3761.  fixup32Offset and fixup32SROffset are booleans that specify what kind of 
  3762.  fixups must be applied when the DLL ,this module represents, is loaded by the 
  3763.  system. 
  3764.  
  3765.  Before we begin here are two variables that are used throughout the 
  3766.  presentation. 
  3767.  
  3768.      ULONG          offExe32 = ...;
  3769.      struct e32_exe exe32    = ...;
  3770.  
  3771.  OffExe32 is the offset of the 32-bit header from the beginning of the file. 
  3772.  This is needed because all (but one) offsets in the header are from the 
  3773.  beginning of the header and not from the beginning of the file.  Exe32 is the 
  3774.  32-bit header. 
  3775.  
  3776.  The offset of the fixup section is obtained by adding the offset of the first 
  3777.  table in the fixup section - the fixup page table - to the offset of the 
  3778.  32-bit header.  The size of the fixup section is obtained directly from the 
  3779.  header. 
  3780.  
  3781.      ULONG offFixups = offExe32 + exe32.e32_fpagetab;
  3782.      ULONG cbFixups  = exe32.e32_fixupsize;
  3783.  
  3784.  Now that we know the offset and the size of the fixup section we can read it. 
  3785.  
  3786.      BYTE* pbFixups = (BYTE*) malloc(cbFixups);
  3787.  
  3788.      DosRead(hExe, pbFixups, cbFixups, &cBytesRead);
  3789.  
  3790.  As we really want to have the information in the form of RMXMODULEs we must 
  3791.  allocate some memory. 
  3792.  
  3793.      ULONG ulModuleSize = exe32.e32_impmodcount * sizeof(RMXMODULE);
  3794.  
  3795.      RMXMODULE* pModules = (RMXMODULE*)malloc(ulModuleSize);
  3796.  
  3797.  The next thing to do is to fill in the module name and file offset.  The 
  3798.  offset of the module name table (from the beginning of the fixup section) is 
  3799.  the difference between the offset (from the beginning of the header) of the 
  3800.  module name table and the offset (also from the beginning of the header) of 
  3801.  the fixup page table. 
  3802.  
  3803.      ULONG offModule = exe32.e32_impmod - exe32.e32_fpagetab;
  3804.      BYTE* pbRawModules = pbFixups + offModules;
  3805.  
  3806.  Now we have enough data in order to fill in the name and offset fields of the 
  3807.  RMXMODULEs. 
  3808.  
  3809.   for (int i = 0; i < exe32.e32_impmodcnt; i++)
  3810.   {
  3811.     // The length of the module name is in the first byte.
  3812.  
  3813.     ULONG
  3814.       ulSize = *pbRawModules;
  3815.     RMXMODULE
  3816.       *pModule = &pModules[i];
  3817.  
  3818.     // And the actual name is then from the second byte forward.
  3819.  
  3820.     pbRawModules++;
  3821.  
  3822.     memcpy(pModule->achName, pbRawModules, ulSize);
  3823.  
  3824.     // The strings are not NULL terminated which is why the NULL
  3825.     // must be set explicitly.
  3826.  
  3827.     pModule->achName[ulSize] = 0;
  3828.     pModule->ulOffset = offFixups + (pbRawModules - pbFixups);
  3829.  
  3830.     pbRawModules += ulSize;
  3831.   }
  3832.  
  3833.  The next task is to check out the fixups.  The fixup table provides a mapping 
  3834.  of a logical page number to an offset into the fixup record table for that 
  3835.  page.  The table contains one additional entry that is an offset to the end of 
  3836.  the fixup record table.  As the offsets are 4 bytes, the number of pages is: 
  3837.  
  3838.      ULONG cPages = (exe32.e32_frectab - exe32.e32_fpagetab) / 4 - 1;
  3839.  
  3840.  As the page table is first in the fixup chunk we can simply cast the fixups 
  3841.  pointer into an unsigned long pointer and, voila, we have the page table.  The 
  3842.  actual records are obtained by adding the proper offset to the beginning of 
  3843.  the fixup section. 
  3844.  
  3845.      ULONG* poffPages = (ULONG*) pbFixups;
  3846.      BYTE*  pbRecords = pbFixups + (exe32.e32_frectab - exe32.e32_fpagetab);
  3847.  
  3848.  In the fixup record table there are four kind of entries.  The length and 
  3849.  structure is different for each kind of entry but the following figure shows 
  3850.  the general layout: 
  3851.  
  3852.              +-----+-----+-----+-----+
  3853.          00h | SRC |FLAGS|SRCOFF/CNT*|
  3854.              +-----+-----+-----+-----+-----+-----+
  3855.      03h/04h |           TARGET DATA *           |
  3856.              +-----+-----+-----+-----+-----+-----+
  3857.              | SRCOFF1 @ |   . . .   | SRCOFFn @ |
  3858.              +-----+-----+----   ----+-----+-----+
  3859.  
  3860.            * These fields are variable size.
  3861.            @ These fields are optional.
  3862.  
  3863.  The SRC byte specifies the type of the fixup to be performed on the fixup 
  3864.  source and the type is specified as follows: 
  3865.  
  3866.   0Fh = Source mask.
  3867.   00h = Byte fixup (8-bits).
  3868.   01h = (undefined).
  3869.   02h = 16-bit Selector fixup (16-bits).
  3870.   03h = 16:16 Pointer fixup (32-bits).
  3871.   04h = (undefined).
  3872.   05h = 16-bit Offset fixup (16-bits).
  3873.   06h = 16:32 Pointer fixup (48-bits).
  3874.   07h = 32-bit Offset fixup (32-bits).
  3875.   08h = 32-bit Self-relative offset fixup (32-bits).
  3876.   10h = Fixup to Alias Flag.
  3877.   20h = Source List Flag.
  3878.  
  3879.  The FLAGS field specifies how the target data should be interpreted and the 
  3880.  flags are defined as follows: 
  3881.  
  3882.   03h = Fixup target type mask.
  3883.   00h = Internal reference.
  3884.   01h = Imported reference by ordinal.
  3885.   02h = Imported reference by name.
  3886.   03h = Internal reference via entry table.
  3887.   04h = Additive Fixup Flag.
  3888.   08h = Reserved.  Must be zero.
  3889.   10h = 32-bit Target Offset Flag.
  3890.   20h = 32-bit Additive Fixup Flag.
  3891.   40h = 16-bit Object Number/Module Ordinal Flag.
  3892.   80h = 8-bit Ordinal Flag.
  3893.  
  3894.  So, in order to find out which fixups are applied for each DLL we have to loop 
  3895.  through all relocation entries and see what they tell us. 
  3896.  
  3897.   for (i = 0; i < cPages; i++)
  3898.   {
  3899.     BYTE
  3900.       *pbCurrentRecord = pbRecords + poffPages[i],
  3901.       *pbNextRecord    = pbRecords + poffPages[i + 1];
  3902.  
  3903.     while (pbCurrentRecord < pbNextRecord)
  3904.       {
  3905.         BYTE
  3906.           flTarget = pbCurrentRecord[1]; // NOTE: 1 not 0
  3907.         ULONG
  3908.           ulSize = 0;
  3909.  
  3910.         switch (flTarget & NRRTYP)
  3911.           {
  3912.           case NRRINT:
  3913.             ulSize = ParseInternalReference(pbCurrentRecord);
  3914.             break;
  3915.  
  3916.           case NRRORD:
  3917.             ulSize = ParseOrdinalImportReference(pbCurrentRecord, pModules);
  3918.             break;
  3919.  
  3920.           case NRRNAM:
  3921.             ulSize = ParseNameImportReference(pbCurrentRecord, pModules);
  3922.             break;
  3923.  
  3924.           case NRRENT:
  3925.             ulSize = ParseInternalEntryReference(pbCurrentRecord);
  3926.           }
  3927.  
  3928.         pbCurrentRecord += ulSize;
  3929.       }
  3930.   }
  3931.  
  3932.  The NNR constants are defined in exe386.h.  Ok, let's then have a look at the 
  3933.  different Parse-functions. 
  3934.  
  3935.  ULONG PreParseReference(BYTE* pbRecord); 
  3936.  
  3937.  The following fields are common to all reference entries regardless of what 
  3938.  their actual type is: 
  3939.  
  3940.       +-----+-----+-----+-----+
  3941.   00h | SRC |FLAGS|SRCOFF/CNT*|
  3942.       +-----+-----+-----+-----+
  3943.       ...
  3944.  
  3945.     * These fields are variable size.
  3946.  
  3947.  What this function actually does is to figure out how much space the common 
  3948.  parts actually occupies.  As the SRC and FLAGS entries are always present the 
  3949.  size is at least 2 bytes. 
  3950.  
  3951.      ULONG ulSize = 2;
  3952.  
  3953.  The item following the SRC and FLAGS field is either a byte specifying how 
  3954.  many offsets there are - in which case the actual relocation entry is followed 
  3955.  by a list of offsets (the SRCOFF1 ...  SRCOFFn in the figure presented 
  3956.  earlier) - or a word specifying the single source offset.  Which one it is, is 
  3957.  specified by the presens or absence of the source list flag in the SRC byte. 
  3958.  
  3959.   BYTE sourceType = pbRecord[0];
  3960.  
  3961.   if (sourceType & NRCHAIN) // "Source List" flag.
  3962.     {
  3963.       ulSize += 1;  // The byte itself.
  3964.  
  3965.       BYTE
  3966.           sourceCount = pbRecord[2];
  3967.  
  3968.       ulSize += sourceCount * 2; // The actual offsets..
  3969.     }
  3970.   else
  3971.     ulSize += 2;
  3972.  
  3973.  So, depending on the way the offsets are presented this function returns 
  3974.  either 4, or 5, 7, 9, ...  and so on. 
  3975.  
  3976.  ULONG ParseInternalReference(BYTE* pRecord); 
  3977.  
  3978.  If it is an internal reference the relocation record looks like: 
  3979.  
  3980.           +-----+-----+-----+-----+
  3981.       00h | SRC |FLAGS|SRCOFF/CNT*|
  3982.           +-----+-----+-----+-----+-----+-----+
  3983.   03h/04h |  OBJECT * |        TRGOFF * @     |
  3984.           +-----+-----+-----+-----+-----+-----+
  3985.           | SRCOFF1 @ |   . . .   | SRCOFFn @ |
  3986.           +-----+-----+----   ----+-----+-----+
  3987.  
  3988.         * These fields are variable size.
  3989.         @ These fields are optional.
  3990.  
  3991.  The call to PreParseReference figures out the size of the common parts. 
  3992.  
  3993.      ULONG ulSize = PreParseReference(pbRecord);
  3994.      BYTE
  3995.        sourceType = pbRecord[0],
  3996.        flTarget   = pbRecord[1];
  3997.  
  3998.  OBJECT is an index into the current module's object table that specifies the 
  3999.  target object.  Depending on a bit in flTarget it is either a byte or a word. 
  4000.  
  4001.      if (flTarget & NR16OBJMOD)
  4002.        ulSize += 2;
  4003.      else
  4004.        ulSize += 1;
  4005.  
  4006.  The target offset field - TRGOFF - is present only if this record does not 
  4007.  represent a 16-bit selector fixup. 
  4008.  
  4009.      if ((sourceType & NRSTYPE) == NRRSEG)
  4010.        ;
  4011.      else
  4012.      {
  4013.  
  4014.  If it is present it is either a 2 or 4 byte entry depending on whether the 
  4015.  32-bit target offset bit has been set. 
  4016.  
  4017.        if (flTarget & NR32BITOFF)
  4018.          ulSize += 4;
  4019.        else
  4020.          ulSize += 2;
  4021.      }
  4022.  
  4023.  ULONG ParseOrdinalImportReference(BYTE* pbRecord, RMXMODULE* pModule); 
  4024.  
  4025.  If it is an ordinal import reference the relocation record looks like: 
  4026.  
  4027.           +-----+-----+-----+-----+
  4028.       00h | SRC |FLAGS|SRCOFF/CNT*|
  4029.           +-----+-----+-----+-----+-----+-----+-----+-----+
  4030.   03h/04h | MOD ORD# *|IMPORT ORD*|     ADDITIVE * @      |
  4031.           +-----+-----+-----+-----+-----+-----+-----+-----+
  4032.           | SRCOFF1 @ |   . . .   | SRCOFFn @ |
  4033.           +-----+-----+----   ----+-----+-----+
  4034.  
  4035.         * These fields are variable size.
  4036.         @ These fields are optional.
  4037.  
  4038.  The common stuff is handled by PreParseReference. 
  4039.  
  4040.      ULONG ulSize = PreParseReference(pbRecord);
  4041.      BYTE
  4042.        sourceType = pbRecord[0],
  4043.        flTarget   = pbRecord[1];
  4044.  
  4045.  Depending on which way the source offsets are represented, the index of the 
  4046.  module ordinal - MODORD - is either 3 or 4. 
  4047.  
  4048.      ULONG index = 3;
  4049.  
  4050.      if (!(sourceType & NRCHAIN))
  4051.        index++;
  4052.  
  4053.  The module index is either a byte or a word, depending on whether the 16-bit 
  4054.  module ordinal flag has been set. 
  4055.  
  4056.   if (flTarget & NR16OBJMOD)
  4057.   {
  4058.     usModule = *((USHORT*) &pbRecord[index]);
  4059.     index += 2;
  4060.     ulSize  += 2;
  4061.   }
  4062.   else
  4063.   {
  4064.     usModule = pbRecord[index];
  4065.     index += 1;
  4066.     ulSize  += 1;
  4067.   }
  4068.  
  4069.  The actual ordinal is then either 1, 2 or 4 bytes depending on whether certain 
  4070.  bits have been set or not. 
  4071.  
  4072.   ULONG ordinal = 0;
  4073.  
  4074.   if (flTarget & NR8BITORD) // "8-bit Ordinal" flag
  4075.   {
  4076.     ordinal = (UCHAR) pbRecord[index];
  4077.     ulSize += 1;
  4078.   }
  4079.   else if (flTarget & NR32BITOFF) // "32-bit Target Offset" flag
  4080.   {
  4081.     ordinal = *((ULONG*) &pbRecord[index]);
  4082.     ulSize += 4;
  4083.   }
  4084.   else
  4085.   {
  4086.     ordinal = *((USHORT*) &pbRecord[index]);
  4087.     ulSize += 2;
  4088.   }
  4089.  
  4090.  I don't use the ordinal for anything, but for a tdump or exehdr kind of 
  4091.  utility you could simply print it out.  The additive fixup value - ADDITIVE - 
  4092.  is optionally present and its size is either 2 or 4 bytes. 
  4093.  
  4094.      if (flTarget & NRADD)
  4095.      {
  4096.        if (flTarget & NR32BITADD)
  4097.          ulSize += 4;
  4098.        else
  4099.          ulSize += 2;
  4100.      }
  4101.  
  4102.  Finally we call UpdateFixupInfo in order to update the proper RMXMODULE entry. 
  4103.  
  4104.      UpdateFixupInfo(sourceType, &pModules[usModule - 1]);
  4105.  
  4106.  ULONG ParseNameImportReference(BYTE* pbRecord, RMXMODULE* pModules); 
  4107.  
  4108.  If it is an name import reference the relocation record looks like: 
  4109.  
  4110.           +-----+-----+-----+-----+
  4111.       00h | SRC |FLAGS|SRCOFF/CNT*|
  4112.           +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
  4113.   03h/04h | MOD ORD# *| PROCEDURE NAME OFFSET*|     ADDITIVE * @      |
  4114.           +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
  4115.           | SRCOFF1 @ |   . . .   | SRCOFFn @ |
  4116.           +-----+-----+----   ----+-----+-----+
  4117.  
  4118.         * These fields are variable size.
  4119.         @ These fields are optional.
  4120.  
  4121.  The beginning of this function is identical to ParseOrdinalImportReference, so 
  4122.  I won't duplicate it here, but continue after the module index has been 
  4123.  figured out. 
  4124.  
  4125.  The procedure name offset is a direct offset into the import procedure name 
  4126.  table and could be used for printing out the names of the functions the 
  4127.  executable imports.  Keep in mind that the strings are pascal strings (initial 
  4128.  byte specifying the length, no NULL at end).  The procedure offset is either a 
  4129.  word or a long depending on the 32-bit target offset flag. 
  4130.  
  4131.   ULONG procedure = 0;
  4132.  
  4133.   if (flTarget & NR32BITOFF) // "32-bit Target Offset" flag
  4134.   {
  4135.     procedure = *((ULONG*) &pbRecord[index]);
  4136.     index += 4;
  4137.     ulSize  += 4;
  4138.   }
  4139.   else
  4140.   {
  4141.     procedure = *((USHORT*) &pbRecord[index]);
  4142.     index += 2;
  4143.     ulSize  += 2;
  4144.   }
  4145.  
  4146.  The entry is then, just like in the ordinal import case, followed by an 
  4147.  optional additive fixup value. 
  4148.  
  4149.      if (flTarget & NRADD)
  4150.      {
  4151.        if (flTarget & NR32BITADD)
  4152.          ulSize += 4;
  4153.        else
  4154.          ulSize += 2;
  4155.      }
  4156.  
  4157.  Again, the corresponding RMXMODULE entry is updated with the information. 
  4158.  
  4159.      UpdateFixupInfo(sourceType, &pModules[usModule - 1]);
  4160.  
  4161.  ULONG ParseInternalEntryReference(BYTE* pbRecord); 
  4162.  
  4163.  If it is an internal entry reference, the relocation entry looks like: 
  4164.  
  4165.       00h | SRC |FLAGS|SRCOFF/CNT*|
  4166.           +-----+-----+-----+-----+-----+-----+
  4167.   03h/04h |  ORD # *  |     ADDITIVE * @      |
  4168.           +-----+-----+-----+-----+-----+-----+
  4169.           | SRCOFF1 @ |   . . .   | SRCOFFn @ |
  4170.           +-----+-----+----   ----+-----+-----+
  4171.  
  4172.         * These fields are variable size.
  4173.         @ These fields are optional.
  4174.  
  4175.  As usual the initial stuff is handled by PreParseReference. 
  4176.  
  4177.      ULONG ulSize = PreParseReference(pbRecord);
  4178.      BYTE
  4179.        flTarget = pbRecord[0];
  4180.  
  4181.  The ordinal is always there, either as a 1 or 2 byte value and the additive 
  4182.  fixup value is optionally there, either as a 2 or 4 byte value. 
  4183.  
  4184.   if (flTarget & NR16OBJMOD) // "16-bit Object Number/Module Ordinal" flag.
  4185.     ulSize += 2;
  4186.   else
  4187.     ulSize += 1;
  4188.  
  4189.   if (flTarget & NRADD) // "Additive Fixup" flag
  4190.   {
  4191.     if (flTarget & NR32BITADD) // "32-bit Additive Fixup" flag
  4192.       ulSize += 4;
  4193.     else
  4194.       ulSize += 2;
  4195.   }
  4196.  
  4197.  VOID UpdateFixupInfo(BYTE sourceType, RMXMODULE* pModule); 
  4198.  
  4199.  Let's then have a look at what UpdateFixupInfo does.  It is very trivial; 
  4200.  based on the source type flags it sets the appropriate flags in RMXMODULE to 
  4201.  TRUE. 
  4202.  
  4203.   switch (sourceType & NRSTYP)
  4204.   {
  4205.    case NRSBYT:
  4206.     pModule->fixup8           = TRUE;
  4207.     break;
  4208.  
  4209.    case NRSSEG:
  4210.     pModule->fixup16Selector  = TRUE;
  4211.     break;
  4212.  
  4213.    case NRSPTR:
  4214.     pModule->fixup1616Pointer = TRUE;
  4215.     break;
  4216.  
  4217.    case NRSOFF:
  4218.     pModule->fixup16Offset    = TRUE;
  4219.     break;
  4220.  
  4221.    case NRPTR48:
  4222.     pModule->fixup1632Pointer = TRUE;
  4223.     break;
  4224.  
  4225.    case NROFF32:
  4226.     pModule->fixup32Offset    = TRUE;
  4227.     break;
  4228.  
  4229.    case NRSOFF32:
  4230.     pModule->fixup32SROffset  = TRUE;
  4231.   }
  4232.  
  4233.  The whole concept here is very brute-force.  Instead of scanning through all 
  4234.  relocation records for a module, we could stop immediately when something is 
  4235.  found that makes patching impossible.  But that would make everything more 
  4236.  complex and the scanning of all relocation records really doesn't take too 
  4237.  long. 
  4238.  
  4239.  RMX-OS2:  An In-Depth View (Part 3) - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  4240.  
  4241.  
  4242. ΓòÉΓòÉΓòÉ 8.6. Patching ΓòÉΓòÉΓòÉ
  4243.  
  4244. Patching 
  4245.  
  4246. So, having obtained all RMXMODULEs of an executable we scan through them and if 
  4247. the name of the module matches one of the DLLs we want to replace, we use the 
  4248. offset to find the proper place in the executable file and simply write the new 
  4249. name.  However, there are a few problems: 
  4250.  
  4251.      The name of the replacement DLL must have the same length as the name of 
  4252.       the original DLL. 
  4253.      If OS/2 would use the checksum entries that are present in the executable 
  4254.       header, then this crude approach would not work. 
  4255.  
  4256.  There is a fix for these problems.  If the patching would be performed as a 
  4257.  proper link, i.e., a completely new executable header is created, there 
  4258.  wouldn't be any constraints on the name and the checksum would also not be a 
  4259.  problem.  However, that would require a great deal more effort and it'll have 
  4260.  to wait until I have more time<grin>. 
  4261.  
  4262.  RMXPATCH.EXE 
  4263.  
  4264.  Along with this article is the full source for rmxpatch.exe.  You can't use it 
  4265.  for anything particular yet as I haven't distributed RMX itself, but it would 
  4266.  probably be fairly easy to use rmxpatch as a base for a tdump or exehdr kind 
  4267.  of utility.  And anyway, once RMX is available (real soon now <grin>) rmxpatch 
  4268.  can be used for patching and unpatching PM programs. 
  4269.  
  4270.  If you start rmxpatch.exe without any arguments it prints: 
  4271.  
  4272.      usage: rmxpatch (-p|-u) [-d] file [file ...]
  4273.  
  4274.             -p: Patch the files for RMX.
  4275.             -u: Unpatch the files, i.e., remove references to RMX.
  4276.             -d: Do  it.  Without this flag rmxpatch  only shows
  4277.                 what it would do had the flag been given.
  4278.  
  4279.  So, without -d rmxpatch only shows what it would do, but it doesn't patch 
  4280.  anything.  I chose to make it rather difficult to patch a program so that 
  4281.  people wouldn't patch programs by mistake.  Let's see then what the output of 
  4282.  rmxpatch look like.  I use here mine.exe - the excellent mine sweeper game - 
  4283.  as example. 
  4284.  
  4285.      [C:\]rmxpatch -p mine.exe
  4286.   mine.exe:
  4287.       Offset    From      To        Comment
  4288.       ---------------------------------------
  4289.       6193      dllc101             ignored
  4290.       6201      DOSCALLS            ignored
  4291.       6210      PMGPI     RXGPI     replacable
  4292.       6216      PMWIN     RXWIN     replacable
  4293.       6222      PMSHAPI   RXSHAPI   replacable
  4294.       6230      HELPMGR   RXLPMGR   replacable
  4295.  
  4296.       Success: the executable can be patched.
  4297.  
  4298.  With -u the output obviously is different as there is nothing to do. 
  4299.  
  4300.      [C:\]rmxpatch -u mine.exe
  4301.   mine.exe:
  4302.       Offset    From      To        Comment
  4303.       ---------------------------------------
  4304.       6193      dllc101             ignored
  4305.       6201      DOSCALLS            ignored
  4306.       6210      PMGPI               ignored
  4307.       6216      PMWIN               ignored
  4308.       6222      PMSHAPI             ignored
  4309.       6230      HELPMGR             ignored
  4310.  
  4311.       Success: the executable can be patched.
  4312.  
  4313.  As expected, there is nothing to unpatch on a program.  It would have been 
  4314.  possible to have deduced from the DLL names what the user actually wants to 
  4315.  do, but I chose to require the user to tell it explicitly.  A smaller risk for 
  4316.  confusion that way.  To actually patch the program -d is required. 
  4317.  
  4318.      [C:\]rmxpatch -p -d mine.exe
  4319.   mine.exe:
  4320.       Offset    From      To        Comment
  4321.       ---------------------------------------
  4322.       6193      dllc101             ignored
  4323.       6201      DOSCALLS            ignored
  4324.       6210      PMGPI     RXGPI     successfully replaced
  4325.       6216      PMWIN     RXWIN     successfully replaced
  4326.       6222      PMSHAPI   RXSHAPI   successfully replaced
  4327.       6230      HELPMGR   RXLPMGR   successfully replaced
  4328.  
  4329.       Success: the executable was successfully patched.
  4330.  
  4331.  The unpatching works in a similar way. 
  4332.  
  4333.  RMX-OS2:  An In-Depth View (Part 3) - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  4334.  
  4335.  
  4336. ΓòÉΓòÉΓòÉ 8.7. Conclusion ΓòÉΓòÉΓòÉ
  4337.  
  4338. Conclusion 
  4339.  
  4340. That was briefly about the mechanism used for replacing DLLs and the tool 
  4341. required for patching applications.  Feel free to use the source of the tool 
  4342. for anything you want and don't hesitate to mail me if there is something you 
  4343. are wondering about.  I haven't decided yet what the next article will be about 
  4344. so you'll just have to wait<grin>. 
  4345.  
  4346. RMX-OS2:  An In-Depth View (Part 3) - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  4347.  
  4348.  
  4349. ΓòÉΓòÉΓòÉ 9. /dev/EDM/BookReview ΓòÉΓòÉΓòÉ
  4350.  
  4351.  
  4352. ΓòÉΓòÉΓòÉ 9.1. Introduction ΓòÉΓòÉΓòÉ
  4353.  
  4354. /dev/EDM2/BookReview 
  4355.  
  4356. Written by Carsten Whimster 
  4357.  
  4358. Introduction 
  4359.  
  4360. /dev/EDM2/BookReview is a monthly column which focuses on development oriented 
  4361. books and materials.  The column is from the point of view of an intermediate 
  4362. PM C programmer and intermediate REXX programmer.  Pick up whichever book 
  4363. strikes your fancy, and join the growing group of people following our PM 
  4364. programming columns.  I have reviewed a number of beginner's books, and will 
  4365. try to concentrate a bit more on intermediate techniques and special topics 
  4366. from now on. 
  4367.  
  4368. Please send me your comments and thoughts so that I can make this column as 
  4369. good as possible.  I read and respond to all mail. 
  4370.  
  4371. This month has quite possibly been the busiest of my life, and there will be no 
  4372. review.  I am halfway done a review of The GUI- OOUI War - Windows vs. OS/2, 
  4373. Mandel, though, so this will appear next month. 
  4374.  
  4375. /dev/EDM2/BookReview - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  4376.  
  4377.  
  4378. ΓòÉΓòÉΓòÉ 9.2. Errata ΓòÉΓòÉΓòÉ
  4379.  
  4380. Errata 
  4381.  
  4382. My WWW page is back on-line, albeit a little short on up-to- dateness, and 
  4383. thoroughness of execution, but for those of you with actual time on your hands, 
  4384. it might prove slightly interesting.  The address is in my author page. 
  4385.  
  4386. Due to having to write a file system two weeks ago, and a virtual memory system 
  4387. this week coming up, I haven't had enough time to do anything real in my life 
  4388. for a long time, and there is no review this month.  If you'd like, you can 
  4389. send me mail at the address on my page at the end, if you want to encourage me, 
  4390. suggest something, or just plain communicate. 
  4391.  
  4392. /dev/EDM2/BookReview - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  4393.  
  4394.  
  4395. ΓòÉΓòÉΓòÉ 9.3. Books Reviewed ΓòÉΓòÉΓòÉ
  4396.  
  4397. Books Reviewed 
  4398.  
  4399.      Real-World Programming for OS/2 2.1, Blain, Delimon, and English 
  4400.  
  4401.         -  SAMS Publishing. ISBN 0-672-30300-0. US$40, CAN$50. 
  4402.         -  Intermediate to Advanced PM C programmers 
  4403.         -  B+ 
  4404.  
  4405.       Lots of good code examples, but sometimes it is too complex for novices. 
  4406.       Accurate.  Well organized.  The index needs a little beefing up.  Good, 
  4407.       but not entirely complete how-to reference.  Good purchase. 
  4408.  
  4409.      Learning to Program OS/2 2.0 Presentation Manager by Example, Knight 
  4410.  
  4411.         -  Van Nostrand Reinhold.  ISBN 0-442-01292-6.  US$40, CAN$50. 
  4412.         -  Beginning PM C Programmers 
  4413.         -  B- 
  4414.  
  4415.       This book can be both frustrating and very rewarding.  It is not very 
  4416.       large, and a bit pricey, but has some excellent chapters on certain 
  4417.       beginning topics, such as messages, resources, IPF, and dialog boxes. 
  4418.       Strictly for beginners.  This book has only one (large) sample program! 
  4419.  
  4420.      Writing OS/2 2.1 Device Drivers in C, 2nd Edition, Mastrianni 
  4421.  
  4422.         -  Van Nostrand Reinhold. ISBN 0-442-01729-4. US$35, CAN$45. 
  4423.         -  Advanced C Programmers, familiar with hardware programming 
  4424.         -  A- 
  4425.  
  4426.       The only thing a device driver programmer would not find in here is how 
  4427.       to write SCSI, ADD, and IFS drivers.  Most everything else is in here, 
  4428.       along with skeleton examples.  An optional DevHlp library of C-callable 
  4429.       functions can be purchased by those who don't have time to write their 
  4430.       own. 
  4431.  
  4432.      OS/2 Presentation Manager GPI, Winn 
  4433.  
  4434.         -  Van Nostrand Reinhold. ISBN 0-442-00739-6. US$35, CAN$45. 
  4435.         -  Intermediate to advanced PM C programmers 
  4436.         -  C+ 
  4437.  
  4438.       This book needs updating for OS/2 2.x.  It is a well-written in- depth 
  4439.       coverage of the OS/2 way of programming for graphics.  It is not an 
  4440.       introductory PM or graphics programming book.  You should know the basics 
  4441.       of PM programming already. 
  4442.  
  4443.      The Art of OS/2 2.1 C Programming, Panov, Salomon, and Panov 
  4444.  
  4445.         -  Wiley-QED. ISBN 0-471-58802-4. US$40, CAN$50. 
  4446.         -  Beginning OS/2 and PM programmers 
  4447.         -  B+ 
  4448.  
  4449.       This is a great introductory PM programming book.  It covers basic OS/2 
  4450.       issues like threads before it jumps into PM programming.  The coverage is 
  4451.       quite thourough, with just enough reference material to make it useful 
  4452.       after you read it through the first time.  The upcoming revised edition 
  4453.       should be a killer. 
  4454.  
  4455.      Mastering OS/2 REXX, Gargiulo 
  4456.  
  4457.         -  Wiley-QED. ISBN 0-471-51901-4. US$40, CAN$50. 
  4458.         -  Intermediate OS/2 users and beginning programmers 
  4459.         -  B 
  4460.  
  4461.       This book is very easy to understand.  If you program with any 
  4462.       regularity, look elsewhere, but if you need an easily read, well- 
  4463.       explained beginner's book, look no further.  Some more detailed, and 
  4464.       complex real-world examples might be useful as you learn the material. 
  4465.       Good coverage of REXX's capabilities. 
  4466.  
  4467.      REXX Reference Summary Handbook, Goran 
  4468.  
  4469.         -  C F S Nevada. ISBN 0-9639854-1-8. US$20, CAN$25. 
  4470.         -  Beginning to advanced REXX programmers 
  4471.         -  A 
  4472.  
  4473.       This little handbook is packed full of useful information.  Includes 
  4474.       chapters on both built-in and some popular commercial libraries. 
  4475.       Well-written and comprehensively indexed.  A must for REXX programmers. 
  4476.  
  4477.      Application Development Using OS/2 REXX, Rudd 
  4478.  
  4479.         -  Wiley-QED. ISBN 0-471-60691-X. US$40, CAN$50 
  4480.         -  Intermediate to advanced REXX programmers 
  4481.         -  A- 
  4482.  
  4483.       Excellent coverage of everything REXX, with the occasional sparse 
  4484.       section. It is a decent reference book, and has enough unusual material 
  4485.       that it will probably find its way into many REXX programmers' libraries. 
  4486.  
  4487.      OS/2 Presentation Manager Programming, Petzold 
  4488.  
  4489.         -  Ziff-Davis Press. ISBN 1-56276-123-4. US$30, CAN$42 
  4490.         -  Beginning PM C programmers 
  4491.         -  A- 
  4492.  
  4493.       This book has the most thorough introduction to PM basics in any book I 
  4494.       have read so far.  It leaves the field wide open for a more thorough 
  4495.       advanced PM book later.  Very well written, very thorough, and very 
  4496.       understandable. Only a lack of in-depth treatment of control windows and 
  4497.       common dialog boxes keep it from being perfect. 
  4498.  
  4499.      Designing OS/2 Applications, Reich 
  4500.  
  4501.         -  John Wiley & Sons. ISBN 0-471-58889-X. US$35, CAN$45 
  4502.         -  All programmers 
  4503.         -  A 
  4504.  
  4505.       This book is about design, and making intelligent decisions in this 
  4506.       process.  It describes the OS/2 programming environment well, and thus 
  4507.       helps you make the proper choices in designing applications.  Highly 
  4508.       recommended to anyone creating more than just command-line tools and very 
  4509.       small programs. 
  4510.  
  4511.      OS/2 Programming, Schildt and Goosey 
  4512.  
  4513.         -  Osborne, McGraw-Hill. ISBN 0-07-881910-5. US$30, CAN$40 
  4514.         -  Introductory PM C programmers 
  4515.         -  B+ 
  4516.  
  4517.       This book is a good, but slightly thin introductory PM book.  It has good 
  4518.       explanations of many things that other books tend to skip over, and 
  4519.       covers a reasonably well selected subset of the API.  Unfortunately, it 
  4520.       doesn't come with a disk, so if you are looking for programs to 
  4521.       cut-and-paste, look elsewhere. 
  4522.  
  4523.  NOTES 
  4524.  
  4525.  This list contains all books I have reviewed, so that you can find what you 
  4526.  are looking for at a glance.  I will be careful to rate books fairly.  If I 
  4527.  feel a need to adjust ratings, I will adjust all of them at the same time, and 
  4528.  write a note explaining why I felt this necessary.  Please note that books 
  4529.  aimed at different audiences should only be compared with great care, if at 
  4530.  all.  I intend to concentrate on the strong points of the books I review, but 
  4531.  I will point out any weaknesses in a constructive manner. 
  4532.  
  4533.  LEGEND 
  4534.  
  4535.  BOOK:  The name of the book, and the author(s). 
  4536.  
  4537.  PUBLISHING INFORMATION:  Publishing company, ISBN, and approximate price. 
  4538.  
  4539.  AUDIENCE:  This is a description of the audience I think the book targets 
  4540.  best.  This is not intended as gospel, just a guideline for people not 
  4541.  familiar with the book. 
  4542.  
  4543.  MARK:  My opinion of the success of the book's presentation, and how well it 
  4544.  targets its audience.  Technical content, accuracy, organization, readability, 
  4545.  and quality of index all weigh heavily here, but the single most important 
  4546.  item is how well the book covers what it says it covers.  Many books try to 
  4547.  cover too much, and get a lower mark as a result. 
  4548.  
  4549.  A+        Ground-breaking, all-around outstanding book. 
  4550.  A         Excellent book. This is what I want to see happen a lot. 
  4551.  A-        Excellent book with minor flaws. 
  4552.  B+        Very good book with minor flaws or omissions. 
  4553.  B         Good book with some flaws and omissions. 
  4554.  B-        Good book, but in need of improvement. 
  4555.  C+        Mediocre book with some potential, but in need of some updating. 
  4556.  C         Mediocre book with some good sections, but badly in need of fixing. 
  4557.  C-        Mediocre book, little good material, desperately in need of an 
  4558.            overhaul. 
  4559.  D         Don't buy this book unless you need it, and nothing else exists. 
  4560.  F         Don't buy this book.  Period. 
  4561.  
  4562.  COMMENTS:  This is a very brief summary of the review proper. 
  4563.  
  4564.  /dev/EDM2/BookReview - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  4565.  
  4566.  
  4567. ΓòÉΓòÉΓòÉ 9.4. Content Index ΓòÉΓòÉΓòÉ
  4568.  
  4569. Content Index 
  4570.  
  4571. This Content Index is designed to let you find the book that covers the topics 
  4572. you need to learn about.  It will eventually have a lot of categories, with 
  4573. each book being rated along each row.  These tables will be quite large, and 
  4574. will continually grow, so please give me your feedback regarding what 
  4575. categories you would like to see, and which you don't.  It may take me a while 
  4576. to flesh them out, so have a little patience. 
  4577.  
  4578. NOTE:  books which cover the same material can look similar in this table, but 
  4579. be different in real life.  The style of a book, for example, can not be seen 
  4580. from a quick table, so make sure that you follow up by reading the reviews of 
  4581. the books you find here.  Finally, be sure that the books you are comparing are 
  4582. aimed at the same audiences. 
  4583.  
  4584. PM C BOOKS 
  4585.  
  4586. ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
  4587. ΓöéBOOK ΓöéMARK ΓöéKernel ΓöéDevice ΓöéVIO andΓöéPM     ΓöéGPI    ΓöéFonts  ΓöéPrint  Γöé
  4588. Γöé     Γöé     ΓöéBasics ΓöéDriver ΓöéAVIO   ΓöéIntro  Γöé       Γöé       Γöé       Γöé
  4589. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4590. ΓöéRWP  ΓöéB+   Γöé2      Γöé0      Γöé0      Γöé4      Γöé4      Γöé4      Γöé3      Γöé
  4591. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4592. ΓöéPME  ΓöéB-   Γöé1      Γöé0      Γöé0      Γöé2      Γöé2      Γöé2      Γöé0      Γöé
  4593. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4594. ΓöéODD  ΓöéA    Γöé0      Γöé5      Γöé0      Γöé0      Γöé1      Γöé0      Γöé1      Γöé
  4595. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4596. ΓöéGPI  ΓöéC+   Γöé0      Γöé0      Γöé0      Γöé0      Γöé5      Γöé2      Γöé3      Γöé
  4597. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4598. ΓöéTAO  ΓöéB+   Γöé3      Γöé2      Γöé1      Γöé4      Γöé1      Γöé2      Γöé0      Γöé
  4599. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4600. ΓöéPMP  ΓöéA-   Γöé1      Γöé0      Γöé1      Γöé5      Γöé3      Γöé4      Γöé2      Γöé
  4601. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4602. ΓöéOSP  ΓöéB+   Γöé2      Γöé0      Γöé0      Γöé3      Γöé2      Γöé1      Γöé0      Γöé
  4603. ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
  4604.  
  4605. REXX BOOKS: 
  4606.  
  4607. ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
  4608. ΓöéBOOK ΓöéMARK ΓöéREXX     ΓöéWPS      ΓöéReferenceΓöé
  4609. Γöé     Γöé     ΓöéIntro    Γöé         Γöé         Γöé
  4610. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4611. ΓöéMOR  ΓöéB    Γöé4        Γöé0        Γöé2        Γöé
  4612. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4613. ΓöéRSH  ΓöéA    Γöé1        Γöé2        Γöé5        Γöé
  4614. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4615. ΓöéADO  ΓöéA-   Γöé3        Γöé2        Γöé4        Γöé
  4616. ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
  4617.  
  4618. BOOK LEGEND: 
  4619.  
  4620. ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
  4621. ΓöéRWP  ΓöéReal-World Programming for OS/2 2.1                                Γöé
  4622. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4623. ΓöéPME  ΓöéLearning to Program OS/2 2.0 Presentation Manager by Example       Γöé
  4624. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4625. ΓöéODD  ΓöéWriting OS/2 2.1 Device Drivers in C, 2nd Edition                  Γöé
  4626. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4627. ΓöéGPI  ΓöéOS/2 Presentation Manager GPI                                      Γöé
  4628. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4629. ΓöéTAO  ΓöéThe Art of OS/2 2.1 C Programming                                  Γöé
  4630. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4631. ΓöéMOR  ΓöéMastering OS/2 REXX                                                Γöé
  4632. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4633. ΓöéRSH  ΓöéREXX Reference Summary Handbook                                    Γöé
  4634. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4635. ΓöéADO  ΓöéApplication Development Using OS/2 REXX                            Γöé
  4636. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4637. ΓöéPMP  ΓöéOS/2 Presentation Manager Programming                              Γöé
  4638. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4639. ΓöéDOA  ΓöéDesigning OS/2 Applications                                        Γöé
  4640. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4641. ΓöéOSP  ΓöéOS/2 Programming                                                   Γöé
  4642. ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
  4643.  
  4644. RATINGS LEGEND: 
  4645.  
  4646. ΓöîΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
  4647. Γöé0ΓöéNo coverage           Γöé
  4648. Γö£ΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4649. Γöé1ΓöéVery light coverage   Γöé
  4650. Γö£ΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4651. Γöé2ΓöéIntroductory coverage Γöé
  4652. Γö£ΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4653. Γöé3ΓöéGood Coverage         Γöé
  4654. Γö£ΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4655. Γöé4ΓöéIn-depth coverage     Γöé
  4656. Γö£ΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  4657. Γöé5ΓöéAuthoritative         Γöé
  4658. ΓööΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
  4659.  
  4660. /dev/EDM2/BookReview - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  4661.  
  4662.  
  4663. ΓòÉΓòÉΓòÉ 9.5. Coming Up ΓòÉΓòÉΓòÉ
  4664.  
  4665. Coming Up 
  4666.  
  4667. I am running a little low on books at the moment, but there is relief in sight. 
  4668. Next month I will be finishing the book The GUI- OOUI War - Windows vs. OS/2, 
  4669. Mandel.  The following are the books I intend to review, in no particular 
  4670. order. 
  4671.  
  4672.      OS/2 Presentation Manager GPI, 2nd edition, Winn 
  4673.      OS/2 Unleashed, Moskowitz and Kerr 
  4674.      The Design of OS/2, 2nd Edititon, Kogan and Deitel 
  4675.      Designing High Powered OS/2 Applications, Reich (tentative title) 
  4676.  
  4677.  I am considering reviewing the IBM OS/2 Redbooks, since they are readily and 
  4678.  cheaply available, and look like good reference. 
  4679.  
  4680.  If anyone has a book they want to see reviewed, I will be happy to oblige. 
  4681.  Just mail me and tell me.  Publishers can send me books at the address on my 
  4682.  personal page at the end of the magazine, and I will review all OS/2 
  4683.  development-related and advanced user books I receive. 
  4684.  
  4685.  
  4686. ΓòÉΓòÉΓòÉ 10. Contributors to this Issue ΓòÉΓòÉΓòÉ
  4687.  
  4688. Are You a Potential Author? 
  4689.  
  4690. We are always looking for (new) authors.  If you have a topic about which you 
  4691. would like to write, send a brief description of the topic electronically to 
  4692. any of the editors, whose addresses are listed below, by the 15th of the month 
  4693. before the month in which your article will appear.  This alerts us that you 
  4694. will be sending an article so that we can plan the issue layout accordingly. 
  4695. After you have done this, get the latest copy of the Article Submission 
  4696. Guidelines from hobbes.nmsu.edu in the /os2/newsltr directory.  (The file is 
  4697. artsub.zip.) The completed text of your article should be sent to us no later 
  4698. than five days prior to the last day of the month; any articles received after 
  4699. that time may be pushed to the next issue. 
  4700.  
  4701. The editors can be reached at the following email addresses: 
  4702.  
  4703.      Larry Salomon - os2man@panix.com (Internet). 
  4704.      Carsten Whimster - bcrwhims@undergrad.math.uwaterloo.ca (Internet). 
  4705.  
  4706.  The following people contributed to this issue in one form or another (in 
  4707.  alphabetical order): 
  4708.  
  4709.      Pete Cassetta 
  4710.      Paul Gallagher 
  4711.      Martin Lafaix 
  4712.      Larry Salomon, Jr. 
  4713.      Carsten Whimster 
  4714.      Johan Wikman 
  4715.      Gordon Zeglinski 
  4716.      Network distributors 
  4717.  
  4718.  Contributors - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  4719.  
  4720.  
  4721. ΓòÉΓòÉΓòÉ 10.1. Pete Cassetta ΓòÉΓòÉΓòÉ
  4722.  
  4723. Pete Cassetta 
  4724.  
  4725. Pete Cassetta is a freelance software developer who specializes in OS/2 and 
  4726. Windows.  He began his first OS/2 program in January, 1988, using OS/2 1.0, and 
  4727. has used every version of OS/2 since then.  He presently resides in San 
  4728. Antonio, Texas with his wife Lydia. 
  4729.  
  4730. You may reach Pete on CompuServe at 75554,3502 (75554.3502@compuserve.com from 
  4731. the Internet), or at the following address: 
  4732.  
  4733. Pete Cassetta
  4734. 624 W. Magnolia Ave. Apt. 1
  4735. San Antonio, TX 78212
  4736. (210) 737-3930
  4737.  
  4738. Contributors - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  4739.  
  4740.  
  4741. ΓòÉΓòÉΓòÉ 10.2. Paul Gallagher ΓòÉΓòÉΓòÉ
  4742.  
  4743. Paul Gallagher 
  4744.  
  4745. Paul Gallagher is from Melbourne, Australia and works in computing support. An 
  4746. avowed Pascal fan from way back, he switched to C/C++ when it became clear 
  4747. Pascal wasn't going to be well supported on OS/2.  The supreme sacrifice. 
  4748. <grin> That's how much he loves OS/2! 
  4749.  
  4750. Correspondence is welcome by Internet mail to: <paulg@resmel.bhp.com.au> 
  4751.  
  4752. Contributors - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  4753.  
  4754.  
  4755. ΓòÉΓòÉΓòÉ 10.3. Martin Lafaix ΓòÉΓòÉΓòÉ
  4756.  
  4757. Martin Lafaix 
  4758.  
  4759. Martin Lafaix is (among other things <grin>) a Team OS/2 member, and he first 
  4760. met OS/2 in the 1.3 days.  His main interest points with OS/2 are Programming 
  4761. environments and developement tools. 
  4762.  
  4763. He is the (proud) author of MLEPM, an EPM add-on which provides PopUp menus, 
  4764. highlighting, ...  and of the soon-to-be-released MLRXSHL, a set of tools for 
  4765. command-line users (including a command-shell front-end, with filename 
  4766. completion, a FILELIST-like directory browser, ...). 
  4767.  
  4768. He can be reached via email at:  lafaix@mimosa.unice.fr 
  4769.  
  4770. or via surface-mail at: 
  4771.  
  4772. Martin Lafaix
  4773. 16 rue de Dijon
  4774. 06000 Nice
  4775. France
  4776.  
  4777. Contributors - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  4778.  
  4779.  
  4780. ΓòÉΓòÉΓòÉ 10.4. Larry Salomon, Jr. ΓòÉΓòÉΓòÉ
  4781.  
  4782. Larry Salomon Jr. 
  4783.  
  4784. Larry Salomon Jr. has been developing OS/2 applications since version 1.1 in 
  4785. 1989.  He has written numerous applications, including the Scramble applet that 
  4786. was included in OS/2 versions 2.0-2.11, and the I-Brow, Magnify, and Screen 
  4787. Capture trio that has been distributed on numerous CD-ROMs. 
  4788.  
  4789. Larry is also the coauthor of the successful book, The Art of OS/2 2.1 C 
  4790. Programming (Wiley-QED).  Finally, he is the CEO/President of IQPac Inc. which 
  4791. is responsible for the publishing of EDM/2 and he is a frequent contributor to 
  4792. the publication. 
  4793.  
  4794. Larry can be reached electronically via the Internet at os2man@panix.com. 
  4795.  
  4796. Contributors - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  4797.  
  4798.  
  4799. ΓòÉΓòÉΓòÉ 10.5. Carsten Whimster ΓòÉΓòÉΓòÉ
  4800.  
  4801. Carsten Whimster 
  4802.  
  4803. Carsten is an undergraduate Computer Science student at the University of 
  4804. Waterloo.  He is currently in third year, and is enjoying it immensely.  He 
  4805. uses Watcom C/C++ 10.0 and Watcom VX-REXX 2.0b. 
  4806.  
  4807. Carsten is the author of some commandline utilities and POV-Panel/2, a popular 
  4808. shareware dashboard-like front-end for the POV-Ray 2.x compilers. POV-Panel/2 
  4809. can be found on ftp-os2.cdrom.com in pub/os2/32bit/graphics and some of the 
  4810. other major OS/2 ftp sites.  He is also a TEAM-OS/2 member, and has adopted a 
  4811. little computer store called The Data Store in Waterloo, Ontario. 
  4812.  
  4813. You may reach Carsten... 
  4814.  
  4815. ...via email: 
  4816.  
  4817. bcrwhims@undergrad.math.uwaterloo.ca - Internet 
  4818.  
  4819. ...Web homepage (I am just setting it up, so it may or may not be on-line): 
  4820.  
  4821. http://www.undergrad.math.uwaterloo.ca/~bcrwhims  - WWW 
  4822.  
  4823. ...via snail mail (notice the changed address): 
  4824.  
  4825. Carsten Whimster
  4826. 318A Spruce Street
  4827. Waterloo, Ontario
  4828. Canada
  4829. N2L 3M7
  4830.  
  4831. Contributors - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  4832.  
  4833.  
  4834. ΓòÉΓòÉΓòÉ 10.6. Johan Wikman ΓòÉΓòÉΓòÉ
  4835.  
  4836. Johan Wikman 
  4837.  
  4838. Johan Wikman received his Master's degree in Computer Science with a thesis 
  4839. "Adding remote execution capability to operating systems".  He has been 
  4840. programming for OS/2, using C++, ever since 1990. 
  4841.  
  4842. Currently he works for Nokia Telecommunications where he takes part in a 
  4843. project that developes network management software in a Unix environment.  In 
  4844. his sparetime he continues working on RMX-OS2, a system that provides remote 
  4845. execution for OS/2. 
  4846.  
  4847. Johan can be reached electronically via the Internet at 
  4848. johan.wikman@ntc.nokia.com, or via ordinary mail: 
  4849.  
  4850. Johan Wikman
  4851. Smedjeviksvagen 23 B 22
  4852. FI-00200 Helsinki
  4853. FINLAND
  4854.  
  4855. Contributors - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  4856.  
  4857.  
  4858. ΓòÉΓòÉΓòÉ 10.7. Gordon Zeglinski ΓòÉΓòÉΓòÉ
  4859.  
  4860. Gordon Zeglinski 
  4861.  
  4862. Gordon Zeglinski is a freelance programmer/consultant who received his Master's 
  4863. degree in Mechanical Engineering with a thesis on C++ sparse matrix objects. 
  4864. He has been programming in C++ for 6 years and also has a strong background in 
  4865. FORTRAN.  He started developing OS/2 applications with version 2.0 . 
  4866.  
  4867. His current projects include a client/server communications program that 
  4868. utilitizes OS/2's features which has entered beta testing.  Additionally, he is 
  4869. involved in the development of a "real-time" automated vehicle based on OS/2 
  4870. and using C++ in which he does device driver development and designs the 
  4871. applications that comprise the control logic and user interface. 
  4872.  
  4873. He can be reached via the Internet at zeglins@cc.umanitoba.ca. 
  4874.  
  4875. Contributors - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  4876.  
  4877.  
  4878. ΓòÉΓòÉΓòÉ 10.8. Network distributors ΓòÉΓòÉΓòÉ
  4879.  
  4880. Network Distributors 
  4881.  
  4882. These people are part of our distribution system to provide EDM/2 on networks 
  4883. other than the Internet.  Their help to provide access to this magazine for 
  4884. others is voluntary and we appreciate them a lot! 
  4885.  
  4886.      Paul Hethmon (hethmon@apac.ag.utk.edu) - Compuserve 
  4887.      Gess Shankar (gess@knex.mind.org) - Internet 
  4888.      Jason B. Tiller (PeerGynt@aol.com) - America On-line 
  4889.      David Singer (singer@almaden.ibm.com) - IBM Internal 
  4890.      Andre Asselin (ASSELIN AT RALVM12) - IBM Internal 
  4891.  
  4892.  If you would like to become a "network distributor", be sure to contact the 
  4893.  editors so that we can give you the credit you deserve! 
  4894.  
  4895.  Contributors - EDM/2 - Mar 1995 - Volume 3, Issue 3 
  4896.  
  4897.  
  4898. ΓòÉΓòÉΓòÉ 11. How Do I Get EDM/2? ΓòÉΓòÉΓòÉ
  4899.  
  4900. How Do I Get EDM/2? 
  4901.  
  4902. EDM/2 can be obtained in any of the following ways: 
  4903.  
  4904. On the Internet 
  4905.  
  4906.      All back issues are available via anonymous FTP from the following sites: 
  4907.         -  hobbes.nmsu.edu in the /os2/newsltr directory. 
  4908.         -  ftp.luth.se in the /pub/os2/programming/newsletter directory. 
  4909.         -  generalhq.pc.cc.cmu.edu in the /pub/newsletters/edm2 directory. 
  4910.      The EDM/2 mailing list.  Send an empty message to edm2-info@knex.mind.org 
  4911.       to receive a file containing (among other things) instructions for 
  4912.       subscribing to EDM/2.  This is a UUCP connection, so be patient please. 
  4913.      IBM's external gopher/WWW server in Almaden. The address is 
  4914.       index.almaden.ibm.com and it is in the "Non-IBM-Originated" submenu of 
  4915.       the "OS/2 Information" menu; the URL is 
  4916.       "gopher://index.almaden.ibm.com/1nonibm/os2nonib.70". 
  4917.  
  4918.  On Compuserve 
  4919.  
  4920.  All back issues are available in the OS/2 Developers Forum 2. 
  4921.  
  4922.  IBM Internal 
  4923.  
  4924.      IBM's internal gopher/WWW server in Almaden. The address is 
  4925.       n6tfx.almaden.ibm.com and it is in the "Non-IBM-Originated Files" menu; 
  4926.       the URL is "gopher://n6tfx.almaden.ibm.com/1!!nonibm/nonibm.70". 
  4927.      IBM's REQUEST command on all internal VM systems.  Enter the VM command 
  4928.       REQUEST LIST FROM ASSELIN AT RALVM12 and a list of the requestable 
  4929.       packages will be sent to you; in this list are the names of the packages 
  4930.       containing the EDM/2 issues. 
  4931.  
  4932.  How do I Get EDM/2? - EDM/2 - Mar 1995 - Volume 3, Issue 3