home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 32 Periodic / 32-Periodic.zip / edmi2-3.zip / EDMI2-3.INF (.txt) < prev    next >
OS/2 Help File  |  1994-03-06  |  250KB  |  3,376 lines

  1.  
  2. ΓòÉΓòÉΓòÉ 1. March 1994's Title Page ΓòÉΓòÉΓòÉ
  3.  
  4.           Welcome to EDM/2 - The Electronic OS/2 Developer's Magazine!
  5.                    Portions copyright (c) by Larry Salomon Jr.
  6.                                 Volume 2, issue 3
  7.  
  8. Copyright Notice and Other Stuff 
  9.  
  10. The editor of this electronic magazine is Larry Salomon, Jr. 
  11.  
  12. Portions of EDM/2 are copyrighted by the editors.  This publication may be 
  13. freely distributed in electronic form provided that all parts are present in 
  14. their original unmodified form.  A reasonable fee may be charged for the 
  15. physical act of distribution; no fee may be charged for the publication itself. 
  16.  
  17. All articles are copyrighted by their authors. No part of any article may be 
  18. reproduced without permission from the original author. 
  19.  
  20. Neither this publication nor the editors are affiliated with International 
  21. Business Machines Corporation. 
  22.  
  23. OS/2 is a registered trademark of International Business Machines Corporation. 
  24. Other trademarks are property of their respective owners.  Any mention of a 
  25. product in this publication does not constitute an endorsement or affiliation 
  26. unless specifically stated in the text. 
  27.  
  28. Administrivia 
  29.  
  30. First of all, I would like to say Happy Anniversary!!! to EDM/2.  Yes, it has 
  31. indeed been one year since Steve Luzynski published the first issue.  We have 
  32. come a long way since then, but we can go a lot further, as long as we always 
  33. have your continued support. Our success has not been only due to the hard work 
  34. of the many authors, but to the readers who have given us all feedback on the 
  35. good and bad points of the magazine.  A hearty thanks to you all(!) and many 
  36. more years for EDM/2 to come! 
  37.  
  38. Prizes, Prizes, Who has the Prizes? 
  39.  
  40. I finally received the copies of Vis-Pro/REXX Bronze Edition from HockWare, 
  41. Inc. (thanks again), which were mailed out.  Since Gavin Baker never contacted 
  42. me after my mail to him bounced, I decided to award the 3rd Place prize to the 
  43. next person down the list who wasn't me and didn't already receive a prize. 
  44. Thus, the winner is... 
  45.  
  46. (drum roll please) 
  47.  
  48. Raja Thiagaraian, with The Unofficial Guide to the Palette Manager, published 
  49. in volume 1, issue 1.  I have sent him email about this and he has accepted the 
  50. prize.  Many congratulations to you, Raja! 
  51.  
  52. New York City C++ SIG 
  53.  
  54. This went very well; it was nice to meet a few of the readers, even if they 
  55. didn't figure out that it was me who plugged the magazine in the announcements 
  56. (who did you think it was, anyway? :) .  I think the plug picked up a few more 
  57. subscribers and/or ftp-ers. 
  58.  
  59. My next "stop" will hopefully be at the IBM Technical Interchange in San 
  60. Fransisco, CA at the end of March.  In case you can be there, too, a bunch of 
  61. people (including me) are planning a food fight (just kidding) at a local 
  62. restaurant or something like that.  Let me know if you want to join us and have 
  63. some fun. 
  64.  
  65. Official Announcement 
  66.  
  67. I posted this on comp.os.os2.programmer.misc, but it should go here also.  This 
  68. month sees the first installment of our new column - Book Review (yes, the 
  69. title is lame, but we can't think of anything better).  The author is Carsten 
  70. Whimster (that is indeed his real name.  Ask him for a complete history that 
  71. goes back to the Vikings, if you're interested :) and he has a pretty ambitious 
  72. job - review a new development or very technical book each month; of course, 
  73. the books will be OS/2 related.  I think you will like this one. 
  74.  
  75. Guess the Graphic 
  76.  
  77. Since I only received one email guess about last month's graphic for the 
  78. communications category (what do I have to do to get a decent response rate? 
  79. "If you order now, we'll send you this free thingymajig that slices, dices, 
  80. pares, and even writes Workplace Shell objects!" :), the answers were a socket, 
  81. named pipe (named Larry, no less), and a token ring.  Ba-doom tssh! 
  82.  
  83. Workplace Shell 
  84.  
  85. Last month I said that I would try to have an introductory WPS article for this 
  86. issue.  Unfortunately, immediately following the issue's release, things got 
  87. really hairy at work, so I never could finish my class development. 
  88.  
  89. The good news is that someone else decided to write such an article, which is 
  90. in this issue.  Enjoy, and thanks to Bj╨ñrn for his submission. 
  91.  
  92. One More Time 
  93.  
  94. And speaking of Bj╨ñrn, in his article submission which he sent through the mail 
  95. was an .INF file.  I usually look at these to get an idea of how much rework 
  96. was necessary.  "Hmm...He did the 'duplicated introduction' thing that I had so 
  97. much trouble with.  But let me try Alt+F anyways."  Much to my surprise, it did 
  98. not display the introduction twice! 
  99.  
  100. The net result is that, if you put no text after the heading for the article, 
  101. the compiler issues a warning message, but when you display the article 
  102. heading, it automatically jumps to the next heading with text.  I quickly 
  103. adopted this trick in this issue. 
  104.  
  105.  
  106. ΓòÉΓòÉΓòÉ 2. Features for March 1994 ΓòÉΓòÉΓòÉ
  107.  
  108. The following articles constitute this issue's features: 
  109.  
  110. o Making Noise with MMPM/2 - Part 2 
  111. o Porting STEP02 to ICLUI 
  112. o Workplace Shell Development 101 
  113.  
  114.  
  115. ΓòÉΓòÉΓòÉ 2.1. Making Noise with MMPM/2 - Part 2 ΓòÉΓòÉΓòÉ
  116.  
  117.  
  118. ΓòÉΓòÉΓòÉ 2.1.1. Introduction ΓòÉΓòÉΓòÉ
  119.  
  120. Written by Marc van Woerkom 
  121.  
  122. Introduction 
  123.  
  124. This is the second article on MMPM/2 programming.  It was originally planned to 
  125. be the final one, but two reasons forced me to extend this series: 
  126.  
  127.  1. Semir Patel's fine article in volume 2, issue 1 of EDM/2 already touched 
  128.     most topics I intended to present here, blasting my concept.  (Watch out 
  129.     for my upcoming DOOM-style game, featuring a certain editor :-) 
  130.  
  131.  2. There is so much to cover about MMPM/2 that two parts are simply not 
  132.     enough. 
  133.  
  134.  
  135. ΓòÉΓòÉΓòÉ 2.1.2. Limitations of Using REXX ΓòÉΓòÉΓòÉ
  136.  
  137. Limitations of Using REXX 
  138.  
  139. When I delved further into MMPM/2 programming it became clear to me that the 
  140. features accessible from the REXX Media Control Interface (MCI) are very 
  141. powerful but that there is still more to see, much like the part of an iceberg 
  142. lying under water.  After all, let's face the music (pun intended); MMPM/2 is 
  143. written in C meaning all of the APIs will be accessible from C. The only APIs 
  144. provided for REXX are: 
  145.  
  146. o mciSendString (mciRxSendString) 
  147.  
  148. o mciGetErrorString (mciRxGetErrorString) 
  149.  
  150. o mciGetDeviceID (mciRxGetDeviceID) 
  151.  
  152. Compare this with the near 200 MMPM/2 API calls accessible through C/C++. 
  153.  
  154. To be fair, it should be possible to close the gaps since VX-REXX 2.0 from 
  155. Watcom seems to have multimedia support.  However, one must stick to C/C++ if 
  156. one wants... 
  157.  
  158. o to get complex feedback (be it a whole structure of information, or 
  159.   synchronization messages) 
  160. o to use the basic Multimedia I/O subsystem (MMIO API) 
  161. o to control streaming and synchronization through the SPI subsystem 
  162. o to use the PM extensions provided by MMPM/2 (i.e. new graphical controls and 
  163.   secondary window support) 
  164.  
  165. This doesn't even include esoteric business like driver development. 
  166. ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇ 
  167.  
  168. After all of this bashing on poor REXX, let's close this section with the 
  169. biggest advantage of REXX: 
  170.  
  171. o It's pretty easy to use and provides a great introduction!  :-) 
  172.  
  173. Please read the first part of this series for more information on this topic. 
  174.  
  175.  
  176. ΓòÉΓòÉΓòÉ 2.1.3. Preliminary Thoughts ΓòÉΓòÉΓòÉ
  177.  
  178. Preliminary Thoughts 
  179.  
  180. Before you start MMPM/2 programming, you need a C or C++ compiler and MMPM/2 
  181. bindings.  I recommend using either: 
  182.  
  183. o one of the IBM compilers (C Set++, Firststep) together with the Developer's 
  184.   Toolkit and the MMPM/2 Toolkit (which has been included in the Developer's 
  185.   Toolkit since the 2.1 release) 
  186. o or, the EMX port of the GNU GCC C/C++/Objective C compiler (revisions 0.8h 
  187.   and 2.5.8 or later) together with the MM4EMX package (revision 1.0). 
  188.  
  189. Although this should suffice to recompile the examples given, and you'll get a 
  190. lot of information from here, you still should acquire some of the literature 
  191. mentioned at the end of the first part of this series and at the end of this 
  192. part. 
  193.  
  194. Another great source of information are the computer networks.  Often you can 
  195. contact someone on the Internet news groups or CompuServe fora who can help you 
  196. or share thoughts with you. 
  197.  
  198. For those with Usenet access I recommend comp.os.os2.multimedia, or (if you 
  199. somehow feel an urge to polish your German a little bit :-) maus.os2.prog. 
  200.  
  201. So much for the basic preparations.  (Hmm...did I already tell you to install 
  202. MMPM/2?  :-) 
  203.  
  204.  
  205. ΓòÉΓòÉΓòÉ 2.1.4. Multimedia Programming Using EMX ΓòÉΓòÉΓòÉ
  206.  
  207. Multimedia Programming Using EMX 
  208.  
  209. To state it shortly - it's no problem to do any MMPM/2 programming with EMX. 
  210.  
  211. As an EMX user you have the choice between using the OS/2 and MMPM/2 headers 
  212. and libraries from the IBM Toolkits or using those of the EMX and MM4EMX 
  213. packages.  Both have their strengths and weaknesses. However, this article 
  214. series should feature nothing which is specific to one of the above 
  215. combinations, or even the EMX system. 
  216.  
  217. The MM4EMX Package 
  218.  
  219. The Multimedia for EMX package (MM4EMX) is a freeware package for the EMX 
  220. development environment.  It contains all necessary 32-bit header files and 
  221. import libraries in the BSD .A and Intel OMF .LIB formats together with source 
  222. code. 
  223.  
  224. The samples presented and explained in this part of the article series are the 
  225. ones from MM4EMX (release 1.0).  As a side note, please contact the author if 
  226. you should note incompatibilities. 
  227.  
  228.  
  229. ΓòÉΓòÉΓòÉ 2.1.5. The Textual Media Control Interface ΓòÉΓòÉΓòÉ
  230.  
  231. The Textual Media Control Interface 
  232.  
  233. The easiest way to use the MCI is via its textual interface, using the 
  234. mciSendString() API call.  Below is an example of this. 
  235.  
  236. #
  237. # bach.mci -- play the Bach MIDI file
  238. #
  239. open Sequencer alias midi2 shareable wait
  240. info midi2 product wait
  241. set midi2 time format mmtime wait
  242. load midi2 c:\mmos2\sounds\bach.mid wait
  243. status midi2 length wait
  244. play midi2 wait
  245. close midi2
  246.  
  247. It opens the Sequencer (MIDI) multimedia device, giving it the alias "midi2". 
  248. The product info is queried and mmtime is chosen as timeformat.  Then a MIDI 
  249. file from Bach is loaded into the context of the device, its length is queried 
  250. and it gets played.  Finally the device is closed. 
  251.  
  252. The following example will take a file of MCI command strings and sends them 
  253. line by line for execution to the MDM via mciSendString(), thus interpreting 
  254. the file. 
  255.  
  256. //////////////////////////////////////////
  257. // mci -- an interpreter for MCI scripts
  258. //
  259. // using emx 0.8h, mm4emx 1.0
  260. //
  261. //     based on a C program by
  262. //     John J. McDonough
  263. //
  264. // Marc E.E. van Woerkom, 2/94
  265. //
  266.  
  267.  
  268. #include <iostream.h>
  269. #include <fstream.h>
  270.  
  271. #include <os2.h>
  272.  
  273. One has to define INCL_OS2MM to include the proper definitions and 
  274. declarations, i.e. the 32-bit version of the MMPM/2 API with naming conventions 
  275. conforming to OS/2. 
  276.  
  277. A defined INCL_MCIOS2 will include the MCI support. 
  278.  
  279. #define  INCL_OS2MM
  280. #define  INCL_MCIOS2
  281. #include <os2me.h>
  282.  
  283.  
  284. //
  285. // this sample demonstrates the following MMPM/2 API's:
  286. //
  287. //     mciSendString()      send a MCI command string, receive an answer
  288. //     mciGetErrorString()  look up the error string for the error code
  289. //
  290.  
  291. int main(int argc, char* argv[])
  292. {
  293.     if ( argc != 2 ) {
  294.         cerr << "usage: mci <filename>\n";
  295.         return 1;
  296.     }
  297.  
  298.     ifstream infile(argv[1]);
  299.     if (!infile) {
  300.         cerr << "error: can't open input file " << argv[1] << '\n';
  301.         return 2;
  302.     }
  303.  
  304.     int line = 0;
  305.  
  306.     const bsize = 128;
  307.     char buff[bsize];
  308.  
  309.     while (!infile.eof()) {
  310.         char c = infile.peek();  // peek one char forward
  311.  
  312.         if (infile.good()) {
  313.  
  314.             infile.get(buff, bsize, '\n');
  315.  
  316.             cout.width(3);
  317.             cout << ++line << ": [" << buff << "]\n";
  318.  
  319.             if (c != '#') {
  320.  
  321. Now we finally got a non-comment line into the variable buff. 
  322.  
  323. The next thing to do is to provide a return buffer for mciSendString(), which 
  324. has to be empty. 
  325.  
  326.    const rsize = 128;
  327.    char rbuff[rsize];
  328.    for (int i=0; i<rsize; i++) rbuff[i] = 0;
  329.  
  330.    ULONG rc = mciSendString(buff,   // buffer with MCI string
  331.                             rbuff,  // return buffer
  332.                             rsize,  // rbuff size
  333.                             0,      // no callback window handle
  334.                             0);     // no user parameter
  335.  
  336.    if (rc == MCIERR_SUCCESS) {
  337.        if (rbuff[0])
  338.            cout << "      -> " << rbuff << "\n\n";
  339.    }
  340.    else {
  341.  
  342. The return code wasn't MCIERR_SUCCESS, so something strange happened.  Because 
  343. an error code is not too enlightening, we use mciGetErrorString() to get a nice 
  344. string. 
  345.  
  346.   ULONG rc2 = mciGetErrorString(rc,      // error code
  347.                                 rbuff,   // return buffer
  348.                                 rsize);  // rbuff size
  349.  
  350. In case the rc is out of range we give up and display the number. 
  351.  
  352.                     if (rc2 == MCIERR_SUCCESS)
  353.                         cerr << "      -> MCI error: " << rbuff << "\n\n";
  354.                     else
  355.                         cerr << "      -> error #" << rc << " has occured!\n\n";
  356.                 }
  357.             }
  358.         }
  359.  
  360.         infile.get(c);  // eat the \r after the \n
  361.     }
  362.  
  363.     return 0;
  364. }
  365.  
  366. All of this is pretty straight forward.  Now we have a tool to try out MCI 
  367. command strings quick and easy.  Below are some other MCI scripts: 
  368.  
  369. #
  370. # boing.mci -- play boing.wav
  371. #
  372. open waveaudio alias wave shareable wait
  373. load wave c:\mmos2\sounds\boing.wav wait
  374. play wave wait
  375. close wave wait
  376.  
  377. This example shows how the audio CD player applet recognizes CDs, it checks for 
  378. the 8 byte long ID: 
  379.  
  380. #
  381. # playcd.mci -- play an audio CD
  382. #
  383. open cdaudio01 alias cdda shareable wait
  384. status cdda media present wait
  385. status cdda mode wait
  386. set cdda time format tmsf wait
  387. status cdda volume wait
  388. status cdda number of tracks wait
  389. status cdda length wait
  390. status cdda type track 1 wait
  391. # check unique ID (8 bytes)
  392. info cdda ID wait
  393. # check unique UPC (bcd number)
  394. info cdda UPC wait
  395. seek cdda to start wait
  396. # this provokes an error, for there is no window to notify
  397. play cdda notify
  398. play cdda wait
  399. close cdda wait
  400.  
  401.  
  402. ΓòÉΓòÉΓòÉ 2.1.6. The Procedural Media Control Interface ΓòÉΓòÉΓòÉ
  403.  
  404. The Procedural Media Control Interface 
  405.  
  406. While the ease of the textual MCI is unbeatable, it has some weaknesses in 
  407. comparison to the procedural MCI. 
  408.  
  409. o It has to be translated (parsed) internally which needs time. 
  410. o Anything that goes beyond a text string can't be returned as a result. 
  411.  
  412. The following example uses the procedural interface to query an audio CD for 
  413. it's table of contents. 
  414.  
  415. ////////////////////////////////
  416. // cdtoc - audio CD toc sample
  417. //
  418. // using emx 0.8h, mm4emx 1.0
  419. //
  420. //
  421. // Marc E.E. van Woerkom, 2/94
  422. //
  423.  
  424.  
  425. #include <os2.h>
  426.  
  427. #define  INCL_OS2MM
  428. #define  INCL_MCIOS2
  429. #include <os2me.h>
  430.  
  431. #include <iostream.h>
  432. #include <iomanip.h>
  433.  
  434.  
  435. //
  436. // mci_err: translate the MCI return code into an error string
  437. //
  438.  
  439. void mci_err(ULONG rc)
  440. {
  441.     const rsize = 128;
  442.     char rbuff[rsize];
  443.  
  444.     ULONG rc2 = mciGetErrorString(rc,      // error code
  445.                                   rbuff,   // return buffer
  446.                                   rsize);  // rbuff size
  447.  
  448.     if (rc2 == MCIERR_SUCCESS)
  449.         cerr << "MCI error: " << rbuff << "\n\n";
  450.     else
  451.         cerr << "error #" << rc << " has occured!\n\n";
  452. }
  453.  
  454. This function prints out a time given in MMTIME format.  It employs the helper 
  455. macros ULONG_LWLB, ULONG_LWHB and ULONG_HWLB to get the information out of the 
  456. ULONG. 
  457.  
  458.  
  459. //
  460. // print_mmtime: print time given in MMTIME as hh.mm.ss
  461. //
  462.  
  463. void print_mmtime(ULONG mmtime)
  464. {
  465.     ULONG hms = HMSFROMMM(mmtime);
  466.  
  467.     // hms packing is: |--|ss|mm|hh|
  468.  
  469.     int hour = int(ULONG_LWLB(hms));
  470.     int min  = int(ULONG_LWHB(hms));
  471.     int sec  = int(ULONG_HWLB(hms));
  472.  
  473.     if (hour)
  474.         cout << setw(4) << setfill('0')
  475.              << hour << '.'
  476.              << setfill('0');
  477.     else
  478.         cout << setfill(' ');  // I believe this shouldn't be neccessary
  479.  
  480.     cout << setw(2) << min << '.';
  481.  
  482.     cout << setw(2) << setfill('0')
  483.          << sec
  484.          << setfill(' ');       // this neither
  485. }
  486.  
  487.  
  488. //
  489. // main
  490. //
  491.  
  492. int main()
  493. {
  494.     cout << "cdtoc -- Audio CD Table of Contents\n\n";
  495.  
  496.     // open the audio CD device
  497.  
  498. Each MCI command has its specific parameter structure.  All unused fields 
  499. should be set to 0. 
  500.  
  501. The device to work with (CDaudio) is identified via its name. 
  502.  
  503.     MCI_OPEN_PARMS mop;
  504.  
  505.     mop.hwndCallback = 0;
  506.     mop.usDeviceID = 0;
  507.     mop.pszDeviceType = MCI_DEVTYPE_CD_AUDIO_NAME;
  508.     mop.pszElementName = 0;
  509.  
  510. The first parameter of mciSendCommand() is the ID of the device.  In this 
  511. special case it's not necessary - the ID is returned to a field of the 
  512. parameter structure. 
  513.  
  514. Next is a message specifying the command, MCI_OPEN in this case. 
  515.  
  516. The third parameter contains some so-called message flags.  Here they tell the 
  517. MDM to wait until the open action is completed and to open it in shared mode. 
  518.  
  519. Then a pointer to the parameter structure is given. 
  520.  
  521. The last parameter is usually 0. 
  522.  
  523.     ULONG rc = mciSendCommand(0,
  524.                               MCI_OPEN,                       // open message
  525.                               MCI_WAIT | MCI_OPEN_SHAREABLE,  // message flags
  526.                               &mop,                           // parameters
  527.                               0);
  528.  
  529.     if (rc != MCIERR_SUCCESS) {
  530.         mci_err(rc);
  531.         return 1;
  532.     }
  533.  
  534. Now we issue a GETTOC command.  It's not accessible from the textual MCI, 
  535. because it returns a bunch of information that doesn't fit into a simple line 
  536. of text.  (Let's hope there is no CD with more than 99 tracks.) 
  537.  
  538.     // ask for the table of contents
  539.  
  540.     const MAXTOCRECS = 99;
  541.     MCI_TOC_REC mtr[MAXTOCRECS];
  542.  
  543.     MCI_TOC_PARMS mtp;
  544.  
  545.     mtp.hwndCallback = 0;
  546.     mtp.pBuf = mtr;
  547.     mtp.ulBufSize = sizeof(mtr);
  548.  
  549. Note that this time (like in most cases) the device to work with is specified 
  550. via the ID obtained from MCI_OPEN 
  551.  
  552.     rc = mciSendCommand(mop.usDeviceID,  // device ID
  553.                         MCI_GETTOC,      // get toc message
  554.                         MCI_WAIT,        // message flags
  555.                         &mtp,            // parameters
  556.                         0);
  557.  
  558.     if (rc != MCIERR_SUCCESS) mci_err(rc);
  559.  
  560.     // close the device
  561.  
  562. MCI_CLOSE doesn't have any special parameters, so the parameter structure is of 
  563. the type MCI_GENERIC_PARMS: 
  564.  
  565.     MCI_GENERIC_PARMS mgp;
  566.     mgp.hwndCallback = 0;
  567.  
  568.     ULONG rc2 = mciSendCommand(mop.usDeviceID, MCI_CLOSE, MCI_WAIT, &mgp, 0);
  569.  
  570.     if (rc2 != MCIERR_SUCCESS) mci_err(rc2);
  571.  
  572.  
  573.     // now show the TOC, if been successful
  574.  
  575.     if (rc == MCIERR_SUCCESS) {
  576.  
  577. OK, MCI_GETTOC was successful, so print out the obtained toc entries: 
  578.  
  579.         int i = 0;
  580.  
  581.         while (mtr[i].TrackNum) {
  582.             cout << "Track" << setw(3);
  583.             cout << int(mtr[i].TrackNum)
  584.                  << ":  Length ";
  585.             print_mmtime(mtr[i].ulEndAddr - mtr[i].ulStartAddr);
  586.             cout << "  [";
  587.             print_mmtime(mtr[i].ulStartAddr);
  588.             cout << " to ";
  589.             print_mmtime(mtr[i].ulEndAddr);
  590.             cout << "]  Control " << int(mtr[i].Control)
  591.                  << ", Country " << mtr[i].usCountry
  592.                  << ", Owner " << mtr[i].ulOwner
  593.                  << ", #" << mtr[i].ulSerialNum << "\n";
  594.             i++;
  595.         }
  596.     }
  597.  
  598.  
  599.     // that's all folks!
  600.  
  601.     return 0;
  602. }
  603.  
  604. Now let's try CDTOC.EXE on a certain audio CD with "Gorgeous Gals", 
  605. "Transsylvanian Parties" etc.  :-) 
  606.  
  607. cdtoc -- Audio CD Table of Contents
  608.  
  609. Track  1:  Length  4.32  [ 0.02 to  4.34]  Control 1, Country 0, Owner 0, #0
  610. Track  2:  Length  2.46  [ 4.34 to  7.21]  Control 1, Country 0, Owner 0, #0
  611. Track  3:  Length  2.45  [ 7.21 to 10.06]  Control 1, Country 0, Owner 0, #0
  612. Track  4:  Length  3.19  [10.06 to 13.26]  Control 1, Country 0, Owner 0, #0
  613. Track  5:  Length  3.24  [13.26 to 16.50]  Control 1, Country 0, Owner 0, #0
  614. Track  6:  Length  2.12  [16.50 to 19.02]  Control 1, Country 0, Owner 0, #0
  615. Track  7:  Length  3.04  [19.02 to 22.07]  Control 1, Country 0, Owner 0, #0
  616. Track  8:  Length  1.48  [22.07 to 23.55]  Control 1, Country 0, Owner 0, #0
  617. Track  9:  Length  2.31  [23.55 to 26.27]  Control 1, Country 0, Owner 0, #0
  618. Track 10:  Length  2.46  [26.27 to 29.14]  Control 1, Country 0, Owner 0, #0
  619. Track 11:  Length  8.19  [29.14 to 37.33]  Control 1, Country 0, Owner 0, #0
  620. Track 12:  Length  2.54  [37.33 to 40.28]  Control 1, Country 0, Owner 0, #0
  621. Track 13:  Length  3.04  [40.28 to 43.32]  Control 1, Country 0, Owner 0, #0
  622. Track 14:  Length  1.31  [43.32 to 45.04]  Control 1, Country 0, Owner 0, #0
  623.  
  624. Yup, this is the table of contents of the Rocky Horror Picture Show soundtrack! 
  625. I chose this CD because track 11 has 3 subtracks (2.46, 3.34, 1.53).  However 
  626. they don't show up here, so I have to guess further what the Control field is 
  627. good for ("track control field"). 
  628.  
  629.  
  630. ΓòÉΓòÉΓòÉ 2.1.7. Querying MMPM/2 System Values ΓòÉΓòÉΓòÉ
  631.  
  632. Querying MMPM/2 System Values 
  633.  
  634. Remember PM's WinQuerySysValue() API call?  MMPM/2 has a counterpart named 
  635. mciQuerySysValue().  But in contrast to PM there are only less than a dozen 
  636. MMPM/2 system values defined, which the following example displays. 
  637.  
  638. ///////////////////////////////////////////
  639. // mmpmvals - MMPM/2 system values sample
  640. //
  641. // using emx 0.8h, mm4emx 1.0
  642. //
  643. //
  644. // Marc E.E. van Woerkom, 2/94
  645. //
  646.  
  647.  
  648. #include <os2.h>
  649.  
  650. #define  INCL_OS2MM
  651. #define  INCL_MCIOS2
  652. #include <os2me.h>
  653.  
  654. #include <iostream.h>
  655.  
  656.  
  657. //
  658. // main
  659. //
  660.  
  661. int main()
  662. {
  663.     cout << "mmpmvals -- MMPM/2 System Values\n\n";
  664.  
  665.     BOOL ClosedCaption;
  666.     mciQuerySysValue(MSV_CLOSEDCAPTION, &ClosedCaption);
  667.     cout << "MSV_CLOSEDCAPTION  :  " << int(ClosedCaption) << "\n";
  668.  
  669.     ULONG MasterVolume;
  670.     mciQuerySysValue(MSV_MASTERVOLUME, &MasterVolume);
  671.     cout << "MSV_MASTERVOLUME   :  " << int(MasterVolume) << "\n";
  672.  
  673.     ULONG Headphones;
  674.     mciQuerySysValue(MSV_HEADPHONES, &Headphones);
  675.     cout << "MSV_HEADPHONES     :  " << int(Headphones) << "\n";
  676.  
  677.     ULONG Speakers;
  678.     mciQuerySysValue(MSV_SPEAKERS, &Speakers);
  679.     cout << "MSV_SPEAKERS       :  " << int(Speakers) << "\n";
  680.  
  681.     CHAR WorkPath[CCHMAXPATH];
  682.     mciQuerySysValue(MSV_WORKPATH, WorkPath);
  683.     cout << "MSV_WORKPATH       :  " << WorkPath << "\n";
  684.  
  685.     ULONG SysqOsValue;
  686.     mciQuerySysValue(MSV_SYSQOSVALUE, &SysqOsValue);
  687.     cout << "MSV_SYSQOSVALUE    :  " << int(SysqOsValue) << "\n";
  688.  
  689.     ULONG SysqOsErrorFlag;
  690.     mciQuerySysValue(MSV_SYSQOSERRORFLAG, &SysqOsErrorFlag);
  691.     cout << "MSV_SYSQOSERRORFLAG:  " << int(SysqOsErrorFlag) << "\n";
  692.  
  693.  
  694.     // that's all folks!
  695.  
  696.     return 0;
  697. }
  698.  
  699. Running MMPMVALS.EXE on my system yields: 
  700.  
  701. mmpmvals -- MMPM/2 System Values
  702.  
  703. MSV_CLOSEDCAPTION  :  1
  704. MSV_MASTERVOLUME   :  100
  705. MSV_HEADPHONES     :  1
  706. MSV_SPEAKERS       :  1
  707. MSV_WORKPATH       :  C:\MMOS2
  708. MSV_SYSQOSVALUE    :  65537
  709. MSV_SYSQOSERRORFLAG:  2
  710.  
  711.  
  712. ΓòÉΓòÉΓòÉ 2.1.8. A First Rendezvous with the MMIO Subsystem ΓòÉΓòÉΓòÉ
  713.  
  714. A First Rendezvous with the MMIO Subsystem 
  715.  
  716. The MMIO subsystem is responsible for I/O on multimedia files. It comes with 
  717. several I/O procedures installed, which can handle specific kinds of data.  The 
  718. example below will show the installed MMIO procedures. 
  719.  
  720. //////////////////////////////////////////
  721. // mmiofmts - MMPM/2 mmio formats sample
  722. //
  723. // using emx 0.8h, mm4emx 1.0
  724. //
  725. //
  726. // Marc E.E. van Woerkom, 2/94
  727. //
  728.  
  729.  
  730. #include <os2.h>
  731.  
  732. #define  INCL_OS2MM
  733. #include <os2me.h>
  734.  
  735. #include <iostream.h>
  736. #include <iomanip.h>
  737.  
  738. Wrap MMFORMATINFO into a C++ class. 
  739.  
  740. //
  741. // mmformatinfo
  742. //
  743.  
  744. class mmformatinfo {
  745.     MMFORMATINFO mmfi;
  746. public:
  747.     mmformatinfo(FOURCC IOProc=0);
  748.     MMFORMATINFO* get_addr() { return &mmfi; }
  749.     LONG          get_NameLength() { return mmfi.lNameLength; }
  750.     PSZ           get_DefaultFormatExt() { return mmfi.szDefaultFormatExt; }
  751.     FOURCC        get_IOProc() { return mmfi.fccIOProc; }
  752. };
  753.  
  754. This constructor clears the MMFORMATINFO structure and initializes the 
  755. fccIOProc field with the proper four character code specific to the MMIO 
  756. procedure and the format it handles.  Use 0 as default argument. 
  757.  
  758. mmformatinfo::mmformatinfo(FOURCC IOProc=0)
  759. {
  760.     char* p = (char*) &mmfi;
  761.     for (int i=0; i<sizeof(mmfi); i++) p[i] = 0;
  762.  
  763.     mmfi.fccIOProc = IOProc;
  764. }
  765.  
  766.  
  767. //
  768. // mmio_err: translate MMIO error code into a string
  769. //
  770.  
  771. void mmio_err(ULONG rc)
  772. {
  773.     cerr << "MMIO error: ";
  774.  
  775.     char* s;
  776.  
  777.     switch (rc) {
  778.     case MMIO_SUCCESS:
  779.         s = "SUCCESS (huh?)";
  780.         break;
  781.     case MMIOERR_UNBUFFERED:
  782.         s = "UNBUFFERD";
  783.         break;
  784.     case MMIOERR_INVALID_HANDLE:
  785.         s = "INVALID HANDLE";
  786.         break;
  787.     case MMIOERR_INVALID_PARAMETER:
  788.         s = "INVALID PARAMETER";
  789.         break;
  790.     case MMIOERR_READ_ONLY_FILE:
  791.         s = "READ ONLY FILE";
  792.         break;
  793.     case MMIOERR_WRITE_ONLY_FILE:
  794.         s = "WRITE ONLY FILE";
  795.         break;
  796.     case MMIOERR_WRITE_FAILED:
  797.         s = "WRITE FAILED";
  798.         break;
  799.     case MMIOERR_READ_FAILED:
  800.         s = "READ FAILED";
  801.         break;
  802.     case MMIOERR_SEEK_FAILED:
  803.         s = "SEEK FAILED";
  804.         break;
  805.     case MMIOERR_NO_FLUSH_NEEDED:
  806.         s = "NO FLUSH NEEDED";
  807.         break;
  808.     case MMIOERR_OUTOFMEMORY:
  809.         s = "OUT OF MEMORY";
  810.         break;
  811.     case MMIOERR_CANNOTEXPAND:
  812.         s = "CANNOT EXPAND";
  813.         break;
  814.     case MMIOERR_FREE_FAILED:
  815.         s = "FREE FAILED";
  816.         break;
  817.     case MMIOERR_CHUNKNOTFOUND:
  818.         s = "CHUNK NOT FOUND";
  819.         break;
  820.     case MMIO_ERROR:
  821.         s = "ERROR";
  822.         break;
  823.     case MMIO_WARNING:
  824.         s = "WARNING";
  825.         break;
  826.     case MMIO_CF_FAILURE:
  827.         s = "CF FAILURE";
  828.         break;
  829.     default:
  830.         cerr << rc;
  831.         s = " (hmm...)";
  832.     }
  833.  
  834.     cerr << s << "\n";
  835. }
  836.  
  837.  
  838. //
  839. // main procedure
  840. //
  841. //
  842. // WARNING: The MMPM/2 stops working on my system
  843. //          if I use a string as argument for the FOURCC mask
  844. //          which is none of the registered ones! (e.g. wuff)
  845. //
  846. //          Looks like a MMPM/2 bug to me.
  847. //          (A major confusion of mmio.dll?)
  848. //
  849.  
  850. int main(int argc, char* argv[])
  851. {
  852.     cout << "mmiofmts -- MMPM/2 MMIO Formats\n\n";
  853.  
  854.  
  855.     // parse args
  856.  
  857.     FOURCC mask = 0;
  858.  
  859.     if (argc>1) {
  860.  
  861. The mmioStringToFOURCC() API call translates a string into a four character 
  862. code. 
  863.  
  864.         mask = mmioStringToFOURCC(argv[1], MMIO_TOUPPER);
  865.  
  866. A FOURCC is a 32-bit variable, containing 4 characters. 
  867.  
  868.         cout << "mask in use is [";
  869.         char* p = (char*) &mask;
  870.         for (int i=0; i<4; i++) cout << p[i];
  871.         cout << "]\n\n";
  872.     }
  873.  
  874.  
  875.     // query # of IOProcedures
  876.  
  877.     mmformatinfo mmfi_spec(mask);
  878.  
  879.     ULONG NumFormats = 0;
  880.  
  881. mmioQueryFormatCount() returns the number of installed MMIO procedures. 
  882.  
  883.     ULONG rc = mmioQueryFormatCount(mmfi_spec.get_addr(),
  884.                                     &NumFormats,
  885.                                     0,
  886.                                     0);
  887.  
  888.     if (rc != MMIO_SUCCESS) {
  889.         mmio_err(rc);
  890.         return 1;
  891.     }
  892.  
  893.     cout << "formats supported: " << NumFormats;
  894.  
  895.     if (!NumFormats) return 0;
  896.  
  897.  
  898.     // get formats
  899.  
  900. Get all format information via mmioGetFormats(). 
  901.  
  902.     mmformatinfo* mmfip = new mmformatinfo[NumFormats];
  903.  
  904.     ULONG FormatsRead = 0;
  905.  
  906.     rc = mmioGetFormats(mmfi_spec.get_addr(),
  907.                         NumFormats,
  908.                         mmfip,
  909.                         &FormatsRead,
  910.                         0,
  911.                         0);
  912.  
  913.     if (rc != MMIO_SUCCESS) {
  914.         mmio_err(rc);
  915.         return 2;
  916.     }
  917.  
  918.     cout << "  (" << FormatsRead << " formats read)\n\n";
  919.  
  920.  
  921.     // print information
  922.  
  923.     cout << "no.   4-cc   name                                     leng  extn\n\n";
  924.  
  925.     for (int i=0; i<NumFormats; i++) {
  926.         cout.setf(ios::right, ios::adjustfield);
  927.         cout << setw(2) << i+1 << ":  [";
  928.  
  929.         FOURCC IOProc = mmfip[i].get_IOProc();
  930.         char* p = (char*) &IOProc;
  931.  
  932.         for (int j=0; j<4; j++) cout << p[j];
  933.         cout << "]  ";
  934.  
  935.         LONG NameLength = mmfip[i].get_NameLength();
  936.  
  937.         if (NameLength) {
  938.             PSZ name = new CHAR[NameLength+1];
  939.             LONG BytesRead = 0;
  940.  
  941. Extract the name of the format via mmioGetFormatName(). 
  942.  
  943.             rc = mmioGetFormatName(mmfip[i].get_addr(),
  944.                                    name,
  945.                                    &BytesRead,
  946.                                    0,
  947.                                    0);
  948.  
  949.             name[NameLength] = 0;
  950.  
  951.             if (rc != MMIO_SUCCESS) {
  952.                 mmio_err(rc);
  953.                 cout << "     ";
  954.             }
  955.             else {
  956.                 cout.setf(ios::left, ios::adjustfield);
  957.                 cout << setw(40) << name << " ("
  958.                      << setw(2) << BytesRead;
  959.             }
  960.  
  961.             delete[] name;
  962.         }
  963.         else
  964.             cout << "-" << setw(43) << "( 0";
  965.  
  966.         cout.setf(ios::left, ios::adjustfield);
  967.         cout << ")  ."
  968.              << setw(3) << mmfip[i].get_DefaultFormatExt() << "\n";
  969.     }
  970.  
  971.     delete[] mmfip;
  972.  
  973.  
  974.     // that's all folks!
  975.  
  976.     return 0;
  977. }
  978.  
  979. So let's run mmiofmts.exe to display the currently installed MMIO procedures: 
  980.  
  981. mmiofmts -- MMPM/2 MMIO Formats
  982.  
  983. formats supported: 15  (15 formats read)
  984.  
  985. no.   4-cc   name                                     leng  extn
  986.  
  987.  1:  [RDIB]  RIFF DIB Image                           (14)  .RDI
  988.  2:  [AVCI]  IBM AVC Still Video Image                (25)  ._IM
  989.  3:  [MMOT]  IBM MMotion Still Video Image            (29)  .VID
  990.  4:  [AVCA]  IBM AVC ADPCM Digital Audio              (27)  ._AU
  991.  5:  [VOC ]  Creative Labs Voice File                 (24)  .VOC
  992.  6:  [WI30]  MS Windows DIB Image                     (20)  .DIB
  993.  7:  [MIDI]  Midi File Format I/O Procedure           (30)  .MID
  994.  8:  [OS13]  IBM OS/2 1.3 PM Bitmap Image             (28)  .BMP
  995.  9:  [OS20]  IBM OS/2 2.0 BMP                         (16)  .BMP
  996. 10:  [AVI ]  AVI IO Procedure                         (16)  .AVI
  997. 11:  [WAVE]  RIFF WAVE Digital Audio                  (23)  .WAV
  998. 12:  [CF  ]  -                                        ( 0)  .
  999. 13:  [BND ]  -                                        ( 0)  .BND
  1000. 14:  [MEM ]  -                                        ( 0)  .MEM
  1001. 15:  [DOS ]  -                                        ( 0)  .DOS
  1002.  
  1003. Save this utility for the next part of this article series when we go to 
  1004. install our own MMIO procedure! 
  1005.  
  1006.  
  1007. ΓòÉΓòÉΓòÉ 2.1.9. Memory Playlists ΓòÉΓòÉΓòÉ
  1008.  
  1009. Memory Playlists 
  1010.  
  1011. Using an MCI script, playing several soundfiles is not smooth, for the 
  1012. waveaudio device must load each soundfile (element) into its context, delaying 
  1013. the play.  A possible work-around is to load the elements into memory before 
  1014. playing starts.  This can be achieved using the MMIO waveaudio I/O procedure 
  1015. and the MMPM/2 waveaudio playlist processor. 
  1016.  
  1017. Loading the waveaudio file via the MMIO subsystem into memory has the advantage 
  1018. that one can extract the necessary data without knowing much about the maybe 
  1019. complicated internal structure of such a file. The .WAV format is a special 
  1020. kind of the more general RIFF (Resource Interchange File Format) multimedia 
  1021. format.  For example, the M1.WAV file is 26504 bytes long, but contains only 
  1022. 26460 bytes of pure sound data.  Using MMIO we don't have to worry where it is 
  1023. located in the file. 
  1024.  
  1025. The usual encoding scheme for sound data is pulse code modulation (PCM).  This 
  1026. means at a fixed rate per second (the sampling frequency) the amplitude of the 
  1027. signal of each channel is converted by an audio to digital converter into a 
  1028. number.  The M1.WAV file was sampled in mono with 11kHz and 8 bit resolution, 
  1029. so it contains 26460 bytes/(11000 bytes/s) = 2.4s worth of audio data. 
  1030.  
  1031. Note:  once loaded into memory, one can easily work with this data in ways such 
  1032. as applying a compression scheme to it, mixing in a second waveaudio file (take 
  1033. the mean of both amplitude values) if you want to play more than one sound at 
  1034. once, adding effects such as echoing (rescale the amplitudes and mix this data 
  1035. with a short delay over the original data), etc. 
  1036.  
  1037. A playlist is an array of playlist instructions, which is processed by the 
  1038. playlist processor.  Among those instructions is not only the command to play a 
  1039. waveaudio file residing in memory, but also flow control instructions which 
  1040. allow jumps, loops and subroutines. 
  1041.  
  1042. Look at this example, which employs a playlist to play a certain rhythm :-) 
  1043.  
  1044. ///////////////////////////////////////
  1045. // rhythm - waveaudio playlist sample
  1046. //
  1047. // using emx 0.8h, mm4emx 1.0
  1048. //
  1049. //
  1050. // Marc E.E. van Woerkom, 2/94
  1051. //
  1052.  
  1053.  
  1054. #include <os2.h>
  1055.  
  1056. #define  INCL_OS2MM
  1057. #include <os2me.h>
  1058.  
  1059. #include <iostream.h>
  1060. #include <iomanip.h>
  1061.  
  1062.  
  1063. // prototypes
  1064.  
  1065. void mci_err(ULONG);
  1066. void mmio_err(ULONG);
  1067.  
  1068. The MMAUDIOHEADER structure will get the header information of the waveaudio 
  1069. file from the MMIO. 
  1070.  
  1071. //
  1072. // mmaudioheader
  1073. //
  1074.  
  1075. class mmaudioheader {
  1076.     MMAUDIOHEADER mmah;
  1077. public:
  1078.     mmaudioheader();
  1079.     MMAUDIOHEADER* get_addr() { return &mmah; }
  1080.     LONG           get_size() { return sizeof(mmah); }
  1081. };
  1082.  
  1083.  
  1084. mmaudioheader::mmaudioheader()
  1085. {
  1086.     char* p = (char*) &mmah;
  1087.     for (int i=0; i<sizeof(mmah); i++) p[i] = 0;
  1088. }
  1089.  
  1090. The constructor of the mem_wav class will load a waveaudio file (given by its 
  1091. filename) into memory via the MMIO subsystem. 
  1092.  
  1093. //
  1094. // mem_wav: a waveaudio file loaded into memory
  1095. //
  1096.  
  1097. class mem_wav {
  1098.     HMMIO  hmmio;
  1099.     PSZ    bptr;
  1100.     ULONG  bsize;
  1101.     ULONG  SamplesPerSec;
  1102.     USHORT BitsPerSample;
  1103. public:
  1104.     mem_wav(char*);
  1105.     ~mem_wav();
  1106.     PSZ    get_bptr() { return bptr; }
  1107.     ULONG  get_bsize() { return bsize; }
  1108.     ULONG  get_SamplesPerSec() { return SamplesPerSec; }
  1109.     USHORT get_BitsPerSample() { return BitsPerSample; }
  1110. };
  1111.  
  1112.  
  1113. mem_wav::mem_wav(char* name)
  1114. {
  1115.  
  1116. The MMIO subsystem looks at the .WAV extension and calls the proper I/O 
  1117. procedure to open it, delivering a handle as the result. 
  1118.  
  1119.     // open the file
  1120.  
  1121.     hmmio = mmioOpen(name, 0, MMIO_READ);
  1122.  
  1123. Now get the header information of the waveaudio file. 
  1124.  
  1125.     // get header
  1126.  
  1127.     mmaudioheader mmah;
  1128.  
  1129.     ULONG BytesRead = 0;
  1130.  
  1131.     ULONG rc = mmioGetHeader(hmmio, mmah.get_addr(), mmah.get_size(),
  1132.                              &BytesRead, 0, 0);
  1133.  
  1134.     if (rc != MMIO_SUCCESS) mmio_err(rc);
  1135.  
  1136. The header contains the length in bytes (needed to allocate the buffer memory), 
  1137. the sampling frequency and the sampling resolution (these settings are needed 
  1138. for a proper reproduction). 
  1139.  
  1140.     // get some infos about the waveaudio file
  1141.  
  1142.     SamplesPerSec = mmah.get_addr()->mmXWAVHeader.WAVEHeader.ulSamplesPerSec;
  1143.     BitsPerSample = mmah.get_addr()->mmXWAVHeader.WAVEHeader.usBitsPerSample;
  1144.  
  1145.     bsize = mmah.get_addr()->mmXWAVHeader.XWAVHeaderInfo.ulAudioLengthInBytes;
  1146.     bptr  = new CHAR[bsize];
  1147.  
  1148. The buffer is allocated, now read all information into it. 
  1149.  
  1150.     // read file
  1151.  
  1152.     rc = mmioRead(hmmio, bptr, bsize);
  1153.  
  1154.     if (rc == MMIO_ERROR) mmio_err(rc);
  1155.  
  1156.     cout << "[file " << name
  1157.          << ": read" << setw(7) << rc
  1158.          << " bytes of" << setw(7) << bsize << "]\n";
  1159.  
  1160. Finally close the file.  That's all!  (And we didn't need to know anything 
  1161. about RIFF chunks, etc.) 
  1162.  
  1163.     // close file
  1164.  
  1165.     rc = mmioClose(hmmio, 0);
  1166.     if (rc != MMIO_SUCCESS) mmio_err(rc);
  1167. }
  1168.  
  1169. The destructor of the class gets rid of the allocated memory resources. 
  1170.  
  1171. mem_wav::~mem_wav()
  1172. {
  1173.     delete[] bptr;
  1174. }
  1175.  
  1176. Now comes the playlist.  A playlist entry is composed of four ULONG variables. 
  1177. The first represents the instruction and the others are its possible arguments. 
  1178.  
  1179. //
  1180. // ple: a playlist entry
  1181. //
  1182.  
  1183. struct ple {
  1184.     ULONG operation;
  1185.     ULONG operand1;
  1186.     ULONG operand2;
  1187.     ULONG operand3;
  1188. };
  1189.  
  1190.  
  1191. This class represents a playlist.  It should be stated that it is tailored for 
  1192. this special example.  (For it reads exactly 7 different samples and the 
  1193. playlist is hardcoded into the setup() member function.  A general class of 
  1194. this kind should be able to deal with a variable amount of samples and should 
  1195. read the playlist from a data file or a resource block.) 
  1196.  
  1197.  
  1198. //
  1199. // playlist: a waveaudio playlist
  1200. //
  1201.  
  1202. class playlist {
  1203.     ple*     pl;
  1204.     int      size, used;
  1205.     mem_wav& m1, m2, m3, m4, m5, s1, p1;
  1206.     void setup();
  1207. public:
  1208.     playlist(mem_wav&, mem_wav&, mem_wav&, mem_wav&,
  1209.              mem_wav&, mem_wav&, mem_wav&, int);
  1210.     ~playlist();
  1211.     ple* get_addr() { return pl; }
  1212.     int add(ULONG =0, ULONG =0, ULONG =0, ULONG =0);
  1213.     int add_branch(ULONG);
  1214.     int add_call(ULONG);
  1215.     int add_data(mem_wav&);
  1216.     int add_exit();
  1217.     int add_return();
  1218. };
  1219.  
  1220. Allocate the playlist entries. 
  1221.  
  1222. playlist::playlist(mem_wav& M1, mem_wav& M2, mem_wav& M3,
  1223.                    mem_wav& M4, mem_wav& M5, mem_wav& S1, mem_wav& P1,
  1224.                    int Size)
  1225.     : m1(M1), m2(M2), m3(M3), m4(M4), m5(M5), s1(S1), p1(P1),
  1226.       size(Size)
  1227. {
  1228.     if (size < 1) cerr << "error: wrong playlist size!\n";
  1229.  
  1230.     pl = new ple[size];
  1231.     used = 0;
  1232.  
  1233.     setup();
  1234. }
  1235.  
  1236.  
  1237. playlist::~playlist()
  1238. {
  1239.     delete[] pl;
  1240. }
  1241.  
  1242. This member function will fill a playlist entry with the proper values. Note 
  1243. the default arguments.  And it returns the number of the current entry which 
  1244. will come in handy when employed in setup(). 
  1245.  
  1246. int playlist::add(ULONG op=0, ULONG opd1=0, ULONG opd2=0, ULONG opd3=0)
  1247. {
  1248.     if (used >= size) {
  1249.         cerr << "error: playlist is too small!\n";
  1250.         return -1;
  1251.     }
  1252.  
  1253.     pl[used].operation = op;
  1254.     pl[used].operand1  = opd1;
  1255.     pl[used].operand2  = opd2;
  1256.     pl[used].operand3  = opd3;
  1257.  
  1258.     return used++;
  1259. }
  1260.  
  1261. A branch operation (jump to a specific entry). 
  1262.  
  1263. int playlist::add_branch(ULONG addr)
  1264. {
  1265.     return add(BRANCH_OPERATION, 0, addr);
  1266. }
  1267.  
  1268. A call operation (call a playlist subroutine). 
  1269.  
  1270. int playlist::add_call(ULONG addr)
  1271. {
  1272.     return add(CALL_OPERATION, 0, addr);
  1273. }
  1274.  
  1275. A data operation (play a waveaudio file from a buffer). 
  1276.  
  1277. int playlist::add_data(mem_wav& mw)
  1278. {
  1279.     return add(DATA_OPERATION, ULONG(mw.get_bptr()), mw.get_bsize());
  1280. }
  1281.  
  1282. An exit operation (end the playlist processing). 
  1283.  
  1284. int playlist::add_exit()
  1285. {
  1286.     return add(EXIT_OPERATION);
  1287. }
  1288.  
  1289. A return operation (return from a playlist subroutine). 
  1290.  
  1291. int playlist::add_return()
  1292. {
  1293.     return add(RETURN_OPERATION);
  1294. }
  1295.  
  1296. This is a hardwired playlist. Note that is ordered in a way that only one 
  1297. forward reference (70) is needed. 
  1298.  
  1299. void playlist::setup()
  1300. {
  1301.  
  1302. Jump to the 70th playlist entry. 
  1303.  
  1304.                   add_branch(70);
  1305.  
  1306. This is one of several subroutines.  It plays the buffer containing the audio 
  1307. data of M1.WAV thrice. 
  1308.  
  1309.     ULONG Intro = add_data(m1);
  1310.                   add_data(m1);
  1311.                   add_data(m1);
  1312.                   add_return();
  1313.  
  1314.     ULONG A10a =  add_data(m2);
  1315.                   add_data(s1);
  1316.                   add_data(p1);
  1317.                   add_data(m3);
  1318.                   add_data(m4);
  1319.                   add_data(m5);
  1320.                   add_data(m4);
  1321.                   add_data(m5);
  1322.                   add_data(m4);
  1323.                   add_data(m5);
  1324.                   add_return();
  1325.  
  1326.     ULONG A10 =   add_data(m2);
  1327.                   add_data(s1);
  1328.                   add_data(p1);
  1329.                   add_data(m5);
  1330.                   add_data(m4);
  1331.                   add_data(m5);
  1332.                   add_data(m4);
  1333.                   add_data(m5);
  1334.                   add_data(m4);
  1335.                   add_data(m5);
  1336.                   add_return();
  1337.  
  1338.     ULONG B10 =   add_data(m2);
  1339.                   add_data(s1);
  1340.                   add_data(p1);
  1341.                   add_data(m1);
  1342.                   add_data(m1);
  1343.                   add_data(m1);
  1344.                   add_data(m1);
  1345.                   add_data(m1);
  1346.                   add_data(m1);
  1347.                   add_data(m1);
  1348.                   add_return();
  1349.  
  1350.     ULONG C10 =   add_data(m2);
  1351.                   add_data(s1);
  1352.                   add_data(p1);
  1353.                   add_data(m2);
  1354.                   add_data(m2);
  1355.                   add_data(m2);
  1356.                   add_data(m2);
  1357.                   add_data(m2);
  1358.                   add_data(m2);
  1359.                   add_data(m2);
  1360.                   add_return();
  1361.  
  1362.     ULONG A6 =    add_data(m2);
  1363.                   add_data(s1);
  1364.                   add_data(p1);
  1365.                   add_data(m5);
  1366.                   add_data(m4);
  1367.                   add_data(m5);
  1368.                   add_return();
  1369.  
  1370.     ULONG B6 =    add_data(m2);
  1371.                   add_data(s1);
  1372.                   add_data(p1);
  1373.                   add_data(m1);
  1374.                   add_data(m1);
  1375.                   add_data(m1);
  1376.                   add_return();
  1377.  
  1378.     ULONG C6 =    add_data(m2);
  1379.                   add_data(s1);
  1380.                   add_data(p1);
  1381.                   add_data(m2);
  1382.                   add_data(m2);
  1383.                   add_data(m2);
  1384.                   add_return();
  1385.  
  1386. Well, I didn't count until here.  I simply printed out the return code of the 
  1387. next call in a prior version of this source and noted it. 
  1388.  
  1389. // #70
  1390.                   add_call(Intro);
  1391.                   add_call(A10a);
  1392.                   add_call(B10);
  1393.                   add_call(A10);
  1394.                   add_call(B10);
  1395.                   add_call(A10);
  1396.                   add_call(C10);
  1397.                   add_call(A6);
  1398.                   add_call(B6);
  1399.                   add_call(A6);
  1400.                   add_call(C10);
  1401.                   add_call(A10);
  1402.                   add_call(C10);
  1403.                   add_call(A10);
  1404.                   add_call(A10);
  1405.                   add_call(A10);
  1406.                   add_call(C10);
  1407.                   add_call(A6);
  1408.                   add_call(C6);
  1409.                   add_call(A6);
  1410.                   add_call(B10);
  1411.                   add_data(s1);
  1412.                   add_exit();
  1413. }
  1414.  
  1415. This class represents the waveaudio device together with an associated 
  1416. playlist.  The characteristics of the mem_wav given to the constructor are used 
  1417. for the processing of the whole playlist. 
  1418.  
  1419. //
  1420. // waveaudio: a waveaudio device
  1421. //
  1422.  
  1423. class waveaudio {
  1424.     MCI_OPEN_PARMS mop;
  1425. public:
  1426.     waveaudio(playlist&, mem_wav&);
  1427.     ~waveaudio();
  1428.     void play();
  1429. };
  1430.  
  1431.  
  1432. waveaudio::waveaudio(playlist& pl, mem_wav& mw)
  1433. {
  1434.  
  1435. Open the waveaudio device via an MCI command message in a way that it will use 
  1436. a playlist as data. 
  1437.  
  1438.     // open device
  1439.  
  1440.     mop.hwndCallback   = 0;
  1441.     mop.usDeviceID     = 0;
  1442.     mop.pszDeviceType  = MCI_DEVTYPE_WAVEFORM_AUDIO_NAME;
  1443.     mop.pszElementName = PSZ(pl.get_addr());
  1444.  
  1445.     ULONG rc = mciSendCommand(0,
  1446.                               MCI_OPEN,                        // open message
  1447.                               MCI_WAIT | MCI_OPEN_SHAREABLE |  // message flags
  1448.                               MCI_OPEN_PLAYLIST,
  1449.                               &mop,                            // parameters
  1450.                               0);
  1451.  
  1452.     if (rc != MCIERR_SUCCESS) mci_err(rc);
  1453.  
  1454. If these values aren't set via MCI_SET, the waveaudio data will be played with 
  1455. improper speed or even be garbled. 
  1456.  
  1457.     // set device parameters
  1458.  
  1459.     MCI_WAVE_SET_PARMS wsp;
  1460.  
  1461.     wsp.hwndCallback    = 0;
  1462.     wsp.ulSamplesPerSec = mw.get_SamplesPerSec();
  1463.     wsp.usBitsPerSample = mw.get_BitsPerSample();
  1464.  
  1465.     rc = mciSendCommand(mop.usDeviceID,
  1466.                         MCI_SET,
  1467.                         MCI_WAIT |
  1468.                         MCI_WAVE_SET_SAMPLESPERSEC |
  1469.                         MCI_WAVE_SET_BITSPERSAMPLE,
  1470.                         &wsp,
  1471.                         0);
  1472.  
  1473.     if (rc != MCIERR_SUCCESS) mci_err(rc);
  1474. }
  1475.  
  1476. Close the waveaudio device. 
  1477.  
  1478. waveaudio::~waveaudio()
  1479. {
  1480.     // close device
  1481.  
  1482.     MCI_GENERIC_PARMS mgp;
  1483.  
  1484.     mgp.hwndCallback = 0;
  1485.  
  1486.     ULONG rc = mciSendCommand(mop.usDeviceID,
  1487.                               MCI_CLOSE,
  1488.                               MCI_WAIT,
  1489.                               &mgp,
  1490.                               0);
  1491.  
  1492.     if (rc != MCIERR_SUCCESS) mci_err(rc);
  1493. }
  1494.  
  1495. Set the waveaudio device on 'play'. 
  1496.  
  1497. void waveaudio::play()
  1498. {
  1499.     // play the playlist
  1500.  
  1501.     MCI_PLAY_PARMS mpp;
  1502.  
  1503.     mpp.hwndCallback = 0;
  1504.  
  1505.     ULONG rc = mciSendCommand(mop.usDeviceID,
  1506.                               MCI_PLAY,
  1507.                               MCI_WAIT,
  1508.                               &mpp,
  1509.                               0);
  1510.  
  1511.     if (rc != MCIERR_SUCCESS) mci_err(rc);
  1512. }
  1513.  
  1514.  
  1515. Routines to print pretty error messages. 
  1516.  
  1517. //
  1518. // mci_err: translate the MCI return code into an error string
  1519. //
  1520.  
  1521. void mci_err(ULONG rc)
  1522. {
  1523.     const rsize = 128;
  1524.     char rbuff[rsize];
  1525.  
  1526.     ULONG rc2 = mciGetErrorString(rc,      // error code
  1527.                                   rbuff,   // return buffer
  1528.                                   rsize);  // rbuff size
  1529.  
  1530.     if (rc2 == MCIERR_SUCCESS)
  1531.         cerr << "MCI error: " << rbuff << "\n\n";
  1532.     else
  1533.         cerr << "error #" << rc << " has occured!\n\n";
  1534. }
  1535.  
  1536.  
  1537. //
  1538. // mmio_err: translate MMIO error code into a string
  1539. //
  1540.  
  1541. void mmio_err(ULONG rc)
  1542. {
  1543.     cerr << "MMIO error: ";
  1544.  
  1545.     char* s;
  1546.  
  1547.     switch (rc) {
  1548.     case MMIO_SUCCESS:
  1549.         s = "SUCCESS (huh?)";
  1550.         break;
  1551.     case MMIOERR_UNBUFFERED:
  1552.         s = "UNBUFFERD";
  1553.         break;
  1554.     case MMIOERR_INVALID_HANDLE:
  1555.         s = "INVALID HANDLE";
  1556.         break;
  1557.     case MMIOERR_INVALID_PARAMETER:
  1558.         s = "INVALID PARAMETER";
  1559.         break;
  1560.     case MMIOERR_READ_ONLY_FILE:
  1561.         s = "READ ONLY FILE";
  1562.         break;
  1563.     case MMIOERR_WRITE_ONLY_FILE:
  1564.         s = "WRITE ONLY FILE";
  1565.         break;
  1566.     case MMIOERR_WRITE_FAILED:
  1567.         s = "WRITE FAILED";
  1568.         break;
  1569.     case MMIOERR_READ_FAILED:
  1570.         s = "READ FAILED";
  1571.         break;
  1572.     case MMIOERR_SEEK_FAILED:
  1573.         s = "SEEK FAILED";
  1574.         break;
  1575.     case MMIOERR_NO_FLUSH_NEEDED:
  1576.         s = "NO FLUSH NEEDED";
  1577.         break;
  1578.     case MMIOERR_OUTOFMEMORY:
  1579.         s = "OUT OF MEMORY";
  1580.         break;
  1581.     case MMIOERR_CANNOTEXPAND:
  1582.         s = "CANNOT EXPAND";
  1583.         break;
  1584.     case MMIOERR_FREE_FAILED:
  1585.         s = "FREE FAILED";
  1586.         break;
  1587.     case MMIOERR_CHUNKNOTFOUND:
  1588.         s = "CHUNK NOT FOUND";
  1589.         break;
  1590.     case MMIO_ERROR:
  1591.         s = "ERROR";
  1592.         break;
  1593.     case MMIO_WARNING:
  1594.         s = "WARNING";
  1595.         break;
  1596.     case MMIO_CF_FAILURE:
  1597.         s = "CF FAILURE";
  1598.         break;
  1599.     default:
  1600.         cerr << rc;
  1601.         s = " (hmm...)";
  1602.     }
  1603.  
  1604.     cerr << s << "\n";
  1605. }
  1606.  
  1607. Main function. Perhaps you should start reading this example from here. 
  1608.  
  1609. //
  1610. // main
  1611. //
  1612.  
  1613. int main()
  1614. {
  1615.     cout << "rhythm -- a Rhythm Generator\n\n";
  1616.  
  1617.  
  1618.     // load waveaudio files into memory
  1619.  
  1620.     cout << "loading waveaudio files into memory ...\n\n";
  1621.  
  1622.     mem_wav m1("m1.wav");
  1623.     mem_wav m2("m2.wav");
  1624.     mem_wav m3("m3.wav");
  1625.     mem_wav m4("m4.wav");
  1626.     mem_wav m5("m5.wav");
  1627.     mem_wav s1("s1.wav");
  1628.     mem_wav p1("p1.wav");
  1629.  
  1630.  
  1631.     // set up playlist
  1632.  
  1633.     cout << "\nsetting up playlist ...\n\n";
  1634.  
  1635.     playlist pl(m1, m2, m3, m4, m5, s1, p1, 100);
  1636.  
  1637.  
  1638.     // play playlist
  1639.  
  1640.     cout << "and ... go!   (you should pump up the volume :-)\n\n";
  1641.  
  1642.     waveaudio wav(pl, m1);
  1643.     wav.play();
  1644.  
  1645.  
  1646.     // that's all folks!
  1647.  
  1648.     cout << "... done. yeah!\n";
  1649.  
  1650.     return 0;
  1651. }
  1652.  
  1653. Try it! :-) 
  1654.  
  1655. Note that this example is not too far away from the .MOD file playing 
  1656. mechanism.  The hardest thing for an extension in this direction is probably 
  1657. getting the proper .MOD file definition.  Since there was an unconfirmed report 
  1658. on Usenet that IBM may deliver a .MOD MMIO procedure in the next MMPM/2 
  1659. release, I personally won't put time into something like that. 
  1660.  
  1661.  
  1662. ΓòÉΓòÉΓòÉ 2.1.10. What's Next? ΓòÉΓòÉΓòÉ
  1663.  
  1664. What's Next? 
  1665.  
  1666. At least two important topics are still on my list: 
  1667.  
  1668. o Expect to see more on the MMIO subsystem.  Procedures for handling compressed 
  1669.   waveaudiofiles are under development. 
  1670.  
  1671. o The PM extensions (graphical buttons, circular sliders and the secondary 
  1672.   windows support) cry for some nice examples. 
  1673.  
  1674.  
  1675. ΓòÉΓòÉΓòÉ 2.1.11. More Literature on MMPM/2 ΓòÉΓòÉΓòÉ
  1676.  
  1677. More Literature on MMPM/2 
  1678.  
  1679. IBM Doc. S53G-2166:
  1680. OS/2 Online Book Collection CD-ROM.
  1681.  
  1682. This CD-ROM contains 144 different OS/2 manuals in *.boo format and
  1683. readers for OS/2 and DOS.  (Highly recommended!)
  1684.  
  1685.  
  1686. ΓòÉΓòÉΓòÉ 2.2. Porting STEP02 to ICLUI ΓòÉΓòÉΓòÉ
  1687.  
  1688.  
  1689. ΓòÉΓòÉΓòÉ 2.2.1. Introduction ΓòÉΓòÉΓòÉ
  1690.  
  1691. Written by Kenton W. Shaver 
  1692.  
  1693. Introduction 
  1694.  
  1695. This article describes the translation of Gavin Baker's STEP02.C to the IBM's 
  1696. C++ libraries for PM (ICLUI).  Hopefully it will assist the programmer who is 
  1697. now learning these libraries or is attempting such ports of their own. 
  1698.  
  1699. The result - CLOCK.EXE - requires the C-Set++ DLLs to run, as it is linked 
  1700. dynamically.  A slightly modified copy of the original program that will 
  1701. compile under C-Set++ is also included so that the reader can conduct their own 
  1702. performance analysis and browsing sessions with the original program and the 
  1703. resulting port, if desired. 
  1704.  
  1705. Scope 
  1706.  
  1707. I'm assuming that you have read Gavin Baker's article in volume 1, issue 2, 
  1708. that you basically understand Presentation Manager programming, and that you 
  1709. have perused the recent articles here appearing about ICLUI [by Gordon 
  1710. Zeglinski, in volume 2, issue 1 - editor] detailing the IEvent, IWindow, and 
  1711. the IHandler clases, etc.  Some important issues will be mentioned again for 
  1712. easier retention. 
  1713.  
  1714.  
  1715. ΓòÉΓòÉΓòÉ 2.2.2. Overall Structure ΓòÉΓòÉΓòÉ
  1716.  
  1717. Overall Structure 
  1718.  
  1719. In the ported program, a main window is created which in turn creates a client 
  1720. window, a menu window, and an object window for its own use; with the exception 
  1721. of the object window, this is what the original program does also. The object 
  1722. window launches a thread in charge of keeping time and updating the main 
  1723. window's client window at one second intervals.  The messages exchanged between 
  1724. the frame window and its object window don't figure for much, though, 
  1725. especially since no easy method to thread the message processing loop of the 
  1726. class myObjectWindow is provided; this device is left in place for 
  1727. illustration. 
  1728.  
  1729. The two other classes introduced here modify IFrameHandler so that it processes 
  1730. WM_SYSCOMMAND events and the creation of another IHandler class capable of 
  1731. processing WM_QUERYTRACKINFO events. 
  1732.  
  1733. The program itself does basically the same thing as STEP02.C did, with the 
  1734. exceptions that it uses DosSleep() instead of WinTimer() to provide the timer 
  1735. support and it subclasses the frame window so that it cannot be resized below 
  1736. certain dimensions. 
  1737.  
  1738.  
  1739. ΓòÉΓòÉΓòÉ 2.2.3. Subclassing ΓòÉΓòÉΓòÉ
  1740.  
  1741. Subclassing 
  1742.  
  1743. What do we mean when we say that our program uses subclassing?  Do we mean 
  1744. subclass in the C++ sense, or in the PM sense?  In the C++ sense, we might be 
  1745. indicating that we have used inheritance in our program, while in the PM sense, 
  1746. we are saying that we have replaced an existing window procedure, thus 
  1747. modifying the behavior of the object in question. 
  1748.  
  1749. Our program does the latter, as illustrated by the following snippet: 
  1750.  
  1751. Boolean mainWindow::trackAction(queryTrackEvent &qte)
  1752. {
  1753.    IWindow::defaultProcedure(qte);
  1754.    grabMinTrackSize(qte).x *= 3;
  1755.    grabMinTrackSize(qte).y *=5;
  1756.    return true; // don't call defaultProcedure again!! thanks
  1757. }
  1758.  
  1759. A member function of an IHandler derived class returns true when no further 
  1760. processing of an event is needed.  This subclassing method returns true each 
  1761. time it is called because it has already called IWindow::defaultProcedure(), 
  1762. IBM's C++ equivalent of WinDefWindowProc(). 
  1763.  
  1764. Because of this method of subclassing, WinSubclassWindow() isn't needed - 
  1765. that's all there is to it. 
  1766.  
  1767.  
  1768. ΓòÉΓòÉΓòÉ 2.2.4. Window Procedures in ICLUI ΓòÉΓòÉΓòÉ
  1769.  
  1770. Window Procedures in ICLUI 
  1771.  
  1772. When looking at the definition of the myObjectWindow class, we see that it only 
  1773. needs a dispatchHandlerEvent() procedure to handle events. This member can make 
  1774. no assumptions about what type of events it will be getting. 
  1775.  
  1776. In contrast, command() can assume that it will only be sent WM_COMMAND events, 
  1777. systemCommand() can be sure it will only get WM_SYSCOMMAND events, and 
  1778. trackAction() will only be called on to handle WM_QUERYTRACKINFO events.  These 
  1779. methods don't need to look at the event ID, but in the case of 
  1780. myObjectWindow::dispatchHandlerEvent(), we have to actually look at the event 
  1781. ID and use a switch block. 
  1782.  
  1783.  
  1784. ΓòÉΓòÉΓòÉ 2.2.5. The About Box ΓòÉΓòÉΓòÉ
  1785.  
  1786. The About Box Figure 1)  CLOCK.CPP about box 
  1787.  
  1788. Modifying the original about box presents little trouble.  First, STEP02.RES is 
  1789. loaded into the Dialog Editor.  Be sure to specify MSGDEFINES.H as included in 
  1790. your resource file. We will rename STEP02.RC to CLOCK.RC to distinguish the old 
  1791. from the new. 
  1792.  
  1793. Now, since we are not going to have any children of aboutBoxClass - the class 
  1794. we'll represent the about box with in our program, it would certainly be okay 
  1795. to inherit from IHandler and then define a new dispatchHandlerEvent() method to 
  1796. process the few messages that an about box is concerned with; since all of them 
  1797. will be WM_COMMAND messages anyway, we'll inherit handler functionality from 
  1798. the ICommandHandler class. 
  1799.  
  1800. In either case, IHandler::handleEventsFor() initiates event processing. 
  1801.  
  1802.  
  1803. ΓòÉΓòÉΓòÉ 2.2.6. General Porting Cautions ΓòÉΓòÉΓòÉ
  1804.  
  1805. General Porting Cautions 
  1806.  
  1807. If you undertake a port of your own from C to C++ and the UI libraries, it 
  1808. might save time to note several items. 
  1809.  
  1810. o C++ has keywords that C doesn't, such as class.  Be sure you leave the 
  1811.   #include <os2.h> statement outside of extern "C" blocks that you create and 
  1812.   that you have the \TOOLKT21\CPLUS\OS2H path specified in the environment 
  1813.   variable INCLUDE.  If you use the C includes in your program, you might 
  1814.   encounter compile-time errors caused by leaving these additional reserved 
  1815.   words in. 
  1816.  
  1817. o Be sure to include IHandler or an IHandler descendant along with such 
  1818.   IWindow-derived classes as IObjectWindow and IFrameWindow, whether it be by 
  1819.   ancestry or by membership. 
  1820.  
  1821. o If you register both IFrameHandler and ICommandHandler they will both call 
  1822.   your command() method, and WM_COMMAND event processing will happen twice in 
  1823.   instances where a false is returned indicating that the event wasn't 
  1824.   processed.  For example, if you just mark the occurrence of "Open file..." 
  1825.   menu selections with squawk(), each selection of this menu item will be noted 
  1826.   twice if both ICommandHandler and IFrameHandler are active. 
  1827.  
  1828. o It is a good idea to copy the #define statements showing the hexadecimal 
  1829.   numbers of the messages that you are using in your program. "grep" or another 
  1830.   such tool will easily extract definitions of the window messages such as 
  1831.   WM_CLOSE from your \TOOLKT21\CPLUS\OS2H directory if need be. 
  1832.  
  1833. o The definition of the TRACKINFO is copied from the headers because it isn't 
  1834.   included with what PM header information we use [this is included if you 
  1835.   #define INCL_WINTRACKRECT before #include-ing <os2.h>. Copying from the 
  1836.   header files should be avoided whenever possible to minimize the amount of 
  1837.   program maintenance when the definitions change - editor]. 
  1838.  
  1839. o Any programs that use the IThreadMemberFn<> template must be compiled and 
  1840.   linked to handle templates.  If you use this template class and tell C-Set++ 
  1841.   to ignore template information, your program will compile and run as normal 
  1842.   but it won't create the desired additional threads! 
  1843.  
  1844.  
  1845. ΓòÉΓòÉΓòÉ 2.3. Workplace Shell Development 101 ΓòÉΓòÉΓòÉ
  1846.  
  1847.  
  1848. ΓòÉΓòÉΓòÉ 2.3.1. Introduction ΓòÉΓòÉΓòÉ
  1849.  
  1850. Written by Bj╨ñrn Fahller 
  1851.  
  1852. Introduction 
  1853.  
  1854. Where to begin? 
  1855.  
  1856. While flipping through the System Object Model, Guide and Reference, I thought, 
  1857. "There's a lot in this thin book, although it doesn't seem impossible."  Later 
  1858. while flipping through the list of WPS classes and methods in the PM 
  1859. Programming Reference, I thought, "What are all these classes?  What do all 
  1860. these methods do?"  Procrastination.  Maybe I'll just write a classical program 
  1861. for now, and take up WPS programming in the next project? 
  1862.  
  1863. Do you recognise this line of thinking?  I have done it for a long time now, 
  1864. but decided I have done it for too long, and it was high time to actually do 
  1865. something. 
  1866.  
  1867. To avoid taking on too large a task, and yet do something useful, I decided to 
  1868. try and write an extended program class.  There is a serious limitation to the 
  1869. normal program class; only one object can be dropped on it.  If several objects 
  1870. are dropped, the associated program will start once for every object. What I 
  1871. want, is a class that can send all the files dropped on the program object to 
  1872. the associated program as a series of parameters.  Can this be done? Yes, it 
  1873. can.  Turn to the next page to see how. 
  1874.  
  1875.  
  1876. ΓòÉΓòÉΓòÉ 2.3.2. What Do We Do Now That We Know What To Do? ΓòÉΓòÉΓòÉ
  1877.  
  1878. What Do We Do Now That We Know What To Do? 
  1879.  
  1880. Creating a class description file 
  1881.  
  1882. The first thing to do is to create a class description file.  Classes are 
  1883. described with the language neutral SOM Object Interface Definition language, 
  1884. or OIDL for short.  This is a file with a .CSC extension. I called mine 
  1885. MAPROG.CSC for "Multiple Argument Program." 
  1886.  
  1887. Inheritance 
  1888.  
  1889. In SOM programming, unlike object oriented programming with, for example, C++, 
  1890. all classes must inherit from some class.  In this case, the class to inherit 
  1891. from is obvious.  We want to make a class with the functionality of the program 
  1892. class, although enhanced.  So, let us begin with inheriting from the WPProgram 
  1893. class. First, this is done by including the definition of WPProgram to the .CSC 
  1894. file. 
  1895.  
  1896. include <wppgm.sc>      // We need the definition of WPProgram, so it can be
  1897.                         // inherited from.
  1898.  
  1899. Once this is done, the basics of the class can be declared, as well as 
  1900. declaring the parent class. 
  1901.  
  1902. class: MAProg,
  1903.        external stem   = maprog,
  1904.        local,
  1905.        external prefix = maprog_,
  1906.        classprefix     = maprogM_,
  1907.        major version   = 1,
  1908.        minor version   = 1;
  1909.  
  1910. parent: WPProgram;
  1911.  
  1912. The details of interest in the above is the name of the class, MAProg, and the 
  1913. parent class. The other fields are unimportant for now. 
  1914.  
  1915. What functionality to change? 
  1916.  
  1917. The default behaviour of WPProgram objects, when objects are dropped on them, 
  1918. is to start the associated program once for every object.  We want to start the 
  1919. associated program once, and send all the objects as parameters.  It seems as 
  1920. if wpDrop is a good candidate for a change.  This is declared in MAPROG.CSC as: 
  1921.  
  1922. methods:
  1923.  
  1924. override wpDrop;
  1925.  
  1926. If other methods needed to be overridden, they would be listed below wpDrop. 
  1927. The same would go for adding new methods. 
  1928.  
  1929. For the minimalist, this is all that is needed.  Running the SOM compiler would 
  1930. create a C source file, where the functionality of wpDrop can be changed.  Let 
  1931. us not be minimalists, though. 
  1932.  
  1933.  
  1934. ΓòÉΓòÉΓòÉ 2.3.3. Nifty Additions ΓòÉΓòÉΓòÉ
  1935.  
  1936. Nifty Additions 
  1937.  
  1938. Class methods 
  1939.  
  1940. If the class was implemented as currently described, and the result registered, 
  1941. a template would turn up in the templates folder.  The template would have the 
  1942. name "Program" and the icon of it would be the same as the one for the program 
  1943. class.  Of course, we want at least the name to be different, and a different 
  1944. icon would be nice too.  To achieve this, the two class methods 
  1945. wpclsQueryIconData and wpclsQueryTitle should be overridden. The following 
  1946. lines in MAPROG.CSC take care of that. 
  1947.  
  1948. override wpclsQueryTitle, class;
  1949.  
  1950. override wpclsQueryIconData, class;
  1951.  
  1952. A class method operates on the class itself, while other methods operate on 
  1953. instances of the class, the objects. 
  1954.  
  1955. All classes that need a template should at least override wpclsQueryTitle, so 
  1956. the template will have a unique name. 
  1957.  
  1958. Impatience 
  1959.  
  1960. So now what?  We have declared what, but what about how?  Patience.  The 
  1961. answers will come. 
  1962.  
  1963. Compiling MAPROG.CSC with the SOM Compiler will result in, among others, a 
  1964. MAPROG.C file.  Now, let us deal with how. 
  1965.  
  1966.  
  1967. ΓòÉΓòÉΓòÉ 2.3.4. Implementation ΓòÉΓòÉΓòÉ
  1968.  
  1969. Implementation 
  1970.  
  1971. The drop 
  1972.  
  1973. How to deal with things being dropped?  In maprog.c, the following lines 
  1974. implement the default drop handling: 
  1975.  
  1976. SOM_Scope MRESULT   SOMLINK maprog_wpDrop(MAProg *somSelf,
  1977.                                           HWND hwndCnr,
  1978.                                           PDRAGINFO pdrgInfo,
  1979.                                           PDRAGITEM pdrgItem)
  1980. {
  1981.    /* MAProgData *somThis = MAProgGetData(somSelf); */
  1982.    MAProgMethodDebug("MAProg","maprog_wpDrop");
  1983.  
  1984.    return parent_wpDrop(somSelf, hwndCnr, pdrgInfo, pdrgItem);
  1985. }
  1986.  
  1987. A call to this method can be handled just as a DM_DROP message being sent to a 
  1988. window procedure, except for one little detail.  The method is called once for 
  1989. every object, with pdrgItem pointing to the item currently handled.  That is 
  1990. exactly what we do not want, but there does not seem to be any way to avoid it. 
  1991. Instead a way around it is needed. 
  1992.  
  1993. It is fortunate that all the DRAGITEM structures can be retrieved from the 
  1994. DRAGINFO structure, and some experimentation has shown that the first time the 
  1995. method is called, pdrgItem points to the first DRAGITEM in an array.  One of 
  1996. many ways around the problem, is this: 
  1997.  
  1998.    if (!DrgAccessDraginfo(pdrgInfo)||
  1999.        DrgQueryDragitemPtr(pdrgInfo, 0) != pdrgItem) // Yucky way to make
  2000.                                                      // sure we only deal
  2001.                                                      // with this method
  2002.                                                      // once/drop
  2003.  
  2004.       return 0;
  2005.  
  2006. Who's holding the queue? 
  2007.  
  2008. wpDrop is called as a direct result of a DM_DROP message being received by the 
  2009. WPS, thus we are holding the message queue while processing this method call. 
  2010. We all know how annoying it is with programs that occupy the message queue. 
  2011. WPS objects occupying the message queue is many times worse, so let us avoid 
  2012. that. 
  2013.  
  2014. Getting the information from the DRAGITEM structures, and starting the 
  2015. application with WinStartApp() is simple.  To do it without unnecessarily 
  2016. occupying the message queue, collect the names of the objects being dropped as 
  2017. quickly as possible, and start a parameter parsing and application starting 
  2018. thread, and return from the method call.  The queue is free, and the 
  2019. application can be started. 
  2020.  
  2021.    for (i=0; i < arguments; i++) {
  2022.       char *s = NULL;
  2023.       PDRAGITEM p = DrgQueryDragitemPtr(pdrgInfo, i);
  2024.       int lenContainer = DrgQueryStrNameLen(p->hstrContainerName);
  2025.       int lenSource = DrgQueryStrNameLen(p->hstrSourceName);
  2026.  
  2027.       argument[i] = (char *)_wpAllocMem(somSelf,
  2028.                                         lenContainer + lenSource +1,
  2029.                                         FALSE);
  2030.       size+= lenContainer + lenSource;
  2031.  
  2032.       if (argument[i]) {
  2033.          DrgQueryStrName(p->hstrContainerName,
  2034.                          lenContainer+1,
  2035.                          argument[i]);
  2036.          DrgQueryStrName(p->hstrSourceName,
  2037.                          lenSource+1,
  2038.                          argument[i]+lenContainer);
  2039.       } /* endif */
  2040.    } /* endfor */
  2041.    pi->somSelf = somSelf;
  2042.    pi->pDragInfo = pdrgInfo;
  2043.    pi->arguments = arguments;
  2044.    pi->argument = argument;
  2045.    _beginthread(startThread, NULL, 4096, (PVOID)pi);
  2046.    DrgDeleteDraginfoStrHandles(pdrgInfo);
  2047.    DrgFreeDraginfo(pdrgInfo);
  2048.    return 0;
  2049.  
  2050. A problem with the above is that dynamic memory management is necessary since 
  2051. the amount of memory needed for the strings cannot be determined at compile 
  2052. time.  This can lead to... 
  2053.  
  2054. Memory leaks? 
  2055.  
  2056. The normal way to allocate dynamic memory when needed, is to malloc() the 
  2057. storage.  If, for some reason, you forget to free a pointer given by malloc(), 
  2058. the storage will be deallocated when the process terminates; therein lies the 
  2059. problem with malloc and WPS programming.  Every byte you allocate with malloc 
  2060. belongs to the same process, the WPS itself.  Every pointer you forget to free 
  2061. will make the whole system perform worse and worse. 
  2062.  
  2063. Fortunately, there is half a salvation, an object-bound malloc-like call. 
  2064. Memory allocated with _wpAllocMem will be deallocated when the object that 
  2065. allocated it, is no longer in use.  Deallocate the memory with _wpFreeMem. 
  2066.  
  2067. Although malloc() works, avoid it whenever possible, and if you for whatever 
  2068. reason must use malloc, be very careful. The last thing you want to hear your 
  2069. customers say is that their whole system became sluggish and unstable after 
  2070. installing your program. 
  2071.  
  2072.  
  2073. ΓòÉΓòÉΓòÉ 2.3.5. Conclusion ΓòÉΓòÉΓòÉ
  2074.  
  2075. Conclusion 
  2076.  
  2077. If someone with less than one week's experience can write a WPS class, anyone 
  2078. can.  A week before writing this, I knew nothing about WPS programming. Now, 
  2079. after having completed this mini-project, I'm working on a real WPS 
  2080. application, so let's get some WPS apps out there! 
  2081.  
  2082.  
  2083. ΓòÉΓòÉΓòÉ 3. Columns ΓòÉΓòÉΓòÉ
  2084.  
  2085. The following columns can be found in this issue: 
  2086.  
  2087. o Book Review - Real World Programming for OS/2 2.1 
  2088. o C++ Corner 
  2089. o Introduction to PM Programming 
  2090. o Scratch Patch 
  2091.  
  2092.  
  2093. ΓòÉΓòÉΓòÉ 3.1. Book Review - Real World Programming for OS/2 2.1 ΓòÉΓòÉΓòÉ
  2094.  
  2095.  
  2096. ΓòÉΓòÉΓòÉ 3.1.1. Introduction ΓòÉΓòÉΓòÉ
  2097.  
  2098. Written by Carsten Whimster 
  2099.  
  2100. Introduction 
  2101.  
  2102. The Book Review column is a new monthly column which will focus on development 
  2103. oriented books and material.  This will mostly consist of programming books, 
  2104. such as this month's Real World Programming for OS/2 2.1, but could also cover 
  2105. books like The Design of OS/2.  The column will be from a beginning PM 
  2106. programmer's eyes (because that's what I am), and hopefully that is most useful 
  2107. to the community in general.  As I get on with the column, however, I expect to 
  2108. get better, and so within a year or so, I will (presumably  :) qualify as an 
  2109. intermediate programmer, or perhaps even somewhat of an expert.  Hopefully you, 
  2110. the readers, will move with me in that respect.  When reading this column, try 
  2111. to pick up whichever book strikes your fancy, and join the group of people 
  2112. following our introductory PM programming columns.  I will try to review books 
  2113. aimed at beginners to start with, and then move on from there. 
  2114.  
  2115. The column will largely be demand-oriented (after I get my own books out of the 
  2116. way early on), so if you have a book you would like reviewed, send me e-mail 
  2117. and tell me about it, or even better, send me the book (sorry, no returns). 
  2118. Finally, please send me your comments and thoughts so that I can make this 
  2119. column as effective as possible.  After all, this is our magazine. 
  2120.  
  2121. This month's selection was chosen because it is the one of my books I like best 
  2122. :).  It was recommended to me on CompuServe, and has a good reputation as a 
  2123. beginner's PM programming book. 
  2124.  
  2125.  
  2126. ΓòÉΓòÉΓòÉ 3.1.2. Review ΓòÉΓòÉΓòÉ
  2127.  
  2128. Review 
  2129.  
  2130. Real World Programming for OS/2 2.1 is a fairly hefty book at no less than 868 
  2131. pages!  It is aimed at (it says here, on the back) intermediate to advanced 
  2132. (users or programmers?), and is meant as a PM programming how-to book.  It 
  2133. comes with a 3.5" diskette with all the source code from the book.  It even has 
  2134. its own installation program, and a very nice one at that.  The authors 
  2135. encourage you to use the source code from the book in your own programs, 
  2136. although they do insist that enough modifications should have been made to make 
  2137. the code basically your own work. 
  2138.  
  2139. After installing the sample code, it is recommended that you set or check the 
  2140. COMPILER, PATH, INCLUDE, and LIBPATH environment variables.  I also found it 
  2141. necessary to set the LIB environment variable.  Makefiles are included for IBM 
  2142. CSet, Borland C++,Watcom C/C++, Zortech C++, and Microsoft's C compiler. Once 
  2143. the environment variables are set properly, each of the sample apps can be 
  2144. recompiled in their own sub-directory by a file called GO.CMD, in case you 
  2145. should want to try your own modifications to the sample code (which is 
  2146. recommended by the authors.) 
  2147.  
  2148. To familiarize you a little with the book, I have included the titles of the 
  2149. chapters here: 
  2150.  
  2151.  1. Programming for OS/2 
  2152.  2. OS/2 Application Window Fundamentals 
  2153.  3. Window Management 
  2154.  4. 2.1 Common Dialogs 
  2155.  5. Menus 
  2156.  6. Presentation Spaces and Drawing 
  2157.  7. Creating and Manipulating PM Fonts and Text 
  2158.  8. Profile Management 
  2159.  9. Memory Management 
  2160. 10. Dynamic Link Libraries 
  2161. 11. Printing 
  2162. 12. Threads and Semaphores 
  2163. 13. Calling 16-Bit Code 
  2164. 14. Communication Basics 
  2165. 15. Miscellaneous Topics 
  2166.  
  2167. I haven't read every chapter of course, but I have used material from a 
  2168. surprising number of them.  The authors recommend that you read chapters one 
  2169. through four, and then go on to whatever chapters interest you, but I only made 
  2170. it through the first three before curiousity and need took me elsewhere. All 
  2171. the chapters have an introduction explaining the importance of the material 
  2172. before launching into the code example.  Each chapter has a sample program to 
  2173. demonstrate the use of the various APIs involved, as well as showing a possible 
  2174. application for these calls.  Code from these sample programs is introduced in 
  2175. the text of each chapter, and at or near the end of each chapter the full 
  2176. source code is presented. 
  2177.  
  2178. Although this book is large, it is not a complete coverage of the API, nor is 
  2179. it meant as such.  It is intended to get the reader started into various 
  2180. important intermediate areas, and to expose what the authors consider the more 
  2181. useful function calls.  The book is sometimes a little sparse in its coverage 
  2182. of some of the relevant data structures, but this is usually easily rectified 
  2183. by cross-referencing with the IBM toolkit reference material. 
  2184.  
  2185. Due to the vast nature of the complete OS/2 API, there are (and must be) areas 
  2186. of interest which the authors for whatever reason, have decided to treat 
  2187. lightly, or skip altogether.  Unfortunately, the application I was working on 
  2188. uses several features of the API which aren't covered well in the book.  This 
  2189. necessitated a lot of foot-work which might have been avoided otherwise.  For 
  2190. example, the TRACKINFO structure used to control frame windows is mentioned 
  2191. only once, and briefly at that.  I also found it difficult to extract the 
  2192. correct information about pop-up menus from the complicated examples to build 
  2193. my own fairly simple pop-up, but eventually I did succeed.  Frequently, I found 
  2194. that the sample code had a large number of intricately woven features which 
  2195. made it quite tricky to pull out just the necessary bits.  In spite of having a 
  2196. good reputation for beginners, it probably is better off targeted at developers 
  2197. who have used the PM API for a few months already, and who consequently have 
  2198. the necessary understanding of the way things work to extract the needed parts, 
  2199. all the needed parts, and nothing but the needed parts from the sample code. 
  2200.  
  2201. Another small, but noticeable flaw is the index at the back.  It has most, if 
  2202. not all of the actual API names and data structures, but when you don't know 
  2203. the name of what you are looking for (which presumably will happen frequently, 
  2204. otherwise use the reference material instead of a programming book,) it can be 
  2205. a tedious job trying to imagine what the name of a certain call or data 
  2206. structure might be.  Unfortunately this is quite common among books, and 
  2207. programming books in particular, and is not just a fault with this book. 
  2208.  
  2209. Despite these minor concerns, the book remains a favorite of mine, and I use it 
  2210. frequently.  The lucid writing, the good organization, and the depth of the 
  2211. material make it enjoyable most of the time.  I recommend this book to anyone 
  2212. willing to dig a little, and who wants something a little more than an 
  2213. introductory book, with useful sample code, and a great breadth of coverage. 
  2214.  
  2215.  
  2216. ΓòÉΓòÉΓòÉ 3.1.3. Summary and Ratings ΓòÉΓòÉΓòÉ
  2217.  
  2218. Summary and Ratings 
  2219.  
  2220. This book is a good buy overall, although it is a little lacking in some areas. 
  2221. It was recommended to me as a beginning PM programmer's book, but I see it as 
  2222. being a little too complex for that, unless you have experience in programming 
  2223. some other GUI.  The skeleton program is good, but separating out the code you 
  2224. need from the more advanced examples can be tedious and difficult.  Had the 
  2225. pace of chapters two and three been more leisurely, I would have picked up a 
  2226. lot more, and felt a lot more at ease with it.  As it was, I found myself 
  2227. constantly picking it up, putting it down, looking elsewhere for simpler 
  2228. examples, and then picking it up again to give it another try.  If I had a bit 
  2229. of PM, X, Mac or Windows programming background, this book would have been 
  2230. perfect. 
  2231.  
  2232. The example programs in the chapters are very good, though, and as the authors 
  2233. like to say, much more than just the normal do-nothing programs that many books 
  2234. have.  The INITOR application actually made it into my \BIN directory with my 
  2235. other utilities, and most of the other programs were interesting at the very 
  2236. least.  Intermediate or experienced PM programmers should love it. 
  2237.  
  2238. ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
  2239. ΓöéBOOK                            ΓöéAUDIENCE    ΓöéMARKΓöéCOMMENTS                           Γöé
  2240. Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
  2241. ΓöéReal World Programming for OS/2 ΓöéIntermediateΓöéB+  ΓöéLots of good code examples, but    Γöé
  2242. Γöé2.1, Blain, Delimon, and        Γöéto Advanced Γöé    Γöésometimes it is too complex for    Γöé
  2243. ΓöéEnglish, SAMS Publishing.  ISBN ΓöéPM C        Γöé    Γöénovices. Accurate.  Well organized.Γöé
  2244. Γöé0-672-30300-0.  US $40, CAN $50.Γöéprogrammers Γöé    ΓöéThe index needs a little beefing   Γöé
  2245. Γöé                                Γöé            Γöé    Γöéup.  Good, but not entirely        Γöé
  2246. Γöé                                Γöé            Γöé    Γöécomplete how-to reference.  Good   Γöé
  2247. Γöé                                Γöé            Γöé    Γöépurchase.                          Γöé
  2248. ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
  2249.  
  2250. This table will contain all books I have reviewed, so that books can be 
  2251. compared against each other.  I will be very careful to rate books fairly 
  2252. relative to each other.  I will rate conservatively until I get a better feel 
  2253. for what's out there.  If I feel a need to adjust ratings, I will adjust all of 
  2254. them at the same time, and write a note explaining why I felt this necessary, 
  2255. for example, a new book came out which was so good that there wasn't enough 
  2256. room at the top of the scale to properly convey this.  :) 
  2257.  
  2258. BOOK:  The name of the book, author(s), publishing company, ISBN, and price 
  2259. (approximate.) 
  2260.  
  2261. AUDIENCE:  This is a description of the audience I think the book targets best. 
  2262. This is not intended as gospel, just a guideline for people not familiar with 
  2263. the book. 
  2264.  
  2265. MARK:  My opinion of the success of the book's presentation, and how well it 
  2266. targets its audience.  Technical content, technical accuracy, organization, 
  2267. readability, and quality of index all weigh heavily here.  I may revise, 
  2268. expand, or add categories in the future.  I don't expect to see any book score 
  2269. less than C, but the scale is there if necessary. 
  2270.  
  2271. A+        Ground-breaking, all-around outstanding book. 
  2272. A         Excellent book. This is what I want to see happen a lot. 
  2273. A-        Excellent book with minor flaws or omissions. 
  2274. B+        Very good book with minor flaws or omissions. 
  2275. B         Good book with some flaws or omissions. 
  2276. B-        Good book, but in need of improvement. 
  2277. C         Mediocre book with some potential, but in need of repairing. 
  2278. D         Don't buy this book unless you need it, and nothing else exists. 
  2279. F         Don't buy this book. Period. 
  2280.  
  2281. COMMENTS:  To some extent, this is a summary of the review proper, but also 
  2282. explains why the book may have gotten a better/worse mark than otherwise 
  2283. expected.  For example, if a book is very popular but didn't fare too well, 
  2284. this discrepancy would be explained here. 
  2285.  
  2286.  
  2287. ΓòÉΓòÉΓòÉ 3.1.4. Coming Up ΓòÉΓòÉΓòÉ
  2288.  
  2289. Coming Up 
  2290.  
  2291. Next month I will be looking at Learning to Program OS/2 2.0 Presentation 
  2292. Manager by Example, Knight.  Other books I intend to review are: 
  2293.  
  2294. o The Art of OS/2 C Programming, Panov and Salomon 
  2295. o OS/2 Presentation Manager Programming, Petzold - 1994 
  2296. o OS/2 Presentation Manager GPI, Winn 
  2297. o The Design of OS/2, 2nd Edititon, Kogan and Deitel - 1994 
  2298.  
  2299. This list is not set in stone, but they are books I am interested in.  I am 
  2300. considering reviewing OS/2 Unleashed, but it is not strictly speaking a 
  2301. development book, so I'm going to wait until the list of real development books 
  2302. has diminished a bit.  By the way, does anyone know why the special edition of 
  2303. OS/2 Unleashed has completely different authors? 
  2304.  
  2305. If anyone has a book they would like to have reviewed, I will be happy to 
  2306. oblige, so long as I can afford it, ie.  no $500 books.  :)  Of course, the 
  2307. request would be fulfilled a lot quicker if accompanied by a book. :) 
  2308.  
  2309.  
  2310. ΓòÉΓòÉΓòÉ 3.2. C++ Corner ΓòÉΓòÉΓòÉ
  2311.  
  2312.  
  2313. ΓòÉΓòÉΓòÉ 3.2.1. Introduction ΓòÉΓòÉΓòÉ
  2314.  
  2315. Written by Gordon Zeglinski 
  2316.  
  2317. Introduction 
  2318.  
  2319. In this issue, we will get back to encapsulating bits and pieces of the OS/2 
  2320. API.  Instead of just dumping out the final version, I will take an iterative 
  2321. approach.  In this issue, the first version of a queue encapsulation hierarchy, 
  2322. will be developed.  In subsequent issues, a refined version will be presented. 
  2323. The focus here, will be on the API calls, and encapsulation concepts. 
  2324.  
  2325. Loose Ends 
  2326.  
  2327. In the last issue, the makefile distributed to build the Watcom version of POV 
  2328. 2.1 was actually a NMAKE file not a WMAKE file. 
  2329.  
  2330. I recently added 4 more megs of RAM to the system here.  This has greatly 
  2331. improved the stability of the WorkFrame.  I suspect its instability was somehow 
  2332. related to swapping.  Hopefully the OS/2 2.1 CSD will be out soon, and help 
  2333. improve its stability even more. 
  2334.  
  2335.  
  2336. ΓòÉΓòÉΓòÉ 3.2.2. Queue Basics ΓòÉΓòÉΓòÉ
  2337.  
  2338. Queue Basics 
  2339.  
  2340. What are Queues? 
  2341.  
  2342. A queue (as implemented by the OS/2 kernel) is a linked list of elements 
  2343. allowing inter-process communication.  Each record in the list contains a 
  2344. pointer to the data, the length of the data, and a ULONG parameter.  The queue 
  2345. can be a FIFO (first-in, first-out) or LIFO (last-in, first out) and can have 
  2346. up to 16 priority levels.  The queue is a one way communication method from the 
  2347. client to the server. 
  2348.  
  2349.  
  2350.             ΓòöΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòù
  2351.             ΓòæULONG ParameterΓòæ - Application Defined
  2352.             ΓòƒΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓòó     (similar to message IDs in PM)
  2353.             ΓòæData Pointer   Γòæ
  2354.             ΓòƒΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓòó
  2355.             ΓòæData Length    Γòæ
  2356.             ΓòÜΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓòÉΓò¥
  2357.  
  2358. Visualization of a queue element 
  2359.  
  2360. You should have noted that queues use pointers to the application defined data. 
  2361. This means that when the client and server are in different processes, shared 
  2362. memory must be used.  Using shared memory efficiently will be a refinement 
  2363. examined in the next revision of this class. 
  2364.  
  2365. The Queue API 
  2366.  
  2367. OS/2 provides the following functions to manipulate queues.  Our goal is to 
  2368. encapsulate these functions for both the client and server processes. 
  2369.  
  2370. o DosCloseQueue() 
  2371. o DosCreateQueue() 
  2372. o DosOpenQueue() 
  2373. o DosPeekQueue() 
  2374. o DosPurgeQueue() 
  2375. o DosQueryQueue() 
  2376. o DosReadQueue() 
  2377. o DosWriteQueue() 
  2378.  
  2379. We will examine some of these functions in this column.  The reader is referred 
  2380. to the Control Program Reference and Guide for an explanation of those 
  2381. functions not covered and for more details on those covered.  As with all 
  2382. kernel functions, a return code of 0 indicates that no error occurred. 
  2383.  
  2384. DosCreateQueue() 
  2385.  
  2386. This function is used by the server process to create the queue.  Its syntax 
  2387. follows: 
  2388.  
  2389. APIRET DosCreateQueue(PHQUEUE phqHandle,
  2390.                       ULONG ulFlags,
  2391.                       PSZ pszName);
  2392.  
  2393. phqHandle           The variable pointed to by this parameter will contain the 
  2394.                     queue handle on return. 
  2395. ulFlags             Used to set the queue type, one of the QUE_FIFO, QUE_LIFO, 
  2396.                     or QUE_PRIORITY flags can be or'd with one of 
  2397.                     QUE_NOCONVERT_ADDRESS or QUE_CONVERT_ADDRESS. 
  2398. pszName             The name of the queue. It must have the prefix "\QUEUES\" . 
  2399.  
  2400. DosOpenQueue() 
  2401.  
  2402. This function is used by the client process to obtain access to the queue. Its 
  2403. syntax follows: 
  2404.  
  2405. APIRET DosOpenQueue(PPID ppidOwner,
  2406.                     PHQUEUE phqHandle,
  2407.                     PSZ pszName);
  2408.  
  2409. The parameters of this function are the same as for the DosCreateQueue() 
  2410. function, except for the ppidOwner parameter.  This parameter is used to return 
  2411. the process ID of the queue's creator to the process calling this function, 
  2412. which can later be used to grant access to the shared memory object. 
  2413.  
  2414. DosWriteQueue() 
  2415.  
  2416. This function is used by the client to place data into the queue.  Its syntax 
  2417. follows: 
  2418.  
  2419. APIRET DosWriteQueue(HQUEUE hqHandle,
  2420.                      ULONG ulParam,
  2421.                      ULONG ulSzBuf,
  2422.                      PVOID pvBuffer,
  2423.                      ULONG ulPriority);
  2424.  
  2425. hqHandle            The handle of the queue to be written to. 
  2426. ulParam             The parameter in the queue record discussed previously. 
  2427. ulSzBuf             Lenght of the data buffer. 
  2428. pvBuffer            Pointer to the data buffer. 
  2429. ulPriority          If the queue was created as a priority based queue, then 
  2430.                     this contains the elements priority.  Otherwise, it is 
  2431.                     ignored. 
  2432.  
  2433. DosReadQueue() 
  2434.  
  2435. This function is used by the server to read data from the queue.  Its syntax 
  2436. follows: 
  2437.  
  2438. APIRET DosReadQueue(HQUEUE hqHandle,
  2439.                     PREQUESTDATA prdRequest,
  2440.                     PULONG pulSzData,
  2441.                     PPVOID ppvData,
  2442.                     ULONG ulCode,
  2443.                     BOOL32 bNoWait,
  2444.                     PBYTE pbPriority,
  2445.                     HEV hevSemaphore);
  2446.  
  2447. hqHandle            The handle of the queue to be read from. 
  2448. prdRequest          Pointer to a REQUESTDATA structure. 
  2449. pulSzData           Pointer to the variable which will receive the length of 
  2450.                     the data item. 
  2451. ppvData             Pointer to the variable which will receive the address of 
  2452.                     the data item. 
  2453. ulCode              A value of 0 is used to read the first element from the 
  2454.                     queue.  A value returned by DosPeekQueue() can be used to 
  2455.                     read the element previously peeked at. 
  2456. bNoWait             If 0, the thread will block until a data element is in the 
  2457.                     queue if one is not already present.  If 1, the thread 
  2458.                     returns immediately. 
  2459. pbPriority          Pointer to the variable which will hold the elements 
  2460.                     priority. 
  2461. hevSemaphore        Handle of an event semaphore which will be posted when a 
  2462.                     element is placed into the queue and the bNoWait is 1. 
  2463.  
  2464. The REQUESTDATA structure is defined as follows: 
  2465.  
  2466. typedef struct _REQUESTDATA
  2467. {
  2468.    PID pid;       // PID of the process which placed the element in the queue
  2469.    ULONG ulData;  // The Param parameter of DosWriteQueue
  2470. } REQUESTDATA;
  2471.  
  2472. Note:  the first time this function is called with a bNoWait of 1, the handle 
  2473. of the event semaphore is stored by the system; this handle must be used in 
  2474. subsequent reads from the queue.  The event semaphore must also be created 
  2475. using the DC_SEM_SHARED flag. 
  2476.  
  2477.  
  2478. ΓòÉΓòÉΓòÉ 3.2.3. Encapsulating the Queue API ΓòÉΓòÉΓòÉ
  2479.  
  2480. Encapsulating the Queue API 
  2481.  
  2482. We start the encapsulation process by listing the properties of queues that we 
  2483. wish to encapsulate: 
  2484.  
  2485. o Reading of queue elements 
  2486. o Writing of queue elements 
  2487. o Creating the queue 
  2488. o Opening the queue 
  2489. o Manipulating the shared memory 
  2490.  
  2491. We also note that the queue has two separate sides, a client and a server. 
  2492. These two sides have several properties in common which will lead us to this 
  2493. first approximation to the queue hierarchy: 
  2494.  
  2495.             ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
  2496.             ΓöéQueueObjΓöé
  2497.             ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
  2498.               Γöé    Γöé
  2499. ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ  ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
  2500. ΓöéClientQueueObjΓöé  ΓöéServerQueueObjΓöé
  2501. ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ  ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
  2502.  
  2503. Queue Hierarchy 
  2504.  
  2505. We will now look at the design of these three classes. 
  2506.  
  2507. QueueObj 
  2508.  
  2509. As the base class for this hierarchy, QueueObj provides the data members that 
  2510. are common to both the client and the server, as well as various support 
  2511. functions.  These data members include the names of the queue and shared memory 
  2512. object, the handle of the queue, the address of the shared memory, and the size 
  2513. of the shared memory.  For the first pass, the shared memory will be maintained 
  2514. by member functions of the hierarchy. 
  2515.  
  2516. The class definition follows: 
  2517.  
  2518. class QueueObj
  2519. {
  2520.    public:
  2521.    enum _Action { None, Create, Open };
  2522.  
  2523.    _exp_ QueueObj(char *ShMemNm,
  2524.                   unsigned long MemSize,
  2525.                   char *QueueNm,
  2526.                   unsigned long flag=QUE_PRIORITY,
  2527.                   _Action Mem=None,
  2528.                   _Action Queue=None);
  2529.    virtual _exp_ ~QueueObj();
  2530.  
  2531.    unsigned long GetError() { return Error; }
  2532.  
  2533.    void _exp_ SetMemName(char *N);
  2534.    void _exp_ OpenMem(unsigned long MemSize);
  2535.    void _exp_ CreateMem(unsigned long MemSize);
  2536.  
  2537.    void _exp_ SetQueueName(char *N);
  2538.    void _exp_ OpenQueue();
  2539.    void _exp_ CreateQueue(unsigned long flag=QUE_PRIORITY);
  2540.  
  2541.    PID GetOwner() { return pidQueueOwner; }
  2542.  
  2543.    protected:
  2544.    typedef unsigned long HQUEUE;
  2545.  
  2546.    HQUEUE hqueue;
  2547.  
  2548.    char *ShareMemName;
  2549.    char *QueueName;
  2550.    char *MemBase;
  2551.    unsigned long ShMemSize;
  2552.  
  2553.    PID pidQueueOwner;
  2554.  
  2555.    unsigned long Error;
  2556. };
  2557.  
  2558. This class provides support methods for both the client and server classes to 
  2559. use.  These methods allow both the shared memory and the queue to be named, 
  2560. created, or opened. 
  2561.  
  2562. ClientQueueObj 
  2563.  
  2564. class ClientQueueObj : public QueueObj
  2565. {
  2566.    public:
  2567.    _exp_ ClientQueueObj(char *ShMemNm,
  2568.                         unsigned long MemSize,
  2569.                         char *QueueNm,
  2570.                         unsigned long flag=QUE_PRIORITY,
  2571.                         int Reserv=0,
  2572.                         _Action Mem=Open,
  2573.                         _Action Queue=Open);
  2574.  
  2575.    int _exp_ Write(ULONG Req,ULONG Priority=15);
  2576.    int _exp_ Write(ULONG Req,char *Buff,ULONG BuffSize,ULONG Priority=15);
  2577.  
  2578.    void * _exp_ RequestMemBlock(unsigned long BlSize);
  2579.    void SetReserveLen(int Len) { ReservedLen=Len; }
  2580.  
  2581.    protected:
  2582.    char *CurrLoc;
  2583.    int CurrLenght;
  2584.    int ReservedLen;
  2585. };
  2586.  
  2587. By default, the constructor for the client object opens the queue and shared 
  2588. memory.  This object is responsible for maintaining the position of the free 
  2589. space in the shared memory object.  The variable ReservedLen is used to reserve 
  2590. a section of shared memory from the start.  The member function Write() uses 
  2591. the variables CurrLoc and CurrLength as parameters in the call to 
  2592. DosWriteQueue(). 
  2593.  
  2594. To write to the queue, the RequestMemBlock() member function is used to request 
  2595. a block of shared memory.  This block is then initialized by the application, 
  2596. and either version of the member function Write() is called.  The second form 
  2597. of the member function requires that the location and length of the shared 
  2598. memory block be specific as parameters while the first assumes the last request 
  2599. block of shared memory is the data buffer source. 
  2600.  
  2601. ServerQueueObj 
  2602.  
  2603. class ServerQueueObj : public QueueObj
  2604. {
  2605.    public:
  2606.    _exp_ ServerQueueObj(char *ShMemNm,
  2607.                         unsigned long MemSize,
  2608.                         char *QueueNm,
  2609.                         unsigned long flag=QUE_PRIORITY,
  2610.                         EventSemaphore *RS=NULL,
  2611.                         _Action Mem=Create,
  2612.                         _Action Queue=Create);
  2613.  
  2614.    PID GetPID() { return ReqDat.pid; }
  2615.    ULONG UserData() { return ReqDat.ulData; }
  2616.    void _exp_ PostSem();
  2617.    void _exp_ ResetSem();
  2618.    void _exp_ WaitOnSem();
  2619.  
  2620.    void* _exp_ Read(ULONG ElCode=0, BOOL wait=0);
  2621.    void* _exp_ Peek(ULONG ElCode=0);
  2622.  
  2623.    ULONG GetDataLen() { return DataLen; }
  2624.    BYTE GetPriority() { return Priority; }
  2625.  
  2626.    protected:
  2627.    EventSemaphore *ReadSem;
  2628.    REQUESTDATA ReqDat;
  2629.    ULONG DataLen;
  2630.    void *Entry;
  2631.    BYTE Priority;
  2632. };
  2633.  
  2634. The constructor for the server object creates both the shared memory and queue. 
  2635. It contains data members and member functions to manipulate the various 
  2636. parameters to the DosReadQueue() function, mentioned earlier.  In addition to 
  2637. this, it also has a pointer to an event semaphore object.  As this issue 
  2638. focuses on queues, I will not go into details on the semaphore objects. 
  2639.  
  2640. This object is a bit easier to use.  After creating an instance of it, the read 
  2641. function is called to remove an element from the queue if one is present. 
  2642.  
  2643.  
  2644. ΓòÉΓòÉΓòÉ 3.2.4. Using the Objects ΓòÉΓòÉΓòÉ
  2645.  
  2646. Using the Objects 
  2647.  
  2648. We will now test the queue server and client objects by creating two simple 
  2649. applications.  The client application will prompt the user to numerical input. 
  2650. It will place this numerical input into the queue, at which point the server 
  2651. will echo the input to the display.  Below is the source code to the queue 
  2652. server with comments added. 
  2653.  
  2654. /*  These definitions are contained in QDefs.h
  2655.  
  2656. #define QName         "DemoQueue"   //Name of the queue
  2657. #define SMemName      "DemoMem"     //Name of the shared memory
  2658.  
  2659. #define NemSize       1024*8        //size of the shared memory block
  2660.                                     //in bytes
  2661.  
  2662. struct QueueMessage{                //format of the queue data packet
  2663.    int Number;
  2664.  
  2665. };
  2666. */
  2667.  
  2668. int main(){
  2669.  
  2670.    int loop;
  2671.    QueueMessage *Mesg;        // pointer to the data packet
  2672.  
  2673.    //create a shared event semphore
  2674.    EventSemaphore Sem((char*)NULL,Semaphore::SemCreate,DC_SEM_SHARED);
  2675.  
  2676.    //create the queue object.
  2677.    //This also creates the shared memory and the actual queue
  2678.    ServerQueueObj InQueue(SMemName,NemSize,QName,0,&Sem);
  2679.  
  2680.  
  2681.    loop=1;
  2682.  
  2683.    //reset the event semaphore
  2684.    Sem.Reset();
  2685.  
  2686.    cout<<"Queue Server Active !"<<endl;
  2687.  
  2688.    while(loop){
  2689.  
  2690.       //attempt to read an element from the queue
  2691.       Mesg=(QueueMessage *) InQueue.Read(0,1);
  2692.  
  2693.       //read from queue until an element occurs
  2694.       while(InQueue.GetError() ){
  2695.          //this semaphore will be posted as soon as an element is put in the queue
  2696.          Sem.Wait();
  2697.          Mesg=(QueueMessage *) InQueue.Read(0,1);
  2698.       }
  2699.  
  2700.  
  2701.       //terminate is the number was -1, else echo the number
  2702.       if(Mesg->Number == -1){
  2703.          loop=0;
  2704.          cout<<"Terminating"<<endl;
  2705.       }
  2706.       else{
  2707.          cout<<"Number= "<<Mesg->Number<<endl;
  2708.       }
  2709.    }
  2710.  
  2711. return 0;
  2712. }
  2713.  
  2714. The queue encapsulation has made using the queues much less tedious.  The code 
  2715. for the client is very similar.  Therefore, it is left as an exercise for the 
  2716. curious to discover how it works. 
  2717.  
  2718. There are limitations with the current queue implementation. They are: 
  2719.  
  2720. o Only 1 client may use the shared memory pool at a time. 
  2721. o Only 1 thread in the client process may use the shared memory pool at a time. 
  2722.  
  2723. The following files are included with this issue: 
  2724.  
  2725. MAKE.CMD            Rexx .CMD file to make the server and client. Assumes the 
  2726.                     compiler is IBM C-Set++. 
  2727. QCLIENT.CPP         Main routine for the client. 
  2728. QSERVE.CPP          Main routine for the server. 
  2729. QUEUEOBJ.CPP        Queue object member function definitions. 
  2730. QUEUEOBJ.H          QueueObj class definition. 
  2731. QDEFS.H             Header used in the client a server source files. 
  2732. SEMTIMOBJ.CPP       Semaphore member functions. 
  2733. SEMTIMOBJ.H         Semaphore object definitions. 
  2734. QSERVE.EXE          Server executable. 
  2735. QCLIENT.EXE         Client executable. 
  2736.  
  2737. Although this code is compiled using C-Set++, other compilers should be able to 
  2738. compile the source files. 
  2739.  
  2740. Note:  QSERVE.EXE must be run before QCLIENT.EXE . 
  2741.  
  2742.  
  2743. ΓòÉΓòÉΓòÉ 3.2.5. Summary ΓòÉΓòÉΓòÉ
  2744.  
  2745. Summary 
  2746.  
  2747. This concludes our first attempt at encapsulating the queue objects.  We have 
  2748. developed queue objects which encapsulate the creation and manipulation of 
  2749. queues and the shared memory they require.  In a future issue, the next version 
  2750. of the queue objects will be presented.  The goal for the next version is to 
  2751. eliminate the constraints that exist in this version. 
  2752.  
  2753.  
  2754. ΓòÉΓòÉΓòÉ 3.3. Introduction to PM Programming ΓòÉΓòÉΓòÉ
  2755.  
  2756.  
  2757. ΓòÉΓòÉΓòÉ 3.3.1. Introduction ΓòÉΓòÉΓòÉ
  2758.  
  2759. Written by Larry Salomon, Jr. 
  2760.  
  2761. Introduction 
  2762.  
  2763. The purpose of this column is to provide the readers out there wh9 are not 
  2764. familiar with PM application development the information necessary to satisfy 
  2765. their curiousity, educate themselves, and give them an advantage over the 
  2766. documentation supplied by IBM.  Of course, much of this stuff could probably be 
  2767. found in one of the many books out there, but the problem with books in general 
  2768. is that they don't answer the questions you have after you read the book the 
  2769. first time through. 
  2770.  
  2771. I will gladly entertain feedback from the readers about what was "glossed over" 
  2772. or what was detailed well, what tangential topics need to be covered and what 
  2773. superfluous crap should have been removed.  This feedback is essential in 
  2774. guaranteeing that you get what you pay for.  :) 
  2775.  
  2776. It should be said that you must not depend solely on this column to teach you 
  2777. how to develop PM applications; instead, this should be viewed as a supplement 
  2778. to your other information storehouses (books, the network conferences, etc.). 
  2779. Because this column must take a general approach, there will be some topics 
  2780. that you would like to see discussed that really do not belong here.  Specific 
  2781. questions can be directed to the Scratch Patch, where an attempt to answer them 
  2782. will be made. 
  2783.  
  2784.  
  2785. ΓòÉΓòÉΓòÉ 3.3.2. Last Month ΓòÉΓòÉΓòÉ
  2786.  
  2787. Last Month 
  2788.  
  2789. Last month, we finished dissecting the (initial) HELLO program that was 
  2790. introduced in volume 2, issue 1.  This month we will starts to look at a 
  2791. significantly changed version of HELLO in order to set the direction of this 
  2792. column for the next few issues.  To recap, though, HELLO's purpose was simply 
  2793. to demonstrate the minimum number of calls required to do anything useful. 
  2794. Other than that, it didn't do anything useful.  :) 
  2795.  
  2796. Up to this point, we have introduced and continued discussion on two areas of 
  2797. PM application development - messages and functions.  Now we are going to add 
  2798. two more - dialog box implementation and resource files. 
  2799.  
  2800.  
  2801. ΓòÉΓòÉΓòÉ 3.3.3. Dialog Boxes ΓòÉΓòÉΓòÉ
  2802.  
  2803. Dialog Boxes 
  2804.  
  2805. In most programs, it is necessary to gather (even frequently) data from the 
  2806. user.  This may be as simple as getting a filename, or it may be as complex as 
  2807. (gulp) filling out an online tax form.  In any case, what is the method by 
  2808. which input is obtained?  CUA defines the mechanism to be a dialog box.  What 
  2809. is a dialog box, you ask?  To be precise, it is a window whose sole purpose is 
  2810. to receive data from the user in order for the application to continue. 
  2811. Because it is a window, it has the window characteristics discussed when this 
  2812. column began. 
  2813.  
  2814. Every one has seen a dialog box or two; whether it is when your word processor 
  2815. prompts you for a file name to load, or when your spreadsheet program asks you 
  2816. to select a printer, you cannot escape these facts of (GUI) life.  However, 
  2817. using them is one thing - implementing them is quite another. 
  2818.  
  2819. There are two components to dialog box implementation - screen layout and code. 
  2820. We will begin to look at each in this issue, and will continue in more detail 
  2821. in future issues as we also look at the various controls. 
  2822.  
  2823. Multilingual 
  2824.  
  2825. Okay, so you are a Pascal hacker from DOS trying to learn C under OS/2.  To 
  2826. make it worse, you have to also learn PM, according to your boss.  But if that 
  2827. weren't enough, now you can tack on another language to learn, another skill to 
  2828. master.  It is called the resource language, and it is the language in which 
  2829. dialog boxes (among other things) are designed. 
  2830.  
  2831. Simply put, a dialog box is a collection of child windows.  Each window is 
  2832. defined separately, with a specific order.  At the next level, there are groups 
  2833. of windows, beginning with the first one having a special style - WS_GROUP - 
  2834. and ending just prior to the beginning of the next group or with the end of the 
  2835. dialog box definition.  Finally, a dialog definition begins with a DIALOG 
  2836. keyword, followed by a BEGIN keyword, and ends with an END keyword. 
  2837.  
  2838. A sample dialog definition follows: 
  2839.  
  2840. DLGTEMPLATE DLG_GRAPHTYPE LOADONCALL MOVEABLE DISCARDABLE
  2841. BEGIN
  2842.    DIALOG  "Create graph", DLG_GRAPHTYPE, 64, 23, 195, 170, NOT FS_DLGBORDER |
  2843.       WS_VISIBLE, FCF_SYSMENU | FCF_TITLEBAR | FCF_SIZEBORDER
  2844.    BEGIN
  2845.       GROUPBOX       "Graph type", -1, 10, 105, 170, 60
  2846.       LTEXT          "Field(s) to graph", -1, 10, 90, 100, 8
  2847.       CONTROL        "", DGT_UB_BARGRAPH, 15, 110, 50, 45, WC_BUTTON, BS_USERBUTTON | WS_GROUP | WS_TABSTOP | WS_VISIBLE
  2848.       CONTROL        "", DGT_UB_LINEGRAPH, 70, 110, 50, 45, WC_BUTTON, BS_USERBUTTON | WS_GROUP | WS_TABSTOP | WS_VISIBLE
  2849.       CONTROL        "", DGT_UB_SCATTERGRAPH, 125, 111, 50, 45, WC_BUTTON, BS_USERBUTTON | WS_GROUP | WS_TABSTOP | WS_VISIBLE
  2850.       LISTBOX        DGT_LB_HEADERLIST, 10, 30, 175, 60, LS_HORZSCROLL | LS_EXTENDEDSEL | WS_GROUP
  2851.       LISTBOX        DGT_LB_FULLIST, 10, 30, 175, 60, LS_HORZSCROLL | LS_EXTENDEDSEL | WS_GROUP | NOT WS_VISIBLE
  2852.       DEFPUSHBUTTON  "~Graph", DGT_PB_GRAPH, 10, 10, 40, 13, WS_GROUP
  2853.       PUSHBUTTON     "~Settings...", DGT_PB_SETTINGS, 55, 10, 40, 13
  2854.       PUSHBUTTON     "Cancel", DLG_PB_CANCEL, 100, 10, 40, 13
  2855.       PUSHBUTTON     "Help", DLG_PB_HELP, 145, 10, 40, 13
  2856.    END
  2857. END
  2858.  
  2859. As you can see, there are many more details regarding these definitions; 
  2860. fortunately, you don't have to know them.  There is a utility called DLGEDIT 
  2861. which allows you to design, on the screen, your dialog boxes and save the 
  2862. result.  We won't describe how to use this utility; see the accompanying 
  2863. documentation for this.  What is the result?  The human-readable source is 
  2864. saved with the extension .DLG and the binary form is saved with the extension 
  2865. .RES.  In order to explain more, we need to broaden the scope of our discussion 
  2866. to... 
  2867.  
  2868. Resource Files 
  2869.  
  2870. A resource file - with the extension .RC - contains the definition of the 
  2871. various resources used by the application and each of these resources can be 
  2872. loaded or accessed via a Win*() function specific to that resource type.  The 
  2873. resource language also includes C-style preprocessor commands to allow the 
  2874. inclusion of C header files containing #define statements for various constants 
  2875. that are needed by both the resource file and the application. 
  2876.  
  2877. For dialogs, Microsoft decided that the Dialog Box Editor shouldn't have to 
  2878. deal with parsing the entire resource file, so it added dialog definition files 
  2879. (.DLG) to the fold.  Integrating the two can get unwieldy - at the end of the 
  2880. .RC file, you indicate the dialog file to include: 
  2881.  
  2882. RCINCLUDE MYDLGS.DLG
  2883.  
  2884. ...but at the beginning of the dialog file, you must somehow also include the 
  2885. #define statements for each dialog, so there is a different statement: 
  2886.  
  2887. DLGINCLUDE 1 MYDLGS.H
  2888.  
  2889. Whoa!  Why the number on the DLGINCLUDE statement but not the RCINCLUDE 
  2890. statement?  The answer is that Microsoft thought that different dialogs would 
  2891. have different sets of constants, so the number is the identifier of the dialog 
  2892. (meaning you can have different #include files refer to different dialog box 
  2893. definitions). 
  2894.  
  2895. If the reasoning seems flawed to you, that is because it is.  My personal taste 
  2896. is to append the .RC file with the contents of the .DLG file.  Preference is 
  2897. the key word here.  Since the dialog editor reads the binary file (.RES), it 
  2898. doesn't matter how we manipulate the human-readable source. 
  2899.  
  2900.  
  2901. ΓòÉΓòÉΓòÉ 3.3.4. Dialog Code ΓòÉΓòÉΓòÉ
  2902.  
  2903. Dialog Code 
  2904.  
  2905. In addition to a dialog screen definition, there is code to be written. Since a 
  2906. dialog is much like a "main window", you can imagine that the code is some form 
  2907. of a window procedure and this is correct.  It is called a dialog procedure and 
  2908. differs from a window procedure in the following ways: 
  2909.  
  2910.  1. The dialog procedure never receives a WM_CREATE message. Instead, it gets a 
  2911.     WM_INITDLG message. 
  2912.  2. The default dialog procedure is not WinDefWindowProc(); it is instead 
  2913.     WinDefDlgProc() and takes the same parameters. 
  2914.  3. A dialog is not explicitly destroyed by the application (usually). Instead, 
  2915.     it is dismissed via the WinDismissDlg() function. 
  2916.  
  2917. WM_INITDLG 
  2918.  
  2919. This message occurs when a dialog box is being created. 
  2920.  
  2921. Parameters 
  2922.  
  2923.    param1 
  2924.  
  2925.       hwndFocus (HWND) 
  2926.  
  2927.          The handle of the window to receive the input focus when 
  2928.          initialization is completed. 
  2929.  
  2930.    param2 
  2931.  
  2932.       pvData (PVOID) 
  2933.  
  2934.          Points to the application data specified as the last parameter on the 
  2935.          call to WinDlgBox() or WinLoadDlg(). 
  2936.  
  2937. Returns 
  2938.  
  2939.    reply 
  2940.  
  2941.       bFocusChanged (BOOL) 
  2942.  
  2943.          Focus change indicator: 
  2944.  
  2945.          FALSE     Give the input focus to the window in param1 
  2946.          TRUE      The input focus was assigned by the application to another 
  2947.                    window so do not give the input focus to any window. 
  2948.  
  2949. We will examine in detail how dialog procedures are written and used in later 
  2950. issues. 
  2951.  
  2952. Dialog boxes come in two flavors, and the type determines the function used to 
  2953. display the dialog box. 
  2954.  
  2955. Modal dialog boxes are those that require interaction by the user before the 
  2956. application or system can continue.  An "Open file" dialog is an example of 
  2957. this. 
  2958.  
  2959. Modeless dialog boxes are those that do not require input for the application 
  2960. to continue.  A toolpad is an example of this. 
  2961.  
  2962. For the former, WinDlgBox() is used to display and process the dialog.  The 
  2963. latter uses WinLoadDlg().  Both functions take the same parameters. 
  2964.  
  2965. ULONG WinDlgBox(HWND hwndParent,
  2966.                 HWND hwndOwner,
  2967.                 PFNWP pfnDlgProc,
  2968.                 HMODULE hmModule,
  2969.                 ULONG ulDlgId,
  2970.                 PVOID pvData);
  2971.  
  2972. hwndParent          The parent window of the dialog box 
  2973. hwndOwner           The desired owner window of the dialog.  This is rarely the 
  2974.                     true owner of the dialog (see below).  For calls to 
  2975.                     WinDlgBox(), the true owner is disabled until the dialog is 
  2976.                     dismissed. 
  2977. pfnDlgProc          Specifies the dialog procedure 
  2978. hmModule            Handle to the DLL where the dialog definition resides 
  2979. ulDlgId             ID of the dialog to be displayed 
  2980. pvData              Application data, passed to the dialog procedure via the 
  2981.                     WM_INITDLG message 
  2982.  
  2983. The true owner of a dialog is calculated by the system in the following manner 
  2984. - each successive parent window of hwndOwner is queried until one is found 
  2985. whose immediate parent is hwndParent.  If found, this becomes the true owner, 
  2986. otherwise hwndOwner becomes the true owner. 
  2987.  
  2988. Dismissal versus Destruction 
  2989.  
  2990. When a dialog is dismissed, it is by default not destroyed until the 
  2991. application exits.  This is for performance reasons; since a large percentage 
  2992. of dialogs are reused by an application many times, this reduces the time 
  2993. between the call to WinDlgBox() and the time the dialog is actually displayed. 
  2994.  
  2995. The ramifications are that if you allocate resources in the dialog procedure, 
  2996. you cannot wait until the WM_DESTROY message to free them, or else you are 
  2997. asking for trouble.  When do you do it?  I have yet to find a good answer to 
  2998. that question... 
  2999.  
  3000.  
  3001. ΓòÉΓòÉΓòÉ 3.3.5. Summary ΓòÉΓòÉΓòÉ
  3002.  
  3003. Summary 
  3004.  
  3005. This month, we began to look at dialog boxes and resource files.  These are 
  3006. collectively large topics, so we will continue next month with these.  In the 
  3007. meantime, a sample application has been provided as hello.zip; this is an 
  3008. enhancement of the previous Hello world application that was discussed in 
  3009. previous issues. 
  3010.  
  3011. Further down the road, we will begin looking at each of the individual 
  3012. controls, what their purpose is, and how they are used (primarily from the 
  3013. context of a dialog box).  Stay tuned! 
  3014.  
  3015.  
  3016. ΓòÉΓòÉΓòÉ 3.4. Scratch Patch ΓòÉΓòÉΓòÉ
  3017.  
  3018. Written by Larry Salomon, Jr. 
  3019.  
  3020. Welcome to this month's "Scratch Patch"!  Each month, I collect various items 
  3021. that fit into this column sent to me via email. The ones that I feel contribute 
  3022. the most to developers, whether in terms of information or as a nifty trick to 
  3023. tuck into your cap, get published in this column. 
  3024.  
  3025. To submit an item, send it via email to my address - os2man@panix.com - and be 
  3026. sure to grant permission to publish it (those that forget will not be 
  3027. considered for publication).  This month, we have the following: 
  3028.  
  3029. o Corrections 
  3030. o Gotcha Notes! 
  3031. o Questions and Answers 
  3032. o Snippet(s) of the Month 
  3033. o Documentation Chop Shop 
  3034. o Want Ads 
  3035.  
  3036.  
  3037. ΓòÉΓòÉΓòÉ 3.4.1. Corrections ΓòÉΓòÉΓòÉ
  3038.  
  3039. Corrections 
  3040.  
  3041. Last month, I incorrectly credited Gordon Zeglinski as the author of the 
  3042. functions in Snippet(s) of the Month.  The true author is Richard Papo, the 
  3043. author of MemSize.  He can be reached via email at 72607.3111@compuserve.com . 
  3044.  
  3045. My apologies for any inconveniences. 
  3046.  
  3047.  
  3048. ΓòÉΓòÉΓòÉ 3.4.2. Gotcha Notes! ΓòÉΓòÉΓòÉ
  3049.  
  3050. Gotcha Notes! 
  3051.  
  3052. This is a part of this column that will appear from time to time, whenever 
  3053. someone else (or myself) manages to stumble upon something that is not obvious, 
  3054. yet doesn't belong anywhere else in the column.  Things that end up here are 
  3055. not documentation errors or vagaries, but just things to be careful of.  If you 
  3056. have something that would be of help to another of your fellow developers, 
  3057. please send it in. 
  3058.  
  3059. The first submission this month deals with MDI applications, minimized icons, 
  3060. and the WM_QUERYTRACKINFO message.  As many of you know, this message is sent 
  3061. to the frame whenever any tracking-related event - most notably moving and 
  3062. sizing - occurs to allow the frame to set parameters for the event.  The point 
  3063. is that, by subclassing the frame window and intercepting this message, you can 
  3064. control some very important things such as minimum or maximum size of the 
  3065. window simply by setting one or more fields in the TRACKINFO  structure pointed 
  3066. to by PVOIDFROMMP(mpParm2). 
  3067.  
  3068. After investigating a bug reported by one of the users of an application I 
  3069. wrote, I discovered - much to my chagrin - that if a window is minimized and 
  3070. the user decides to move the icon, the frame receives this message.  If, as was 
  3071. the case in my application, the frame is subclassed and the ptlMaxTrackSize is 
  3072. blindly set without any concern about the type of operation in progress, the 
  3073. icon will disappear when the operation is finished! 
  3074.  
  3075. The way to avoid this is to check that you are not minimized (by calling 
  3076. WinQueryWindowULong(hwndWnd,QWL_STYLE) and checking for the WS_MINIMIZED bit) 
  3077. before proceeding to set any fields in the TRACKINFO structure. 
  3078.  
  3079. On a related topic, it should be noted that this message is not sent to the 
  3080. frame when the user maximizes the frame.  The message that is sent to the frame 
  3081. is WM_ADJUSTWINDOWPOS. 
  3082.  
  3083. ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇ 
  3084.  
  3085. Eberhard Mattes noted that, for menu items with the style MIS_BITMAP, it isn't 
  3086. stated that bitmaps of menus are loaded from the .EXE file and not resource 
  3087. DLL.  He presents a solution to this in the Snippet(s) of the Month section of 
  3088. this column. 
  3089.  
  3090.  
  3091. ΓòÉΓòÉΓòÉ 3.4.3. Questions and Answers ΓòÉΓòÉΓòÉ
  3092.  
  3093. Questions and Answers 
  3094.  
  3095. You have to love it when it seems that everyone else is writing your column for 
  3096. you.  ;)  This answer to a frequently asked question comes from Juergen Hermann 
  3097. (jh@ccave.ka.sub.org) 
  3098.  
  3099. The answer to the question "how to bind a default icon to an .EXE file" is that 
  3100. the system looks for an icon resource with the (magic) number 1, so you have to 
  3101. use the following in your resource file. 
  3102.  
  3103. ICON 1 PRELOAD MYAPP.ICO
  3104.  
  3105. It was pointed out that this will cause trouble if you define a constant for 
  3106. the number 1 because it conflicts with DID_OK, which is defined by the Toolkit. 
  3107. My experience has indicated that the first icon resource found in the 
  3108. executable is used, but since the first icon resource in my application are 
  3109. usually the lowest numbered also, this could instead by the criteria.  Given 
  3110. that you can't get much lower than 1 (0?), the behavior I have seen would be 
  3111. consistent with what is described above. 
  3112.  
  3113.  
  3114. ΓòÉΓòÉΓòÉ 3.4.4. Snippet(s) of the Month ΓòÉΓòÉΓòÉ
  3115.  
  3116. Snippet(s) of the Month 
  3117.  
  3118. Eberhard Mattes, well-known for his numerous contributions including the EMX 
  3119. development system, sent us the following code to load a bitmap and set it into 
  3120. a menu item.  The code is adapted from DVIPM. 
  3121.  
  3122. /* The bitmap of a menu entry having style MIS_BITMAP is loaded from
  3123.    the main executable.  This function loads it from the resource DLL.
  3124.  
  3125.    Input:  hwndFrame    Handle of the frame window owning the menu
  3126.            hMod         Module handle of the DLL containing the bitmap
  3127.                         resource
  3128.            idMenu       ID of the menu item (must have style MIS_BITMAP)
  3129.            idBitmap     ID of the bitmap
  3130. */
  3131. void load_menu_bitmap (HWND hwndFrame, HMODULE hmod,
  3132.                        USHORT idMenu, USHORT idBitmap)
  3133. {
  3134.   HPS hps;
  3135.   HWND hwndMenu;
  3136.   HBITMAP hbm;
  3137.  
  3138.   hps = WinGetScreenPS (HWND_DESKTOP);
  3139.   hwndMenu = WinWindowFromID (hwndFrame, FID_MENU);
  3140.   hbm = GpiLoadBitmap (hps, hmod, idBitmap, 0, 0);
  3141.   WinSendMsg (hwndMenu, MM_SETITEMHANDLE,
  3142.               MPFROMSHORT (idMenu), MPFROMLONG (hbm));
  3143.   WinReleasePS (hps);
  3144. }
  3145.  
  3146.  
  3147. ΓòÉΓòÉΓòÉ 3.4.5. Documentation Chop Shop ΓòÉΓòÉΓòÉ
  3148.  
  3149. Documentation Chop Shop 
  3150.  
  3151. The documentation of MM_SETITEMHANDLE appears to be wrong.  It says that 
  3152. mpParm1 is the item index, but instead it should be the ID of the menu item 
  3153. with which it is to be associated.  This message seems to search all child 
  3154. submenus for the menu item. 
  3155.  
  3156.  
  3157. ΓòÉΓòÉΓòÉ 3.4.6. Want Ads ΓòÉΓòÉΓòÉ
  3158.  
  3159. Want Ads 
  3160.  
  3161. Below are the hot topics as of this issue's writing.  Feel free to write on any 
  3162. of these. 
  3163.  
  3164. Workplace Shell Programming (hot) - lately, I have noticed two things: 1) lots 
  3165. of people want to learn how to write new Workplace Shell classes and 2) no one 
  3166. who knows anything about it is telling the rest of us how to do it.  This 
  3167. month's article is a start, but is not enough! :) 
  3168.  
  3169. Client/Server (hot) - using either named pipes (with or without a network) or 
  3170. sockets, client/server programming is all the rage these days.  On a related 
  3171. note, some people have also expressed an interest in learning about interfacing 
  3172. with the various protocol drivers (e.g.  NDIS, IPX/SPX, etc.).  Any articles in 
  3173. this area are most welcome. 
  3174.  
  3175. Multimedia (warm) - last month we had two articles on this topic.  However, 
  3176. they both dealt with sound, which we all know is not the only alternative media 
  3177. type.  Articles on anything else - MIDI, video, etc. - are needed. 
  3178.  
  3179. Animation (warm) - a few readers expressed an interest in the various animation 
  3180. techniques that can be applied to PM applications.  The ultimate article, in my 
  3181. opinion, would be one that develops a sprite library a la the Commodore 64's 
  3182. (and Amiga's?) built-in routines, since this is probably the hardest component 
  3183. of any good animation sequence.  Call this a "personal request"... 
  3184.  
  3185.  
  3186. ΓòÉΓòÉΓòÉ 4. How do I get EDM/2? ΓòÉΓòÉΓòÉ
  3187.  
  3188. EDM/2 can be obtained in any of the following ways: 
  3189.  
  3190. On the Internet 
  3191.  
  3192. o All back issues are available via anonymous FTP from ftp.cdrom.com in the 
  3193.   /pub/os2/2_x/program/newsltr directory. 
  3194. o The EDM/2 mailing list.  Send an empty message to edm2-info@knex.via.mind.org 
  3195.   to receive a file containing (among other things) instructions for 
  3196.   subscribing to EDM/2. 
  3197. o IBM's external gopher server in Almaden, of which I don't have the address. 
  3198.  
  3199. On Compuserve 
  3200.  
  3201. All back issues are available in the OS/2 Developers Forum 2. 
  3202.  
  3203. IBM Internal 
  3204.  
  3205. o IBM's internal gopher server in Almaden, of which I don't have the address. 
  3206. o IBM's REQUEST command on all internal VM systems.  Enter the VM command 
  3207.   REQUEST LIST FROM ASSELIN AT RALVM12 and a list of the requestable packages 
  3208.   will be sent to you; in this list are the names of the packages containing 
  3209.   the EDM/2 issues. 
  3210.  
  3211.  
  3212. ΓòÉΓòÉΓòÉ 5. Contributors to this Issue ΓòÉΓòÉΓòÉ
  3213.  
  3214. Are You a Potential Author? 
  3215.  
  3216. We are always looking for (new) authors.  If you have a topic about which you 
  3217. would like to write, send a brief description of the topic electronically to 
  3218. any of the editors, whose addresses are listed below, by the 15th of the month 
  3219. before the month in which your article will appear.  This alerts us that you 
  3220. will be sending an article so that we can plan the issue layout accordingly. 
  3221. After you have done this, get the latest copy of the Article Submission 
  3222. Guidelines  from ftp.cdrom.com  in the /pub/os2/2_x/program/newsltr directory. 
  3223. (the file is artsub.zip)  The completed text of your article should be sent to 
  3224. us no later than five days prior to the last day of the month; any articles 
  3225. received after that time may be pushed to the next issue. 
  3226.  
  3227. The editors can be reached at the following email addresses: 
  3228.  
  3229. o Larry Salomon - os2man@panix.com (Internet). 
  3230.  
  3231. The following people contributed to this issue in one form or another (in 
  3232. alphabetical order): 
  3233.  
  3234. o Bj╨ñrn Fahller 
  3235. o Larry Salomon, Jr. 
  3236. o Kenton W. Shaver 
  3237. o Marc van Woerkom 
  3238. o Carsten Whimster 
  3239. o Gordon Zeglinski 
  3240. o Network distributors 
  3241.  
  3242.  
  3243. ΓòÉΓòÉΓòÉ 5.1. Bj╨ñrn Fahller ΓòÉΓòÉΓòÉ
  3244.  
  3245. Bj╨ñrn Fahller 
  3246.  
  3247. Bj╨ñrn Fahller is about to finish his Master's degree in Computer Science at 
  3248. Lule╨û University, Sweden.  Before the university studies, he worked with ASIC 
  3249. design for the telecommunications industry, as well as miscellaneous 
  3250. programming jobs.  His major fields of interest are Human-Computer interaction 
  3251. and object oriented design. 
  3252.  
  3253. After having worked with a short software project for the University of British 
  3254. Columbia, in Vancouver, Canada, the summer of '93, he is now trying to get back 
  3255. to Beautiful B.C. by searching for jobs there. 
  3256.  
  3257. Since a little over a year ago he has also tried to run the OS/2 ftp site, 
  3258. ftp.luth.se, but is constantly fighting the disk space problem. 
  3259.  
  3260.  He can be reached on the Internet at bjorn@ludd.luth.se 
  3261.  
  3262.  
  3263. ΓòÉΓòÉΓòÉ 5.2. Larry Salomon, Jr. ΓòÉΓòÉΓòÉ
  3264.  
  3265. Larry Salomon 
  3266.  
  3267. Larry Salomon wrote his first Presentation Manager application for OS/2 version 
  3268. 1.1 in 1989.  Since that time, he has written numerous VIO and PM applications, 
  3269. including the Scramble applet included with OS/2 and the I-Brow/Magnify/Screen 
  3270. Capture trio being distributed by IBM with the Professional Developers Kit 
  3271. CD-ROM.  Currently, he works for International Masters Publishers in Stamford, 
  3272. Connecticut and resides in Bellerose, New York with his wife Lisa. 
  3273.  
  3274. Larry can be reached electronically via the Internet at os2man@panix.com. 
  3275.  
  3276.  
  3277. ΓòÉΓòÉΓòÉ 5.3. Kenton W. Shaver ΓòÉΓòÉΓòÉ
  3278.  
  3279. Kenton can be contacted at kenton+@CMU.EDU. 
  3280.  
  3281.  
  3282. ΓòÉΓòÉΓòÉ 5.4. Marc van Woerkom ΓòÉΓòÉΓòÉ
  3283.  
  3284. Marc Ernst Eddy van Woerkom is a physics student at the Rheinisch-Westfaelische 
  3285. Technische Hochschule Aachen in Germany.  Currently, he works there at the 
  3286. Aachen Center for Solidification in Space, ACCESS, on his Diplomarbeit. 
  3287.  
  3288. When he isn't wrestling with FORTRAN 77 code for his diploma, he enjoys the 
  3289. progress made in the last 15 years i.e. programming in C++ on his private OS/2 
  3290. machine.  Next to his computer addiction, he likes to cross the nearby Dutch 
  3291. border to see a good movie, read science-fiction, play Badminton on occasion 
  3292. and also watch a lot of cable television. 
  3293.  
  3294. Marc can be reached at either of the following addresses: 
  3295.  
  3296. o marc_van-woerkom@ac3.maus.de (home) 
  3297. o marc@pxcl1.gi.rwth-aachen.de (work) 
  3298.  
  3299.  
  3300. ΓòÉΓòÉΓòÉ 5.5. Carsten Whimster ΓòÉΓòÉΓòÉ
  3301.  
  3302. Carsten Whimster 
  3303.  
  3304. I am an undergraduate computer science student at the University of Waterloo, 
  3305. and an OS/2 enthusiast as of OS/2 2.0.  I am currently in my third year, taking 
  3306. mainly operating system, language, compiler, and graphics courses, as much as 
  3307. possible.  :) 
  3308.  
  3309. This summer I will be working at the University as a tutor in CS241, an 
  3310. introductory course to compilers. 
  3311.  
  3312. I am a beginning OS/2 PM programmer with a few projects on the go, and many 
  3313. more in my head.  I try to buy as many books as I can afford on the subject, 
  3314. but if you work for a publisher, I would certainly not turn down any donations, 
  3315. and if the books are interesting and development-oriented, I will try to review 
  3316. them as time permits. 
  3317.  
  3318. I am a TEAM-OS/2 member, and stay busy trying to keep up with several OS/2 
  3319. groups on the Internet. 
  3320.  
  3321. I am also an original member of the Kitchener-Waterloo OS/2 Users' Group, and 
  3322. have recently volunteered to help out organizing the events. 
  3323.  
  3324. It's a miracle my girl-friend puts up with my doing all these things :) 
  3325.  
  3326. You may reach me... 
  3327.  
  3328. ...via email: 
  3329.  
  3330. bcrwhims@undergrad.math.uwaterloo.ca - Internet 
  3331.  
  3332. gopher://descartes.math.uwaterloo.ca:70/h0/mathSOC/.csc/.www/.bcrwhimster/homepage.html 
  3333. - Mosaic homepage 
  3334.  
  3335. ...via snail mail: 
  3336.  
  3337. Carsten Whimster
  3338. 319 Erb Street West, 3rd floor
  3339. Waterloo, Ontario
  3340. Canada
  3341. N2L 1W4
  3342.  
  3343.  
  3344. ΓòÉΓòÉΓòÉ 5.6. Gordon Zeglinski ΓòÉΓòÉΓòÉ
  3345.  
  3346. Gordon Zeglinski 
  3347.  
  3348. Gordon Zeglinski is a freelance programmer/consultant who received his Master's 
  3349. degree in Mechanical Engineering with a thesis on C++ sparse matrix objects. 
  3350. He has been programming in C++ for 6 years and also has a strong background in 
  3351. FORTRAN.  He started developing OS/2 applications with version 2.0 . 
  3352.  
  3353. His current projects include a client/server communications program that 
  3354. utilitizes OS/2's features which has entered beta testing.  Additionally, he is 
  3355. involved in the development of a "real-time" automated vehicle based on OS/2 
  3356. and using C++ in which he does device driver development and designs the 
  3357. applications that comprise the control logic and user interface. 
  3358.  
  3359. He can be reached via the Internet at zeglins@cc.umanitoba.ca. 
  3360.  
  3361.  
  3362. ΓòÉΓòÉΓòÉ 5.7. Network distributors ΓòÉΓòÉΓòÉ
  3363.  
  3364. Network Distributors 
  3365.  
  3366. These people are part of our distribution system to provide EDM/2 on networks 
  3367. other than the Internet.  Their help to provide access to this magazine for 
  3368. others is voluntary and we appreciate them a lot! 
  3369.  
  3370. o Paul Hethman (hethman@cs.utk.edu) - Compuserve 
  3371. o Gess Shankar (gess@knex.via.mind.org) - Internet 
  3372. o David Singer (singer@almaden.ibm.com) - IBM Internal 
  3373. o Andre Asselin (ASSELIN AT RALVM12) - IBM Internal 
  3374.  
  3375. If you would like to become a "network distributor", be sure to contact the 
  3376. editors so that we can give you the credit you deserve!