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

  1.  
  2. ΓòÉΓòÉΓòÉ 1. Feb 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 2
  8.  
  9. Administrivia 
  10.  
  11. Oh boy, it has been one wild month.  Many things have happened, and I have 
  12. survived to tell the tale. 
  13.  
  14. First, the Bad News... 
  15.  
  16. First, the bad news must come out:  the Internet service provider I use had a 
  17. disk crash; while only one set of home directories was affected, you can bet 
  18. your bottom dollar that I was a part of that set.  Since I had not yet 
  19. downloaded the submissions for this issue when the crash occurred, I lost 
  20. everything.  To make it worse, due to complications, the nightly backups were 
  21. bad too, so I could only be restored to the last full backup which was on 
  22. January 25.  The end result is that this issue is a bit slimmer and later than 
  23. usual. 
  24.  
  25. ...Then, the Good News 
  26.  
  27. Fortunately, the good news significantly outweighs the bad news.  The first 
  28. thing you will notice, if you look at the Copyright Notice section, is that 
  29. EDM/2  now is published by IQPac Inc. which I formed with the intent of taking 
  30. this magazine to new heights.  IQPac Inc. will not yet be paying anyone, but 
  31. plans to do so as soon as a consistent revenue stream is established.  The best 
  32. thing about having a corporation is that I can now justify to my wife - the 
  33. accountant - the need to buy a lot of new hardware; I've already purchased a 
  34. LaserJet 4MP and plan to get a ScanJet IIcx and a #9 GXE64 Pro in the near 
  35. future.  <grin> 
  36.  
  37. The next thing you will notice, again if you look at the Copyright Notice 
  38. section, is that EDM/2 is now an OS/2 Accredited publication.  This is great, 
  39. as far as I'm concerned, because it gives us a lot more credibility as a 
  40. publication.  Look for IQPac Inc. in the next edition of Sources and Solutions. 
  41.  
  42. Another point for our side came during the fiasco when Mr. Labant retired from 
  43. IBM.  I sent off to Lou Gerstner, for my own reasons, a four page fax 
  44. containing a lot of stuff; included in that "stuff" was a mention of IQPac Inc. 
  45. and EDM/2.  I did subsequently receive a reply from one of Mr. Gerstner's staff 
  46. members acknowledging the favorable reception of the fax by Mr. Gerstner. 
  47.  
  48. Wanna Laugh? 
  49.  
  50. In the "Good for a Chuckle" department, pick up a copy of the new ARCsolo for 
  51. OS/2 version 1.5 (Cheyenne Software) when it hits your shelves.  If you look at 
  52. the screen shots on the box, you'll see that the name of the tape is "EDM/2." 
  53. That's what you get when you put me in charge of taking screen shots.  <grin> 
  54.  
  55. A Slight Change 
  56.  
  57. Due to the fact that this issue is so late, I have decided to push the next 
  58. part of the VIOWIN series until next issue, which isn't that far away.  My 
  59. apologies for this inconvenience. 
  60.  
  61. And Another Change 
  62.  
  63. Since Gordon Zeglinski's column has of late focused on SOM related topics, we 
  64. felt it would be more accurate if the name of his column was changed from C++ 
  65. Corner to OOPS Avenue. 
  66.  
  67. And Yet Another Change 
  68.  
  69. EDM/2 next month will see the beginning of a "Letters to the Editors" page, 
  70. where you can voice your opinions about the magazine, columnists, or 
  71. programming issues in general.  Make sure you send your email to me - 
  72. os2man@panix.com - with a subject line similar to "Letters to the Editors". 
  73.  
  74. One Bigger Change 
  75.  
  76. I don't normally re-release an issue due to problems that I made when it was 
  77. first released, but this issue had special problems in it: 
  78.  
  79.      The legally-required statements that correspond to the OS/2 Accredited 
  80.       Logo were inadvertently left out.  These were added. 
  81.      Martin Lafaix's article was intended to be a rough draft.  It has been 
  82.       removed and will be included in the next issue (hopefully). 
  83.  
  84.  And the Votes are Tallied 
  85.  
  86.  Finally, the votes for the Reader's Choice Awards were much greater in number, 
  87.  but still not as much as I'd hoped.  Considering that I estimate that EDM/2 
  88.  has over 2000 readers, 112 (valid) votes (some of the readers did not take 
  89.  full advantage of the 3 votes they were able to cast) is still small. 
  90.  However, it's much better than the 14 I received last year!  The results are 
  91.  displayed below.  There is a sad note to this and that is that I have yet to 
  92.  get committments from anyone willing to donate anything to the winners.  I am 
  93.  currently waiting to hear from my liason in the Media Relations group in IBM, 
  94.  so I'll keep the winners posted as I receive more information. 
  95.  
  96.  Place     Article/Column (Votes) 
  97.  1.        "WPS Programming the Easy Way" (18 votes) 
  98.  2.        "Introduction to PM Programming" (15 votes) 
  99.  3.        "/dev/EDM/BookReview" (14 votes) 
  100.  4.        "C++ corner" (13 votes) 
  101.  5.        "Workplace Shell Development 101" (10 votes) 
  102.  6.        "Making Noise with MMPM/2" (6 votes) 
  103.  7.        "Sprites and Animation" (6 votes) 
  104.  8.        "TCP/IP Socket Programming in REXX" (5 votes) 
  105.  9.        "Scratch Patch" (5 votes) 
  106.  10.       "The Design and Implementation of VIOWIN" (4 votes) 
  107.  11.       "Coding for Dollars:  Copy Protection and Prevention" (4 votes) 
  108.  12.       "Visual REXX Faceoff" (3 votes) 
  109.  13.       "Resources and Decompiling Them" (3 votes) 
  110.  14.       "Using SYSLEVEL Files in Your Applications" (2 votes) 
  111.  15.       "Adding Sounds to Your OS/2 Application" (2 votes) 
  112.  16.       "Utilizing Hooks for Added Capabilities" (1 vote) 
  113.  17.       "Controlling Yourself:  A Framework for Configurable Options" (1 
  114.            vote) 
  115.  
  116.  I didn't know what to expect, but the winner was not something I was too 
  117.  surprised about.  My column coming in 2nd place is something I did not expect, 
  118.  though, and though Carsten does an excellent job in his column, I did not 
  119.  expect him to place either. 
  120.  
  121.  After further reflection of the results, it seems unfair that a column should 
  122.  be eligible, since it (theoritically) has 12 chances to win your vote. So, 
  123.  after a quick confirmation with Carsten and Gordon, the columns were removed 
  124.  from the results.  This means that the top three articles of the year were: 
  125.  
  126.    1. "WPS Programming the Easy Way", Frank Matthijs 
  127.    2. "Workplace Shell Development 101", Bj╨ñrn Fahller 
  128.    3. "Making Noise with MMPM/2", Marc van Woerkom 
  129.  
  130.  Congratulations to the winners! 
  131.  
  132.  The Funny Pages 
  133.  
  134.  I have saved some of the funnier votes and they are displayed below.  I 
  135.  sincerely hope that I do not offend anyone by displaying these, although I 
  136.  have left out the names of the submitters.  The first item is that "OS/2 
  137.  Installable File Systems" and "Threads in PM Applications" both received 2 
  138.  votes each, even though they were written last year.  The rest are below. 
  139.  
  140.  "Um, what?  I did not know about any voting.  Could you refresh me on what to 
  141.  vote for?" 
  142.  
  143.  "Well, since EMX 09a is the only development environment I've used for OS/2 so 
  144.  far, I'd have to vote for it." 
  145.  
  146.  "My nomination:  OS2 Warp Unleashed.  (problem is it's not released yet) The 
  147.  v2.11 book was excellent.  Second vote would go to Petzold's OS/2 Presentation 
  148.  Manager Programming by ZD Press." 
  149.  
  150.  "I'll be happy to vote but I've only just subscribed within the last few days. 
  151.  I will say that I'm very excited about the existance of and on-line 
  152.  availability of your publication." 
  153.  
  154.  "Before you chastise me for not reading your mailings.......we lost serious 
  155.  amounts of data on our server about a week ago and that was my first receipt 
  156.  of your magazine.  If you would like to repost, I would be happy to vote for 
  157.  something." 
  158.  
  159.  "I vote for Larry Salomon, Jr." 
  160.  
  161.  "Ok I'll send you a note.  I am not voting because I just got all of the 
  162.  issues installed on our LAN for the rest of my department and I haven't had 
  163.  time to read all of them yet.  While I have your attention do you know of a 
  164.  paint program for OS/2 that will create OS/2 bmp's.  One of these days I might 
  165.  be able to get you a article on client/server methods and practices, I was a 
  166.  developer at IBM on the DOS/Windows TCP/IP product and now that I am with MCI 
  167.  I write PM based LAN apps using TCP/IP and NETBui.  I find it interesting that 
  168.  there has been no mention of the GPF screen/code generator.  I live and die by 
  169.  this app, it's probably the best spent $1000+ I have ever made on computer 
  170.  software.  Hell maybe I'll do a review of it for ya.  Enough of this rambling 
  171.  for now, I have 2 months of work to do in 1!" 
  172.  
  173.  "This may not be possible given the rules of the EDM/2 publishing guidelines, 
  174.  but my vote would be, precisely because of the information expressed above, 
  175.  that *YOU* receive the Reader's Choice award this year.  IMO, you do the 
  176.  lion's share of the work, and probably receive little, if any, formal 
  177.  recognition for your efforts.  I hereby nominate Larry Salomon for the 1994 
  178.  EMD/2 Reader's Choice award, based on his outstanding contribution to the 
  179.  success of the EDM/2 newsletter, and the work involved in publishing this 
  180.  excellent reference for those of us struggling to get things working." 
  181.  
  182.  "I couldn't pick out just one article, sorry.  I just want to give you a vote 
  183.  of appreciation for EDM." 
  184.  
  185.  That's it for this month!  Enjoy what has made it into this issue and we'll 
  186.  see you next month! 
  187.  
  188.  Title Page - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  189.  
  190.  
  191. ΓòÉΓòÉΓòÉ 2. Copyright Notice ΓòÉΓòÉΓòÉ
  192.  
  193. Copyright Notice 
  194.  
  195. EDM/2 is published by IQPac Inc.  IQPac Inc. can be reached via U.S. Mail at 
  196. the following address: 
  197.  
  198. IQPac Inc.
  199. 89-17 Moline Street
  200. Bellerose, New York 11428
  201. U.S.A.
  202.  
  203.  Editor-in-chief     Larry Salomon Jr. 
  204.  Associate editor    Carsten Whimster 
  205.  Contributing editor Gordon Zeglinski 
  206.  
  207.  CEO/President       Larry Salomon Jr. 
  208.  
  209.  All material is copyrighted by its original author.  No part of this magazine 
  210.  may be reproduced without permission from the original author. 
  211.  
  212.  This publication may be freely distributed in electronic form provided that 
  213.  all parts are present in their original unmodified form.  A reasonable fee may 
  214.  be charged for the physical act of distribution; no fee may be charged for the 
  215.  publication itself. 
  216.  
  217.  Neither IQPac Inc. nor this publication are affiliated with International 
  218.  Business Machines Corporation. 
  219.  
  220.  OS/2 is a registered trademark of International Business Machines Corporation. 
  221.  Other trademarks are property of their respective owners.  Any mention of a 
  222.  product in this publication does not constitute an endorsement or affiliation 
  223.  unless specifically stated in the text. 
  224.  
  225.  The OS/2 Accredited Logo is a trademark of International Business Machines 
  226.  Corporation and is used by IQPac Inc. under license.  This On-line Publication 
  227.  is independently produced by IQPac Inc. and IBM is not responsible in any way 
  228.  for its contents. 
  229.  
  230.  IQPac Inc. is an accredited member of the IBM Independent Vendor League. 
  231.  
  232.  Copyright Notice - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  233.  
  234.  
  235. ΓòÉΓòÉΓòÉ 3. The Infinately Floating Spinbutton ΓòÉΓòÉΓòÉ
  236.  
  237.  
  238. ΓòÉΓòÉΓòÉ 3.1. Introduction ΓòÉΓòÉΓòÉ
  239.  
  240. The Infinately Floating Spinbutton 
  241.  
  242. Written by Marc Mittelmeijer and Eric Slaats 
  243.  
  244. Introduction 
  245.  
  246. Most of the OS/2 controls are very useful and flexible.  But every once in a 
  247. while you bounce against the limits of what's possible with the given controls. 
  248. (Handling floats in spinbuttons and containers for one thing is an issue.) 
  249. When we were building an interface for a neural network problem we needed a 
  250. spinbutton control to handle an infinite range of (undetermined) numeric 
  251. values. 
  252.  
  253. This isn't a big problem; the spinbutton can accept boundaries when handling 
  254. integers which can be set or reset using the SPBM_SETLIMITS message.  The 
  255. problem occurred when we wanted the spinbutton to handle floats.  The control 
  256. is incapable of handling floats.  Of course its possible to convert floats to 
  257. strings and a spinbutton can accept strings.  But this would mean a limited 
  258. range of predefined (float to string) values.  We needed a infinite range of 
  259. undetermined values!  In this article we'll discuss our solution to this 
  260. problem.  But first, we need to dissect the spinbutton. 
  261.  
  262. The Infinately Floating Spinbutton - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  263.  
  264.  
  265. ΓòÉΓòÉΓòÉ 3.2. How Do Spinbuttons Work? ΓòÉΓòÉΓòÉ
  266.  
  267. How Do Spinbuttons Work? 
  268.  
  269. The spinbutton is one the most useful controls OS/2 has to offer.  It's 
  270. described in the book The Art of OS/2 2.1 programming but for all those lost 
  271. souls who do not have a copy, here is a short description. 
  272.  
  273. A spinbutton is a combination of an entryfield and a set of up and down arrow 
  274. buttons.  The buttons can be used to spin through a list of predefined choices. 
  275. If the spinbutton isn't read only, the user can type in a value of choice. 
  276. (Which doesn't necessarily have to be in the predefined list of choices.) 
  277. Spinbuttons are typically used to cycle through choices as months of the year, 
  278. days of the week, hours of the day. 
  279.  
  280. An example of the use of spinbuttons is found in the settings of the clock 
  281. applet.  Here spinbuttons are used to set the time and the date. 
  282.  
  283. Spinbuttons can be defined in a number of ways.  One of the most attractive 
  284. features (which will not be described here) is the ability to link several 
  285. spinbuttons together, so they react to one set of up and down buttons.  The 
  286. following styles can be used creating a spinbutton. 
  287.  
  288.  Style               Description 
  289.  SPBS_ALLCHARACTERS  Any char can be typed in.  This is the default. 
  290.  SPBS_NUMERICONLY    Only the digits 0-9 are accepted. 
  291.  SPBS_READONLY       Nothing can be typed in the spinfield. 
  292.  SPBS_MASTER         When more spinfields are coupled this spinbutton provides 
  293.                      the up and down keys and acts as a master which controls 
  294.                      the others. 
  295.  SPBS_SERVANT        A servant spinbutton will have no buttons and will spin 
  296.                      under the master's control. 
  297.  SPBS_JUSTLEFT       Left justify the spinbutton text. 
  298.  SPBS_JUSTRIGHT      Right justify the spinbutton text. 
  299.  SPBS_JUSTCENTER     Center the spinbutton text. 
  300.  PBS_NOBORDER        Surpress drawing a border. 
  301.  SPBS_FASTSPIN       The speed in which the spinbutton cycles through the 
  302.                      choices will increase (every two seconds) when one of the 
  303.                      buttons is held down. 
  304.  SPBS_PADWITHZEROS   The entryfield is padded with zeros (up to a maximum of 
  305.                      11) to the first non zero digit. 
  306.  
  307.  A set of values for the spinbutton can be set up in two ways.  You can define 
  308.  upper and lower integer boundaries with a message to the spinbutton and let it 
  309.  spin between them or you can supply the spinbutton with an array of predefined 
  310.  (character) values. 
  311.  
  312.  These two types of spinbuttons are demonstrated in the following simple 
  313.  program.  We used a dialog box to demonstrate the spinbuttons because this 
  314.  eliminated the hassle necessary to build a 'normal' window.  The .RC file for 
  315.  dialog box is build using the resource workshop of Borland C++ for OS/2 and is 
  316.  straightforward. 
  317.  
  318.   //------------------------------------------------------------------------
  319.   // FILE: Spinbut1.rc
  320.   //------------------------------------------------------------------------
  321.  
  322.   #include "Spinbut1.H"
  323.  
  324.   DLGTEMPLATE SPINDLG
  325.   BEGIN
  326.      DIALOG "Spin example1", 100, 14, 92, 122, 71, NOT FS_DLGBORDER | FS_SIZEBORDER | WS_VISIBLE, FCF_SYSMENU | FCF_TITLEBAR | FCF_MINBUTTON | FCF_MAXBUTTON
  327.      BEGIN
  328.         CONTROL "", SPINBUT1, 9, 40, 73, 12, WC_SPINBUTTON, SPBS_MASTER | SPBS_ALLCHARACTERS | SPBS_JUSTLEFT | WS_VISIBLE
  329.         CONTROL "", SPINBUT2, 8, 8, 74, 12, WC_SPINBUTTON, SPBS_MASTER | SPBS_NUMERICONLY | SPBS_JUSTLEFT | SPBS_FASTSPIN | WS_VISIBLE
  330.         CONTROL "With array of values", 103, 11, 55, 98, 8, WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | DT_MNEMONIC | WS_VISIBLE | WS_GROUP
  331.         CONTROL "With integer boundaries", 104, 9, 22, 106, 8, WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | DT_MNEMONIC | WS_VISIBLE | WS_GROUP
  332.        END
  333.   END
  334.  
  335.  This .RC file defines a dialog box with two spinbuttons and some text above 
  336.  each.  Both spinbuttons are defined as SPBS_MASTER.  This means they both have 
  337.  arrow-buttons.  SPINBUT1 will take any character as input,SPINBUT2 will accept 
  338.  only numeric (integer) input.  This dialog is the base for the following 
  339.  program which will set SPINBUT1 to a set of predefined values and SPINBUT2 to 
  340.  a range between two given boundaries. 
  341.  
  342.  This dialog box will look like this: 
  343.  
  344.   //-------------------------------------------------------------------------
  345.   // FILE: Spinbut1.h
  346.   //-------------------------------------------------------------------------
  347.   #define SPINBUT1    101
  348.   #define SPINBUT2    102
  349.   #define SPINDLG     1
  350.  
  351.   //-------------------------------------------------------------------------
  352.   // FILE: Spinbut1.cpp
  353.   //-------------------------------------------------------------------------
  354.   #define  INCL_WIN
  355.   #include <os2.h>
  356.   #include "spinbut1.h"
  357.  
  358.   PCHAR achSpin1Array[] = {  "One",
  359.                              "Two",
  360.                              "Three",
  361.                              "Four",
  362.                              "Five",
  363.                              "Six",
  364.                              "Seven",
  365.                              "Eight",
  366.                              "Nine",
  367.                              "Ten",
  368.                              "And once again"
  369.                             };
  370.  
  371.   //-------------------------------------------------------------------------
  372.   // Prototypes
  373.   //-------------------------------------------------------------------------
  374.   MRESULT EXPENTRY SpinDlg (HWND, ULONG ,MPARAM, MPARAM);
  375.  
  376.   //-------------------------------------------------------------------------
  377.   // Main
  378.   //
  379.   // Sets up a simple dialogbox to demonstrate spinbuttons. By using a dialog
  380.   // none of the usual window control has to be included.
  381.   //-------------------------------------------------------------------------
  382.   void main(void)
  383.        {
  384.        HAB  hab;
  385.        HMQ  hmq;
  386.  
  387.        hab = WinInitialize(0);
  388.        hmq = WinCreateMsgQueue(hab,0);
  389.  
  390.        WinDlgBox(HWND_DESKTOP,
  391.                  HWND_DESKTOP,
  392.                  SpinDlg,
  393.                  NULLHANDLE,
  394.                  SPINDLG,
  395.                  0);
  396.  
  397.        WinDestroyMsgQueue(hmq);
  398.        WinTerminate(hab);
  399.        }
  400.  
  401.  
  402.   //-------------------------------------------------------------------------
  403.   // dialog procedure
  404.   //-------------------------------------------------------------------------
  405.   MRESULT EXPENTRY SpinDlg(HWND hwndDlg, ULONG ulMsg, MPARAM mpParm1, MPARAM mpParm2)
  406.        {
  407.        switch (ulMsg)
  408.             {
  409.             case WM_INITDLG:
  410.                  {
  411.                  WinSendDlgItemMsg(hwndDlg,                  // Spinbut1 to predefined
  412.                                    SPINBUT1,
  413.                                    SPBM_SETARRAY,
  414.                                    MPFROMP (achSpin1Array),
  415.                                    MPFROMSHORT (11));
  416.  
  417.                  WinSendDlgItemMsg(hwndDlg,                  // Spinbut2 to range
  418.                                    SPINBUT2,
  419.                                    SPBM_SETLIMITS,
  420.                                    MPFROMLONG (100),
  421.                                    MPFROMLONG (0));
  422.                  }
  423.             }
  424.        return WinDefDlgProc(hwndDlg, ulMsg, mpParm1, mpParm2);
  425.        }
  426.  
  427.  The complete working code can be found in SPINBUT1.ZIP . 
  428.  
  429.  In the dialog procedure, the only message that is intercepted is the 
  430.  WM_INITDLG message.  This message is send when the dialog box is created. 
  431.  This message functions just like the WM_CREATE message, only it's specially 
  432.  for dialog boxes.  When the WM_INITDLG message is generated, we can initiate 
  433.  the two spinbuttons. 
  434.  
  435.  SPINBUT1 is initiated using the SPBM_SETARRAY message.  In this message the 
  436.  array achSpin1Array is attached to the spinbutton.  If the spinbutton is used, 
  437.  the values as they are declared in this array are shown. 
  438.  
  439.  SPINBUT2 is initiated using the SPBM_SETLIMITS message.  In this case the 
  440.  upper limit is 100 and the lower limit is 0. The spinbutton will show the 
  441.  integer values from 0 to 100. 
  442.  
  443.  The Infinately Floating Spinbutton - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  444.  
  445.  
  446. ΓòÉΓòÉΓòÉ 3.3. The Infinite Spinbutton. ΓòÉΓòÉΓòÉ
  447.  
  448. The Infinite Spinbutton. 
  449.  
  450. Our goal was to build a spinbutton with no predefined boundaries.  Also the 
  451. next value a spinbutton shows after a button-up or button down action should be 
  452. derived from the current value.  So if a user types in a value and uses the 
  453. buttons afterwards, the new value should be derived from the typed value. 
  454.  
  455. NOTE:  the spinbutton we're building will be working with numeric values.  So 
  456. the derived values are also numeric.  It's possible to take the same technique 
  457. and use it with alphanumeric values. 
  458.  
  459. The trick used to accomplish this is to set up a new value array for the 
  460. spinbutton each time the value in the button is changed.  This value array 
  461. should have three entries, the middle one being the current value.  If a button 
  462. is pressed, the button shows one of the other two values.  At that point a new 
  463. array of values should be calculated and should be attached to the spinbutton. 
  464.  
  465. "1.04" is the current value in the spinbutton.  The value array looks something 
  466. like this: 
  467.  
  468. ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
  469. Γöé'1.03'Γöé'1.04'Γöé'1.05'Γöé
  470. ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
  471.  
  472. The up button is pressed.  So '1.05' will be the next current value.  '1.05' 
  473. should also be the next center value in the value array.  The array will change 
  474. to: 
  475.  
  476. ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
  477. Γöé'1.04'Γöé'1.05'Γöé'1.06'Γöé
  478. ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
  479.  
  480. To accomplish such a task, we could intercept the SPBN_UPARROW and the 
  481. SPBN_DOWNARROW notification messages and change the values in the value array 
  482. when the up or down arrow has been pressed.  The code to accomplish this may 
  483. look like this: 
  484.  
  485. case WM_CONTROL:
  486.    {
  487.    switch (SHORT2FROMMP(mpParm1))
  488.           {
  489.        case SPBN_DOWNARROW:
  490.        case SPBN_UPARROW:
  491.           {
  492.              char achRetValue[12];
  493.  
  494.              WinSendDlgItemMsg(hwndDlg, SPINBUT1,
  495.                                SPBM_QUERYVALUE,
  496.                                MPFROMP (achRetValue),
  497.                                MPFROM2SHORT (sizeof(achRetValue), 0));
  498.  
  499.              sprintf(achValues[0], "%.*f", 2, atof(achRetValue)-0.01);
  500.              sprintf(achValues[1], "%.*f", 2, atof(achRetValue));
  501.              sprintf(achValues[2], "%.*f", 2, atof(achRetValue)+0.01);
  502.  
  503.              WinSendDlgItemMsg(hwndDlg, SPINBUT1,     // Spinbut1 to predefined
  504.                                SPBM_SETARRAY,
  505.                                MPFROMP (achValues),
  506.                                MPFROMSHORT (3));
  507.  
  508.              WinSendDlgItemMsg(hwndDlg, SPINBUT1,     // Make middle one current
  509.                                SPBM_SETCURRENTVALUE,
  510.                                MPFROMLONG (1),
  511.                                MPFROMLONG (0));
  512.             }
  513.         return MRFROMSHORT (TRUE);
  514.       }
  515.    }
  516.  
  517. NOTE:  the array which will be attached to the spinbutton must contain 3 
  518. values! 
  519.  
  520. The complete code for this example can be found in SPINBUT2.ZIP. 
  521.  
  522. This example code will increment or decrement the spinbutton value with 0.01. 
  523. Of course, this is a choice, it's very simple to change the code so the change 
  524. will be 0.5. 
  525.  
  526. After a button click, the spinbutton will switch to the next value in the value 
  527. array and will generate a notification message.  The code reacts to the 
  528. SPBN_DOWNARROW and the SPBN_UPARROW.  With the SPBM_QUERYVALUE the spinbutton 
  529. is queried.  The returned value in achRetValue is a value of the value array. 
  530. With the returned value the new values for the achValue array will be set.  In 
  531. this example a (float) in/decrement of .01 is chosen.  The changed array has to 
  532. be attached to the spinbutton, and the middle value has to be made current. 
  533.  
  534. Building an infinite spinbutton this way has one major flaw.  The button will 
  535. not derive it's next value from a value entered by a user.  The reason is: 
  536. when the spinbutton gets a SPBM_QUERYVALUE, it will read from the attached 
  537. value array using an index.  It won't read from the entry field attached to the 
  538. spinbutton.  So the value entered will be discarded if it isn't in the values 
  539. array!  Also, it would be nice to leave all the notification messages untouched 
  540. and take a universal approach in constructing an infinite spinbutton.  In the 
  541. next section we will analyze this problem. 
  542.  
  543. The Infinately Floating Spinbutton - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  544.  
  545.  
  546. ΓòÉΓòÉΓòÉ 3.4. Adding Spoilers and Other Gadgets ΓòÉΓòÉΓòÉ
  547.  
  548. Adding Spoilers and Other Gadgets 
  549.  
  550. In the previous section the major trick to create an infinite spinbutton which 
  551. can handle floats is explained.  There is a more elegant way to achieve an 
  552. infinite spinbutton, which will also react to user input.  Before we use a new 
  553. bag of tricks, let's first examine a spinbutton more precisely. 
  554.  
  555. There is a nice tool to examine windows in an application.  It's called PMTREE 
  556. and it is an applet from IBM.  We got it from the Developers Connection CD-ROM, 
  557. but it is also available from a number of anonymous sites. If we examine a 
  558. spinbutton with this tool, we will see that a spinbutton is build from several 
  559. childwindows.  The top-most window in a spinbutton is an entryfield window. 
  560. This is useful information, because this gives us a way to directly query the 
  561. value in a spinbutton.  Even if a user entered a value which can't be found in 
  562. the attached array, the entered value will be retrieved. 
  563.  
  564. Retrieving the Value in the Entryfield 
  565.  
  566. We can query the string in an entryfield by using WinQueryWindowText().  To use 
  567. this API, we need the window-handle of the entryfield.  We know the entryfield 
  568. is the top child of the spinbutton childwindows.  Thus, if we know the handle 
  569. of the spinbutton, we can retrieve the handle of the corresponding entryfield 
  570. using the WinQueryWindow() function and specifying QW_TOP as the parameter. 
  571.  
  572. WinQueryWindow (hwndSpin, QW_TOP);
  573.  
  574. By combining these two functions, we can think of some code which will retrieve 
  575. the value in the spinbutton-entryfield given the handle of the spinbutton. 
  576.  
  577. char achSpinValue[32];
  578.  
  579. WinQueryWindowText(WinQueryWindow(hwndSpin, QW_TOP),
  580.                    sizeof(achSpinValue),
  581.                    achSpinValue);
  582.  
  583. If the value in the spinfield is known, the value array can be set up.  We 
  584. calculate the float value from the string returned by WinQueryWindowText() and 
  585. use this value to calculate the value array values. 
  586.  
  587. flSpinValue = atof(chSpinValue);
  588. sprintf(achValues[0],"%.*f", 2, flSpinValue + -0.01);
  589. sprintf(achValues[1],"%.*f", 2, flSpinValue + 0);
  590. sprintf(achValues[2],"%.*f", 2, flSpinValue + 0.01);
  591.  
  592. If we use the code this way, we can only in/decrement the value in the 
  593. spinbutton with 0.01.  It's more interesting to in/decrease the value in the 
  594. spinbutton with a fraction of its current value.  The next code increases the 
  595. value with 2% for values outside the range -1,1.  It can easily be modified to 
  596. work with another value, or even with a parameter. 
  597.  
  598. //-----------------------------------------------------------------------
  599. // Set the multiplication factor and fill values array
  600. //-----------------------------------------------------------------------
  601. if (fabs(flSpinValue) < 1)
  602.      factor = 0.01;
  603. else
  604.      factor = (float) ceil(fabs(flSpinValue/50));
  605.  
  606. sprintf(achValues[0],"%.*f", 2, flSpinValue + -factor);
  607. sprintf(achValues[1],"%.*f", 2, flSpinValue + 0);
  608. sprintf(achValues[2],"%.*f", 2, flSpinValue + factor);
  609.  
  610. The next step is to attach the array to the spinbutton and set the middle value 
  611. as the current.  This can be done using the messages SPBM_SETARRAY and 
  612. SPBM_SETCURRENTVALUE. 
  613.  
  614. And now for the big trick!  PM generates a SPBM_SPINUP or SPBM_SPINDOWN message 
  615. if one of the spinbutton arrow buttons are pressed.  If we set the new value 
  616. array before the messages SPBM_SPINUP and SPBM_SPINDOWN are processed, we know 
  617. for certain that the values array will be build upon the current value in the 
  618. spinbutton, whether or not it was user-entered!  So the processing of 
  619. SPBM_SPINUP or SPBM_SPINDOWN will use this new value array and display the 
  620. correct value. 
  621.  
  622. But how can we intercept these messages so that the value array can be changed 
  623. before it's used?  The technique to achieve this is called subclassing. 
  624.  
  625. The Infinately Floating Spinbutton - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  626.  
  627.  
  628. ΓòÉΓòÉΓòÉ 3.5. And Now For Some Subclassing ΓòÉΓòÉΓòÉ
  629.  
  630. And Now For Some Subclassing 
  631.  
  632. Really the most elegant way of creating an infinite spinbutton is to use 
  633. subclassing.  Subclassing is a technique in which a call to the standard 
  634. procedure for a particular kind of window is intercepted.  This way, a 
  635. programmer can define their own handling of a message.  We want to intercept 
  636. the messages SPBM_SPINUP and SPBM_SPINDOWN.  These messages are send by the PM 
  637. when the arrowbuttons attached to a spinbutton are pressed and cause the value 
  638. in the spinbutton to skip one value in the attached array. 
  639.  
  640. The spinbutton can be subclassed when handling the WM_INITDLG message.  The 
  641. function to call is WinSubclassWindow().  With this function we can insert a 
  642. function which will be called instead of the standard window function. 
  643.  
  644. NOTE:  because the standard function won't be called, the subclass function 
  645. should call the standard function for all the messages it doesn't compute! 
  646.  
  647. pfnwOldProc = WinSubclassWindow(WinWindowFromID(hwndDlg, SPINBUT1), CalcSpin);
  648.  
  649. pfnwOldProc is the return pointer to the original window function.  This 
  650. pointer value should be available in the subclass function so the subclass 
  651. function can call the original window function.  The general way to deal with 
  652. this is putting the pointer in a window word.  Because we're not talking about 
  653. window words and we're a little lazy, we define a global variable for it. 
  654.  
  655. The name of the subclass function is CalcSpin.  This function has the same 
  656. anatomy as a normal window procedure.  It's prototype looks like this: 
  657.  
  658. MRESULT EXPENTRY CalcSpin (HWND, ULONG ,MPARAM, MPARAM);
  659.  
  660. At this point we should be able to write the complete subclass function. The 
  661. function always calls the original window procedure.  Only if the messages 
  662. SPBM_SPINUP or SPBM_SPINDOWN are received, the function should interfere.  The 
  663. subclass function looks like this. 
  664.  
  665. //---------------------------------------------------------------------------------
  666. // Subclass for spinbutton
  667. //---------------------------------------------------------------------------------
  668. MRESULT EXPENTRY CalcSpin(HWND hwndDlg, ULONG ulMsg, MPARAM mpParm1, MPARAM mpParm2)
  669.      {
  670.      if (ulMsg == SPBM_SPINUP || ulMsg == SPBM_SPINDOWN)
  671.           {
  672.           float  flSpinValue, factor;
  673.           char   chSpinValue[12];
  674.  
  675.           WinQueryWindowText(WinQueryWindow(hwndDlg,QW_TOP),12,chSpinValue);
  676.           flSpinValue = atof(chSpinValue);
  677.           //-----------------------------------------------------------------------
  678.           // Set the multiplication factor and fill values array
  679.           //-----------------------------------------------------------------------
  680.           if (fabs(flSpinValue) < 1)
  681.                factor = 0.01;
  682.           else
  683.                factor = (float) ceil(fabs(flSpinValue/50));
  684.  
  685.           sprintf(achValues[0],"%.*f", 2, flSpinValue + -factor);
  686.           sprintf(achValues[1],"%.*f", 2, flSpinValue + 0);
  687.           sprintf(achValues[2],"%.*f", 2, flSpinValue + factor);
  688.  
  689.           //-----------------------------------------------------------------------
  690.           // Set the new array for the spinbutton and make the middle item the
  691.           // cuurent one so the user can go up or down.
  692.           //-----------------------------------------------------------------------
  693.           pfnwOldProc(hwndDlg, SPBM_SETARRAY, MPFROMP (achValues), MPFROMSHORT(3));
  694.           pfnwOldProc(hwndDlg, SPBM_SETCURRENTVALUE, (MPARAM)1, (MPARAM)0);
  695.           }
  696.      return pfnwOldProc(hwndDlg, ulMsg, mpParm1, mpParm2);
  697.      }
  698.  
  699. The complete working code can be found in SPINBUT3.ZIP. 
  700.  
  701. It should be noted that subclassing takes a performance penalty because more 
  702. functions have to be called for every message generated!  So the EXAMPLE2 
  703. version which works with notification messages might be slightly faster.  On a 
  704. 486DX system this difference should be insignificant. 
  705.  
  706. The Infinately Floating Spinbutton - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  707.  
  708.  
  709. ΓòÉΓòÉΓòÉ 3.6. Add the Spare Tire! ΓòÉΓòÉΓòÉ
  710.  
  711. Add the Spare Tire! 
  712.  
  713. Now that we have a spinbutton which is reacting to user input, and can handle 
  714. floats, it would be nice to have some functions to read a float from the 
  715. spinbutton, or to put a float in the spinbutton. 
  716.  
  717. The first function reads the current value in the spinbutton and returns a 
  718. float value.  In this function we'll use the knowledge that the entryfield is 
  719. the first child of a spinbutton. 
  720.  
  721. //--------------------------------------------------------------------------------------------
  722. // ReadSpin
  723. //
  724. // Read the value in the sle field of the and return the float equevalent
  725. //--------------------------------------------------------------------------------------------
  726. float ReadSpin(HWND hwndDlg, ULONG ulId)
  727.      {
  728.      HWND  hwndSpinSle;        // Handle off sb sle field (child sb)
  729.      HWND  hwndSpinbut;        // Handle Spin button
  730.      char  Output[16];
  731.  
  732.      hwndSpinbut = WinWindowFromID(hwndDlg, ulId);      // Query spinbutton handle
  733.      hwndSpinSle = WinQueryWindow(hwndSpinbut, QW_TOP); // Query first child spinbuuton
  734.                                                         // (= entry field)
  735.      WinQueryWindowText(hwndSpinSle, 16, Output);
  736.  
  737.      return (atof(Output));
  738.      }
  739.  
  740. The WriteSpin() function uses the same knowledge.  It puts a given float value 
  741. as text in the entryfield of the spinbutton.  Because the way it's constructed 
  742. the subclass will use this value if an up or down arrow is used. 
  743.  
  744. NOTE:  if you want the message SPBM_QUERYVALUE to return a valid value, you've 
  745. got to set the value array in the writespin function and attach it to the 
  746. spinbutton.  This can easily be achieved by using code from calcspin. 
  747.  
  748. //--------------------------------------------------------------------------------------------
  749. // WriteSpin
  750. //
  751. // Write the float value in Input as a char[12] in the sle of a Spinbutton
  752. //--------------------------------------------------------------------------------------------
  753. void WriteSpin(HWND hwndDlg, ULONG ulId, float WriteValue)
  754.      {
  755.      char  Value[16];
  756.  
  757.      sprintf(Value,"%.*f", 2, WriteValue);
  758.      WinSetWindowText(WinQueryWindow(WinWindowFromID(hwndDlg, ulId), QW_TOP), Value);
  759.      }
  760.  
  761. A complete working code example is found in the file SPINBUT4.ZIP. 
  762.  
  763. In the SPINBUT4 program both of the above functions are used to fill and read a 
  764. spinbutton. 
  765.  
  766. Concluding Notes 
  767.  
  768. In this article we described some possible algorithm for spinbuttons to handle 
  769. an infinite range of values which can be floating point numbers.  Two different 
  770. approaches were discussed, using notification messages and using subclassing. 
  771.  
  772. The infinite spinbutton could be refined by adding input control so the user 
  773. can't enter illegal characters.  This can be done by capturing the WM_CHAR 
  774. message. 
  775.  
  776. The examples are build and compiled using Borland C++ version 1.5. 
  777.  
  778. The Infinately Floating Spinbutton - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  779.  
  780.  
  781. ΓòÉΓòÉΓòÉ 4. RMX-OS2:  An In-Depth View (Part 2) ΓòÉΓòÉΓòÉ
  782.  
  783.  
  784. ΓòÉΓòÉΓòÉ 4.1. Introduction ΓòÉΓòÉΓòÉ
  785.  
  786. RMX-OS2:  An In-Depth View (Part 2) 
  787.  
  788. Written by Johan Wikman 
  789.  
  790. Introduction 
  791.  
  792. This is the second article in a series where I am describing RMX.  RMX is a 
  793. system for allowing OS/2 PM applications to run remotely, i.e., to run on one 
  794. computer and be used an another.  A somewhat more thorough overview of RMX is 
  795. available in the previous issue of EDM/2. 
  796.  
  797. One central feature of RMX is the replacement of the local procedure calls an 
  798. application makes to PM with equivalent remote procedure calls over a network 
  799. to a remote computer.  In this article I will describe the low level transfer 
  800. mechanism that is used for implementing the RPCs. 
  801.  
  802. RMX-OS2:  An In-Depth View (Part 2) - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  803.  
  804.  
  805. ΓòÉΓòÉΓòÉ 4.2. Remote Procedure Calls ΓòÉΓòÉΓòÉ
  806.  
  807. Remote Procedure Calls 
  808.  
  809. Although most of you probably know what remote procedure calls are, I 
  810. nevertheless provide here a brief introductory lesson.  If RPCs are familiar 
  811. you can simply skip this section. 
  812.  
  813. Whenever we have two communicating processes, the data exchanged must somehow 
  814. be transferred between them.  Consider a situation where a client process asks 
  815. a server process to add two integers and return the result back. Pseudo code 
  816. for achieving that may look like: 
  817.  
  818.    1. write both integers to a buffer 
  819.    2. send the buffer to server 
  820.    3. block while waiting for server to reply 
  821.    4. extract result from buffer 
  822.  
  823.  This works, but it would be tedious if the client process writer would have to 
  824.  write all that code (and its a lot more in reality when error checking, etc. 
  825.  is included) only to get two integers added.  If this sequence is examined 
  826.  closer, it is pretty obvious that it resembles very much an ordinary function 
  827.  call.  If we just wrap the pseudo code above in a function, a client process 
  828.  need not even be aware that the function is executed remotely. 
  829.  
  830.  The general procedure when a client calls an RPC is as follows:  1) the client 
  831.  calls a stub procedure, 2) the stub procedure marshals the arguments into 
  832.  suitable transfer format and sends them to the server, 3) the server unpacks 
  833.  the arguments and executes the actual function, 5) the server marshals the 
  834.  return value and sends it back to the client, 6) the client extracts the 
  835.  return value and returns it to the client process in an ordinary way. 
  836.  
  837.  So, my task in RMX was to replace every PM function with an RPC as described 
  838.  above.  This was the-other-way-around compared with how RPCs usually are 
  839.  implemented.  The usual procedure is to describe the RPCs in a special purpose 
  840.  language and then generate client and server stubs and C- prototypes from that 
  841.  description.  But there was nothing I could do about that since PM API was 
  842.  already defined and had to provide an RPC implementation for it. 
  843.  
  844.  RMX-OS2:  An In-Depth View (Part 2) - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  845.  
  846.  
  847. ΓòÉΓòÉΓòÉ 4.3. What is required for RPCs ΓòÉΓòÉΓòÉ
  848.  
  849. What is required for RPCs 
  850.  
  851. In order to implement RPCs there are some fairly obvious primitives that are 
  852. needed.  A client and a server both need to send data to and read data from 
  853. their peer.  These primitives are not sufficient, however, as a connection - in 
  854. whatever way it is implemented - between a client and a server does not come 
  855. into being by itself.  In practice we need to be able to: 
  856.  
  857.    1. create a connection (server) 
  858.    2. place a connection into listening state (server) 
  859.    3. disconnect a connection (server) 
  860.    4. open a connection (client) 
  861.    5. close a connection (server/client) 
  862.    6. read data from a connection (server/client) 
  863.    7. write data to a connection (server/client) 
  864.  
  865.  As the client and server usually are running on different machines, the client 
  866.  also needs a way to find out the name of the server connection. 
  867.  
  868.  When I started working on RMX I had, of practical reasons, to implement the 
  869.  communications mechanism using named pipes.  At the same time, however, I did 
  870.  not want to build the use of named pipes into RMX as it would have made it 
  871.  quite difficult to later start supporting TCP/IP or something else.  So what I 
  872.  did, was to specify a communications API according to the needs of RMX and 
  873.  implement it using named pipes.  As the communications DLL is dynamically 
  874.  loaded (based on the value of an environment variable) during start-up, it can 
  875.  easily be replaced by another DLL - e.g.  implemented on top of TCP/IP - and 
  876.  no changes are needed in RMX.  This is provided, of course, that the new DLL 
  877.  fulfils the requirements of the API exactly. 
  878.  
  879.  Recently I found the complete TCP/IP package on a Developers Connection CD I 
  880.  have, and after a few days of pondering (with the help of Client/Server 
  881.  Programming with OS/2 2.1 by Orfali and Harkey _ an excellent book) I managed 
  882.  to put together an TCP/IP based RMX communications DLL.  So, my initial design 
  883.  turned out to work.  <grin> 
  884.  
  885.  RMX-OS2:  An In-Depth View (Part 2) - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  886.  
  887.  
  888. ΓòÉΓòÉΓòÉ 4.4. RMX Communications API ΓòÉΓòÉΓòÉ
  889.  
  890. RMX Communications API 
  891.  
  892. Ok, so here is the API.  I only present the prototypes here and continue with 
  893. the implementation on top of named pipes and TCP/IP in the next section. 
  894.  
  895. RmxGetServiceName 
  896.  
  897. This is used by the server to obtain the name to use when creating a connection 
  898. and by the client to obtain the well-known entry point of the server. 
  899.  
  900. RmxCreate 
  901.  
  902. This is used by the server to create a connection.  The name the server uses is 
  903. always obtained from RmxGetServiceName().  The connection created must be 
  904. blocking (calls to a empty connection must block the caller). 
  905.  
  906. RmxCreateUnique 
  907.  
  908. This is used by the server to create a new unique connection.  The name the 
  909. function uses for creating the connection is given back to the caller so that 
  910. it - by some means - can pass it to a client. 
  911.  
  912. RmxOpen 
  913.  
  914. This is used by the client to open an existing connection. 
  915.  
  916. RmxClose 
  917.  
  918. This is used is by both the client and the server to close a connection. The 
  919. server always first calls RmxDisConnect() and only then RmxClose(). 
  920.  
  921. RmxConnect 
  922.  
  923. This is used by the server to place a connection into listening state.  In 
  924. practice it causes the server to block and wait for clients to connect. 
  925.  
  926. RmxDisConnect 
  927.  
  928. This is used by the server to acknowledge that the client has closed the 
  929. connection. 
  930.  
  931. RmxWrite 
  932.  
  933. This is used by the server and the client to write data to the peer. 
  934.  
  935. RmxRead 
  936.  
  937. This is used by both the server and client to read data from the peer. 
  938.  
  939. Error codes 
  940.  
  941. As the idea was to be able to implement the RMX communications API on top of 
  942. any real communications mechanism, I had to define a bunch of error codes that 
  943. hopefully would suffice for all implementations.  But in fact, as RMX treats 
  944. most errors as fatal, a boolean would have been enough.  For instance, if the 
  945. connection to the display computer is broken, there is no other option but to 
  946. terminate the application. 
  947.  
  948.  Error Code                         Description 
  949.  RMXAPI_OK                          The function was successfully executed. 
  950.  RMXERR_ENLARGE_BUFFER              The provided buffer was too small.  Enlarge 
  951.                                     and call again. 
  952.  RMXERR_UNKNOWN_SERVICE             The service requested is not known. 
  953.  RMXERR_GENERAL                     An all purpose error code. 
  954.  RMXERR_BAD_NAME                    The name is invalid or illegal. 
  955.  RMXERR_BAD_NETWORK                 The network or the server is not running. 
  956.  RMXERR_CONNECTION_NOT_CONNECTED    The server has disconnected the connection. 
  957.  RMXERR_CONNECTION_BUSY             The connection is busy.  Wait and retry. 
  958.  RMXERR_INVALID_HANDLE              The handle provided is no good. 
  959.  RMXERR_OUT_OF_MEM                  Out of memory. 
  960.  RMXERR_OUT_OF_RESOURCES            Out of some critical resource. 
  961.  
  962.  RMX-OS2:  An In-Depth View (Part 2) - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  963.  
  964.  
  965. ΓòÉΓòÉΓòÉ 4.5. How RMX uses the API ΓòÉΓòÉΓòÉ
  966.  
  967. How RMX uses the API 
  968.  
  969. Until now, I've been talking solely about a client and a server although RMX in 
  970. fact also consists of an engine.  The purpose of RMX is to allow a PM 
  971. application to run remotely.  So, if we have a client application on computer A 
  972. and want to use the application on computer B it means in practice that on B 
  973. there must be a server that will execute the RPCs of the client application. 
  974.  
  975. So far so good, but what if we want to run several application remotely? Should 
  976. the same server provide services to all remote applications?  It would work, 
  977. although it would be a pain to keep all data in the server belonging to one 
  978. remote application inaccessible from another remote applications. 
  979.  
  980. Furthermore, would one of the remote applications cause the server to crash (it 
  981. still does happen <grin>) then all remote applications would be affected. One 
  982. solution is to have one server - with a well-known name - whose only task is to 
  983. start a dedicated engine on behalf of a client's request.  The engine creates a 
  984. unique connection whose name is transferred over the server connection back to 
  985. the client.  The client closes the connection to the server and opens a 
  986. connection to the engine. 
  987.  
  988. Ok, so how is does the communication take place on function call level?  An 
  989. observant reader notices that RmxCreateUnique() is not mentioned at all, and 
  990. that a function RmxGetUniqueName() seems to be used.  The reason is that 
  991. initially there was no RmxCreateUnique(), but the same functionality was 
  992. obtained by first calling RmxGetUniqueName() and then RmxCreate().  While I was 
  993. implementing the TCP/IP DLL I noticed that it is a lot simpler to combine the 
  994. two.  I just hadn't the time to update the picture. 
  995.  
  996. The connection setup follows the following steps: 
  997.  
  998.    1. When the server is started it calls RmxGetServiceName() to get the name 
  999.       it should listen to. 
  1000.    2. The server creates the connection, using RmxCreate(), and issues 
  1001.       RmxConnect() on the connection.  This causes the server to block. 
  1002.    3. When the client is started it calls RmxGetServiceName() to get the name 
  1003.       of the server.  It obtains the name of the host from the environment 
  1004.       variable RMXDISPLAY (In the demo client application provided with this 
  1005.       article, the hostname is given on the command line.) 
  1006.    4. The client then opens a connection using RmxOpen().  This will cause the 
  1007.       RmxConnect() function in the server to return. 
  1008.    5. The server immediately issues a call to RmxRead(), which again will cause 
  1009.       the server to block. 
  1010.    6. The client sends the required connection setup info to the server using 
  1011.       RmxWrite().  This will cause the RmxRead() call in the server to return. 
  1012.    7. Immediately after the call to RmxWrite(), the client calls RmxRead() in 
  1013.       order to get the reply from the server.  This will cause the client to 
  1014.       block. 
  1015.    8. If the start-up parameters are acceptable, the server starts a new 
  1016.       engine. The engine calls RmxGetUniqueName() to get a unique new name and 
  1017.       uses RmxCreate() to create the connection. 
  1018.    9. The engine passes the engine name back to the server which, using 
  1019.       RmxWrite(), sends it back to the client.  Meanwhile the engine calls 
  1020.       RmxConnect() which will cause it to block. 
  1021.   10. The client will now return from RmxRead().  It will immediately call 
  1022.       RmxClose() to close the connection. 
  1023.   11. The server calls RmxDisConnect() to close the connection instance. 
  1024.   12. The client uses the value of the environment variable RMXDISPLAY and the 
  1025.       returned engine name for opening a connection to the engine using 
  1026.       RmxOpen(). 
  1027.   13. When the client calls RmxOpen() the engine will return from RmxConnect(). 
  1028.   14. The client enters a loop where it calls RmxWrite() to send requests to 
  1029.       the engine and calls RmxRead() to receive replies.  The engine will 
  1030.       similarly enter a loop where it calls RmxRead() to get a request from a 
  1031.       client, process the request and send a reply using RmxWrite(). 
  1032.   15. Although not shown in the figure, the client will eventually call 
  1033.       RmxClose() to close the connection.  The engine will detect this, exit 
  1034.       its own loop, call RmxDisConnect() and RmxClose() and finally exit. 
  1035.  
  1036.  RMX-OS2:  An In-Depth View (Part 2) - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  1037.  
  1038.  
  1039. ΓòÉΓòÉΓòÉ 4.6. RMXPIPE.DLL ΓòÉΓòÉΓòÉ
  1040.  
  1041. RMXPIPE.DLL 
  1042.  
  1043. Ok, let's look at the named pipe implementation.  All source is available in 
  1044. RMXCOMMS.ZIP delivered with this issue of EDM/2. 
  1045.  
  1046. Types 
  1047.  
  1048. One thing that has to be specified when named pipes are used is the size of the 
  1049. named pipe buffers.  I chose to use buffers of 4096 bytes (one page). Bigger 
  1050. buffers would give better throughput when big chunks of data is being 
  1051. transferred - with the cost of more memory being used - but as in RMX mostly 
  1052. small chunks are being sent, 4K was just about right. 
  1053.  
  1054.      const ULONG SIZE_PAGE = 4096;
  1055.      const ULONG SIZE_PIPE = SIZE_PAGE;
  1056.  
  1057. There is a need for a buffer outside the actual named pipes as well.  As I 
  1058. wanted the entire data structure to reside within a (multiple if need be) page, 
  1059. I defined the buffer size using the pipe size. 
  1060.  
  1061.      const ULONG SIZE_BUFFER = SIZE_PIPE - sizeof(HPIPE);
  1062.  
  1063.      struct CONNECTION
  1064.      {
  1065.        HPIPE hPipe;
  1066.        BYTE  bytes[SIZE_BUFFER];
  1067.      };
  1068.  
  1069. Now CONNECTION fits conveniently within a page.  A user should not see this 
  1070. structure, however, as it will be different depending on the underlying 
  1071. communications mechanism.  Therefore the only thing a user sees is a, 
  1072.  
  1073.      typedef void* HCONNECTION;
  1074.  
  1075. that can only be used as a handle. 
  1076.  
  1077. ULONG RmxGetServiceName(PCSZ pcszService, ULONG* pulSize, PSZ pszName);
  1078.  
  1079. In the TCP/IP world there is standard way of finding server applications on 
  1080. remote hosts.  In the directory /etc (\TCPIP\etc on OS/2) there is a file 
  1081. services that contains well-known entry points for different services. 
  1082. Unfortunately there is no similar convention with named pipes. 
  1083.  
  1084. At first I had the RMX named pipe names hard coded in RMXPIPE.DLL but while 
  1085. writing this article I realised that hard coding the names effectively would 
  1086. mean that nobody else could use RMXPIPE.DLL directly but would have to modify 
  1087. it.  Consequently I decided to implement a similar kind of functionality as the 
  1088. TCP/IP \etc\services. 
  1089.  
  1090. As there is no obvious place in OS/2 to put a file like that I chose to require 
  1091. that it has to be in the same directory as RMXPIPE.DLL and that its name has to 
  1092. be RMXPIPE.DAT.  Currently it looks like: 
  1093.  
  1094. #$Header$
  1095. #
  1096. # This file associates official service names with a pipe
  1097. name.
  1098. #
  1099. # The form for each entry is:
  1100. # <official service name>  <pipe name>
  1101. #
  1102. # RMXPIPE.DLL will automatically provide the initial
  1103. required '\PIPE'
  1104. # of a name so it need not be explicitly provided.
  1105. #
  1106.  
  1107. rmxserver \rmx\server      # RMX Server Process
  1108. rmxstarter     \rmx\starter     # RMX Starter Daemon
  1109. rmxtester \rmx\tester      # RMX Test Server
  1110.  
  1111. The implementation of RmxGetServiceName() simply scans this file and returns 
  1112. the corresponding pipe name. 
  1113.  
  1114. The server uses the returned name directly with RmxCreate(). The client cannot 
  1115. in general use the name directly with RmxOpen(), as the name uniquely 
  1116. identifies the service within the computer but not on the network. 
  1117.  
  1118. The name of the host is required for uniquely identifying the service on the 
  1119. network.  In RMX this is solved by requiring that the host name is provided in 
  1120. the environment variable RMXDISPLAY.  If RMXDISPLAY is specified, RMX attempt 
  1121. to redirect the application to the remote host, if it is not, the application 
  1122. is run locally. 
  1123.  
  1124.      [C:\]set RMXDISPLAY=\\LOKE
  1125.      [C:\]start app
  1126.  
  1127. The client application provided along this article takes the host as a command 
  1128. line argument. 
  1129.  
  1130. ULONG RmxCreate(PCSZ pcszName, HCONNECTION* phConn);
  1131.  
  1132. RmxCreate() is used by a server process to create a connection. The only 
  1133. argument it takes is the name of the connection and a pointer to a variable 
  1134. where the connection handle will be returned.  Most IPC mechanism can be 
  1135. configured in a million different ways - just look at the prototype for 
  1136. DosCreateNPipe() for instance - but as I only wanted to create connections of a 
  1137. specific kind there was no reason to have any additional arguments in the 
  1138. prototype of RmxCreate().  What RMX needs from a connection is: 
  1139.  
  1140.      blocking behaviour, reads on an empty connection blocks 
  1141.      write through, no buffering 
  1142.      read-write mode, possible to read and write using the same handle 
  1143.      unlimited instances, multiple instances with the same name can be created 
  1144.  
  1145.  Let's see how these requirements can be achieved with named pipes.  The 
  1146.  prototype of DosCreateNPipe() looks like: 
  1147.  
  1148.       APIRET APIENTRY DosCreateNPipe(PCSZ   pszName,
  1149.                                      PHPIPE pHpipe,
  1150.                                      ULONG  openmode,
  1151.                                      ULONG  pipemode,
  1152.                                      ULONG  cbInbuf,
  1153.                                      ULONG  cbOutbuf,
  1154.                                      ULONG  msec);
  1155.  
  1156.  The interesting arguments are openmode and pipemode. Openmode defines the mode 
  1157.  in which the pipe is opened and pipemode defines the mode of the pipe.  To get 
  1158.  the behaviour I wanted I defined the open flags as: 
  1159.  
  1160.       const ULONG NP_OPEN_FLAGS = NP_NOWRITEBEHIND       |
  1161.                                   NP_NOINHERIT           |
  1162.                                   NP_ACCESS_DUPLEX;
  1163.  
  1164.  and the pipe flags as: 
  1165.  
  1166.       const ULONG NP_PIPE_FLAGS = NP_WAIT                |
  1167.                                   NP_TYPE_BYTE           |
  1168.                                   NP_READMODE_BYTE       |
  1169.                                   NP_UNLIMITED_INSTANCES;
  1170.  
  1171.  Using these constants the pipe can be created. 
  1172.  
  1173.       ULONG
  1174.         rc;
  1175.       HPIPE
  1176.         hPipe;
  1177.  
  1178.       rc = DosCreateNPipe(pcszName,
  1179.                           &hPipe,
  1180.                           NP_CREATE_FLAGS,
  1181.                           NP_PIPE_FLAGS,
  1182.                           SIZE_PIPE,
  1183.                           SIZE_PIPE,
  1184.                           0);
  1185.  
  1186.  As the HCONNECTION that will be returned from RmxCreate() is in fact a 
  1187.  CONNECTION, some memory must also be allocated.  As this takes place in a DLL 
  1188.  I chose to allocate the memory using DosAllocMem() directly, the main reason 
  1189.  being that I don't know how safe it is to call runtime library functions 
  1190.  inside a DLL that will be used by multithreaded application, that are not 
  1191.  necessarily even compiled with the same compiler. 
  1192.  
  1193.       ULONG
  1194.         rc;
  1195.       PVOID
  1196.         pvMemory;
  1197.  
  1198.       do
  1199.         rc = DosAllocMem(&pvMemory, sizeof(CONNECTION),
  1200.                          PAG_READ | PAG_WRITE | PAG_COMMIT);
  1201.       while (rc == ERROR_INTERRUPT);
  1202.  
  1203.       CONNECTION *pconn = (PCONNECTION) pvMemory;
  1204.  
  1205.       memset(pconn, sizeof(CONNECTION), 0);
  1206.  
  1207.       pconn->hPipe = hPipe;
  1208.  
  1209.   ULONG RmxCreateUnique(ULONG*         pulSize,
  1210.                         PSZ            pszName,
  1211.                         HCONNECTION*   phConn);
  1212.  
  1213.  RmxCreateUnique() is used by the engine to create a unique connection.  The 
  1214.  caller needs to provide a buffer sufficiently large where the used name will 
  1215.  be returned.  Internally the biggest question is how to create a unique name. 
  1216.  A reasonably reliable method is to use the process id, thread ordinal and the 
  1217.  time. 
  1218.  
  1219.        const CHAR  acUniqueName[]  =
  1220.        "\\PIPE\\RMXPIPE\\UNIQUE\\";
  1221.  
  1222.        PPIB
  1223.          ppib;
  1224.        PTIB
  1225.          ptib;
  1226.  
  1227.        DosGetInfoBlocks(&ptib, &ppib);
  1228.  
  1229.        DATETIME dateTime;
  1230.  
  1231.        DosGetDateTime(&dateTime);
  1232.  
  1233.        USHORT
  1234.          pid        = (USHORT) ppib->pib_ulpid,
  1235.          tord       = (USHORT) ptib->tib_ordinal,
  1236.          seconds    = (USHORT) dateTime.seconds,    // <= 59
  1237.          hundredths = (USHORT) dateTime.hundredths;  // <= 99
  1238.  
  1239.        sprintf(pszName, "%s%hu%hu%hu%hu",
  1240.                acUniqueName, pid, tord, seconds, hundredths);
  1241.  
  1242.  and then create the named pipe in a loop that eventually either succeeds 
  1243.  properly or fails completely. 
  1244.  
  1245.        do
  1246.        {
  1247.            GetUniqueName(achUniqueName);
  1248.  
  1249.            rc = RmxCreate(achUniqueName, phConn);
  1250.        }
  1251.        while (rc == RMXERR_CONNECTION_BUSY);
  1252.  
  1253.   ULONG  RmxOpen(PCSZ pcszHost, PCSZ pcszPort, HCONNECTION* hConn);
  1254.  
  1255.  RmxOpen() is used by a client process to open a connection.  The arguments it 
  1256.  takes are the name of the host and the port to open and a pointer to a 
  1257.  variable where the connection handle will be returned.  When named pipes are 
  1258.  used the host name will be the name of a server and the port the name of a 
  1259.  named pipe. 
  1260.  
  1261.  To get the qualified name we simply need to concatenate the host and the port 
  1262.  names.  In order to do that we need a buffer.  Instead of first allocating a 
  1263.  buffer for this purpose and then another for the actual connection handle we 
  1264.  first allocate the connection handle buffer and use it for storing the string. 
  1265.  The allocation is done pretty much the same way as in RmxCreate(). 
  1266.  
  1267.        CONNECTION
  1268.          *pconn = (PCONNECTION) pvMemory;
  1269.        PSZ
  1270.          pszName = (PSZ) pconn;
  1271.  
  1272.        pszName[0] = 0;
  1273.  
  1274.        if (pcszHost)
  1275.          strcpy(pszName, pcszHost);
  1276.  
  1277.        strcat(pszName, pcszPort);
  1278.  
  1279.  The host name can be NULL which effectively means that we attempt to open the 
  1280.  pipe on the same computer.  Once the name is built we can open the actual 
  1281.  pipe. 
  1282.  
  1283.  In RMX this function will be called behind the scenes of an application which 
  1284.  may be doing a lot of things, e.g. open a large number of files.  Hence we 
  1285.  make sure the function does not fail because there are no handles left. 
  1286.  
  1287.        const ULONG NP_MODE_FLAGS = OPEN_FLAGS_WRITE_THROUGH |
  1288.                                    OPEN_FLAGS_FAIL_ON_ERROR |
  1289.                                    OPEN_FLAGS_NO_CACHE |
  1290.                                    OPEN_FLAGS_SEQUENTIAL |
  1291.                                    OPEN_SHARE_DENYNONE |
  1292.                                    OPEN_ACCESS_READWRITE |
  1293.                                    OPEN_FLAGS_NOINHERIT;
  1294.  
  1295.       HPIPE
  1296.         hPipe;
  1297.       ULONG
  1298.         rc;
  1299.  
  1300.       do
  1301.       {
  1302.         ULONG
  1303.           ulActionTaken; // Not used.
  1304.  
  1305.         rc = DosOpen(pszName,
  1306.                      &hPipe,
  1307.                      &ulActionTaken,
  1308.                      0L,
  1309.                      FILE_NORMAL,
  1310.                      FILE_OPEN,
  1311.                      NP_MODE_FLAGS,
  1312.                      0L);
  1313.  
  1314.         if (rc == ERROR_TOO_MANY_OPEN_FILES)
  1315.         {
  1316.           LONG
  1317.             lReqCount = 1; // We need one additional handle
  1318.  
  1319.           ULONG
  1320.             ulCurMaxFH;    // Not used.
  1321.  
  1322.           DosSetRelMaxFH(&lReqCount, &ulCurMaxFH);
  1323.         }
  1324.       }
  1325.       while (rc == ERROR_TOO_MANY_OPEN_FILES);
  1326.  
  1327.  Then we set the state of the pipe the same way as it was set in RmxCreate(). 
  1328.  I am not entirely sure whether this is necessary or if the state is 
  1329.  automatically set to the same as what was used when the pipe was created, but 
  1330.  better safe than sorry. 
  1331.  
  1332.       if (rc == NO_ERROR)
  1333.       {
  1334.         // The connection must be blocking.
  1335.         rc = DosSetNPHState(hPipe, NP_WAIT |
  1336.   NP_READMODE_BYTE);
  1337.         ...
  1338.  
  1339.   ULONG RmxClose(HCONNECTION hConn);
  1340.  
  1341.  RmxClose() is simply implemented by deallocating the memory that was allocated 
  1342.  in RmxCreate(), and closing the pipe 
  1343.  
  1344.       CONNECTION
  1345.         *pconn = (PCONNECTION) hConn;
  1346.  
  1347.       DosClose(pconn->hPipe);
  1348.       DosFreeMem(pconn);
  1349.  
  1350.   ULONG RmxConnect(HCONNECTION hConn);
  1351.  
  1352.  This function is mapped directly to DosConnect().  It blocks until a client 
  1353.  opens the pipe. 
  1354.  
  1355.       CONNECTION
  1356.         *pconn = (CONNECTION*) hConn;
  1357.       ULONG
  1358.         rc = 0;
  1359.  
  1360.       do
  1361.         rc = DosConnectNPipe(pconn->hPipe);
  1362.       while (rc == ERROR_INTERRUPT);
  1363.  
  1364.   ULONG RmxDisConnect(HCONNECTION hConn);
  1365.  
  1366.  Like RmxConnect() is mapped to DosConnect(), RmxDisConnect() is mapped to 
  1367.  DosDisConnect(). 
  1368.  
  1369.       CONNECTION
  1370.         *pconn = (CONNECTION*) hConn;
  1371.  
  1372.       DosDisConnectNPipe(pconn->hPipe);
  1373.  
  1374.   ULONG RmxWrite(HCONNECTION hConn, PCBYTE pbBuffer,
  1375.                  ULONG  ulBytesToWrite);
  1376.  
  1377.  Okay, now we are getting to the more interesting stuff.  This functions takes 
  1378.  a handle to a connection, a pointer to a buffer and an unsigned long 
  1379.  specifying how many bytes to write.  Contrary to usual write-functions, it 
  1380.  does not return the number of bytes written.  This is because in RMX, where 
  1381.  this function is used for implementing RPCs, the function must either succeed 
  1382.  or completely fail.  Also, if the number of bytes to transfer is too large for 
  1383.  the underlying implementation (in this case named pipes) the function must 
  1384.  itself deal with the task of splitting the message into smaller packets. 
  1385.  
  1386.        CONNECTION
  1387.          *pconn = (PCONNECTION) hConn;
  1388.        BYTE
  1389.          *pbData = pconn->bytes;
  1390.  
  1391.  The length of the entire message is sent in the first 4 bytes as an unsigned 
  1392.  integer. 
  1393.  
  1394.        *((ULONG*) pbData) = ulBytesToWrite;
  1395.  
  1396.        ULONG
  1397.          ulBytesInBuffer = SIZE_BUFFER - sizeof(ULONG);
  1398.        ULONG
  1399.          ulBytesToCopy   = ulBytesInBuffer < ulBytesToWrite ?
  1400.                            ulBytesInBuffer : ulBytesToWrite;
  1401.  
  1402.        memcpy(pbData + sizeof(ULONG), pbBuffer, ulBytesToCopy);
  1403.  
  1404.  Okay, so what is happening here?  After the size, we copy as much actual data 
  1405.  as can fit into the data area of the connection handle.  An alternative would 
  1406.  be to first send the size, and then send the data directly out of the buffer 
  1407.  provided by the caller.  But that would mean that no matter how small a 
  1408.  message, it would always be sent in at least two separate packets.  In the 
  1409.  code that follows note that during the first DosWrite(), pbData points to the 
  1410.  connection buffer, but on subsequent calls it points directly to the buffer 
  1411.  given by the caller. 
  1412.  
  1413.        ULONG
  1414.          ulBytesLeft   = ulBytesToWrite + sizeof(ULONG),
  1415.          ulBytesToSend = ulBytesToCopy  + sizeof(ULONG);
  1416.        LONG
  1417.          lBytesSent = -sizeof(ULONG);
  1418.        ULONG
  1419.          rc = RMXAPI_OK;
  1420.  
  1421.        do
  1422.        {
  1423.          ULONG
  1424.            ulBytesSent;
  1425.  
  1426.          rc = DosWrite(hPipe, pbData, ulBytesToSend, &ulBytesSent);
  1427.  
  1428.          lBytesSent  += ulBytesToSend;
  1429.          ulBytesLeft -= ulBytesToSend;
  1430.  
  1431.          if (ulBytesLeft != 0)
  1432.          {
  1433.              pbData = (PSZ)pbBuffer + lBytesSent;
  1434.  
  1435.              ulBytesToSend = SIZE_PIPE < ulBytesLeft ?
  1436.                              SIZE_PIPE : ulBytesLeft;
  1437.          }
  1438.        }
  1439.        while (ulBytesLeft != 0);
  1440.  
  1441.  The funny looking plus and minus sizeof(ULONG) is there because the first 
  1442.  message contains the size information which is not part of the actual message 
  1443.  and must be excluded when we figure out whether data needs to be copied still. 
  1444.  This ought to be cleaned up somwhat, I get nervous when signed and unsigned 
  1445.  quantities are mixed in this fashion. 
  1446.  
  1447.   ULONG RmxRead(HCONNECTION hConn,  PBYTE pbBuffer, ULONG ulSize,
  1448.                 ULONG* pulBytesRead);
  1449.  
  1450.  This function takes a handle to a connection, a pointer to a buffer, an 
  1451.  unsigned long that specifies the size of the buffer, and a pointer to an 
  1452.  unsigned long where the size of the read message is returned.  If the buffer 
  1453.  is not big enough the function returns an error code indicating that the 
  1454.  buffer must be enlarged and the required size is returned in the variable 
  1455.  pointed to by pulBytesRead. 
  1456.  
  1457.  If the variable indicating the length is 0, this is a true new call to 
  1458.  RmxRead(). 
  1459.  
  1460.        CONNECTION
  1461.          *pconn = (PCONNECTION) hConn;
  1462.        HPIPE
  1463.          hPipe = pconn->hPipe;
  1464.        ULONG
  1465.          *pulLength = (ULONG*) pconn->bytes;
  1466.  
  1467.  If it is 0, we read the size as a separate operation.  We have to do that 
  1468.  because we do not want to read to the size to the buffer provided by the 
  1469.  caller, but to the buffer associated with the handle. 
  1470.  
  1471.        if (*pulLength == 0)
  1472.        {
  1473.            ULONG
  1474.              ulBytesLeft = sizeof(ULONG),
  1475.              ulBytesRead = 0;
  1476.  
  1477.            do
  1478.            {
  1479.                PBYTE
  1480.                  pbData = ((PBYTE) pulLength) + ulBytesRead;
  1481.  
  1482.                DosRead(hPipe, pbData, ulBytesLeft, &ulBytesRead);
  1483.  
  1484.                ulBytesLeft -= ulBytesRead;
  1485.            }
  1486.            while (ulBytesLeft != 0);
  1487.  
  1488.            if (*pulLength > ulSize)
  1489.            {
  1490.              *pulBytesRead = *pulLength;
  1491.              return RMXERR_ENLARGE_BUFFER;
  1492.            }
  1493.        }
  1494.  
  1495.  Perhaps it is overkill to read 4 bytes in a loop like that, but at least we'll 
  1496.  be certain that we get the data we are interested in.  The loop for reading 
  1497.  the actual message data looks pretty much the same.  Note that pbData now 
  1498.  points to the buffer provided by the caller. 
  1499.  
  1500.        ULONG
  1501.          ulBytesLeft = *pulLength,
  1502.          ulBytesRead = 0,
  1503.          ulTotalBytesRead = 0;
  1504.  
  1505.        do
  1506.        {
  1507.            PBYTE
  1508.              pbData = pbBuffer + ulTotalBytesRead;
  1509.  
  1510.            rc = DosRead(hPipe, pbData, ulBytesLeft, &ulBytesRead);
  1511.  
  1512.            ulBytesLeft -= ulBytesRead;
  1513.            ulTotalBytesRead += ulBytesRead;
  1514.        }
  1515.        while (ulBytesLeft != 0);
  1516.  
  1517.        *pulBytesRead = *pulLength;
  1518.  
  1519.  The length is set to 0 to indicate that the entire message has been read. 
  1520.  
  1521.        *pulLength = 0;
  1522.  
  1523.  RMX-OS2:  An In-Depth View (Part 2) - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  1524.  
  1525.  
  1526. ΓòÉΓòÉΓòÉ 4.7. RMXTCPIP.DLL ΓòÉΓòÉΓòÉ
  1527.  
  1528. RMXTCPIP.DLL 
  1529.  
  1530. Okay, now we begin to look at the equivalent TCP/IP implementation.  When I 
  1531. initially specified the communications API I followed the named pipe model 
  1532. fairly closely.  The reason was that I wanted it to be easy to implement the 
  1533. RMX API on top of them, but also because I had very little experience with 
  1534. TCP/IP and I didn't exactly know how they work.  But, I was confident that it 
  1535. would later be possible to fit them into the RMX model. 
  1536.  
  1537. If we look at named pipes and TCP/IP in a multithreaded client-server 
  1538. situation, the basic scenario can be pictured as (_beginthread() is called when 
  1539. a client opens the connection the server blocks on): 
  1540.  
  1541. From this figure it is fairly obvious that there is not a direct mapping 
  1542. between named pipes and sockets.  And while it is trivial to implement the RMX 
  1543. API using named pipes, sockets require a little bit more thought. 
  1544.  
  1545. The main problem is the difference in how multiple connections with the same 
  1546. name is handled.  With named pipes the server simply calls DosCreateNPipe() and 
  1547. DosConnect() multiple times.  The name given to DosCreateNPipe() is each time 
  1548. the same.  With sockets you first create a socket using socket(), bind() and 
  1549. listen(), and then you call accept() multiple times.  Each call to accept() 
  1550. returns a new socket handle.  Like most other things in computer science this 
  1551. can be solved by adding an extra level of indirection. 
  1552.  
  1553. ULONG RmxGetServiceName(PCSZ pcszService, ULONG* pulSize, PSZ pszName);
  1554.  
  1555. Now that we are running on top of TCP/IP we can directly use the 
  1556. \tcpip\etc\services file.  The file should contain entries like: 
  1557.  
  1558.      rmxserver      4711/tcp
  1559.      rmxstarter     4712/tcp
  1560.      rmxsecurity    4713/tcp
  1561.      rmxtester      4714/tcp
  1562.  
  1563. The first column contains the public name of a server, and the second the port 
  1564. number and the protocol to be used.  The port numbers are something that 
  1565. basically the world has to agree upon, so I just chose a few numbers that did 
  1566. not seem to be used by anybody.  They are easily changed if they happen be in 
  1567. conflict with something else.  The socket library directly provides functions 
  1568. for dealing with this file.  As the port is returned in network format we have 
  1569. to convert it to host format using ntohs(). 
  1570.  
  1571.      servent
  1572.          *s = getservbyname((PSZ) pcszService, "tcp");
  1573.  
  1574.      if (!s)
  1575.        return RMXERR_UNKNOWN_SERVICE;
  1576.  
  1577.      sprintf(pszName, "%d", ntohs((unsigned short) s->s_port));
  1578.  
  1579. ULONG RmxCreate(PCSZ pcszName, HCONNECTION* phConn);
  1580.  
  1581. RmxCreate() is used by a server process to create a connection. And as it is 
  1582. supposed to be called several times with the same name we cannot directly map 
  1583. it onto the usual socket creation functions.  The first thing we do is to 
  1584. convert the given name to a port number (the user supposedly has called 
  1585. RmxGetServiceName() before this). 
  1586.  
  1587.      int
  1588.        port = atoi(pcszPort);
  1589.  
  1590.      return CreateConnection(htons((unsigned short) port), phConn);
  1591.  
  1592. All work is done by CreateConnection() which is also used by RmxCreateUnique(). 
  1593. As we need to be able to detect whether the caller actually wants to create a 
  1594. new socket or if he only wants to have a new instance of the same socket we 
  1595. need to have an additional data structure for that purpose. 
  1596.  
  1597.      struct SOCKET
  1598.      {
  1599.        unsigned short port;   // The port number in network format.
  1600.        int            socket; // The socket handle.
  1601.        unsigned       users;  // Number of users of this instance.
  1602.        SOCKET*        next;   // Pointer to next instance (or NULL).
  1603.        SOCKET*        prev;   // Pointer to previous instance (or NULL).
  1604.      };
  1605.  
  1606.      const ULONG SIZE_BUFFER = SIZE_CONNECTION -
  1607.                                sizeof(SOCKET*) -
  1608.      sizeof(int);
  1609.  
  1610.      struct CONNECTION
  1611.      {
  1612.        SOCKET* sharedSocket;
  1613.        int     privateSocket;
  1614.        BYTE    bytes[SIZE_BUFFER];
  1615.      };
  1616.  
  1617. The first thng CreateConnection() does is to get an instance of SOCKET.  I 
  1618. don't show it here (it's all available in the source code) but PickSOCKET() 
  1619. scans through all existing SOCKET instances and if it finds one instance where 
  1620. the port number is the same as the one requested, it increases the use count 
  1621. and returns it.  Otherwise it creates a new instance. 
  1622.  
  1623.      SOCKET
  1624.        *pSocket = PickSOCKET(port);
  1625.  
  1626.      if (pSocket->socket == 0)
  1627.        rc = InitializeSOCKET(pSocket);
  1628.  
  1629.      PCONNECTION
  1630.        pconn = AllocConnection();
  1631.  
  1632.      pconn->sharedSocket = pSocket;
  1633.  
  1634.      *phConn = pconn;
  1635.  
  1636. If it was an all new instance there is no real socket associated with it yet. 
  1637. The socket is created in InitializeSOCKET() that as its first action calls 
  1638. CreateSocket().  CreateSocket() creates the socket handle and deals with system 
  1639. call interruptions.  It is in a separate function because it is also used by 
  1640. RmxOpen(). 
  1641.  
  1642.      static int CreateSocket()
  1643.      {
  1644.        int
  1645.          serrno = 0,
  1646.          socket = 0;
  1647.  
  1648.        do
  1649.        {
  1650.            socket = ::socket(AF_INET, SOCK_STREAM, 0);
  1651.  
  1652.            if (socket < 0)
  1653.              serrno = sock_errno();
  1654.        }
  1655.        while (serrno == SOCEINTR);
  1656.  
  1657.        if (serrno != 0)
  1658.          return 0;
  1659.  
  1660.        return socket;
  1661.      }
  1662.  
  1663. The socket handle is just a handle, it cannot be used for anything yet.  It 
  1664. must first be bound which means that a local address is assigned to it.  If the 
  1665. specified port is 0, the system allocates a unique port on behalf of the 
  1666. caller. 
  1667.  
  1668. int
  1669.   socket = CreateSocket();
  1670.  
  1671. sockaddr_in
  1672.   in;
  1673.  
  1674.      in.sin_family      = AF_INET;
  1675.      in.sin_port        = pSocket->port;
  1676.      in.sin_addr.s_addr = INADDR_ANY;
  1677.  
  1678.      ::bind(socket, (sockaddr*) &in,
  1679.      sizeof(sockaddr_in));
  1680.  
  1681. The operation is not complete yet.  We need to have a connection request queue 
  1682. for the incoming requests.  That is taken care of by listen(). 
  1683.  
  1684.      ::listen(socket, SOMAXCONN);
  1685.  
  1686. If the socket was created without an explicit port number, we ask the system 
  1687. what the actual port number is. 
  1688.  
  1689.      if (pSocket->port == 0)
  1690.      {
  1691.          int
  1692.           size = sizeof(sockaddr_in);
  1693.  
  1694.          getsockname(socket, (sockaddr*) &in, &size);
  1695.          pSocket->port = in.sin_port; // Note, network
  1696.      order
  1697.      }
  1698.  
  1699.      pSocket->socket = socket;
  1700.  
  1701. The call to AllocConnection() simply allocates an instance of CONNECTION and 
  1702. returns it.  This completes the implemetation of RmxCreate().  Although not 
  1703. necessarily entirely obvious this arrangement allows RmxCreate() to be called 
  1704. multiple times using the same port number, yet the actual socket is created 
  1705. only once, on subsequent calls only its use-count is increased. 
  1706.  
  1707. ULONG RmxCreateUnique(ULONG*       pulSize,
  1708.                       PSZ          pszName,
  1709.                       HCONNECTION* phConn);
  1710.  
  1711. RmxCreateUnique() is implemented exactly like RmxCreate() except that the port 
  1712. number provided is zero.  That will then cause the system to allocate some 
  1713. unique port. 
  1714.  
  1715.      CreateConnection(0, phConn);
  1716.  
  1717.      PCONNECTION
  1718.        pconn = (PCONNECTION) *phConn;
  1719.  
  1720.      sprintf(pszPort, "%d", ntohs(pconn->sharedSocket->port));
  1721.  
  1722. ULONG  RmxOpen(PCSZ pcszHost, PCSZ pcszPort, HCONNECTION*
  1723. hConn);
  1724.  
  1725. As the host and port names are expressed textually they must be converted into 
  1726. integers before we can use them.  The port is simply converted using atoi(), 
  1727. the host needs a little bit more attention. 
  1728.  
  1729.      int
  1730.        port = atoi(pcszPort);
  1731.  
  1732. First we check whether the host has been specified in ordinary dotted decimal 
  1733. notation (e.g. 192.26.110.20). 
  1734.  
  1735.      unsigned long
  1736.        address = 0;
  1737.  
  1738.      if (pcszHost)
  1739.      {
  1740.          address = inet_addr((PSZ) pcszHost);
  1741.  
  1742.          if (address == (unsigned long) -1)
  1743.          {
  1744.  
  1745. If it hasn't we attempt to look it up from the hosts file, or from a nameserver 
  1746. if present (handled automatically by gethostbyname()). 
  1747.  
  1748.             hostent
  1749.               *host = gethostbyname((PSZ) pcszHost);
  1750.  
  1751.             address = *(unsigned long*) host->h_addr;
  1752.          }
  1753.      }
  1754.  
  1755. When we have the complete address, we can create and connect the socket. First 
  1756. we need the actual socket handle. 
  1757.  
  1758.      int
  1759.        socket = CreateSocket();
  1760.  
  1761. Just like when we were creating the connection this socket cannot be used for 
  1762. anything.  It has to be connected to a real address.  We use the port and 
  1763. address we just figured out.  Again the port must be converted into network 
  1764. format.  The address is in network format already when it is returned by 
  1765. inet_addr() or gethostbyname(). 
  1766.  
  1767.      sockaddr_in
  1768.        sin;
  1769.  
  1770.      sin.sin_family      = AF_INET;
  1771.      sin.sin_port        = htons((unsigned short) port);
  1772.      sin.sin_addr.s_addr = address;
  1773.  
  1774.      ::connect(socket, (sockaddr*) &sin,
  1775.      sizeof(sockaddr_in));
  1776.  
  1777. The only thing left is to allocate the connection handle. 
  1778.  
  1779.      PCONNECTION
  1780.        pconn = AllocConnection();
  1781.  
  1782.      pconn->privateSocket = socket;
  1783.  
  1784.      *phConn = pconn;
  1785.  
  1786. As the memory returned by AllocConnection() is set to 0, pconn->sharedSocket 
  1787. will also be NULL. 
  1788.  
  1789. ULONG RmxClose(HCONNECTION hConn);
  1790.  
  1791. RmxClose() is used by both the client and the server to close a connection. 
  1792. The client will never have a shared socket, and the server should not - 
  1793. provided it has first (as it ought to) called RmxDisConnect() - have a private 
  1794. socket. 
  1795.  
  1796.      PCONNECTION
  1797.        pconn = (PCONNECTION) hConn;
  1798.  
  1799.      if (pconn->sharedSocket)
  1800.        ReleaseSOCKET(pconn->sharedSocket);
  1801.  
  1802.      int
  1803.        socket = pconn->privateSocket;
  1804.  
  1805.      DosFreeMem(pconn);
  1806.  
  1807.      if (socket)
  1808.        ::soclose(socket);
  1809.  
  1810. ReleaseSOCKET() decrements the use count of the shared socket, and if it 
  1811. reaches 0, it closes the real socket and deletes the SOCKET instance. 
  1812.  
  1813. ULONG RmxConnect(HCONNECTION hConn);
  1814.  
  1815. This function can be directly mapped to the socket call accept(). Accept() 
  1816. blocks the caller until a client opens a connection. 
  1817.  
  1818.      PCONNECTION
  1819.        pconn = (PCONNECTION) hConn;
  1820.  
  1821.      int
  1822.         socket = ::accept(pconn->sharedSocket->socket, 0, 0);
  1823.  
  1824.      pconn->privateSocket = socket;
  1825.  
  1826. Accept() returns a unique socket handle that can be used for communicating with 
  1827. the client that opened the connection 
  1828.  
  1829. ULONG RmxDisConnect(HCONNECTION hConn);
  1830.  
  1831. RmxDisConnect() is called by the server to close a specific client connection. 
  1832.  
  1833.      PCONNECTION
  1834.        pconn = (PCONNECTION) hConn;
  1835.  
  1836.      ::soclose(pconn->privateSocket);
  1837.      pconn->privateSocket = 0;
  1838.  
  1839. The shared socket is still there, so we can use the same connection handle for 
  1840. servicing a new client by calling RmxConnect(). 
  1841.  
  1842. ULONG RmxWrite(HCONNECTION hConn, PCBYTE pbBuffer, ULONG  ulBytesToWrite);
  1843. ULONG RmxRead(HCONNECTION hConn,  PBYTE pbBuffer, ULONG ulSize, ULONG*
  1844. pulBytesRead);
  1845.  
  1846. The socket implementation of RmxRead() and RmxWrite() is almost identical to 
  1847. the named pipe implementation.  The main difference is that instead of using 
  1848. DosRead() and DosWrite() we use recv() and send(). 
  1849.  
  1850. RMX-OS2:  An In-Depth View (Part 2) - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  1851.  
  1852.  
  1853. ΓòÉΓòÉΓòÉ 4.8. RMXCOMMS.DLL ΓòÉΓòÉΓòÉ
  1854.  
  1855. RMXCOMMS.DLL 
  1856.  
  1857. If you look at the makefiles of RMXPIPE.DLL and RMXTCPIP.DLL you'll see that I 
  1858. generate no import library from the DLLs.  That is on purpose.  The way I see 
  1859. it, these two DLLS are only an implementation of an API.  Any program that uses 
  1860. the API should be able to use another DLL that implements the same API using 
  1861. another communications mechanism. 
  1862.  
  1863. If an application is linked with an import library generated from RMXPIPE.DLL, 
  1864. for example, then that application is forever married to the DLL. If the 
  1865. application should use another DLL it must be relinked with an import library 
  1866. generated from the other DLL. 
  1867.  
  1868. If the application is instead linked with RMXCOMMS.LIB which is generated from 
  1869. RMXCOMMS.DLL, the application need never be relinked in order to use a 
  1870. particular implementation of the RMX communications API. 
  1871.  
  1872. RMXCOMMS.DLL provides exactly the same interface as RMXPIPE.DLL and 
  1873. RMXTCPIP.DLL, and is in fact built using the same header as they are.  Its 
  1874. implementation is, however, quite different. 
  1875.  
  1876. First a few types are defined:  PPFN is a pointer to a function pointer and 
  1877. Function is structure holding an ordinal and a pointer to a function pointer. 
  1878.  
  1879.      typedef PFN* PPFN;
  1880.  
  1881.      struct Function
  1882.      {
  1883.        ULONG ulOrdinal;
  1884.        PPFN  ppfnAddress;
  1885.      };
  1886.  
  1887. Then a number of module variables.  As you notice there is one function pointer 
  1888. for each function defined by the RMX communications API.  The addresses of the 
  1889. function pointers are stored, together with the corresponding ordinal, in an 
  1890. array of function structures. 
  1891.  
  1892. static HMODULE hmodRmxComms;
  1893. static ULONG RMXENTRY (*rmxClose)(HCONNECTION);
  1894. static ULONG RMXENTRY (*rmxConnect)(HCONNECTION);
  1895. static ULONG RMXENTRY (*rmxCreate)(PCSZ, HCONNECTION*);
  1896. static ULONG RMXENTRY (*rmxCreateUnique)(ULONG*, PSZ, HCONNECTION*);
  1897. static ULONG RMXENTRY (*rmxDisConnect)(HCONNECTION);
  1898. static ULONG RMXENTRY (*rmxGetServiceName)(PCSZ, ULONG*, PSZ);
  1899. static ULONG RMXENTRY (*rmxOpen)(PCSZ, PCSZ, HCONNECTION*);
  1900. static ULONG RMXENTRY (*rmxRead)(HCONNECTION, PBYTE, ULONG, ULONG*);
  1901. static ULONG RMXENTRY (*rmxWrite)(HCONNECTION, PCBYTE, ULONG);
  1902. static Function afFunctions[] =
  1903. {
  1904.   { ORD_RMXCLOSE,          (PPFN) &rmxClose },
  1905.   { ORD_RMXCONNECT,        (PPFN) &rmxConnect },
  1906.   { ORD_RMXCREATE,         (PPFN) &rmxCreate },
  1907.   { ORD_RMXCREATEUNIQUE,   (PPFN) &rmxCreateUnique },
  1908.   { ORD_RMXDISCONNECT,     (PPFN) &rmxDisConnect },
  1909.   { ORD_RMXGETSERVICENAME, (PPFN) &rmxGetServiceName },
  1910.   { ORD_RMXOPEN,           (PPFN) &rmxOpen },
  1911.   { ORD_RMXREAD,           (PPFN) &rmxRead },
  1912.   { ORD_RMXWRITE,          (PPFN) &rmxWrite }
  1913. };
  1914.  
  1915. const ULONG cFunctions     = sizeof(afFunctions)/sizeof(Function);
  1916.  
  1917. Let's then look at the DLL initialization routine of RMXCOMMS.DLL.  The first 
  1918. thing is to query the value of the environment variable RMXCOMMS. 
  1919.  
  1920.       PCSZ
  1921.         pcszRmxComms;
  1922.  
  1923.       if (DosScanEnv("RMXCOMMS", &pcszRmxComms) != NO_ERROR)
  1924.        return 0;
  1925.  
  1926. The value of RMXCOMMS is assumed to be the name of the actual communications 
  1927. DLL to use. 
  1928.  
  1929.       if (DosLoadModule(0, 0, pcszRmxComms, &hmodRmxComms) != NO_ERROR)
  1930.        return 0;
  1931.  
  1932. Finally the functions can be resolved. 
  1933.  
  1934.       for (int i = 0; i < cFunctions; i++)
  1935.         {
  1936.           ULONG
  1937.             ulOrdinal   = afFunctions[i].ulOrdinal;
  1938.           PPFN
  1939.             ppfnAddress = afFunctions[i].ppfnAddress;
  1940.  
  1941.           ULONG
  1942.             rc = DosQueryProcAddr(hmodRmxComms, ulOrdinal, 0, ppfnAddress);
  1943.  
  1944.           if (rc != NO_ERROR)
  1945.             return 0;
  1946.         }
  1947.  
  1948. This means in practice that once the DLL has successfully been loaded, the 
  1949. function pointers will point at valid functions.  Now each exported function 
  1950. can simply be implemented in the following fashion. 
  1951.  
  1952.      ULONG RMXENTRY RmxCreate(PCSZ pszName, HCONNECTION* phConn)
  1953.      {
  1954.        return rmxCreate(pszName, phConn);
  1955.      }
  1956.  
  1957. Example 
  1958.  
  1959. Suppose we have an application CLIENT.EXE that has been linked with 
  1960. RMXCOMMS.LIB which was generated from RMXCOMMS.DLL.  And suppose that there 
  1961. exists the proper communications DLLs RMXPIPE.DLL, RMXTCPIP.DLL and RMXIPX.DLL. 
  1962. Then the following three client processes will all use different DLLS. 
  1963.  
  1964.      [C:\]set RMXCOMMS=RMXPIPE
  1965.      [C:\]start client.exe
  1966.      [C:\]set RMXCOMMS=RMXTCPIP
  1967.      [C:\]start client.exe
  1968.      [C:\]set RMXCOMMS=RMXIPX
  1969.      [C:\]start client.exe
  1970.  
  1971. RMX-OS2:  An In-Depth View (Part 2) - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  1972.  
  1973.  
  1974. ΓòÉΓòÉΓòÉ 4.9. Sample Programs ΓòÉΓòÉΓòÉ
  1975.  
  1976. Sample Programs 
  1977.  
  1978. Along with this issue of EDM/2 you should get RMXCOMMS.ZIP that contains 
  1979. RMXCOMMS.DLL, RMXPIPE.DLL and RMXTCPIP.DLL, and the test/demo programs 
  1980. SERVER.EXE, ENGINE.EXE and CLIENT.EXE, along with all the source.  In order to 
  1981. try them out, you should place the DLLs in some directory along the LIBPATH. 
  1982. Remember to place the file RMXPIPE.DAT in the same directory as RMXPIPE.DLL, 
  1983. and remember to update the \tcpip\etc\services file if you intend to use 
  1984. RMXTCPIP.DLL.  SERVER.EXE and ENGINE.EXE should be in the same directory. 
  1985.  
  1986. If you want to run the server/engine pair and the client in separate computers 
  1987. you'll need a network.  The DLLs must of course be present in both computers. 
  1988.  
  1989. Starting the server 
  1990.  
  1991. Launch a windowed command prompt and go to the directory where SERVER.EXE 
  1992. resides. 
  1993.  
  1994.      [C:\RMX\DEMO]set RMXCOMMS=RMXPIPE
  1995.      [C:\RMX\DEMO]server
  1996.  
  1997. The server prints a notification message when it starts. 
  1998.  
  1999. Starting the client 
  2000.  
  2001. The client requires a few command line parameters in order to start.  If it is 
  2002. started with the wrong parameters it prints: 
  2003.  
  2004.      usage: client [-t #] ([-h host]|-e engine) [-v]
  2005.  
  2006.          -t #      : Number of threads
  2007.          -h host   : The name of the host
  2008.          -e engine : Use this as engine connection
  2009.          -v verbose: More chit chat
  2010.  
  2011.      Note the blank between the flag and the value
  2012.  
  2013.      example: client -t 5 -h \\ODIN
  2014.               client -v
  2015.  
  2016. The -t flags specifies how many threads the client should use.  As default it 
  2017. uses 5. The flag -h specifies the host and -v turns on verbose mode 
  2018. (recommended).  If you are running locally don't specify a host, 
  2019.  
  2020.      [C:\RMX\DEMO]set RMXCOMMS=RMXPIPE
  2021.      [C:\RMX\DEMO]client -v
  2022.  
  2023. otherwise the argument of the flag should be the name of the computer where the 
  2024. server is running.  The host name varies of course depending on which 
  2025. communications DLL you are using.  In my environment I use: 
  2026.  
  2027.      [C:\RMX\DEMO]set RMXCOMMS=RMXPIPE
  2028.      [C:\RMX\DEMO]client -h \\loke -v
  2029.  
  2030. with named pipes and 
  2031.  
  2032.      [C:\RMX\DEMO]set RMXCOMMS=RMXTCPIP
  2033.      [C:\RMX\DMEO]client -h loke -v
  2034.  
  2035. or 
  2036.  
  2037.      [C:\RMX\DEMO]set RMXCOMMS=RMXTCPIP
  2038.      [C:\RMX\DMEO]client -h 192.26.110.21 -v
  2039.  
  2040. with TCP/IP. 
  2041.  
  2042. When started the client opens a connection to the server and asks it to start 
  2043. an engine, the server starts the engine that creates a connection whose name is 
  2044. transferred back to the client.  The client opens a new connection to the 
  2045. engine and starts sending messages of varying size to the engine that 
  2046. subsequently sends them unaltered back.  The client then verifies that there 
  2047. has been no changes in the message. 
  2048.  
  2049. RMX-OS2:  An In-Depth View (Part 2) - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  2050.  
  2051.  
  2052. ΓòÉΓòÉΓòÉ 4.10. Compiling the source ΓòÉΓòÉΓòÉ
  2053.  
  2054. Compiling the source 
  2055.  
  2056. I've written the code using Borland C++ 1.5 so if you have that compiler you 
  2057. should be able - perhaps with a little tinkering - to build the DLLs and the 
  2058. applications.  If you have another compiler you'll probably have to make more 
  2059. modifications.  If you do make changes I'd appreciate if you would mail them to 
  2060. me so I can include them in the "official" version. 
  2061.  
  2062. A word of caution 
  2063.  
  2064. I've been using the RMXPIPE.DLL implementation for quite some time while I've 
  2065. been developing RMX and I have not experienced any problems.  However, now when 
  2066. I tested the programs for this article, the client occasionally received an 
  2067. error code (ERROR_REQ_NOT_ACCEP to be exact) when it tried to send data to the 
  2068. server.  I havn't experienced any problems at all with RMXTCPIP.DLL so I blame 
  2069. the named pipe implementation of OS/2.  <grin> 
  2070.  
  2071. RMX-OS2:  An In-Depth View (Part 2) - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  2072.  
  2073.  
  2074. ΓòÉΓòÉΓòÉ 4.11. Implementing RPCs ΓòÉΓòÉΓòÉ
  2075.  
  2076. Implementing RPCs 
  2077.  
  2078. Even if this API hides quite a few of the complex details of client-server 
  2079. programming, it is still quite tedious to work with.  Fortunately it can be 
  2080. made easier.  This article is long as it is so I won't go into any details but 
  2081. I'll show you how WinInitialize is implemented in RMX.  I might return to this 
  2082. subject in a later article.  C++ templates can make the world a better place to 
  2083. program in... 
  2084.  
  2085. Client 
  2086.  
  2087. HAB APIENTRY WinInitialize(ULONG flOptions)
  2088. {
  2089.   ClientConnection
  2090.     &c = ClientConnection::connection();
  2091.   Packet
  2092.     &p = c.freshPacket();
  2093.  
  2094.   p << RMX_OS2PMWIN;
  2095.   p << (ORDINAL) ORD_WIN32INITIALIZE;
  2096.   p << flOptions;
  2097.  
  2098.   c.sendReceive();
  2099.  
  2100.   HAB
  2101.     hab;
  2102.  
  2103.   p >> hab;
  2104.  
  2105.   return hab;
  2106. }
  2107.  
  2108. Server 
  2109.  
  2110. void PMWin::winInitialize(Packet& p)
  2111. {
  2112.   ULONG
  2113.     flOptions;
  2114.  
  2115.   p >> flOptions;
  2116.  
  2117.   HAB
  2118.     hab = WinInitialize(flOptions);
  2119.  
  2120.   p.reply() << hab;
  2121. }
  2122.  
  2123. RMX-OS2:  An In-Depth View (Part 2) - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  2124.  
  2125.  
  2126. ΓòÉΓòÉΓòÉ 4.12. Conclusion ΓòÉΓòÉΓòÉ
  2127.  
  2128. Conclusion 
  2129.  
  2130. So, that was the low level transfer mechanism of RMX.  Feel free to use it in 
  2131. any way you see fit and don't hesitate to mail me if there is something you 
  2132. wonder about.  The next article will be about how to mark an application for 
  2133. use with RMX. 
  2134.  
  2135. RMX-OS2:  An In-Depth View (Part 2) - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  2136.  
  2137.  
  2138. ΓòÉΓòÉΓòÉ 5. /dev/EDM/BookReview ΓòÉΓòÉΓòÉ
  2139.  
  2140.  
  2141. ΓòÉΓòÉΓòÉ 5.1. Introduction ΓòÉΓòÉΓòÉ
  2142.  
  2143. /dev/EDM2/BookReview 
  2144.  
  2145. Written by Carsten Whimster 
  2146.  
  2147. Introduction 
  2148.  
  2149. /dev/EDM2/BookReview is a monthly column which focuses on development oriented 
  2150. books and materials.  The column is from the point of view of an intermediate 
  2151. PM C programmer and intermediate REXX programmer.  Pick up whichever book 
  2152. strikes your fancy, and join the growing group of people following our PM 
  2153. programming columns.  I have reviewed a number of beginner's books, and will 
  2154. try to concentrate a bit more on intermediate techniques and special topics 
  2155. from now on. 
  2156.  
  2157. Please send me your comments and thoughts so that I can make this column as 
  2158. good as possible.  I read and respond to all mail. 
  2159.  
  2160. OS/2 Programming is an introductory book to PM programming.  It was released 
  2161. just around the same time as OS/2 2.1, so it is slightly out of date, like 
  2162. almost all PM books at the moment. 
  2163.  
  2164. /dev/EDM2/BookReview - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  2165.  
  2166.  
  2167. ΓòÉΓòÉΓòÉ 5.2. Errata ΓòÉΓòÉΓòÉ
  2168.  
  2169. Errata 
  2170.  
  2171. Well, I have now been working on my POVEd program (an editor tailored to 
  2172. POV-Ray) for some time, and have picked up a basic proficiency, and a few 
  2173. intermediate techniques, so I now consider myself an intermediate PM C 
  2174. programmer.  I still have loads to learn, but I won't be stumbling over really 
  2175. basic concepts any more. 
  2176.  
  2177. /dev/EDM2/BookReview - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  2178.  
  2179.  
  2180. ΓòÉΓòÉΓòÉ 5.3. OS/2 Programming - Your Fast-Track Guide to OS/2 ΓòÉΓòÉΓòÉ
  2181.  
  2182. OS/2 Programming - Your Fast-Track Guide to OS/2 
  2183.  
  2184. This book is aimed at the intermediate skill level, it says on the back. Does 
  2185. that mean intermediate programmers, intermediate C programmers, intermediate 
  2186. OS/2 users, or what?  A little clarification would be helpful for the buyer. 
  2187. In any case, I would rank it as a beginning OS/2 PM C programmer book.  Here 
  2188. are the chapters: 
  2189.  
  2190. Part One:  Introduction to OS/2 Programming 
  2191.  
  2192.       1.  OS/2: An Overview 
  2193.       2.  Fundamentals of OS/2 Programming 
  2194.  
  2195.  Part Two:  Programming the Presentation Manager 
  2196.  
  2197.       3.  Presentation Manager Programming Overview 
  2198.       4.  Processing Messages 
  2199.       5.  Message Boxes and Menus 
  2200.       6.  Dialog Boxes 
  2201.       7.  Control Windows 
  2202.       8.  Icons and Graphics 
  2203.  
  2204.  Part Three:  Exploring the API 
  2205.  
  2206.       9.  An Introduction to Multitasking 
  2207.       10.  Serialization and Inter-Process Communication 
  2208.       11.  File I/O 
  2209.       12.  Creating and Using Dynamic Link Libraries 
  2210.  
  2211.  Index 
  2212.  
  2213.  The layout of the book is quite good, although I think that I prefer the 
  2214.  non-PM stuff at the beginning, not at the end.  That way, the sample programs 
  2215.  can include these features where appropriate.  Having said that, this layout 
  2216.  works too. 
  2217.  
  2218.  Chapters 1 and 2 are fairly straight forward, and there really isn't much to 
  2219.  say, other than the fact that the introduction is fairly comprehensive in 
  2220.  explaining what a PC is, what OS/2 is, and a bit about the history of both. 
  2221.  The OS/2 fundamentals chapter goes through the OS/2 C programming environment 
  2222.  a bit, and explains how the header files work, what a DEF file is, what C 
  2223.  prototypes are, how the OS/2 API is broken down, and a few other minor 
  2224.  background issues.  There is no real detail here, just a brief overview. 
  2225.  
  2226.  Section two moves into actual programming, and this is done in the classical 
  2227.  way of explaining mouse, windows, message queue, and so on, and then giving a 
  2228.  skeleton program.  This book concentrates on CSet++, by the way, so Watcom and 
  2229.  Borland users are on their own here.  Unfortunately, I am not crazy about the 
  2230.  variable naming style that the authors use (e.g.  hand_ab for the 
  2231.  application's HAB), but that is a question of taste.  The skeleton is 
  2232.  explained, and so it goes.  A plus here is the explanation of the DEF file, 
  2233.  and what it does for you.  This explanation is concise and clear, and I now 
  2234.  know what a DEF file is for <grin> The next chapter deals with messages, and 
  2235.  the macros are presented for converting MPARAMs.  The device context and the 
  2236.  presentation space are loosely explained, and a few GPI functions and 
  2237.  constants are introduced to allow colored text to be used.  Key presses are 
  2238.  explained, and how to catch them in the message loop.  Mouse messages are 
  2239.  briefly introduced as well.  Finally, a custom message is shown implemented. 
  2240.  All fairly brief, and well written. 
  2241.  
  2242.  Chapter five concentrates on message boxes and menus.  It is nice to see the 
  2243.  message box covered well, because as you get better, they are excellent for 
  2244.  quick-and-dirty debugging, and they are also very useful when you are just 
  2245.  beginning to flesh out the interface of a larger application.  At least, if 
  2246.  you attach message boxes to everything, you know that the gears of the 
  2247.  interface are working.  Menus are, of course, the most used part of the PM 
  2248.  interface, and deserve a good treatment.  They get a good treatment here, 
  2249.  including how to make accelerator keys work with them. 
  2250.  
  2251.  Chapter 6 introduces dialog boxes.  They are presented mainly as an 
  2252.  information gathering tool, in accordance with the CUA guidelines.  Some 
  2253.  fairly predictable examples are given, but no great amount of writing is spent 
  2254.  on this. 
  2255.  
  2256.  Control windows are next.  Unfortunately, and in accordance with the 
  2257.  apparently pervasive OS/2 programming book authors' philosophy, only a few are 
  2258.  shown in detail, and the rest are skipped over lightly.  The two that are 
  2259.  given more than just a cursory example are list boxes and sliders.  Whether 
  2260.  they represent all the others well is up to the reader to decide. 
  2261.  
  2262.  Icons and graphics form the next stop along the bumpy route of learning the PM 
  2263.  API.  How to use the system defined icons and pointers is shown, followed by 
  2264.  how to define your own, and use them instead.  Again, good programs, but a 
  2265.  little short. 
  2266.  
  2267.  The final section is called "Exploring the API", but perhaps this section 
  2268.  might be called "Kernel Programming" more accurately, since only non-PM calls 
  2269.  are demonstrated in this section.  Threads and sessions are each given a 
  2270.  decent introduction, along with how to wait for something the right way. 
  2271.  
  2272.  It is only natural to introduce concurrency issues after waiting has been 
  2273.  mentioned, and so the next chapter discusses semaphores, and critical 
  2274.  sections.  The explanations are lucid, and good, but again the programs are a 
  2275.  little on the skinny and functionally challenged side.  IPC is also introduced 
  2276.  in this chapter, with explanations and examples of shared memory and pipes. 
  2277.  
  2278.  File input and output is briefly treated in chapter eleven, along with a brief 
  2279.  introduction to system information, and how to obtain it. 
  2280.  
  2281.  The last chapter is on DLLs, and as you must expect by now, it is merely an 
  2282.  introduction.  It shows how to put a function into a DLL, and not much more. 
  2283.  
  2284.  /dev/EDM2/BookReview - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  2285.  
  2286.  
  2287. ΓòÉΓòÉΓòÉ 5.4. Summary ΓòÉΓòÉΓòÉ
  2288.  
  2289. Summary 
  2290.  
  2291. This book is quite nice, and a good introduction to PM programming.  Its main 
  2292. difficulty is the fact that it is slimmer than the competition.  There are 
  2293. several other good books out there, which cover more, or cover the same in more 
  2294. detail, and which cost about the same.  If this book were priced around the 
  2295. US$20-25 mark, it would be a good bargain.  In spite of this, I quite like it. 
  2296. The code and commenting style isn't quite what I prefer, but the code examples 
  2297. are good, and are built up well.  The choice of material is good, and the 
  2298. sequence of the material is good.  It is a nice, easy introduction to the PM 
  2299. API, and has some nice tips and tricks.  The motivation of the material is 
  2300. better than average. 
  2301.  
  2302. I would like to see better coverage of the various control windows, but this 
  2303. accusation could be leveled at most introductory PM books. 
  2304.  
  2305. I must say that I was a bit shocked to find that the book had no disk, and that 
  2306. the disk would cost US$24.95!  For this price, a book should include a disk, 
  2307. and most do. 
  2308.  
  2309. Overall, quite a nice book, but I have a feeling that most people will skip it, 
  2310. and pick up one of the other, larger books for about the same price.  That is a 
  2311. shame, because this book has a lot to offer in the way of explanations. It 
  2312. really does need to come with the disk for free, though. 
  2313.  
  2314. /dev/EDM2/BookReview - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  2315.  
  2316.  
  2317. ΓòÉΓòÉΓòÉ 5.5. Books Reviewed ΓòÉΓòÉΓòÉ
  2318.  
  2319. Books Reviewed 
  2320.  
  2321.      Real-World Programming for OS/2 2.1, Blain, Delimon, and English 
  2322.  
  2323.         -  SAMS Publishing. ISBN 0-672-30300-0. US$40, CAN$50. 
  2324.         -  Intermediate to Advanced PM C programmers 
  2325.         -  B+ 
  2326.  
  2327.       Lots of good code examples, but sometimes it is too complex for novices. 
  2328.       Accurate.  Well organized.  The index needs a little beefing up.  Good, 
  2329.       but not entirely complete how-to reference.  Good purchase. 
  2330.  
  2331.      Learning to Program OS/2 2.0 Presentation Manager by Example, Knight 
  2332.  
  2333.         -  Van Nostrand Reinhold.  ISBN 0-442-01292-6.  US$40, CAN$50. 
  2334.         -  Beginning PM C Programmers 
  2335.         -  B- 
  2336.  
  2337.       This book can be both frustrating and very rewarding.  It is not very 
  2338.       large, and a bit pricey, but has some excellent chapters on certain 
  2339.       beginning topics, such as messages, resources, IPF, and dialog boxes. 
  2340.       Strictly for beginners.  This book has only one (large) sample program! 
  2341.  
  2342.      Writing OS/2 2.1 Device Drivers in C, 2nd Edition, Mastrianni 
  2343.  
  2344.         -  Van Nostrand Reinhold. ISBN 0-442-01729-4. US$35, CAN$45. 
  2345.         -  Advanced C Programmers, familiar with hardware programming 
  2346.         -  A- 
  2347.  
  2348.       The only thing a device driver programmer would not find in here is how 
  2349.       to write SCSI, ADD, and IFS drivers.  Most everything else is in here, 
  2350.       along with skeleton examples.  An optional DevHlp library of C-callable 
  2351.       functions can be purchased by those who don't have time to write their 
  2352.       own. 
  2353.  
  2354.      OS/2 Presentation Manager GPI, Winn 
  2355.  
  2356.         -  Van Nostrand Reinhold. ISBN 0-442-00739-6. US$35, CAN$45. 
  2357.         -  Intermediate to advanced PM C programmers 
  2358.         -  C+ 
  2359.  
  2360.       This book needs updating for OS/2 2.x.  It is a well-written in- depth 
  2361.       coverage of the OS/2 way of programming for graphics.  It is not an 
  2362.       introductory PM or graphics programming book.  You should know the basics 
  2363.       of PM programming already. 
  2364.  
  2365.      The Art of OS/2 2.1 C Programming, Panov, Salomon, and Panov 
  2366.  
  2367.         -  Wiley-QED. ISBN 0-471-58802-4. US$40, CAN$50. 
  2368.         -  Beginning OS/2 and PM programmers 
  2369.         -  B+ 
  2370.  
  2371.       This is a great introductory PM programming book.  It covers basic OS/2 
  2372.       issues like threads before it jumps into PM programming.  The coverage is 
  2373.       quite thourough, with just enough reference material to make it useful 
  2374.       after you read it through the first time.  The upcoming revised edition 
  2375.       should be a killer. 
  2376.  
  2377.      Mastering OS/2 REXX, Gargiulo 
  2378.  
  2379.         -  Wiley-QED. ISBN 0-471-51901-4. US$40, CAN$50. 
  2380.         -  Intermediate OS/2 users and beginning programmers 
  2381.         -  B 
  2382.  
  2383.       This book is very easy to understand.  If you program with any 
  2384.       regularity, look elsewhere, but if you need an easily read, well- 
  2385.       explained beginner's book, look no further.  Some more detailed, and 
  2386.       complex real-world examples might be useful as you learn the material. 
  2387.       Good coverage of REXX's capabilities. 
  2388.  
  2389.      REXX Reference Summary Handbook, Goran 
  2390.  
  2391.         -  C F S Nevada. ISBN 0-9639854-1-8. US$20, CAN$25. 
  2392.         -  Beginning to advanced REXX programmers 
  2393.         -  A 
  2394.  
  2395.       This little handbook is packed full of useful information.  Includes 
  2396.       chapters on both built-in and some popular commercial libraries. 
  2397.       Well-written and comprehensively indexed.  A must for REXX programmers. 
  2398.  
  2399.      Application Development Using OS/2 REXX, Rudd 
  2400.  
  2401.         -  Wiley-QED. ISBN 0-471-60691-X. US$40, CAN$50 
  2402.         -  Intermediate to advanced REXX programmers 
  2403.         -  A- 
  2404.  
  2405.       Excellent coverage of everything REXX, with the occasional sparse 
  2406.       section. It is a decent reference book, and has enough unusual material 
  2407.       that it will probably find its way into many REXX programmers' libraries. 
  2408.  
  2409.      OS/2 Presentation Manager Programming, Petzold 
  2410.  
  2411.         -  Ziff-Davis Press. ISBN 1-56276-123-4. US$30, CAN$42 
  2412.         -  Beginning PM C programmers 
  2413.         -  A- 
  2414.  
  2415.       This book has the most thorough introduction to PM basics in any book I 
  2416.       have read so far.  It leaves the field wide open for a more thorough 
  2417.       advanced PM book later.  Very well written, very thorough, and very 
  2418.       understandable. Only a lack of in-depth treatment of control windows and 
  2419.       common dialog boxes keep it from being perfect. 
  2420.  
  2421.      Designing OS/2 Applications, Reich 
  2422.  
  2423.         -  John Wiley & Sons. ISBN 0-471-58889-X. US$35, CAN$45 
  2424.         -  All programmers 
  2425.         -  A 
  2426.  
  2427.       This book is about design, and making intelligent decisions in this 
  2428.       process.  It describes the OS/2 programming environment well, and thus 
  2429.       helps you make the proper choices in designing applications.  Highly 
  2430.       recommended to anyone creating more than just command-line tools and very 
  2431.       small programs. 
  2432.  
  2433.      OS/2 Programming, Schildt and Goosey 
  2434.  
  2435.         -  Osborne, McGraw-Hill. ISBN 0-07-881910-5. US$30, CAN$40 
  2436.         -  Introductory PM C programmers 
  2437.         -  B+ 
  2438.  
  2439.       This book is a good, but slightly thin introductory PM book.  It has good 
  2440.       explanations of many things that other books tend to skip over, and 
  2441.       covers a reasonably well selected subset of the API.  Unfortunately, it 
  2442.       doesn't come with a disk, so if you are looking for programs to 
  2443.       cut-and-paste, look elsewhere. 
  2444.  
  2445.  NOTES 
  2446.  
  2447.  This list contains all books I have reviewed, so that you can find what you 
  2448.  are looking for at a glance.  I will be careful to rate books fairly.  If I 
  2449.  feel a need to adjust ratings, I will adjust all of them at the same time, and 
  2450.  write a note explaining why I felt this necessary.  Please note that books 
  2451.  aimed at different audiences should only be compared with great care, if at 
  2452.  all.  I intend to concentrate on the strong points of the books I review, but 
  2453.  I will point out any weaknesses in a constructive manner. 
  2454.  
  2455.  LEGEND 
  2456.  
  2457.  BOOK:  The name of the book, and the author(s). 
  2458.  
  2459.  PUBLISHING INFORMATION:  Publishing company, ISBN, and approximate price. 
  2460.  
  2461.  AUDIENCE:  This is a description of the audience I think the book targets 
  2462.  best.  This is not intended as gospel, just a guideline for people not 
  2463.  familiar with the book. 
  2464.  
  2465.  MARK:  My opinion of the success of the book's presentation, and how well it 
  2466.  targets its audience.  Technical content, accuracy, organization, readability, 
  2467.  and quality of index all weigh heavily here, but the single most important 
  2468.  item is how well the book covers what it says it covers.  Many books try to 
  2469.  cover too much, and get a lower mark as a result. 
  2470.  
  2471.  A+        Ground-breaking, all-around outstanding book. 
  2472.  A         Excellent book. This is what I want to see happen a lot. 
  2473.  A-        Excellent book with minor flaws. 
  2474.  B+        Very good book with minor flaws or omissions. 
  2475.  B         Good book with some flaws and omissions. 
  2476.  B-        Good book, but in need of improvement. 
  2477.  C+        Mediocre book with some potential, but in need of some updating. 
  2478.  C         Mediocre book with some good sections, but badly in need of fixing. 
  2479.  C-        Mediocre book, little good material, desperately in need of an 
  2480.            overhaul. 
  2481.  D         Don't buy this book unless you need it, and nothing else exists. 
  2482.  F         Don't buy this book.  Period. 
  2483.  
  2484.  COMMENTS:  This is a very brief summary of the review proper. 
  2485.  
  2486.  /dev/EDM2/BookReview - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  2487.  
  2488.  
  2489. ΓòÉΓòÉΓòÉ 5.6. Content Index ΓòÉΓòÉΓòÉ
  2490.  
  2491. Content Index 
  2492.  
  2493. This Content Index is designed to let you find the book that covers the topics 
  2494. you need to learn about.  It will eventually have a lot of categories, with 
  2495. each book being rated along each row.  These tables will be quite large, and 
  2496. will continually grow, so please give me your feedback regarding what 
  2497. categories you would like to see, and which you don't.  It may take me a while 
  2498. to flesh them out, so have a little patience. 
  2499.  
  2500. NOTE:  books which cover the same material can look similar in this table, but 
  2501. be different in real life.  The style of a book, for example, can not be seen 
  2502. from a quick table, so make sure that you follow up by reading the reviews of 
  2503. the books you find here.  Finally, be sure that the books you are comparing are 
  2504. aimed at the same audiences. 
  2505.  
  2506. PM C BOOKS 
  2507.  
  2508. ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
  2509. ΓöéBOOK ΓöéMARK ΓöéKernel ΓöéDevice ΓöéVIO andΓöéPM     ΓöéGPI    ΓöéFonts  ΓöéPrint  Γöé
  2510. Γöé     Γöé     ΓöéBasics ΓöéDriver ΓöéAVIO   ΓöéIntro  Γöé       Γöé       Γöé       Γöé
  2511. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2512. ΓöéRWP  ΓöéB+   Γöé2      Γöé0      Γöé0      Γöé4      Γöé4      Γöé4      Γöé3      Γöé
  2513. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2514. ΓöéPME  ΓöéB-   Γöé1      Γöé0      Γöé0      Γöé2      Γöé2      Γöé2      Γöé0      Γöé
  2515. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2516. ΓöéODD  ΓöéA    Γöé0      Γöé5      Γöé0      Γöé0      Γöé1      Γöé0      Γöé1      Γöé
  2517. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2518. ΓöéGPI  ΓöéC+   Γöé0      Γöé0      Γöé0      Γöé0      Γöé5      Γöé2      Γöé3      Γöé
  2519. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2520. ΓöéTAO  ΓöéB+   Γöé3      Γöé2      Γöé1      Γöé4      Γöé1      Γöé2      Γöé0      Γöé
  2521. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2522. ΓöéPMP  ΓöéA-   Γöé1      Γöé0      Γöé1      Γöé5      Γöé3      Γöé4      Γöé2      Γöé
  2523. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2524. ΓöéOSP  ΓöéB+   Γöé2      Γöé0      Γöé0      Γöé3      Γöé2      Γöé1      Γöé0      Γöé
  2525. ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
  2526.  
  2527. REXX BOOKS: 
  2528.  
  2529. ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
  2530. ΓöéBOOK ΓöéMARK ΓöéREXX     ΓöéWPS      ΓöéReferenceΓöé
  2531. Γöé     Γöé     ΓöéIntro    Γöé         Γöé         Γöé
  2532. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2533. ΓöéMOR  ΓöéB    Γöé4        Γöé0        Γöé2        Γöé
  2534. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2535. ΓöéRSH  ΓöéA    Γöé1        Γöé2        Γöé5        Γöé
  2536. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2537. ΓöéADO  ΓöéA-   Γöé3        Γöé2        Γöé4        Γöé
  2538. ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
  2539.  
  2540. BOOK LEGEND: 
  2541.  
  2542. ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
  2543. ΓöéRWP  ΓöéReal-World Programming for OS/2 2.1                                Γöé
  2544. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2545. ΓöéPME  ΓöéLearning to Program OS/2 2.0 Presentation Manager by Example       Γöé
  2546. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2547. ΓöéODD  ΓöéWriting OS/2 2.1 Device Drivers in C, 2nd Edition                  Γöé
  2548. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2549. ΓöéGPI  ΓöéOS/2 Presentation Manager GPI                                      Γöé
  2550. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2551. ΓöéTAO  ΓöéThe Art of OS/2 2.1 C Programming                                  Γöé
  2552. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2553. ΓöéMOR  ΓöéMastering OS/2 REXX                                                Γöé
  2554. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2555. ΓöéRSH  ΓöéREXX Reference Summary Handbook                                    Γöé
  2556. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2557. ΓöéADO  ΓöéApplication Development Using OS/2 REXX                            Γöé
  2558. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2559. ΓöéPMP  ΓöéOS/2 Presentation Manager Programming                              Γöé
  2560. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2561. ΓöéDOA  ΓöéDesigning OS/2 Applications                                        Γöé
  2562. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2563. ΓöéOSP  ΓöéOS/2 Programming                                                   Γöé
  2564. ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
  2565.  
  2566. RATINGS LEGEND: 
  2567.  
  2568. ΓöîΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
  2569. Γöé0ΓöéNo coverage           Γöé
  2570. Γö£ΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2571. Γöé1ΓöéVery light coverage   Γöé
  2572. Γö£ΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2573. Γöé2ΓöéIntroductory coverage Γöé
  2574. Γö£ΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2575. Γöé3ΓöéGood Coverage         Γöé
  2576. Γö£ΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2577. Γöé4ΓöéIn-depth coverage     Γöé
  2578. Γö£ΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2579. Γöé5ΓöéAuthoritative         Γöé
  2580. ΓööΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
  2581.  
  2582. /dev/EDM2/BookReview - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  2583.  
  2584.  
  2585. ΓòÉΓòÉΓòÉ 5.7. Coming Up ΓòÉΓòÉΓòÉ
  2586.  
  2587. Coming Up 
  2588.  
  2589. I am running a little low on books at the moment, but there is relief in sight. 
  2590. Next month I will be looking at The GUI-OOUI War - Windows vs.  OS/2, Mandel. 
  2591. The following are the books I intend to review, in no particular order. 
  2592.  
  2593.      OS/2 Presentation Manager GPI, 2nd edition, Winn 
  2594.      OS/2 Unleashed, Moskowitz and Kerr 
  2595.      The Design of OS/2, 2nd Edition, Kogan and Deitel 
  2596.      Designing High Powered OS/2 Applications, Reich (tentative title) 
  2597.  
  2598.  I am considering reviewing the IBM OS/2 Redbooks, since they are readily and 
  2599.  cheaply available, and look like good reference. 
  2600.  
  2601.  If anyone has a book they want to see reviewed, I will be happy to oblige. 
  2602.  Just mail me and tell me.  Publishers can send me books at the address on my 
  2603.  personal page at the end of the magazine, and I will review all OS/2 
  2604.  development-related and advanced user books I receive. 
  2605.  
  2606.  /dev/EDM2/BookReview - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  2607.  
  2608.  
  2609. ΓòÉΓòÉΓòÉ 6. OOPS Avenue ΓòÉΓòÉΓòÉ
  2610.  
  2611.  
  2612. ΓòÉΓòÉΓòÉ 6.1. Introduction ΓòÉΓòÉΓòÉ
  2613.  
  2614. OOPS Avenue 
  2615.  
  2616. Written by Gordon Zeglinski 
  2617.  
  2618. A Look at What's New in SOM 2.1 
  2619.  
  2620. I finally got the SOM 2.1 toolkit.  In this issue, we'll look at its new 
  2621. features and then compare its bench mark results with those of SOM 2.0. 
  2622.  
  2623. Before we move on, let's look at some miscellaneous details.  I strongly 
  2624. recommend that anyone installing SOM 2.1 over SOM 2.0 edit their CONFIG.SYS 
  2625. files and then reboot OS/2 to disable the 2.0 toolkit before installing.  This 
  2626. will make installation much simpler and helps insure it works the first time. 
  2627. The installation is pretty much the same as that of the 2.0 toolkit.  Outside 
  2628. of a small install manual, the toolkit no longer contains any printed 
  2629. documentation.  The CD contains a set of PostScript files one can print to make 
  2630. manuals, and some FrameView files.  This is where I get really annoyed at IBM. 
  2631. They only ship a Windows and AIX version of the on-line documentation viewer! 
  2632. This means that you have to run WIN-OS/2.  Thus, losing all the resources it 
  2633. uses while running the memory hungry development tools.  This quickly makes 20 
  2634. megs of RAM look pretty small. 
  2635.  
  2636. OOPS Avenue - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  2637.  
  2638.  
  2639. ΓòÉΓòÉΓòÉ 6.2. What's New in SOM 2.1 ΓòÉΓòÉΓòÉ
  2640.  
  2641. What's New in SOM 2.1 
  2642.  
  2643. There are many new features in the 2.1 toolkit.  Overall I think a fair summary 
  2644. would be saying it's more C++ like but still retains all of the OOP abilities 
  2645. from the 2.0 version.  Direct To SOM (DTS) C++ compiler support is now included 
  2646. along with support for the 32 bit TCP/IP stack available in TCP/IP 2.0 for OS/2 
  2647. and the Internet Access Kit in Warp.  The use of somInit and somUninit is no 
  2648. longer recommended. Instead, a more C++ like method of doing initializers is 
  2649. recommended.  The methods somDefaultInit, somDefaultConstCopyInit and 
  2650. somDestruct have been added.  These methods correspond to the default 
  2651. constructor, copy constructor and destructor in C++.  In addition there's now 
  2652. support for an assignment method.  The somDefaultConstAssign method provides 
  2653. this mechanism and is equivalent to the operator = in C++. 
  2654.  
  2655. In our previous SOM articles, metaclasses were used to provide class data and 
  2656. constructors.  Recall that class data is analogous to static data in C++. SOM 
  2657. 2.1 now supports staticdata variables.  When this is combined with the new 
  2658. method of specifying constructors/initializers, metaclasses are rarely needed 
  2659. now.  The modifiers somallocate and somdeallocate allow user defined memory 
  2660. allocation and deallocation functions to be used when creating an instance of 
  2661. the object.  This is similar to overloading the new and delete operators in 
  2662. C++.  This concludes our brief look at what's new in SOM 2.1.  I haven't 
  2663. covered any of the new features in DSOM because we haven't previous looked at 
  2664. DSOM. 
  2665.  
  2666. OOPS Avenue - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  2667.  
  2668.  
  2669. ΓòÉΓòÉΓòÉ 6.3. Benchmarking SOM 2.1 ΓòÉΓòÉΓòÉ
  2670.  
  2671. Benchmarking SOM 2.1 
  2672.  
  2673. The SOM 2.1 documentation claims that SOM 2.1 is faster than SOM 2.0.  In this 
  2674. section, we'll present the revamped SOM bench mark code and compare the results 
  2675. of running the SOM 2.1 benchmark with those previous obtained for SOM 2.0 and 
  2676. C++. 
  2677.  
  2678. Let's start by looking at the new SOMBENCH.IDL file. 
  2679.  
  2680. #ifndef __SOMBENCH_h__
  2681. #define __SOMBENCH_h__
  2682.  
  2683. #include <somobj.idl>
  2684.  
  2685. //-----------------------------------
  2686. // SOMfoo
  2687. //   Our SOM based test object
  2688. //-----------------------------------
  2689.  
  2690.  
  2691. interface SOMFoo : SOMObject
  2692. {
  2693.  
  2694.    attribute long Count;          // Stores the Count Data
  2695.  
  2696.    void IncrementCount();        //procedure
  2697.    void OffIncrementCount();     //offset
  2698.    void LookIncrementCount();    //lookup
  2699.  
  2700.  
  2701. #ifdef __SOMIDL__
  2702. implementation
  2703. {
  2704.     releaseorder: _get_Count,_set_Count,IncrementCount,\
  2705.                   OffIncrementCount,LookIncrementCount;
  2706.  
  2707.     IncrementCount: procedure;
  2708.     LookIncrementCount:namelookup;
  2709.  
  2710.     callstyle=oidl;
  2711.     filestem = sombench;
  2712.     somDefaultInit: override;  // initialize instance
  2713. variables to 0
  2714.     somDestruct:override; // just for fun
  2715.  
  2716. };
  2717. #endif /* __SOMIDL__ */
  2718. };
  2719.  
  2720. #endif /* __SOMBENCH_h__ */
  2721.  
  2722. There's no major difference between this version and the previous version of 
  2723. sombench.  In fact, the only difference is that somInit and somUnitit have been 
  2724. replaced by somDefaultInit and somDestruct.  Even though the definition isn't 
  2725. much different, the bench mark results are different.  The following table 
  2726. shows the results of the benchmarks. 
  2727.  
  2728. ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
  2729. ΓöéResolution Type     ΓöéC++       ΓöéSOM 2.0   ΓöéSOM 2.1   Γöé
  2730. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2731. Γöéprocedure           Γöé219       Γöé656       Γöé844       Γöé
  2732. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2733. Γöévirtual/offset      Γöé281       Γöé719       Γöé1000      Γöé
  2734. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2735. Γöéname lookup macro   Γöén/a       Γöé22031     Γöé1031      Γöé
  2736. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2737. Γöéname lookup explicitΓöén/a       Γöé175031    Γöé130062    Γöé
  2738. ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
  2739.  
  2740. Table 1. Comparison of call type overhead between C++, SOM 2.0 and SOM 2.1 
  2741.  
  2742. The times indicated are in milliseconds for 1000000 calls to a given function. 
  2743. The results for SOM 2.1 show that there are some speed increases and some 
  2744. decreases.  In particular, the name lookup resolution method shows dramatic 
  2745. speed improvements. 
  2746.  
  2747. OOPS Avenue - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  2748.  
  2749.  
  2750. ΓòÉΓòÉΓòÉ 6.4. Profile Files Revisited ΓòÉΓòÉΓòÉ
  2751.  
  2752. Profile Files Revisited 
  2753.  
  2754. To explore the new initialization methods, we will once again look at the "ini 
  2755. file" manipulation object.  The definition for the revised SOM_CPPini file 
  2756. follows. 
  2757.  
  2758. #ifndef __SOMCPP_idl__
  2759. #define __SOMCPP_idl__
  2760.  
  2761. #include <somcls.idl>
  2762.  
  2763. //-----------------------------------
  2764. // SOMfoo
  2765. //   Our SOM based test object
  2766. //-----------------------------------
  2767.  
  2768. interface ProfileFile;
  2769.  
  2770. interface SOM_CPPini : SOMObject
  2771. {
  2772.    typedef unsigned long ULONG;
  2773.    typedef sequence<char> buffType;
  2774.  
  2775.  
  2776.    void OpenFile(in string FileName);
  2777.    ULONG GetError();
  2778.  
  2779.    unsigned long GetHandle();
  2780.    void SetHandle(in unsigned long H);
  2781.  
  2782.    void  WriteString(in string App,in string Key, in string String);
  2783.          // write a string
  2784.  
  2785.    void  WriteData(in string App, in string Key,in buffType Buff,
  2786.          in ULONG BLen);        // write the Buffer
  2787.  
  2788.    void  WriteInt(in string App,in string Key,in long Num);
  2789.          // write an "int"
  2790.  
  2791.  
  2792.  
  2793.    ULONG GetDataSize(in string App,in string Key);
  2794.          // return the lenght of the data
  2795.  
  2796.    void  GetData(in string App,in string Key,in buffType Buff,
  2797.          inout ULONG BufMax);     // return the data in Buff
  2798.  
  2799.    ULONG GetString(in string App,in string Key,in buffType Buff,
  2800.          in ULONG BufMax, in string DefArg);
  2801.          // return the String in Buff, defaults to DefArg if App/Key not
  2802.          // present
  2803.  
  2804.    ULONG GetStringNoDef(in string App,in string Key, in buffType Buff,
  2805.          in LONG BufMax);         // return the String in Buff
  2806.  
  2807.    long  GetInt(in string App,in string Key,in long DefArg);
  2808.          // return the "int" in App/Key or the DefArg if App/Key
  2809.          // not present
  2810.  
  2811.    long  GetIntNoDef(in string App,in string Key);
  2812.          // return the "int" in App/Key
  2813.  
  2814.    void ProfileFileFromName(inout somInitCtrl ctrl, in string name);
  2815.    void ProfileFileFromHandle(inout somInitCtrl ctrl, in ULONG H);
  2816.  
  2817. #ifdef __SOMIDL__
  2818. implementation
  2819. {
  2820.  
  2821.     ProfileFile INIFile;
  2822.  
  2823.     releaseorder: GetHandle,SetHandle,GetError,OpenFile, \
  2824.                   WriteString,WriteData,WriteInt,GetDataSize,GetData, \
  2825.                   GetString,GetStringNoDef,GetInt,GetIntNoDef, \
  2826.                   ProfileFileFromName,ProfileFileFromHandle;
  2827.  
  2828.     callstyle=oidl;
  2829.     filestem = somcpp;
  2830.     somDefaultInit: override;    // initialize instance variables to 0
  2831.     somDestruct:override;        // just for fun
  2832.     ProfileFileFromHandle:init;
  2833.     ProfileFileFromName:init;
  2834.  
  2835.     passthru C_xh_after=   "#define INCL_WINSHELLDATA" \
  2836.                            "#include <os2.h>" \
  2837.                            "#include \"iniflobj.h\"";
  2838.  
  2839. };
  2840. #endif /* __SOMIDL__ */
  2841. };
  2842.  
  2843. #endif /* __SOMBENCH_h__ */
  2844.  
  2845. Additions to the interface definition are marked in bold.  The revised 
  2846. definition has the functions ProfileFileFromName and ProfileFileFromHandle 
  2847. declared as initializers.  This allows us to use a more C++ like method of 
  2848. creating an instance of SOM_CPPini.  Following is the revised main routine for 
  2849. the object test file. 
  2850.  
  2851. #include "somcpp.xh"
  2852. #include <stdio.h>
  2853.  
  2854. void main(){
  2855.    int i;
  2856.  
  2857.    SOM_CPPini *Prf=new SOM_CPPini("test.ini");
  2858.  
  2859.    Prf->WriteInt("App","Key",10);
  2860.    i=Prf->GetIntNoDef("App","Key");
  2861.  
  2862.    printf("i= %i\r\n",i);
  2863.  
  2864.    delete Prf;
  2865. }
  2866.  
  2867. The new SOM_CPPini("test.ini") operation will create an instance of the SOM 
  2868. object SOM_CPPini and then call the initializer ProfileFileFromName.  The SOM 
  2869. compiler will map each SOM initializer on to a C++ constructor provided the 
  2870. arguments to the SOM methods are different.  Basically, this is the same rule 
  2871. that is imposed on C++ constructors; no two constructors can have the same 
  2872. arguments. 
  2873.  
  2874. OOPS Avenue - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  2875.  
  2876.  
  2877. ΓòÉΓòÉΓòÉ 6.5. Wrapping Things Up ΓòÉΓòÉΓòÉ
  2878.  
  2879. Wrapping Things Up 
  2880.  
  2881. We now come to the end of our peek at SOM 2.1.  The executables, which should 
  2882. run under WARP, and source code to these examples is included with this issue. 
  2883. We have briefly looked at the new features in SOM 2.1, benchmarked SOM 2.1 
  2884. using the simple test code, and then explored the new methods used to 
  2885. initialize SOM objects.  We have seen that many of the new features allow SOM 
  2886. objects to be more C++ like. 
  2887.  
  2888. In the next issue, I hope to start looking at DSOM.  I think DSOM/COBRA have 
  2889. the potential to radically alter the way client/server programming is done. 
  2890. For this reason, and my on going experience with programming INTERcomm and 
  2891. AdeptXBBS, the look at DSOM will be heavily skewed towards client/server type 
  2892. scenarios. 
  2893.  
  2894. OOPS Avenue - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  2895.  
  2896.  
  2897. ΓòÉΓòÉΓòÉ 7. Introduction to PM Programming ΓòÉΓòÉΓòÉ
  2898.  
  2899.  
  2900. ΓòÉΓòÉΓòÉ 7.1. Introduction ΓòÉΓòÉΓòÉ
  2901.  
  2902. Introduction to PM Programming 
  2903.  
  2904. Written by Larry Salomon, Jr. 
  2905.  
  2906. Introduction 
  2907.  
  2908. The purpose of this column is to provide the readers out there who are not 
  2909. familiar with PM application development the information necessary to satisfy 
  2910. their curiosity, educate themselves, and give them an advantage over the 
  2911. documentation supplied by IBM.  Of course, much of this stuff could probably be 
  2912. found in one of the many books out there, but the problem with books in general 
  2913. is that they don't answer the questions you have after you read the book the 
  2914. first time through. 
  2915.  
  2916. I will gladly entertain feedback from the readers about what was "glossed over" 
  2917. or what was detailed well, what tangential topics need to be covered and what 
  2918. superfluous crap should have been removed.  This feedback is essential in 
  2919. guaranteeing that you get what you pay for.  <grin> 
  2920.  
  2921. It should be said that you must not depend solely on this column to teach you 
  2922. how to develop PM applications; instead, this should be viewed as a supplement 
  2923. to your other information storehouses (books, the network conferences, etc.). 
  2924. Because this column must take a general approach, there will be some topics 
  2925. that you would like to see discussed that really do not belong here.  Specific 
  2926. questions can be directed to me via email and I will do my best to answer them 
  2927. in a timely fashion. 
  2928.  
  2929. Last Month 
  2930.  
  2931. Last month, we finished the WC_LISTBOX class and I asked for feedback.  I did 
  2932. receive some, for which I am very grateful, but the majority of them were 
  2933. requests for advanced topics, like the container and the notebook controls.  We 
  2934. will eventually cover these window classes, but it is essential that we look at 
  2935. the rudimentary ones first, so that you can better appreciate what these 
  2936. advanced classes do for you. 
  2937.  
  2938. This month, we will look at the WC_TITLEBAR and WC_STATIC window classes. 
  2939. Since both of these classes are not too involving, covering them both should 
  2940. not be a problem. 
  2941.  
  2942. Introduction to PM Programming - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  2943.  
  2944.  
  2945. ΓòÉΓòÉΓòÉ 7.2. The Titlebar ΓòÉΓòÉΓòÉ
  2946.  
  2947. The Titlebar 
  2948.  
  2949. The titlebar class (WC_TITLEBAR) is usually not created directly, but is 
  2950. created for you by the frame window through the WinCreateStdWindow() call.  It 
  2951. has two primary functions:  1) to display the text of the standard window to 
  2952. which it belongs, and 2) to act as a shortcut to two primary windowing 
  2953. functions, move and maximize/restore. 
  2954.  
  2955. The titlebar does accept two messages:  TBM_SETHILITE and TBM_QUERYHILITE. 
  2956.  
  2957. TBM_SETHILITE 
  2958.  
  2959. This message sets the highlight state of the control. 
  2960.  
  2961. Parameters 
  2962.  
  2963. SHORT1FROMMP(mpParm1) - the new highlight state of the control. 
  2964.  
  2965.  TRUE      Draw the control in highlighted state. 
  2966.  FALSE     Draw the control in normal state. 
  2967.  
  2968.  Returns 
  2969.  
  2970.  SHORT1FROMMR() - success indicator. 
  2971.  
  2972.  TRUE      Successful completion. 
  2973.  FALSE     Error occurred. 
  2974.  
  2975.  TBM_QUERYHILITE 
  2976.  
  2977.  This message returns the highlight state of the control. 
  2978.  
  2979.  Returns 
  2980.  
  2981.  SHORT1FROMMR() - highlight state of the control. 
  2982.  
  2983.  TRUE      The control is drawn in highlighted state. 
  2984.  FALSE     The control is drawn in normal state. 
  2985.  
  2986.  Highlight state is used to indicate that the window has the focus. Normally, 
  2987.  you do not need to use these messages, since the frame control will use them 
  2988.  on your behalf. 
  2989.  
  2990.  To set or query the text displayed in the titlebar, use WinSetWindowText() and 
  2991.  WinQueryWindowText().  Again, the frame makes this easy by setting or querying 
  2992.  it for you whenever you set or query the frame window's text. 
  2993.  
  2994.  For reference, it should be noted that, whenever the user attempts to move or 
  2995.  maximize/restore the window using the titlebar, the titlebar sends its owner 
  2996.  (the frame usually) a WM_QUERYTRACKINFO message.  The owner can fill in this 
  2997.  structure according to its needs to restrict movement or resizing as 
  2998.  necessary.  We will not discuss this further. 
  2999.  
  3000.  That's all there is to the WC_TITLEBAR class. 
  3001.  
  3002.  Introduction to PM Programming - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  3003.  
  3004.  
  3005. ΓòÉΓòÉΓòÉ 7.3. The Static ΓòÉΓòÉΓòÉ
  3006.  
  3007. The Static 
  3008.  
  3009. The static control is just that:  static.  It never changes.  The point of the 
  3010. static control is to display text, rectangles, icons, bitmaps, etc.  A table of 
  3011. the static styles is shown below: 
  3012.  
  3013.  Style                         Results in 
  3014.  SS_AUTOSIZE                   Automatically size to contain the text, icon, or 
  3015.                                bitmap 
  3016.  SS_BITMAP                     Bitmap 
  3017.  SS_BKGNDFRAME                 Unfilled rectangle painted with the background 
  3018.                                color 
  3019.  SS_BKGNDRECT                  Filled rectangle painted with the background 
  3020.                                color 
  3021.  SS_FGNDFRAME                  Unfilled rectangle painted with the foreground 
  3022.                                color 
  3023.  SS_FGNDRECT                   Filled rectangle painted with the foreground 
  3024.                                color 
  3025.  SS_GROUPBOX                   Groupbox 
  3026.  SS_HALFTONERECT               Filled rectangle painted with a halftoned color 
  3027.  SS_HALFTONEFRAME              Unfilled rectangle painted with a halftoned 
  3028.                                color 
  3029.  SS_ICON                       Icon 
  3030.  SS_SYSICON                    System icon 
  3031.  SS_TEXT                       Text 
  3032.  
  3033.  All but the first style are mutually exclusive.  The first style can only be 
  3034.  used with SS_TEXT, SS_BITMAP, SS_ICON, or SS_SYSICON.  Additionally, if 
  3035.  SS_TEXT is used, you may also or one of the constants from each of the 
  3036.  following groups to align the text in a specific manner. 
  3037.  
  3038.  Horizontal alignment 
  3039.  
  3040.  Constant            Alignment 
  3041.  DT_LEFT             Left aligned 
  3042.  DT_CENTER           Center aligned 
  3043.  DT_RIGHT            Right aligned 
  3044.  
  3045.  Vertical alignment 
  3046.  
  3047.  Constant            Alignment 
  3048.  DT_TOP              Top aligned 
  3049.  DT_VCENTER          Center aligned 
  3050.  DT_BOTTOM           Bottom aligned 
  3051.  
  3052.  Window text for SS_TEXT statics can be set and queried using the 
  3053.  WinSetWindowText() and WinQueryWindowText() functions. They accept no 
  3054.  messages, nor send any messages. 
  3055.  
  3056.  Using SS_ICON, SS_BITMAP, and SS_SYSICON statics 
  3057.  
  3058.  When creating one of these three types of static controls, the question arises 
  3059.  about how one specifies the icon, bitmap, or system icon to display. The 
  3060.  answer is in the window text.  For the first two, the text must be in the form 
  3061.  "#n" where n is the ASCII representation of the resource identifier of the 
  3062.  icon or bitmap to be displayed.  A noteworthy caveat is that the icon or 
  3063.  bitmap must reside in the .EXE resources; this can create problems if you're 
  3064.  writing a general purpose DLL that cannot make assumptions about the .EXE 
  3065.  resources.  For the last one, the number represents the ASCII representation 
  3066.  of the appropriate SPTR_* constant corresponding to the system icon that you 
  3067.  wish to be displayed. 
  3068.  
  3069.  Opportunities for Mayhem 
  3070.  
  3071.  Because the static control does nothing except "just sit there," there are 
  3072.  plenty of opportunities to create quick-n- dirty solutions to problems by 
  3073.  creating a static window and then immediately subclassing it with the intent 
  3074.  of enhancing its behavior. 
  3075.  
  3076.  Introduction to PM Programming - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  3077.  
  3078.  
  3079. ΓòÉΓòÉΓòÉ 7.4. That's It! ΓòÉΓòÉΓòÉ
  3080.  
  3081. That's It! 
  3082.  
  3083. That's it for this month.  Next month, we'll begin looking at the WC_MENU 
  3084. window class and we will see how it has become a staple in the bag of tricks 
  3085. that every OS/2 programmer has.  Again, I will gladly accept any feedback that 
  3086. you have regarding this column and its future. 
  3087.  
  3088. Introduction to PM Programming - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  3089.  
  3090.  
  3091. ΓòÉΓòÉΓòÉ 8. Contributors to this Issue ΓòÉΓòÉΓòÉ
  3092.  
  3093. Are You a Potential Author? 
  3094.  
  3095. We are always looking for (new) authors.  If you have a topic about which you 
  3096. would like to write, send a brief description of the topic electronically to 
  3097. any of the editors, whose addresses are listed below, by the 15th of the month 
  3098. before the month in which your article will appear.  This alerts us that you 
  3099. will be sending an article so that we can plan the issue layout accordingly. 
  3100. After you have done this, get the latest copy of the Article Submission 
  3101. Guidelines from ftp.cdrom.com in the /pub/os2/2_x/program/newsltr directory. 
  3102. (The file is artsub.zip.)  The completed text of your article should be sent to 
  3103. us no later than five days prior to the last day of the month; any articles 
  3104. received after that time may be pushed to the next issue. 
  3105.  
  3106. The editors can be reached at the following email addresses: 
  3107.  
  3108.      Larry Salomon - os2man@panix.com (Internet). 
  3109.      Carsten Whimster - bcrwhims@undergrad.math.uwaterloo.ca (Internet). 
  3110.  
  3111.  The following people contributed to this issue in one form or another (in 
  3112.  alphabetical order): 
  3113.  
  3114.      Marc Mittelmeijer 
  3115.      Larry Salomon, Jr. 
  3116.      Eric Slaats 
  3117.      Carsten Whimster 
  3118.      Johan Wikman 
  3119.      Gordon Zeglinski 
  3120.      Network distributors 
  3121.  
  3122.  Contributors - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  3123.  
  3124.  
  3125. ΓòÉΓòÉΓòÉ 8.1. Marc Mittelmeijer ΓòÉΓòÉΓòÉ
  3126.  
  3127. Marc Mittelmeijer 
  3128.  
  3129. Marc Mittelmeijer studied mathematics at the faculty Mathematics and 
  3130. Informatics at the Technical University of Eindhoven.  Since 1984 Marc teaches 
  3131. mathematics and information technology at the Hogeschool Eindhoven, Faculteit 
  3132. Economie Sr Bestuurlijke informatiekunde.  Besides teaching he is researching 
  3133. the behavior of neural networks in a financial environment. 
  3134.  
  3135. Marc can be reached at M.Mittelmeijer@fe.hse.nl 
  3136.  
  3137. Contributors - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  3138.  
  3139.  
  3140. ΓòÉΓòÉΓòÉ 8.2. Larry Salomon, Jr. ΓòÉΓòÉΓòÉ
  3141.  
  3142. Larry Salomon Jr. 
  3143.  
  3144. Larry Salomon Jr. has been developing OS/2 applications since version 1.1 in 
  3145. 1989.  He has written numerous applications, including the Scramble applet that 
  3146. was included in OS/2 versions 2.0-2.11, and the I-Brow, Magnify, and Screen 
  3147. Capture trio that has been distributed on numerous CD-ROMs. 
  3148.  
  3149. Larry is also the coauthor of the successful book, The Art of OS/2 2.1 C 
  3150. Programming (Wiley-QED).  Finally, he is the CEO/President of IQPac Inc. which 
  3151. is responsible for the publishing of EDM/2 and he is a frequent contributor to 
  3152. the publication. 
  3153.  
  3154. Larry can be reached electronically via the Internet at os2man@panix.com. 
  3155.  
  3156. Contributors - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  3157.  
  3158.  
  3159. ΓòÉΓòÉΓòÉ 8.3. Eric Slaats ΓòÉΓòÉΓòÉ
  3160.  
  3161. Eric Slaats 
  3162.  
  3163. Eric Slaats teaches information technology at the Hogeschool Eindhoven, 
  3164. Faculteit Economie Sr Bestuurlijke Informatiekunde.  Besides teaching he is 
  3165. researching the behavior of neural networks in a financial environment.  He 
  3166. started programming OS/2 PM to build an interface for a neural network problem. 
  3167.  
  3168. Eric can be reached electronically via the internet at E.Slaats@fe.hse.nl 
  3169.  
  3170. Contributors - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  3171.  
  3172.  
  3173. ΓòÉΓòÉΓòÉ 8.4. Carsten Whimster ΓòÉΓòÉΓòÉ
  3174.  
  3175. Carsten Whimster 
  3176.  
  3177. Carsten is an undergraduate Computer Science student at the University of 
  3178. Waterloo.  He is currently in third year, and is enjoying it immensely.  He 
  3179. uses Watcom C/C++ 10.0 and Watcom VX-REXX 2.0b. 
  3180.  
  3181. Carsten is the author of some commandline utilities and POV-Panel/2, a popular 
  3182. shareware dashboard-like front-end for the POV-Ray 2.x compilers. POV-Panel/2 
  3183. can be found on ftp-os2.cdrom.com in pub/os2/32bit/graphics and some of the 
  3184. other major OS/2 ftp sites.  He is also a TEAM-OS/2 member, and has adopted a 
  3185. little computer store called The Data Store in Waterloo, Ontario. 
  3186.  
  3187. You may reach Carsten... 
  3188.  
  3189. ...via email: 
  3190.  
  3191. bcrwhims@undergrad.math.uwaterloo.ca - Internet 
  3192.  
  3193. ...Web homepage (I am just setting it up, so it may or may not be on-line): 
  3194.  
  3195. http://www.undergrad.math.uwaterloo.ca/~bcrwhims  - WWW 
  3196.  
  3197. ...via snail mail (notice the changed address): 
  3198.  
  3199. Carsten Whimster
  3200. 318A Spruce Street
  3201. Waterloo, Ontario
  3202. Canada
  3203. N2L 3M7
  3204.  
  3205. Contributors - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  3206.  
  3207.  
  3208. ΓòÉΓòÉΓòÉ 8.5. Johan Wikman ΓòÉΓòÉΓòÉ
  3209.  
  3210. Johan Wikman 
  3211.  
  3212. Johan Wikman received his Master's degree in Computer Science with a thesis 
  3213. "Adding remote execution capability to operating systems".  He has been 
  3214. programming for OS/2, using C++, ever since 1990. 
  3215.  
  3216. Currently he works for Nokia Telecommunications where he takes part in a 
  3217. project that developes network management software in a Unix environment.  In 
  3218. his sparetime he continues working on RMX-OS2, a system that provides remote 
  3219. execution for OS/2. 
  3220.  
  3221. Johan can be reached electronically via the Internet at 
  3222. johan.wikman@ntc.nokia.com, or via ordinary mail: 
  3223.  
  3224. Johan Wikman
  3225. Smedjeviksvagen 23 B 22
  3226. FI-00200 Helsinki
  3227. FINLAND
  3228.  
  3229. Contributors - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  3230.  
  3231.  
  3232. ΓòÉΓòÉΓòÉ 8.6. Gordon Zeglinski ΓòÉΓòÉΓòÉ
  3233.  
  3234. Gordon Zeglinski 
  3235.  
  3236. Gordon Zeglinski is a freelance programmer/consultant who received his Master's 
  3237. degree in Mechanical Engineering with a thesis on C++ sparse matrix objects. 
  3238. He has been programming in C++ for 6 years and also has a strong background in 
  3239. FORTRAN.  He started developing OS/2 applications with version 2.0 . 
  3240.  
  3241. His current projects include a client/server communications program that 
  3242. utilitizes OS/2's features which has entered beta testing.  Additionally, he is 
  3243. involved in the development of a "real-time" automated vehicle based on OS/2 
  3244. and using C++ in which he does device driver development and designs the 
  3245. applications that comprise the control logic and user interface. 
  3246.  
  3247. He can be reached via the Internet at zeglins@cc.umanitoba.ca. 
  3248.  
  3249. Contributors - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  3250.  
  3251.  
  3252. ΓòÉΓòÉΓòÉ 8.7. Network distributors ΓòÉΓòÉΓòÉ
  3253.  
  3254. Network Distributors 
  3255.  
  3256. These people are part of our distribution system to provide EDM/2 on networks 
  3257. other than the Internet.  Their help to provide access to this magazine for 
  3258. others is voluntary and we appreciate them a lot! 
  3259.  
  3260.      Paul Hethmon (hethmon@apac.ag.utk.edu) - Compuserve 
  3261.      Gess Shankar (gess@knex.mind.org) - Internet 
  3262.      Jason B. Tiller (PeerGynt@aol.com) - America On-line 
  3263.      David Singer (singer@almaden.ibm.com) - IBM Internal 
  3264.      Andre Asselin (ASSELIN AT RALVM12) - IBM Internal 
  3265.  
  3266.  If you would like to become a "network distributor", be sure to contact the 
  3267.  editors so that we can give you the credit you deserve! 
  3268.  
  3269.  Contributors - EDM/2 - Feb 1995 - Volume 3, Issue 2 
  3270.  
  3271.  
  3272. ΓòÉΓòÉΓòÉ 9. How Do I Get EDM/2? ΓòÉΓòÉΓòÉ
  3273.  
  3274. How Do I Get EDM/2? 
  3275.  
  3276. EDM/2 can be obtained in any of the following ways: 
  3277.  
  3278. On the Internet 
  3279.  
  3280.      All back issues are available via anonymous FTP from the following sites: 
  3281.         -  ftp.cdrom.com in the /pub/os2/2_x/program/newsltr directory. 
  3282.         -  ftp.luth.se in the /pub/os2/programming/newsletter directory. 
  3283.         -  generalhq.pc.cc.cmu.edu in the /pub/newsletters/edm2 directory. 
  3284.      The EDM/2 mailing list.  Send an empty message to edm2-info@knex.mind.org 
  3285.       to receive a file containing (among other things) instructions for 
  3286.       subscribing to EDM/2.  This is a UUCP connection, so be patient please. 
  3287.      IBM's external gopher/WWW server in Almaden. The address is 
  3288.       index.almaden.ibm.com and it is in the "Non-IBM-Originated" submenu of 
  3289.       the "OS/2 Information" menu; the URL is 
  3290.       "gopher://index.almaden.ibm.com/1nonibm/os2nonib.70". 
  3291.  
  3292.  On Compuserve 
  3293.  
  3294.  All back issues are available in the OS/2 Developers Forum 2. 
  3295.  
  3296.  IBM Internal 
  3297.  
  3298.      IBM's internal gopher/WWW server in Almaden. The address is 
  3299.       n6tfx.almaden.ibm.com and it is in the "Non-IBM-Originated Files" menu; 
  3300.       the URL is "gopher://n6tfx.almaden.ibm.com/1!!nonibm/nonibm.70". 
  3301.      IBM's REQUEST command on all internal VM systems.  Enter the VM command 
  3302.       REQUEST LIST FROM ASSELIN AT RALVM12 and a list of the requestable 
  3303.       packages will be sent to you; in this list are the names of the packages 
  3304.       containing the EDM/2 issues. 
  3305.  
  3306.  How do I Get EDM/2? - EDM/2 - Feb 1995 - Volume 3, Issue 2