═══ 1. May 1995 Title Page ═══ The Electronic Developer's Magazine for OS/2 Portions copyright (c) by IQPac Inc. Volume 3, issue 5 Administrivia The last month has zoomed by and it's time for another issue of the magazine. This issue seems to be a bit on the slim side but that is because of finals, no doubt about it. You will finally see the end of the VIOWIN series, as well as another fantastic installment about RMX-OS/2. On the Net One of the advantages of working in the "network systems development" group of a large company is that one undoubtedly gets a connection to the Internet. I still have no answer for why that is; maybe it's because the two names have the word "net" in them. Anyway, I now have a T1 link to the Internet, via an ethernet connection, from my machine at work. I will try, from time-to-time, to place some interesting demos of things- to-come in EDM/2 there, retrievable via anonymous FTP. The machine name is os2man.sysdev.telerate.com and there is currently a demo of a Stock Graphing program. The demo utilizes a set of graphing classes that I wrote in C++ using Watcom's compiler. The set of classes has some work to be done, as you will see if you get the demo, but they are already quite usable. I plan to finish the classes and will rewrite them on my home machine ("clean room") so that I can present a series in EDM/2 on their design and implementation. Regarding the Future of the Magazine It needs to be stated again that the magazine will not be charging for subscriptions. Enough said. We are behind schedule in our preparation for this change, however; originally, it was intended that the first issue should be in July (volume 3, issue 7). This deadline can still be made, but we are far from completion of the steps necessary to enact the change. Just thought you'd like to know. Letters and Announcements We have been getting scarce feedback, although better than nothing, about the new Letters and Announcements sections. We are glad that you enjoy them. I will reiterate the need for new columnists, however. Please send mail to me - os2man@panix.com - if you're interested. Also, authors are always welcome; please get the latest Article Submission Guidelines if you plan to write an article for the magazine. ═══ 2. Copyright Notice ═══ Copyright Notice EDM/2 is published by IQPac Inc. IQPac Inc. can be reached via U.S. Mail at the following address: IQPac Inc. 7 East Broadway, Box 804 New York, NY 10038 U.S.A. Editor-in-chief Larry Salomon Jr. Associate editor Carsten Whimster Contributing editor Gordon Zeglinski CEO/President Larry Salomon Jr. All material is copyrighted by its original author. No part of this magazine may be reproduced without permission from the original author. This publication may be freely distributed in electronic form provided that all parts are present in their original unmodified form. A reasonable fee may be charged for the physical act of distribution; no fee may be charged for the publication itself. Neither IQPac Inc. nor this publication are affiliated with International Business Machines Corporation. OS/2 is a registered trademark of International Business Machines Corporation. Other trademarks are property of their respective owners. Any mention of a product in this publication does not constitute an endorsement or affiliation unless specifically stated in the text. The OS/2 Accredited Logo is a trademark of International Business Machines Corporation and is used by IQPac Inc. under license. This On-line Publication is independently produced by IQPac Inc. and IBM is not responsible in any way for its contents. IQPac Inc. is an accredited member of the IBM Independent Vendor League. Copyright Notice - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 3. Letters ═══ Letters To write to EDM/2, send your email to os2man@panix.com and use the Subject: line "Letters". HTML Revisited Ernest T Christley (ernestc@mercury.interpath.net) writes: "I took interest in the fact that with issue 4 many of your readers are requesting EDM/2 in HTML format. You should seriously consider adopting Howard Gilbert's 'Sphydir'. It will generate HTML and IPF code simultaneously, and makes HTML production a no-brainer, drag-and-drop, WPS enabled walk in the park." ──────────────────── Scratch Patch Resurrected scratch01@aol.com writes: "If you still answer these in EDM/2, I have a question that shouldn't be too difficult...(why do I feel like I've missed something really obvious?) I've noticed in several of the IBM apps (ex. VIEW.EXE) which use an MDI, the main window's system menu is the standard system menu. However the system menu on any child windows is different in that it uses CTRL key combinations instead of ALT key combinations. I've noticed this behavior in several MDI type Windows applications. Is there a simple way to do this for child windows, or would I have to 'manually' change each system menu entry at window creation time?" Larry Salomon Jr. responds: What needs to be done is to create your own accelerator table in your resource file, load it using WinLoadAccelTable, and then set it as the child frame using WinSetAccelTable. On the WinSetAccelTable call, make sure that the last parameter is not NULLHANDLE, or you'll replace the accelerator table for the entire application. ──────────────────── Sprites and the Magical Color Black Jonathan Abbey (jonabbey@eden.com) writes: "Hi Larry. I'm trying to make use of some of the ideas that you put together in the sprite library you presented in EDM/2. I'm trying to do this on a cross platform basis (we are developing class libraries for a game we're doing and we want the class interface to be portable across OS/2 PM, X, and Windows, if not Mac as well), and I'm wondering whether the techniques you presented would be able to work if the transparent color was not black. That is, is there something magical about black? Is it guaranteed to map into all 0 bits on the display or something? I guess if I can't figure out some way to be able to use an arbitrary transparency index, I could remap things, so that all black turns into 0,0,1 or some such, and the transparency index is converted to black. I guess the only other ways of doing it would be by using GpiSetPel and/or by cutting chunks out of large sprites, convert them to icons, and blit them bit by bit to an off screen buffer, then swap it onto screen. Yuck. I can't believe the GPI doesn't provide for 3 blit operands. Anyway, thanks for any clarification you might be able to provide." Larry Salomon Jr. responds: The color black was chosen in conjunction with the ROP operation specified in creating the monochrome bitmask. Think of how the process works conceptually and then consider how you would create the mask. You might be able to try the mapping method that you hinted at, but I do not know if it would work. Converting the sprites to icons will not work because the algorithm presented in the series is identical in concept to that which the system uses, except that the application is responsible for creating the masks and not the system. ──────────────────── Custom Controls Needs a Custom Fix Mark Mathews (mark.mathews@channel1.com) writes: "The article 'Building Custom Controls' was great! We are porting DEU (for DOOM) to OS/2 and we have been stuck on how to make a status bar for about 1 month. Thank you!!!!! In the EDITOR1 example one should use the height from MINMAX instead of the MENU. The reason is when changing the window size the height of the menu may change, where MINMAX will not change. Did anyone try to compile the source code with C Set++ 2.1? I tried to compile EDITOR1.RC and I keep getting errors on MIS_BITMAP. I'm not sure yet. I'll investigate. I'm sending you EDITOR2.ZIP. I modified EDITOR to only have a status bar and vertical and horizontal scrollbars ONLY. It works fine. My question is, what is the the square windows on the lower right side of the window where the horizontal and vertical scrollbars meet? For some reason that section has become transparent. Can you help me? I also want to say excellent work on the article 'Making Executable Smaller.' Could the author expand on the subject of optimizing C code. Over the years there are little tricks you can use to make the code faster and smaller. For example, junk =3; if (temp==45) junk = 5; is faster and smaller than: junk = temp==45 ? 5 : 3 BTW, if EDM/2 goes public please don't write articles like the other programmers' magazines. Continue just what your doing. If you do that I will be first in line to buy an EDM/2 subscription." Eric Slaats responds: Your suggestion to use the min/max control to set the statusbar height is really elegant. If the menu height changes, the statusbar height will also change. This solution prevents it, excellent. I'm afraid I can't help you with your C-Set 2.1 problem. I don't own C-Set, and I don't know anyone who does (or anyone who builds programs under OS/2 for that matter). Maybe someone else can address this matter. The problem you've got with the scrollbars really baffles me. When I executed your .EXE file, it clearly showed the transparent area. After recompilation however, it was gone! I wasn't able to invoke the error again. However I've noticed that a PM program can be extremely sensitive to compiler settings. When using the MLE control I couldn't get the find message to work properly. I've tried recompiling examples that worked properly, but it would not work. After toying with the compiler settings after reading about smaller .EXE files, I noticed that these messages worked! Maybe there's a solution here (keep me posted). I've added a the new EDITOR.CPP code (in CUSTOM.ZIP). This new code uses your min/max trick and it is modified so that the ALT key combinations will work. ──────────────────── scratch01@aol.com writes: "Your article in the April '95 edition of EDM/2 ['Building Custom Controls'] has been very helpful. I have one question about the toolbar. In the article you said something to the effect of '...the last menu loaded is the one attached to the frame...' (the same thing is mentioned in a similar example from IBM on Hobbes called FRMSUB.ZIP). However I have run into a difficulty with this. When I press the ALT key (or the combination of ALT-anything) the only menus that respond are the system menu and the toolbar (respond, as in becoming selected or 'indented'). There is no response from the main (text) menu. In fact, the only way to get a response is to use the mouse. If you are aware of this, do you have a solution? BTW, I tried reversing the loading order of the WinLoadMenu statements, and the program trapped." Eric Slaats responds: The ALT thing you've noticed is a bug. I personally never use this feature but (as your messages states) others do. It was rather sloppy to overlook this problem. So I've come up with a solution. (The code is included in CUSTOM1.ZIP). Simply reversing the loading order of the WinLoadMenu statements isn't a solution. The program will trap because the last loaded menu (toolbar) in this case will be handled twice and the menu won't be handled. So how does this new code work? The first thing we do is (indeed) swapping the WinLoadMenu statements so the Toolbar will be loaded last. This way the toolbar will be loaded last and it will be treated like a regular menu. Next we change the subclass window procedure for the frame window. It must size and place the normal menu window. So where at first the toolbar was handled we will now handle the menu. It is still desirable that the toolbar is placed under the menubar. The toolbar is now placed on top of the client window. So we have to put the menubar on top and replace the toolbar directly underneath it. This is done by the following lines: pSWP[usMenu].x = pSWP[usToolbar].x; pSWP[usMenu].y = pSWP[usToolbar].y + pSWP[usToolbar].cy - pSWP[usMenu].cy; pSWP[usToolbar].y = pSWP[usMenu].y - pSWP[usToolbar].cy; The menubar is placed on top by using the position and cy of the toolbar. (Remember the toolbar has a larger height than the menubar). The last line replaces the toolbar just under the menubar. So now every control has its desired place and the ALT keys will work properly because the PM will attach them to the menubar (highest menu loaded) The code changes are marked with CODE CHANGE !!!!!!!!! ──────────────────── Kudos and Stuff Graham TerMarsch (cs27524@vcc8.langara.bc.ca) writes: Well, just finished reading EDM/2 volume 3, issue 3, and decided that I've just got to write and tell you a few things... Firstly, thank you so much for releasing EDM/2. I've searched for, hoarded, handed out, and read every single issue since its beginning and have loved every issue; they were all helpful for something I've been working on. If you're still accepting snippets, I'm sure I've got a few lying around that I'd send along. I know it might be too little too late and have no excuse for it, but if you'd like some I'd be happy to send them along. As for what I'd like to see in an upcoming issue, I'll put my vote in with the gentleman who suggested some info on making toolbars and button bars for an application. I've spoken to several developers who all do it different, from creating it as another menu option on the frame to creating a completely new frame control from scratch. In a more broad sense, subclassing of windows would be a really good topic; most everything I'm hung up on right now reflects not knowing how to take proper advantage of window subclasing (especially the frame window). Ok, maybe I'm a bit selfish on this but I'm allowed, it's my suggestion. I won't babble forever, but will instead thank you and all of the contributors to EDM/2 once again for the service that you've done to all OS/2 developers. I can say that if it wasn't for EDM/2, I probably would've given up on trying to further my PM programming skills and would've just bought a quick and dirty package instead. Letters - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 4. Announcements ═══ Announcements New Book Explains OS/2 Graphics Interface A new book from John Wiley & Sons addresses the Graphics Programming Interface (GPI) for OS/2 Warp, providing step-by- step instructions and accompanying coding examples. The book, written by Stephen Knight and Jeffrey Ryan, covers the full range of OS/2 GPI functions, including the latest additions to OS/2 Warp Version 3. Programming the OS/2 Warp Version 3 GPI includes:  Drawing primitives  Working in coordinate spaces  Character fonts, metrics, sizing and positioning  Metafiles  Transformations  Over 60 screen shots, drawings and tables Accompanying disk includes:  Graphics editor application  Text browser file  Query printer information  Other utilities The book (416 pages, $39.95 U.S.) can be ordered as ISBN 0- 471-10718-2 or through IBM as SR28-5681. Publisher's order line: (800) CALL-WILEY (800-225-5945). Fax: (908) 203- 3200. For more information, e-mail: CompBks@JWiley.Com. John Wiley & Sons, a leading supplier of books about OS/2 and related products, is a member of IBM's Independent Vendor League (IVL). The IVL supports individuals and companies who develop and market books, newsletters, magazines, training videos, courseware and consulting services for OS/2 and other IBM personal software products. This news release is from the IBM IVL News Service and may be freely copied and distributed. For information about the IVL, call (203) 452-7704, fax (203) 268-1075 or e-mail gailo@vnet.ibm.com. Send news and distribution changes to ivlinfo@vnet.ibm.com. (OS/2 is a registered trademark of IBM Corporation.) ──────────────────── OS/2 Utilities CD-ROM Is Now Shipping EMS Professional Shareware is now shipping the 15th (Apr'95) edition of its OS/2 Utilities CD-ROM, with 774 carefully selected PD/Shareware ZIP files and a database and search program so you can quickly find/view/copy/etc. $25 special "Net" price includes 3 CD-ROMs with a free copy of the 211,000 record PC products database. EMS Professional Shareware 4505 Buckhurst Ct. Olney MD 20832-1830 Voice: 301-924-3594 Fax: 301-963-2708 ems@wdn.com http://www.xmission.com/~wwwads/ems ──────────────────── Perl 4 With Sockets Located at hobbes.nmsu.edu /os2/unix/sockperl.zip Perl for OS/2 with sockets. (This build also has use of crypt(), fork() and -D debug options.) Built using EMX 09a with fix03 applied. This build passes all the regression tests as the distribution version of perl.exe. Of course this version won't run under dos at all, presumably the sock_ini call is protect-mode only. While perl5 with sockets is available also, some sites / systems may not be able to support this larger .EXE, .DLL. No changes were made to Kai's sources for Perl / OS/2. I've run the entire test battery (using a csh script, as I don't have MAKE). Aside from pure Unix-isms and problems in comparing strings with '' on OS/2, the only unexplained error I found was in op/magic.t, where SIG action "DEFAULT" is supported? The only other anomaly is that this perl stops creating new sockets after the 257th. Perhaps this is an OS/2 or OS/2 TCP/IP limitation. I have taken reasonable care in testing the resultant binary, but of course there is NO WARRANTY. I have tested only a small subset of the Perl or EMX support for Berkeley sockets. Thanks as always to Larry for Perl, Kai for the OS/2 version, and Eberhard Mattes for the excellent EMX environment. ──────────────────── Adams-Blake Publishing Announces Book on How To Be A Consultant People who are proficient in OS2 programming have a unique opportunity to work independently as a contract consultant. While not for everyone, for most, doing contract work will result in a significant increase in income. Others, especially those with children or other responsibilities, prefer the more flexible work schedule. There are also those who seek to work out of their home. However, it is not get-rich-quick by any stretch of the imagination. Starting an independent consulting business is not the easiest thing in the world but it's absolutely doable by most technically skilled people. Finding clients and knowing how to conduct oneself in face-to-face sales situations are the two most difficult tasks for most people. Those who want to work through brokers or agents can do so, but they have to know how to negotiate so the agent doesn't end up with all the money. Technical people have the most difficulty with sales and marketing. Marketing is not sales. Marketing is finding someone to sell to. Sales is the face-to-face meeting. With respect to face-to-face sales, it is not like a job interview. You must be in control and direct each "scene" of the play. You're equal, not subservient to the client. There is no one strategy but there are many different ways the technical sale can be (or should be) closed. There are a multitude of agents who act as third party matchmakers. This can be a good way to start out, however you will give up a good part of your billing income. With agents, there are many contract pitfalls, such as the "you're not paid until we're paid" clause you must be aware of and be able to negotiate around. And of course, you have to be qualified. Yet, it's not technical knowledge that is the most necessary ingredient to success. You must have a take-a-risk personality. You have to want an "adventure." You have to be motivated by money because no one is going to stroke your ego while on the job. Our publishing company has a 320 page title on the subject of technical consulting. With respect to the non-commercial nature of this group, we do not mention title, price or availability. This announcement is neither an offer to sell, nor a solicitation of an offer to buy. The offer is made only by the prospectus. If you have an interest and would like information, contact us by e-mail. Jennifer Church Adams-Blake Publishing abpub@aol.com ──────────────────── Designing Object-Oriented User Interfaces IS International's four-day course on designing user interfaces has been updated to include designing interfaces for OS/2 Warp. The course incorporates presentations, demonstrations, workshops, and a sample application scenario to teach the complete object-oriented interface design process. Topics include:  GUI / OOUI history, concepts and philosophy  User interface design principles and benefits  Design process  Task analysis  Components and interaction  Designing, building and testing low fidelity prototypes  Developing prototypes with IBM's VisualAge The course is offered in both public and on-site venues. It is also available through IBM's Object Technology University and IBM Education and Training as the Designing Object- Oriented Interfaces Workshop, course N5010. Sessions are taught by Ian Stopps and Harris Kravatz, both experienced in the design, teaching and documentation of user interfaces, including IBM's Common User Access (CUA) standard. For more information, contact IS International at (800) 276- 1075, fax (407) 994-4373, e-mail info@isii.com, or http://www.isii.com. IS International, an advanced education and consulting company specializing in object-oriented design and development, is a member of IBM's Independent Vendor League (IVL). The IVL supports individuals and companies who develop and market books, newsletters, magazines, training videos, courseware and consulting services for OS/2 and other IBM personal software products. This news release is from the IBM IVL News Service and may be freely copied and distributed. For information about the IVL, call (203) 452-7704, fax (203) 268-1075 or e-mail gailo@vnet.ibm.com. Send news and distribution changes to ivlinfo@vnet.ibm.com. (OS/2 is a registered trademark of IBM Corporation.) ──────────────────── GNU Fortran (G77) Has Been Ported To EMX. It's available for anonymous ftp on ftp.uni-stuttgart.de. Look for g77bin.zip (binaries) and g77src.zip (source) in directory /pub/systems/os2/emx0.9a/contrib. It's also available for anonymous ftp on ftp.leo.org. Look for g77bin.zip (binaries) and g77src.zip (source) in directory /pub/comp/os/os2/gnu/emx+gcc/contrib. Please note that this is a quite early release of g77 and has several bugs. Please send bug reports for g77 to the address given in /emx/gnu/gcc-2.6/f/INSTALL, except for bugs introduced by the port, which should be reported to me. ──────────────────── FREE DEMO - ZGRAF C++ Graph Toolkit The ZGRAF C++ Graph Toolkit is a library of routines for making tech/business graphs in several environments, including OS/2 Presentation Manager and Windows/Windows NT. Graph styles include X/Y, Bar, Pie, Area, Ribbon, Scatter, Polar, Log, 2-D Function, 3-D Surface Graphs, Smith Chart, and more! The toolkit sells for $30 (Personal Version) or $45 ( Commercial Developer Version) and includes full C++ library source code. There are no royalties. You can download a free demo from our BBS (see phone number below). For a demo disk, just send e-mail with your name/address/company information (Serious inquiries only, please. Allow 2-4 weeks for delivery). Thanks for your interest in our products. ZGRAF Software Products 1831 Old Hickory Ct. New Albany, IN 47150 Phone: (812) 949-9524 BBS: (812) 949-0416, 14.4K/9600 BPS, 8/1/None Compuserve: 70742,1356 ──────────────────── REXX Cookbook by Merrill Callaway The REXX Cookbook by Merrill Callaway is now available from WHITESTONE. The 319-page book is a tutorial written for both beginners and more experienced programmers. Extensive use of REXX programming examples helps illustrate topics in a real-life context. In addition to basic concepts, The REXX Cookbook shows how to write programs that work with everyday OS/2 applications and files, including PostScript, Structured Query Language (SQL), DB2/2, AmiPro, and others. The REXX Cookbook, ISBN # 0-9632773-4-0, sells in the U.S. for $27.95. A companion disk, THE REXX FILES, ISBN # 0- 9632773-5-9, is priced at $14.95 U.S. For more information, contact WHITESTONE at e-mail 5979987@MCIMAIL.COM or Voice (505) 268-0678. WHITESTONE, a publisher of books about OS/2 and related products, is a member of IBM's Independent Vendor League (IVL). The IVL supports individuals and companies who develop and market books, newsletters, magazines, training videos, courseware and consulting services for OS/2 and other IBM personal software products. This news release is from the IBM IVL News Service and may be freely copied and distributed. For information about the IVL, call (203) 452-7704, fax (203) 268-1075 or e-mail gailo@vnet.ibm.com. Send news and distribution changes to ivlinfo@vnet.ibm.com. ──────────────────── JYACC Announces JAM For OS/2 At DB-Expo DB-EXPO, San Francisco, CA - May 2, 1995 - Today, JYACC, Inc., announced the OS/2 version of JAM (JYACC Application Manager), the leading cross-platform tool for building client/server and distributed applications. JYACC will show the new release for the first time at DB-Expo Booth 1162 in San Francisco's Moscone Center. JAM for OS/2 allows developers to build 32 bit GUI applications that fully exploit the stability and preemptive multi-tasking features of the OS/2 platform, said Darryl Zack, JYACC Vice President of Sales and Marketing. The ability to build OS/2 applications with JAM and re-deploy them on over 100 platforms and operating environments is an essential component in our customers enterprise computing strategy. With the release of JAM for OS/2 Warp, customers will be able to build and deploy business-critical applications that are portable enough to span the enterprise, said Jim Grant, Vice President, IBM Solution Developer Operations. This new product from JYACC continues their tradition of support for IBM client/server solutions, including AIX on the RS/6000, and DB2/2 and represents another important vendor who has committed support for OS/2, the industry's leading 32-bit operating system. Applications developed with JAM for OS/2 can be deployed on OS/2 versions 2.x and Warp, as well as 100 other platforms and operating environments, including MS-Windows, DOS, Motif, VAX/VMS, Macintosh, and virtually every implementation of UNIX. JAM also integrates seamlessly with more than twenty of the most popular RDBMS products, and provides transparent access to legacy systems via DRDA and Microsofts ODBC. The $2,000 JAM for OS/2 package includes:  The complete JAM development environment.  A database driver for JYACCs built-in prototyping database (JDB).  A second, customer-selected database driver. (Users can purchase additional database drivers if they wish. Drivers are currently available for Oracle, Sybase, Informix, and DB2. Drivers for other RDMS products will be offered later this year.)  Complete documentation. JAM for OS/2 continues the JYACC tradition of never charging runtime fees. Contact: Eric Block (212) 267-7722 or royalties for JAM-built applications. About JAM and JYACC JAM combines four significant advanced technologies -- visual programming, repository-driven development, object- orientation, and high performance transaction management. In addition to offering a fully graphical development environment, JAM is the industry's most advanced application development tool in delivering codeless programming and productivity. JAM is the centerpiece of a fully integrated family of application development products that includes a report writing tool, and interfaces to more than 20 database engines, the leading transaction monitors (including Novell's TUXEDO and Transarc's Encina), and CASE tools (including Cadre's Teamwork and Innovator SERM from MID, GmbH of Germany). JYACC offers products and services that enable developers to build high performance client/server applications quickly and cost-effectively, integrate them into their business environment, and maximize each application's value. Founded in 1978 as a software consulting firm, JYACC first shipped JAM in 1985. With ten years as an industry leader and tens of thousands of JAM development licenses sold worldwide, JYACC has impeccable credentials in the open systems marketplace. Thanks to its power and field-proven performance, JAM has been selected by IS organizations throughout the Fortune 1000 to build the most demanding, mission critical applications. JYACC is based in New York City with offices in Boston, Massachusetts; Parsippany, New Jersey; San Francisco, California; Chicago, Illinois; Atlanta, Georgia; and Orlando, Florida. JYACC also has offices in London (UK) and Paris, France. JYACC Headquarters is located at 116 John Street, New York, New York 10038. JAM and Jterm are registered trademarks of JYACC, Inc. JAM/TPi, JAM/CASE interface, and JAM/ReportWriter are trademarks of JYACC. OS/2 and Warp are registered trademarks of IBM, Corporation. Other brands and product names appearing in this document may be trademarks or registered trademarks of their respective companies. ──────────────────── Prominare Launches Prominare Designer Prominare Designer, an OS/2 PM programmer's Rapid Application Development tool for creating fully featured GUIs for OS/2, has been launched by Prominare Inc., of Canada. Prominare Designer allows the developer to create visual interfaces for an application in a matter of minutes without having to write any source code. This productivity gain remains high throughout the development of the application, even as new visual interface elements are added. Prominare Designer generates code for multiple class libraries and multiple compilers, and offers the capability to generate code across platforms, e.g. OS/2, Windows 3.1, and Windows NT, all from one tool. This feature makes the process of developing for multiple platforms easy. Acting as an extended resource editor, Prominare supports all versions of OS/2, including Warp. Its inherent flexibility enables the power of C and C++ to be fully exploited, with the added benefits of intelligent code generation. Programmers can construct compact, efficient code without unnecessary overheads or proprietary modules. Programmers can use one interface to create both the resources and source code for PM applications. The resource editing capabilities of Prominare are backwardly compatible with all versions of OS/2. This offers unique facilities in the areas of custom controls and PM Control Extensions (PMCX), facilities which are lacking in other tools. Prominare supports all OS/2 PM controls including multimedia (MMPM/2) and Pen for OS/2. Another impressive ability is that of Prominare-created applications to easily conform to individual companies' programming standards, coding conventions and selected level of CUA compliance. This ability to allow programmers to modify Prominare's source code generation rules ensures that the source code generated conforms with company naming conventions and coding standards. This capability is essential for speeding up programming in major companies which are developing large applications, as the code is in a form that their programmers already understand. Matt Smith, Prominare Designer architect, said, "Prominare speeds the process by removing unnecessary generation phases. When the visual design for the application is modified, Prominare adds source code for only the parts that have been modified since the last generation operation was carried out. "Prominare's inherent intelligence enables the application developer to change the look and feel of the interface without having to reprogram the actions of individual controls. The application developer can add his or her own source code to that generated and Prominare ensures that the application developer's source code remains untouched the next time a change is made to the visual design, even when new source code based on the changes is added. Prominare Designer has a suggested retail price of $699 (US) and can be ordered through Indelible Blue, Programmer's Paradise, Egghead Software, OS/2 Express, ImageSoft in the US. In the UK, it can be order through Microtransfer and OneStop OS/2. A complete list of qualified resellers can found through Prominare's WWW server at http://www.prominare.com/prominare. Further detail on Prominare Designer can be found through Prominare's WWW server or by sending email to designer@prominare.com. Prominare is the Latin word which means "to drive forward," and Prominare Designer allows developers to do just that. Announcements - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 5. The Design and Implementation of VIOWIN (Part 8) ═══ ═══ 5.1. Introduction ═══ The Design and Implementation of VIOWIN (Part 8) Written by Larry Salomon, Jr. Introduction For my job, I once had to write an application that ran only when OS/2 booted from the floppy diskettes. Because I had no access to the functionality PM provides, I resorted to a line-oriented interface, where messages were displayed on the screen and scrolled up when necessary. It was a good interface, I thought; it was fully NLS enabled and had intelligent defaults so the user basically only had to type in the name of the application. Unfortunately, the Quality Assurance team didn't concur with my opinion. "We want a nice interface!" one exclaimed. "Yeah, one with different windows and such!" another shouted. I was backed into a corner that I could only get out of one way. This series describes the design and implementation of VIOWIN, a library that implements a subset of the Win APIs provided by PM for fullscreen sessions. The reasoning behind writing this series is that it provided me and will hopefully provide you with some unique insights into how a windowing system is developed; and since it is based on PM, your familiarity with the already defined interface will increase your capability to fully understand what is being described. Obviously, this series assumes you have PM application development experience, but it isn't required. This Month This month, we will (finally) wrap up the series with the VWWC_BUTTON class. The Design and Implementation of VIOWIN (Part 8) - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 5.2. Button Controls ═══ Button Controls VIOWIN implements three types of button controls: pushbuttons, checkboxes, and radiobuttons. Checkboxes and radiobuttons are assumed to be the "auto" type, meaning that they process the painting themselves. Ownerdrawn buttons are not supported. I need not describe the functionality of each, but it should be noted that even though all three types belong to the same window class, they are different enough that they can "logically" be considered of distinct classes. There are four helper functions used by the static control (the term "subclass" is used below to refer to the different types of button classes): pushButtonProc() - processes the messages for the push button "subclass." checkBoxProc() - processes the messages for the check box "subclass." radioButtonProc() - processes the messages for the radio button "subclass." findGroupButton() - finds a specified radio button within a group (first, last, previous, or next). The first three simply process the WM_PAINT, WM_CHAR, and BM_ messages. The Design and Implementation of VIOWIN (Part 8) - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 5.3. The Instance Data and Other Things ═══ The Instance Data and Other Things The instance data is shown in the code below. The definitions of the fields are listed afterwards. #define RB_SELECTED 'o' #define CB_SELECTED 'x' typedef struct _INSTDATA { ULONG ulSzStruct; ULONG ulStyle; BOOL bChecked; } INSTDATA, *PINSTDATA; Field Definition ulSzStruct Size of the structure in bytes ulStyle Style of the window bChecked TRUE if this is a checkbox and it is checked. FALSE otherwise. The Design and Implementation of VIOWIN (Part 8) - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 5.4. The pushButtonProc() Function ═══ The pushButtonProc() Function The appearance of the button is simply a rectangle with the text centered within. If the button has the focus, the text colors (foreground and background) are inverted. While it would have been nice to draw a border around the button, it was determined that the 200% increase in height (since extra two rows are necessary but the button only has one row of text) wasn't worth it. MRESULT EXPENTRY pushButtonProc(HVWWND hwndWnd, ULONG ulMsg, MPARAM mpParm1, MPARAM mpParm2) //------------------------------------------------------------------------- // This "sub-window procedure" handles the push button-specific messages //------------------------------------------------------------------------- { PINSTDATA pidData; pidData=vwQueryWindowPtr(hwndWnd,1); switch (ulMsg) { case WM_PAINT: { RECTL rclWnd; CHAR achText[256]; ULONG ulFore; ULONG ulBack; vwQueryWindowRect(hwndWnd,&rclWnd); rclWnd.xRight--; rclWnd.yTop--; vwQueryWindowText(hwndWnd,sizeof(achText),achText); ulFore=vwQueryForeColor(hwndWnd); ulBack=vwQueryBackColor(hwndWnd); //---------------------------------------------------------------- // Fill ourselves with the background color. //---------------------------------------------------------------- vwFillRect(hwndWnd,&rclWnd,ulBack); //---------------------------------------------------------------- // If we have the focus, invert the colors to indicate this //---------------------------------------------------------------- if (vwQueryFocus()==hwndWnd) { ulFore^=0x000000FF; ulBack^=0x000000FF; } /* endif */ //---------------------------------------------------------------- // Draw the button text //---------------------------------------------------------------- vwDrawText(hwndWnd, -1, achText, &rclWnd, ulFore, ulBack, DT_CENTER|DT_VCENTER); } break; case WM_CHAR: { USHORT usFlags; USHORT usMods; //---------------------------------------------------------------- // Code like this is what makes me appreciate the similarities // of the library to the corresponding PM code. Here, we // check to see if we've been selected using the or // keys (either one). //---------------------------------------------------------------- usFlags=KC_VIRTUALKEY | KC_KEYUP; usMods=KC_ALT | KC_CTRL | KC_SHIFT; if ((CHARMSG(&ulMsg)->fs & usFlags)!=usFlags) { return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2); } /* endif */ if ((CHARMSG(&ulMsg)->fs & usMods)!=0) { return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2); } /* endif */ switch (CHARMSG(&ulMsg)->vkey) { case VK_SPACE: case VK_ENTER: case VK_NEWLINE: vwPostMsg(hwndWnd,BM_CLICK,0,0); break; default: return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2); } /* endswitch */ } break; case WM_QUERYDLGCODE: return MRFROMLONG(DLGC_BUTTON); case BM_CLICK: vwPostMsg(VWHWND_DESKTOP, WM_COMMAND, MPFROMSHORT(vwQueryWindowUShort(hwndWnd,QWS_ID)), 0); break; case BM_QUERYCHECKINDEX: return MRFROMSHORT(-1); case BM_QUERYHILITE: break; case BM_SETHILITE: break; case BM_QUERYCHECK: return MRFROMSHORT(FALSE); case BM_SETCHECK: return MRFROMSHORT(FALSE); case BM_SETDEFAULT: break; default: return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2); } /* endswitch */ return MRFROMLONG(FALSE); } The Design and Implementation of VIOWIN (Part 8) - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 5.5. The checkBoxProc() Function ═══ The checkBoxProc() Function Note the choice (in the "Instance Data and Other Things" section) of character for the checked state of the button ("x"). While "√" is probably a better choice, this character might not exist in other codepages, so we'll stick to one that is more likely to exist. MRESULT EXPENTRY checkBoxProc(HVWWND hwndWnd, ULONG ulMsg, MPARAM mpParm1, MPARAM mpParm2) //------------------------------------------------------------------------- // This "sub-window procedure" handles the check box-specific messages //------------------------------------------------------------------------- { PINSTDATA pidData; pidData=vwQueryWindowPtr(hwndWnd,1); switch (ulMsg) { case WM_PAINT: { CHAR achFmt[256]; CHAR chCheck; CHAR achText[256]; ULONG ulFore; ULONG ulBack; vwQueryWindowText(hwndWnd,sizeof(achFmt),achFmt); //---------------------------------------------------------------- // See if we're checked and, if so, display the appropriate // character in the brackets. //---------------------------------------------------------------- chCheck=pidData->bChecked?CB_SELECTED:' '; sprintf(achText,"[%c] %s",chCheck,achFmt); ulFore=vwQueryForeColor(hwndWnd); ulBack=vwQueryBackColor(hwndWnd); //---------------------------------------------------------------- // If we have the focus, invert the colors to indicate this //---------------------------------------------------------------- if (vwQueryFocus()==hwndWnd) { ulFore^=0x000000FF; ulBack^=0x000000FF; } /* endif */ //---------------------------------------------------------------- // Draw the button text as we have built it above //---------------------------------------------------------------- vwDrawText(hwndWnd, -1, achText, NULL, ulFore, ulBack, DT_LEFT|DT_VCENTER|DT_ERASERECT); } break; case WM_CHAR: { USHORT usFlags; USHORT usMods; usFlags=KC_VIRTUALKEY | KC_KEYUP; usMods=KC_ALT | KC_CTRL | KC_SHIFT; if ((CHARMSG(&ulMsg)->fs & usFlags)!=usFlags) { return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2); } /* endif */ if ((CHARMSG(&ulMsg)->fs & usMods)!=0) { return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2); } /* endif */ //---------------------------------------------------------------- // If the user pressed the spacebar, toggle the checked state // by sending ourselves a BM_CLICK message //---------------------------------------------------------------- switch (CHARMSG(&ulMsg)->vkey) { case VK_SPACE: vwPostMsg(hwndWnd,BM_CLICK,0,0); break; default: return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2); } /* endswitch */ } break; case WM_QUERYDLGCODE: return MRFROMLONG(DLGC_BUTTON); case BM_CLICK: //------------------------------------------------------------------- // Toggle the check state, repaint ourselves, and send the // notification to our owner, which is always the desktop. //------------------------------------------------------------------- pidData->bChecked=!pidData->bChecked; vwSendMsg(hwndWnd,WM_PAINT,0,0); vwSendMsg(VWHWND_DESKTOP, WM_CONTROL, MPFROM2SHORT(vwQueryWindowUShort(hwndWnd,QWS_ID), BN_CLICKED), MPFROMHWND(hwndWnd)); break; case BM_QUERYCHECKINDEX: return MRFROMSHORT(-1); case BM_QUERYHILITE: break; case BM_SETHILITE: break; case BM_QUERYCHECK: return MRFROMSHORT(pidData->bChecked); case BM_SETCHECK: pidData->bChecked=SHORT1FROMMP(mpParm1); vwSendMsg(hwndWnd,WM_PAINT,0,0); break; case BM_SETDEFAULT: break; default: return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2); } /* endswitch */ return MRFROMLONG(FALSE); } The Design and Implementation of VIOWIN (Part 8) - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 5.6. The radioButtonProc() Function ═══ The radioButtonProc() Function This one was the most tricky to handle because of the processing of the arrow keys. In PM, pressing the up or left arrow moves the focus to the previous button in the group and the down and right arrow moves the focus to the next button in the group. After spending a couple of days on this and still not successfully accomplishing the desired behavior, I removed the code to the findGroupButton() function which greatly simplified this window procedure. MRESULT EXPENTRY radioButtonProc(HVWWND hwndWnd, ULONG ulMsg, MPARAM mpParm1, MPARAM mpParm2) //------------------------------------------------------------------------- // This "sub-window procedure" handles the radio button-specific messages //------------------------------------------------------------------------- { PINSTDATA pidData; pidData=vwQueryWindowPtr(hwndWnd,1); switch (ulMsg) { case WM_PAINT: { CHAR achFmt[256]; CHAR chCheck; CHAR achText[256]; ULONG ulFore; ULONG ulBack; vwQueryWindowText(hwndWnd,sizeof(achFmt),achFmt); //---------------------------------------------------------------- // See if we're checked and, if so, display the appropriate // character in the brackets. //---------------------------------------------------------------- chCheck=pidData->bChecked?RB_SELECTED:' '; sprintf(achText,"(%c) %s",chCheck,achFmt); ulFore=vwQueryForeColor(hwndWnd); ulBack=vwQueryBackColor(hwndWnd); //---------------------------------------------------------------- // If we have the focus, invert the colors to indicate this //---------------------------------------------------------------- if (vwQueryFocus()==hwndWnd) { ulFore^=0x000000FF; ulBack^=0x000000FF; } /* endif */ //---------------------------------------------------------------- // Draw the button text as we have built it above //---------------------------------------------------------------- vwDrawText(hwndWnd, -1, achText, NULL, ulFore, ulBack, DT_LEFT|DT_VCENTER|DT_ERASERECT); } break; case WM_CHAR: { USHORT usFlags; USHORT usMods; usFlags=KC_VIRTUALKEY | KC_KEYUP; usMods=KC_ALT | KC_CTRL | KC_SHIFT; if ((CHARMSG(&ulMsg)->fs & usFlags)!=usFlags) { return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2); } /* endif */ if ((CHARMSG(&ulMsg)->fs & usMods)!=0) { return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2); } /* endif */ //---------------------------------------------------------------- // If the user pressed or either key, select // ourselves. If they pressed the or keys, click the // previous button in the group. If they pressed the or // keys, click the next button in the group. //---------------------------------------------------------------- switch (CHARMSG(&ulMsg)->vkey) { case VK_SPACE: case VK_ENTER: case VK_NEWLINE: vwPostMsg(hwndWnd,BM_CLICK,0,0); break; case VK_UP: case VK_LEFT: { HVWWND hwndPrev; hwndPrev=findGroupButton(hwndWnd,QW_PREV); if (hwndPrev==NULLHANDLE) { hwndPrev=findGroupButton(hwndWnd,QW_BOTTOM); } /* endif */ vwSendMsg(hwndPrev,BM_CLICK,MPFROMSHORT(TRUE),0); } break; case VK_DOWN: case VK_RIGHT: { HVWWND hwndNext; hwndNext=findGroupButton(hwndWnd,QW_NEXT); if (hwndNext==NULLHANDLE) { hwndNext=findGroupButton(hwndWnd,QW_TOP); } /* endif */ vwSendMsg(hwndNext,BM_CLICK,MPFROMSHORT(TRUE),0); } break; default: return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2); } /* endswitch */ } break; case WM_QUERYDLGCODE: return MRFROMLONG(DLGC_BUTTON); case BM_CLICK: { SHORT sIndex; HVWWND hwndCheck; //---------------------------------------------------------------- // If we've been clicked, we need to find the button _within our // group_ that is currently selected, unselect it, and then // select ourselves. This is not as trivial as it sounds, but // the code below doesn't indicate this. See findGroupButton(). // - - - - - - - - - - - - - - // Send ourselves a BM_QUERYCHECKINDEX to see who currently is // selected in the group. //---------------------------------------------------------------- sIndex=SHORT1FROMMR(vwSendMsg(hwndWnd,BM_QUERYCHECKINDEX,0,0)); //---------------------------------------------------------------- // If sIndex is not -1, find the button and uncheck it. //---------------------------------------------------------------- if (sIndex!=-1) { hwndCheck=findGroupButton(hwndWnd,QW_TOP); while (sIndex>0) { hwndCheck=findGroupButton(hwndCheck,QW_NEXT); sIndex--; } /* endwhile */ } else { hwndCheck=NULLHANDLE; } /* endif */ if (hwndCheck!=NULLHANDLE) { vwSendMsg(hwndCheck,BM_SETCHECK,MPFROMSHORT(FALSE),0); } /* endif */ //---------------------------------------------------------------- // Check ourselves, set the focus to us, and send the notification // to our owner (the desktop). //---------------------------------------------------------------- vwSendMsg(hwndWnd,BM_SETCHECK,MPFROMSHORT(TRUE),0); vwSetFocus(hwndWnd); vwSendMsg(VWHWND_DESKTOP, WM_CONTROL, MPFROM2SHORT(vwQueryWindowUShort(hwndWnd,QWS_ID), BN_CLICKED), MPFROMHWND(hwndWnd)); } break; case BM_QUERYCHECKINDEX: { HWND hwndGroup; SHORT sIndex; //---------------------------------------------------------------- // Moving the findGroupButton() code to a separate function // made this a lot easier. Amen to code-readability! // - - - - - - - - - - - - - - // Start from the top and enumerate all of the buttons in the // group until we find one that is checked. //---------------------------------------------------------------- hwndGroup=findGroupButton(hwndWnd,QW_TOP); if (hwndGroup==NULLHANDLE) { return MRFROMSHORT(-1); } /* endif */ sIndex=0; if (SHORT1FROMMR(vwSendMsg(hwndGroup,BM_QUERYCHECK,0,0))==TRUE) { return MRFROMSHORT(sIndex); } /* endif */ hwndGroup=findGroupButton(hwndGroup,QW_NEXT); while (hwndGroup!=NULLHANDLE) { sIndex++; if (SHORT1FROMMR(vwSendMsg(hwndGroup,BM_QUERYCHECK,0,0))==TRUE) { return MRFROMSHORT(sIndex); } /* endif */ hwndGroup=findGroupButton(hwndGroup,QW_NEXT); } /* endwhile */ return MRFROMSHORT(-1); } case BM_QUERYHILITE: break; case BM_SETHILITE: break; case BM_QUERYCHECK: return MRFROMSHORT(pidData->bChecked); case BM_SETCHECK: pidData->bChecked=SHORT1FROMMP(mpParm1); vwSendMsg(hwndWnd,WM_PAINT,0,0); break; case BM_SETDEFAULT: break; default: return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2); } /* endswitch */ return MRFROMLONG(FALSE); } The Design and Implementation of VIOWIN (Part 8) - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 5.7. The findGroupButton() Function ═══ The findGroupButton() Function This does the hard work for the radio buttons. Note the following behavioral characteristics:  The previous button of the top button in the group does not exist.  The next button of the bottom button in the group does not exist.  The previous or next button in the group must be a radio button, but having a checkbox in the middle of the group does not delineate the group. HVWWND findGroupButton(HVWWND hwndWnd,LONG lCmd) //------------------------------------------------------------------------- // This function finds the group button in the appropriate position given // a button handle in the same group. // // Input: hwndWnd - button handle in the group to check // lCmd - a QW_ constant (from the vwQueryWindow() function) // Returns: specified button handle if successful, NULLHANDLE otherwise //------------------------------------------------------------------------- { HVWWND hwndTop; HVWWND hwndBottom; HVWWND hwndReturn; ULONG ulStyle; CHAR achClass[256]; BOOL bIsRadioButton; HVWWND hwndLast; //---------------------------------------------------------------------- // Of the four QW_ constants, QW_PREV and QW_NEXT make recursive calls // specifying QW_TOP and QW_BOTTOM respectively. Thus, the latter two // must be entirely self-contained in order to avoid an endless // recursive loop. Additionally, QW_PREV cannot call QW_NEXT, nor can // QW_NEXT call QW_PREV. //---------------------------------------------------------------------- switch (lCmd) { case QW_PREV: //------------------------------------------------------------------- // Query the top of the group and check for equality with the // specified window. If they match, return NULLHANDLE. //------------------------------------------------------------------- hwndTop=findGroupButton(hwndWnd,QW_TOP); if (hwndWnd==hwndTop) { return NULLHANDLE; } /* endif */ hwndReturn=hwndWnd; //------------------------------------------------------------------- // Work our way backwards in the window chain until we find the // first radio button. //------------------------------------------------------------------- do { hwndReturn=vwQueryWindow(hwndReturn,QW_PREV); if (hwndReturn==NULLHANDLE) { return NULLHANDLE; } /* endif */ ulStyle=vwQueryWindowULong(hwndReturn,QWL_STYLE); vwQueryClassName(hwndReturn,sizeof(achClass),achClass); bIsRadioButton=((strcmp(achClass,VWWC_BUTTON)==0) && (((ulStyle & BS_AUTORADIOBUTTON)!=0) || ((ulStyle & BS_RADIOBUTTON)!=0))); } while (!bIsRadioButton); /* enddo */ return hwndReturn; case QW_NEXT: //------------------------------------------------------------------- // Query the bottom of the group and check for equality with the // specified window. If they match, return NULLHANDLE. //------------------------------------------------------------------- hwndBottom=findGroupButton(hwndWnd,QW_BOTTOM); if (hwndWnd==hwndBottom) { return NULLHANDLE; } /* endif */ hwndReturn=hwndWnd; //------------------------------------------------------------------- // Work our way forewards in the window chain until we find the // first radio button. //------------------------------------------------------------------- do { hwndReturn=vwQueryWindow(hwndReturn,QW_NEXT); if (hwndReturn==NULLHANDLE) { return NULLHANDLE; } /* endif */ ulStyle=vwQueryWindowULong(hwndReturn,QWL_STYLE); vwQueryClassName(hwndReturn,sizeof(achClass),achClass); bIsRadioButton=((strcmp(achClass,VWWC_BUTTON)==0) && (((ulStyle & BS_AUTORADIOBUTTON)!=0) || ((ulStyle & BS_RADIOBUTTON)!=0))); } while (!bIsRadioButton); /* enddo */ return hwndReturn; case QW_TOP: //------------------------------------------------------------------- // Start with the window specified and work our way backwards until // we either reach the beginning //------------------------------------------------------------------- hwndReturn=hwndWnd; hwndLast=hwndReturn; ulStyle=vwQueryWindowULong(hwndReturn,QWL_STYLE); while ((ulStyle & WS_GROUP)==0) { hwndReturn=vwQueryWindow(hwndReturn,QW_PREV); //---------------------------------------------------------------- // If we've reached the beginning of the window chain return the // last valid one that we saw //---------------------------------------------------------------- if (hwndReturn==NULLHANDLE) { return hwndLast; } /* endif */ ulStyle=vwQueryWindowULong(hwndReturn,QWL_STYLE); vwQueryClassName(hwndReturn,sizeof(achClass),achClass); bIsRadioButton=((strcmp(achClass,VWWC_BUTTON)==0) && (((ulStyle & BS_AUTORADIOBUTTON)!=0) || ((ulStyle & BS_RADIOBUTTON)!=0))); //---------------------------------------------------------------- // If the current window (hwndReturn) is of the class VWWC_BUTTON // and the style indicates it's a radio button, remember this // window handle //---------------------------------------------------------------- if (bIsRadioButton) { hwndLast=hwndReturn; } /* endif */ } /* endwhile */ return hwndReturn; case QW_BOTTOM: hwndReturn=hwndWnd; hwndLast=hwndReturn; //------------------------------------------------------------------- // Start here and search forward until we find either the end of // the window chain or the beginning of the next group. Note that // we can't set ulStyle to the window style because, if this is // the first button in the group, we will return it instead of the // proper value. //------------------------------------------------------------------- ulStyle=0; while ((ulStyle & WS_GROUP)==0) { hwndReturn=vwQueryWindow(hwndReturn,QW_NEXT); //---------------------------------------------------------------- // If we've reached the end of the window chain return the // last valid one that we saw //---------------------------------------------------------------- if (hwndReturn==NULLHANDLE) { return hwndLast; } /* endif */ ulStyle=vwQueryWindowULong(hwndReturn,QWL_STYLE); vwQueryClassName(hwndReturn,sizeof(achClass),achClass); bIsRadioButton=((strcmp(achClass,VWWC_BUTTON)==0) && (((ulStyle & BS_AUTORADIOBUTTON)!=0) || ((ulStyle & BS_RADIOBUTTON)!=0))); //---------------------------------------------------------------- // If the current window (hwndReturn) is of the class VWWC_BUTTON // and the style indicates it's a radio button, remember this // window handle //---------------------------------------------------------------- if ((bIsRadioButton) && ((ulStyle & WS_GROUP)==0)) { hwndLast=hwndReturn; } /* endif */ } /* endwhile */ return hwndLast; default: return NULLHANDLE; } /* endswitch */ } The Design and Implementation of VIOWIN (Part 8) - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 5.8. The Window Procedure ═══ The Window Procedure The actual window procedure simply handles the WM_CREATE and WM_DESTROY messages, to allocate and destroy the instance data. Other messages are passed to the appropriate "sub window procedure" based on the window style. MRESULT EXPENTRY VwButtonClassProc(HVWWND hwndWnd, ULONG ulMsg, MPARAM mpParm1, MPARAM mpParm2) //------------------------------------------------------------------------- // This window procedure simply processes the WM_CREATE and WM_DESTROY // messages and otherwise calls the appropriate "sub window procedure" // to handle the other messages. This must be done this way since all // button types are of the same class. //------------------------------------------------------------------------- { PINSTDATA pidData; pidData=vwQueryWindowPtr(hwndWnd,1); switch (ulMsg) { case WM_CREATE: pidData=calloc(1,sizeof(INSTDATA)); if (pidData==NULL) { return MRFROMSHORT(TRUE); } /* endif */ vwSetWindowPtr(hwndWnd,1,pidData); pidData->ulSzStruct=sizeof(INSTDATA); pidData->ulStyle=vwQueryWindowULong(hwndWnd,QWL_STYLE); pidData->bChecked=FALSE; return MRFROMSHORT(FALSE); case WM_DESTROY: vwSetWindowPtr(hwndWnd,1,NULL); free(pidData); return MRFROMSHORT(FALSE); default: if ((pidData->ulStyle & BS_AUTOCHECKBOX)!=0) { return checkBoxProc(hwndWnd,ulMsg,mpParm1,mpParm2); } else if ((pidData->ulStyle & BS_CHECKBOX)!=0) { return checkBoxProc(hwndWnd,ulMsg,mpParm1,mpParm2); } else if ((pidData->ulStyle & BS_AUTORADIOBUTTON)!=0) { return radioButtonProc(hwndWnd,ulMsg,mpParm1,mpParm2); } else if ((pidData->ulStyle & BS_RADIOBUTTON)!=0) { return radioButtonProc(hwndWnd,ulMsg,mpParm1,mpParm2); } else { //---------------------------------------------------------------- // Assume it is a pushbutton. //---------------------------------------------------------------- return pushButtonProc(hwndWnd,ulMsg,mpParm1,mpParm2); } /* endif */ } /* endswitch */ } The Design and Implementation of VIOWIN (Part 8) - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 5.9. Conclusion ═══ Conclusion This concludes the VIOWIN implementation. It has been demonstrated that a subset of PM can be defined for character mode applications, if the proper subset is defined by the designer. By implementing a subset of PM instead of developing a new user interface, we allow the programmer to leverage their PM experience when developing character-mode interfaces. This results in a much higher productivity, since they can "just do it" instead of having to concern themselves with learning how to program a new user interface (which includes the pitfalls and idiosyncrasies also). Where can we go from here? To be honest, I have thought of reimplementing VIOWIN many times to include overlapping windows, a hierarchical parent-child and owner-ownee set of relationships, more window classes (especially listboxes and menus), and then more resource types (especially dialog boxes). After this is done, it could be investigated whether or not a subset of the ICLUI ("IBM Class Libraries for User Interface," I believe) could be implemented on top of the new VIOWIN to allow C++ programmers to also gain the advantages of a familiar programming paradigm. The Design and Implementation of VIOWIN (Part 8) - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 6. RMX-OS2: An In-Depth View (Part 4) ═══ ═══ 6.1. Introduction ═══ RMX-OS2: An In-Depth View (Part 4) Written by Johan Wikman Introduction This is the fourth article in a series where I am describing RMX. RMX is a system that allows OS/2 PM applications to run remotely, that is, to run on one computer and be used on another. Now, supposing you have an application that you want to run remotely, how do you start it on the remote computer? In this article I will describe how that can be done. I assume you have read the previous articles, especially the one dealing with the mechanism RMX uses for the communication between different processes. RMX-OS2: An In-Depth View (Part 4) - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 6.2. The Problem ═══ The Problem In the normal case, when applications are run locally, starting an application is not a problem. If the application has an program-object it is enough to double-click on that, or you can simply start it from the command line. Starting an application from within another application is almost as trivial. Using one of the spawn() functions from the C-library or DosExecPgm() directly it is easy to start other applications. The documentation of DosExecPgm() states that it cannot be used for starting an application that is of different type (fullscreen, windowed, PM application) than the starting application, but DosStartSession() must be used instead. I don't know what the situation actually is, because I have at least not experienced any problems when starting PM applications (using DosExecPgm()) from non-PM applications. Anyway, when running remote applications the situation is quite different. Obviously DosExecPgm() is not capable of starting an application on another computer. The conclusion is that we need some mechanism on the other computer that starts the application that is to appear on the local computer. So, what is this mechanism? Well, the mechanism is another remote application that is prepared to accept commands from the local computer and start other remote applications. This is almost a CATCH-22 situation. To run remote applications we need another remote application (actually mechanism as it need not be a single application) that starts them for us. RMX-OS2: An In-Depth View (Part 4) - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 6.3. Standard Solutions ═══ Standard Solutions Depending on what network is being used, there are certain standard solutions for running applications remotely. TCP/IP Apart from being a protocol, the TCP/IP concept includes a lot of different applications. Provided the right daemons are running (I won't go into that now) it is possible to use TELNET for logging into a computer and running programs remotely. Currently I have two computers at home, the hostname of one of them is odin, and the hostname of the other is loke. While sitting at odin, I can log into loke the following way: [C:\]telnet loke After having entered my password, I get an almost ordinary command-line prompt. [-C:\] Although I sit at odin, whatever I do is executed in the context of loke. Typing dir shows me the context of the C-drive of loke. If I run command-line programs they also run on loke, but I can interract with them on odin I am sitting at. However, If I start a PM application then it will appear on loke, and not on odin. That is, TELNET as such does not allow PM application to be used remotely. If you remember from previous articels, all that is required in order to run an RMX application is that the environment variable RMXDISPLAY has been set. So, if I want to run a PM application on loke, yet be able to use it on odin I would: [-C:\]set RMXDISPLAY=odin[-C:\]start pulse.exe This works, provided PULSE.EXE has been patched the way I described in a previous article. Named Pipes LAN Server (perhaps some other products as well) provides the possibility of running programs on another computer using the command NETRUN. I have never tried it out, but supposedly it would be possible to set the RMXDISPLAY variable and start an application (patched for RMX) using it. RMX-OS2: An In-Depth View (Part 4) - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 6.4. Custom Solutions ═══ Custom Solutions Even if the TELNET option works and even if TELNET as such is very useful, the option is rather limited. In practice it would be, I can imagine, quite difficult to create a program object that automatically would start a remote application. Also, the TELNET option is present only on TCP/IP networks. For these reasons, but also because it was a nice problem, I developed a custom solution that is independent from any applications provided by the network software. The solution consists of three programs: RMXSTRTR.EXE, RMXSTART.EXE and RMXSTOP.EXE. RMXSTRTR.EXE and RMXSTART.EXE runs on different computers. RMXSTRTR.EXE is a daemon application that usually is started when the computer is booted. It sits there idle, waiting for start requests from RMXSTART.EXE that is run on some other computer. RMXSTOP.EXE provides a graceful way of stopping a (possibly) detached RMXSTRTR.EXE. RMX-OS2: An In-Depth View (Part 4) - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 6.5. RMXSTRTR.EXE ═══ RMXSTRTR.EXE The overall structure of RMXSTRTR.EXE is illustrated in the following figure. So, what is going on? When RMXSTRTR.EXE is started, it initializes itself, which among other things involves the creation of two semaphores. 1. If it succeeds in the initializing, it spawns a second thread. 2. The startup thread then blocks on one of the semaphores. 3. The started thread creates a connection, which it subsequently blocks on. 4. When some instance of RMXSTART.EXE somewhere on the network opens the connection, the thread 5. immediately spawns a new thread and passes the connection handle along. The current thread (thread 2) then loops back to step 4. 6. The new thread reads the start request from RMXSTART, 7. starts the requested application, and 8. sends a response telling whether the starting succeeded. Finally it just dies. Having a separate thread for each client ensures that the likelihood that RMXSTRTR would be busy when an instance of RMXSTART attempts to open a connection is quite small. Nevertheless, under heavy load the task of spawning a new thread might take a while, so a better approach would perhaps be to have a number of threads ready for immediate dispatch. Main function of RMXSTRTR Let's look then at the main function of RMXSTRTR in greater detail (the actual code is available in a zip-file you should have got along with this issue). set_new_handler(OutOfMem); ios::sync_with_stdio(); The first call establishes a function that is called when the C++ operator new fails to allocate memory. It is unlikely that an out-of-memory situation ever occurs, but it doesn't hurt. The function specified simply writes a message and terminates the application. The following call synchronizes the C++ output mechanism (cout) with the C output mechanism (printf). This is necessary because both are being used within RMXSTRTR. ULONG ulFunctionOrder = MAKEUSHORT(EXLST_ADD, 0xFF); DosExitList(ulFunctionOrder, CleanUp); Then we register a cleanup-function. The function registered - CleanUp() - will be called by the system regardless of how RMXSTRTR is terminated. The first argument of DosExitList() specifies what we want to do. The high-order word should be 0, and the low-order word contains the actual information as two one-byte fields. The lower of the bytes specifies the action: EXLST_ADD indicates that we are adding a function to the cleanup-functions, EXLST_REMOVE indicates that we want to remove an existing function, and EXLST_EXIT indicates that we have done our processing and the system should call the next function. EXLST_EXIT is used only from within the actual cleanup-function. The higher of the bytes specifies where the registered function should be placed in the list of cleanup- functions. Functions with an order code of 0x0 are invoked first, and functions with an order code of 0xFF are invoked last. hev = CreateEventSemaphore(); hmtx = CreateMutexSemaphore(); Two semaphores - one of them an event-semaphore and the other a mutex-semaphore - are created. The main thread blocks on the event-semaphore when it has initialized everything and has started the thread that does the actual job. As I will show later, the name used for the semaphore is constructed from the value of the environment variable RMXCOMMS. Doing this gives us two benefits:  It effectively means that there can only be one RMXSTRTR running using a specific RMXCOMMS DLL. This is what we want, as it does not make sense to have several instances of RMXSTRTR that uses the same communications DLL. On the other hand it allows several instances of RMXSTRTR to run, provided they use a different DLL.  As the name of the semaphore is constructed from the value of RMXCOMMS it means that we later can open the semaphore and post it, thus causing the (possibly detached) RMXSTRTR to exit. It is here that RMXSTRTR exits after having printed an error message if the creation of the event semaphore fails, as that indicates that an RMXSTRTR using the same RMXCOMMS DLL already is running. The mutex semaphore is used for synchronizing the output of different threads. PSZ pszName = GetStarterName(); The next this to do is to obtain the name RMXSTRTR should listen on. GetStarterName() is implemented on top of the RMXCOMMS function RmxGetServiceName(). That means that the contents of the buffer that is returned varies depending on which actual communications DLL is being used. That doesn't matter, however, as we do not care what it contains. void (*firstThread)(void*) = (void (*)(void*)) StarterLoop; int tid = _beginthread(firstThread, SIZE_STACK, pszName); Once the semaphores have been created and the name RMXSTRTR should be listening on has been obtained, the thread that does the actual work can be started. The function _beginthread() is the C RTL function for creating threads. The first argument is a pointer to a function that takes a void* as argument and returns nothing, the second argument is the size of the stack, and the third argument is an argument that should be provided to the thread function (the one given as first argument). This is the Borland format, the prototype is slightly different in IBM and other compilers. The function we want to use as thread function - StarterLoop() - is otherwise ok, except that it takes a PSZ as argument and not a void*. The funny looking variable declaration above the call to _beginthread() is simply there for making StarterLoop() acceptable as argument. This is perfectly safe as PSZ and void* are fully compatible in this context. DosWaitEventSem(hev, SEM_INDEFINITE_WAIT); Once the new thread has been started, the main thread simply blocks on the created semaphore. There it stays forever if need be. Starter Loop Essentially the starter loop - as shown in the figure earlier - is very simple. It is a tight loop where a connection is created, a connecting instance of RMXSTART is waited for, and a handler thread that does the job is spawned. while (TRUE) { create connection wait for RMXSTART spawn handler thread } The connection is created the following way. HCONNECTION hConn; ULONG rc; rc = RmxCreate(pcszName, &hConn); The name of the connection - pcszName - was obtained as an argument to the function. It is the same name as was used in the call to _beginthread() in the main thread. If the call is successful, hConn contains the connection handle. RmxConnect(hConn); This call blocks until somebody opens the connection. The task of actually starting an application is a rather heavy operation, so a separate thread is dedicated for it. _beginthread(HandlerThread, SIZE_STACK, (VOID*) hConn); The connection handle is provided as thread argument. Immediately when the thread has been started a new connection is created and RMXSTRTR is ready for another client. Handling the Client The client wants RMXSTRTR to start an application. Exactly which one, the client (RMXSTART) has to tell RMXSTRTR. The first thing to do then, is to read the entire request the client sent. For that purpose a small structure has been defined. struct Request{ PBYTE pbRequest; ULONG ulSize; }; So first the entire request is read. Request request; ReadRequest(hConn, &request); The hConn was received by the thread when it was started. ReadRequest() (which I won't look at in detail) simply reads everything the client has sent and allocates a sufficiently large buffer on behalf of the caller where it stores the data. ulSize is updated to the actual size of the data sent by the client. We need to know the exact size of the request as we cannot trust the client to provide us with data of proper format. Once the request has been read, we must parse it. A proper request is of one of the following formats, DISPLAY0APPLICATION00 DISPLAY0APPLICATION0ATTRIBUTES00 where DISPLAY, APPLICATION and ATTRIBUTES denotes strings (without ending NULL). That is, two or three catenated ASCIIZ strings followed by an additional NULL. PSZ pszDisplay, pszApplication, pszArguments; ParseRequest(&request, &pszDisplay, &pszApplication, &pszArguments); ParseRequest() takes a request and sets the three provided PSZ pointers to point at the correct place in the request buffer. Once the request has successfully been parsed, we can call the function that actually starts the application. StartApp(pszDisplay, pszApplication, pszArguments); When the application has been started a return code is sent back to the client. What the return code - rc - actually is depends on whether everything done so far has succeeded. SendResponse(hConn, rc); Finally when everything is ready, the connection can be disconnected and closed. RmxDisConnect(hConn);RmxClose(hConn); Starting the Application Ok, now we have got so far that the request sent by the client (RMXSTART.EXE) has been read and parsed. Now it's time to start the application. But, we don't yet know for sure that the application the client specified actually exists. The first this to do is to find out if the application path specified by the client can be used directly. ULONG rc = NO_ERROR;CHAR achPath[CCHMAXPATH];if (access(pcszApplication, 0)){ Access() returns zero if the string provided as first argument denotes an application. That is, we end up in the if-branch if the application is not found. The next thing is to look for the application on the path. rc = DosSearchPath(SEARCH_IGNORENETERRS | SEARCH_ENVIRONMENT | SEARCH_CUR_DIRECTORY, "PATH", pcszApplication, achPath, CCHMAXPATH); if (rc == NO_ERROR) pcszApplication = achPath; If DosSearchPath() returns NO_ERROR then the application was found. In that case the fully qualified name of the application is copied to achPath. Also if the function succeeds we set pcszApplication to point to achPath. That way, in the code to follow, we need not worry where the final application name is to be found, but can simply use pcszApplication. Now that we know the applications exists, we can finally launch it. if (rc == NO_ERROR) LaunchApp(pcszDisplay, pcszApplication, pcszArguments); If you look at the code I've provided, you'll see that I've commented out some code that would be executed before LaunchApp(). As I wrote RMXSTRTR to be used for starting remote application it must first be verified that the application to be started can be run remotely. After all, the application to be started is supposed to turn up on another computer somewhere and if that simply is not possible it doesn't make much sense starting it in the first place. Launching the Application The first thing that is done when the application is to be launched is that the environment is cloned. PSZ pszEnv = CloneEnvironment(pcszDisplay), pszArg = 0; Why is that done? Well, if you have read the previous articles about RMX you know that the value of the environment variable RMXDISPLAY specifies the computer where the remote application will turn up. So, the value of that variable must be set to the value given by the client. Why not simply set RMXDISPLAY to the value desired and then start the application as it is easy to start a child process so that it inherits the environment. That could be done, provided RMXSTRTR would be single-threaded. Now that RMXSTRTR is multi-threaded that cannot be done, as several threads could simultaneously modify the variable. Synchronizing the launching of applications using semaphores would be a viable alternative, but launching the application is the most heavily-used operation in RMXSTRTR and it would be a pity not to use threads there. So, CloneEnvironment() clones the current environment and sets/replaces the value of RMXDISPLAY with the value specified by the client. Finally it returns the copy. If the application has arguments we must build a proper argument string. if (pcszArguments){ ULONG ulApplication = strlen(pcszApplication) + 1, ulArguments = strlen(pcszArguments) + 1, ulSize = ulApplication + ulArguments + 1; pszArg = new CHAR [ulSize]; strcpy(pszArg, pcszApplication); strcpy(&pszArg[ulApplication], pcszArguments); pszArg[ulSize - 1] = 0; } According to the documentation of DosExecPgm(), the argument string should consist of the application name and a NULL, followed by all arguments and a double NULL. RESULTCODES resultCodes; ULONG rc = DosExecPgm(0, 0, EXEC_BACKGROUND, pszArg, pszEnv, &resultCodes, pcszApplication); Then finally the application is started. Logging Throughout RMXSTRTR a function named Print() is used for printing information. It is essentially a thread-safe version of printf(). RMXSTRTR is linked using the MT RTL, but even so I noticed that the printout of different threads occasionally got interleaved. static VOID Print(PCSZ pszFormat, ...) { if (DosRequestMutexSem(hmtx, SEM_INDEFINITE_WAIT)) return; printf("[%d, %d]: ", pid, CurrentTid()); va_list arguments; va_start(arguments, pszFormat); vprintf(pszFormat, arguments); va_end(arguments); DosReleaseMutexSem(hmtx); } The mutex semaphore that was created in main() is used for synchronizing different threads. First the current pid and tid is printed out. Then using the va_start, va_list and va_end macros the string with possible additional arguments is printed. Creating the Event Semaphore The event semaphore name is created from the name of the used communications DLL. The name of the DLL has to specified in the variable RMXCOMMS. So, first we obtain that name. PCSZ pcszRmxComms = getenv("RMXCOMMS"); Then the name is catenated to a common prefix. CHAR achSemName[CCHMAXPATH]; strcpy(achSemName, "\\SEM32\\RMX\\STARTER\\"); strcat(achSemName, pcszRmxComms); And then the semaphore is created. HEV hev = 0; if (DosCreateEventSem(achSemName, &hev, DC_SEM_SHARED, FALSE) != NO_ERROR) hev = 0; return hev; If this functions fails it is an indication that another instance of RMXSTRTR that uses the same communications DLL is running. RMX-OS2: An In-Depth View (Part 4) - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 6.6. RMXSTART.EXE ═══ RMXSTART.EXE RMXSTART is a great deal simpler than RMXSTRTR. It basically makes a request out of the arguments, opens a connection to RMXSTRTR, sends the request, waits for a reply and finally closes the connection. If RMXSTART is started without arguments it prints out: usage: rmxstart display cpu application [arguments] DISPLAY refers to the local computer, that is, the one where the application will appear. CPU refers to the computer where the application will run, that is, in practice a computer where RMXSTRTR is running. Nothing prevents the DISPLAY and CPU from being the same. APPLICATION is, of course, the application to be started, and ARGUMENTS is the arguments that should be provided to the application. If more than one argument is to be provided to the application then they must all be enclosed in quotes. The main function When RMXSTART is started we set the arguments to a few variables for easier access. int main(int argc, char* argv[]) { PCSZ pcszDisplay = argv[1], pcszHost = argv[2], pcszApplication = argv[3], pcszArguments = 0; if (argc == 5) pcszArguments = argv[4]; The name of the starter is, the same way as in RMXSTRTR, obtained from: PSZ pszName = GetStarterName(); Now that we know the host (the computer where the application is to be run) and the name of the starter we open a connection to the starter. HCONNECTION hConn = OpenConnection(pcszHost, pszName); Once the connection has been opened, the request can be sent. If it is successfully sent we wait for the reply. if (SendRequest(hConn, pcszDisplay, pcszApplication, pcszArguments)) ReadReply(hConn); Finally the connection is closed. RmxClose(hConn); Opening the Connection When the connection is opened we must take precautions for the event that RMXSTRTR is busy. HCONNECTION hConn = 0; ULONG ulAttempts = 1, rc; do{ rc = RmxOpen(pcszHost, pcszPort, &hConn); if (rc == ERROR_PIPE_BUSY) DosSleep(ulAttempts * 200); } while ((rc == ERROR_PIPE_BUSY) && (ulAttempts++ < MAX_ATTEMPTS)); Using RmxOpen() we attempt to open the connection. If it fails because RMXSTRTR is busy, then we sleep for a while and try again. Each time the function fails, we sleep a little longer. But not forever; after a specified number of max attempts we just give up. Sending the Request In order to start a remote application we must send the name of our local computer, the name of the application and possible arguments. It is simply a question of concatenating the two (possibly three) strings and adding an extra NULL. First we must find out how much memory the entire request needs. ULONG lenDisplay = strlen(pcszDisplay) + 1, lenApplication = strlen(pcszApplication) + 1, lenArguments = pcszArguments ? strlen(pcszArguments) + 1 : 0, lenRequest = lenDisplay + lenApplication + lenArguments + 1; One is added to each sublength as otherwise we wouldn't reserve enough memory for each NULL. Finally an additional byte is added to the length of the entire request. When the length is known, the memory can be allocated. PSZ pszRequest = new CHAR[lenRequest], p = pszRequest; Then each string is copied to the request buffer. strcpy(p, pcszDisplay); p += lenDisplay; strcpy(p, pcszApplication); p += lenApplication; if (pcszArguments){ strcpy(p, pcszArguments); p += lenArguments; } Finally the double NULL is added. *p = 0; Now the request can be sent to RMXSTRTR. ULONG rc = RmxWrite(hConn, pszRequest, lenRequest); Reading the Reply Even if it is unlikely that RMXSTRTR would ever return anything but a status code, it is better to make sure that any kind of reply can be handled. We need a few variables for that. ULONG ulBytesRead, ulSize = SIZE_BUFFER; BYTE *pbReply = 0; ULONG rc; Then we can enter the loop for reading the reply. do{ pbReply = new BYTE [ulSize]; rc = RmxRead(hConn, pbReply, ulSize, &ulBytesRead); if (rc == RMXERR_ENLARGE_BUFFER) { delete [] pbReply; ulSize = ulBytesRead; } } while (rc == RMXERR_ENLARGE_BUFFER); We spin in the loop until we have a buffer sufficiently large (shouldn't ever need more than two attempts). The loop is safe as, even if I didn't mention it, a handler for the out-of-memory situation has been set in main(). if (rc || (ulBytesRead != sizeof(ULONG))) { delete [] pbReply; cerr << "rmxstart: Failed to read reply from starter." << endl; return; } If RmxRead() returns an error, or if the size of the returned reply is something other than 4 bytes, we give up. The rest of the function is simply a switch on the returned status code along with appropriate messages to the user. RMX-OS2: An In-Depth View (Part 4) - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 6.7. RMXSTOP.EXE ═══ RMXSTOP.EXE RMXSTOP is the simplest of the three applications. Its task is to gracefully stop a running RMXSTRTR. The RMXSTRTR to stop is either specified by explicitly giving the name of the communications DLL the RMXSTRTR is using, or by allowing RMXSTOP to use the current value of RMXCOMMS. If no arguments has been given RMXSTOP uses the value of RMXCOMMS. PCSZ pcszRmxComms = 0; if (argc == 1) { pcszRmxComms = getenv(RMXCOMMS); if (pcszRmxComms == 0) return EXIT_INIT; } If no arguments have been given and the environment variable RMXCOMMS has not been specified, there is nothing we can do. If arguments have been given we verify that they are of proper format. else { if (strcmp(argv[1], "-c")) return EXIT_INIT; pcszRmxComms = argv[2]; } If the flag is "-c" we use argument following it as the DLL name. From the DLL name we build the semaphore name. CHAR achSemName[CCHMAXPATH]; strcpy(achSemName, RMXSEMPREFIX); strcat(achSemName, pcszRmxComms); RMXSEMPREFIX is a common prefix (actually "\\SEM32\\RMX\\STARTER\\") that is also used by RMXSTRTR when it creates the event semaphore. Once the complete name is available we can open the semaphore. HEV hev = 0; if (DosOpenEventSem(achSemName, &hev) == NO_ERROR) { DosPostEventSem(hev); DosCloseEventSem(hev); } If it succeeds we know that the main thread of some RMXSTRTR is blocked on that semaphore. Hence, if we post the semaphore the RMXSTRTR process will terminate. If the opening fails, we know no RMXSTRTR is running using the specified communications DLL. else { cerr << "rmxstop: No rmxstrtr using " << pcszRmxComms << " seems to be running." << endl; } RMX-OS2: An In-Depth View (Part 4) - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 6.8. Building the Applications ═══ Building the Applications Along with the article you should get a zip-file, RMXSTART.ZIP, that contains all source and ready-made applications and DLLs. The makefiles are made for NMAKE.EXE (the make provided with IBM's compiler) and the Borland compiler. Pretty heavy editing is probably needed if you use something else. Before starting the build, you have to set the environment variable RMXROOT to point to the directory where you unzipped the zip-file. E.g. [C:\]set RMXROOT=C:\RMX Then you simply: [C:\]cd RMX [C:\RMX]nmake RMX-OS2: An In-Depth View (Part 4) - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 6.9. Running the Applications ═══ Running the Applications The first thing you should do is to either include the directory RMX\DLL in your LIBPATH, or move the DLLs to some directory already in your LIBPATH. Remember that RMXPIPE.DLL requires the file RMXPIPE.DAT to be in the same directory where it is and that RMXTCPIP.DLL requires that the file RMX\RMXCOMMS\RMXTCPIP\services is appended to your TCPIP\ETC\services file. To start RMXSTRTR, e.g.: [C:\RMX\BIN]set RMXCOMMS=RMXTCPIP [C:\RMX\BIN]rmxstrtr Supposing the name of your computer is odin and the name of the computer where RMXSTRTR is running is loke, you could start pulse.exe on that computer with the following commands. [C:\RMX\BIN]set RMXCOMMS=RMXTCPIP [C:\RMX\BIN]rmxstart odin loke c:\os2\apps\pulse.exe If you are using named pipes, the commands would be something like: [C:\RMX\BIN]set RMXCOMMS=RMXPIPE [C:\RMX\BIN]rmxstart \\odin \\loke c:\os2\apps\pulse.exe Remember, these programs only provide a means for starting an application on another computer. They do not, in themselves, redirect any output anywhere. RMX-OS2: An In-Depth View (Part 4) - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 6.10. Conclusion ═══ Conclusion In this article I described the programs that can be used for starting applications on other computers. The programs are slightly modified, compared with the "official" RMX versions, in that they also start applications that have not been patched for RMX. That is, it is possible to start remote applications that you subsequently cannot interact with. I have uploaded a complete version of RMX to hobbes and the zip resides in the directory network\other. If you install that package, then you actually could start a PM application on a remote computer and use it on your local. But please, keep in mind that the version is early beta. Don't try it out if you expect a finished product. If you have any problems with RMX, feel free to send me mail. RMX-OS2: An In-Depth View (Part 4) - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 7. /dev/EDM/BookReview ═══ ═══ 7.1. Introduction ═══ /dev/EDM2/BookReview Written by Carsten Whimster Introduction In /dev/EDM2/BookReview, I focus on development books and materials. The column is written from the point of view of an intermediate PM C programmer and intermediate REXX programmer. Pick up whichever book strikes your fancy, and join the growing group of people following our PM programming columns. I have already reviewed a number of beginner's books, and will try to concentrate a bit more on intermediate techniques and special topics from now on. Please send me your comments and thoughts so that I can make this column as good as possible. I read and respond to all mail. OS/2 Warp Unleashed, Moskowitz, Kerr, et al is a book I have wanted to review for a while (well, this version is actually brand-new, but you know what I mean). I have just received a copy of the new edition, hot off the press, and the book, while good for novices too, really comes into its own in the hands of power users. Many programmers are power users, so many of these tips should be useful in setting up your development machine. /dev/EDM2/BookReview - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 7.2. Errata ═══ Errata My WWW home page grows and grows. It is getting a little more stable and up-to-date now, but as with everyone I know who has one, it is always being changed. In the coming few months I may try to create an EDM/2 "homepage" and have a lot of information about EDM/2 and OS/2 programming on it. We'll see how much time I get for this project. My school (University of Waterloo), like many schools, has a large Microsoft presence, and many decisions are based on not stepping on Microsoft's toes, since they donate large amounts of software to the school. If anyone out there has any experience with changing the attitudes of the powers that be, please e-mail me. I would like to increase the OS/2 awareness and enthusiasm on campus, but it is hard. There is at the moment a small hard-core OS/2 following, and I would like to increase that to a large hardcore presence . Van Nostrand Reinhold and John Wiley & Sons (I think I got that right) have apparently announced the sale of all of VNR's OS/2 titles to John Wiley & Sons. Personally, I think that VNR jumped ship at the worst possible time, with Warp's growing sales, but there is obviously a lot I don't know about the situation. This probably means that getting books from VNR or John Wiley & Sons is going to be difficult for the next little while. I am therefore looking for ideas for the next couple of review columns. E-mail me at bcrwhims@undergrad.math.uwaterloo.ca if you have any ideas of good programming/power user books for me to review. /dev/EDM2/BookReview - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 7.3. OS/2 Warp Unleashed - Deluxe Edition ═══ OS/2 Warp Unleashed - Deluxe Edition This book is just full of both common and obscure hints and tricks; it has chapters on the applets, the Internet, and just about anything else you can imagine. Here is a list of the chapters: 1. Installation 2. System Configuration 3. Reconfiguration 4. Workplace Shell 5. Workplace Shell Objects 6. Configuring the Workplace Shell 7. Command-line Interface 8. REXX Programming 9. Virtual DOS Machines 10. WIN-OS/2 - Windows in OS/2 11. The Video Subsystem 12. Fonts 13. Printing 14. File Systems 15. Multimedia 16. Productivity Applets 17. Networking 18. Troubleshooting 19. OS/2 and the Internet 20. Portable Computing with OS/2 Appendix A. The OS/2 Unleashed CD-ROM Appendix B. Resources Appendix C. OS/2 System Messages This book is huge! Over 1200 pages, to be exact. Hence, I don't think that I can read everything in one month, and also have a semblance of a life. I will read whatever I am interested in, and base my review on these chapters. In the process, I will probably (hopefully) cover the items of interest for most of the column's readers. I skipped certain chapters, since my machine is already set up, installed, and pretty well configured the way I want it, but there were still a large number of chapters of interest to me. Having skipped chapter 1, I started reading in chapter 2. Many of the tips in this chapter (and the other chapters) can be found on the Internet in one of the various newsgroups, or in a file on an ftp site, but I have never seen anyone explain this stuff in such elaborate detail, and with such accuracy and confidence as the authors of this book (et al covers about a dozen other authors), many of which actually develop or assist in developing OS/2 itself. The usual foundations are well explained in this book, although the authors are careful not to explain anything that they feel IBM already explained well either in the documentation that came with OS/2, or in the on-line help files. Unfortunately, there is a large amount of information which doesn't fall into the latter category, so the authors have the opportunity to improve on what you have seen before. Chapter 2 has a number of interesting tidbits of information about the config.sys, and the file system. Many of the usual tips are here, but there are many extra tips as well. David Moskowitz, who wrote this chapter, shows you how to set up OS/2 for a minimum system, a "better" system, and a power user system. These types of tips abound, but the descriptions given here are better than usual, complete with rationale. Explanations are given for most, if not all, of the unusual and obscure config.sys parameters. Chapter 3 shows you how to move things around, which parts of OS/2 can be deleted under what circumstances, and gives more configuration tips. The first item is how to remove OS/2 Warp! If this is all you wanted to do, you would probably just scan the book at the bookstore, and you certainly wouldn't be reading this column. Unless you were running a 100 OS/2 machine site, perhaps. There is a really neat section on how to use the Recovery Choices screen available on booting with the Alt-F1 keystroke to set up custom config.sys'es. The end result is similar to what DOS 6.x offers with both config.sys and autoexec.bat, but more integrated. It outlines what to do if you want to use your desktop like you used to use the Windows desktop, before you switched. Also explained is how to recover from INI file corruption, and how to set up multiple desktop configurations. There is a section with MSHELL and TSHELL, and how to use these correctly, as well as how to use these concepts to use any other program as your desktop. Once you have your desktop set up, you probably won't want to change it too much, but there is a lot of interesting information here. If you develop on a small system, perhaps you may consider going to an MSHELL desktop to conserve RAM, and regain a little memory, and hence performance. Chapters 4, 5, and 6 all discuss the Workplace Shell. Chapter 4 is a slightly more in-depth tutorial than IBM gave us with OS/2. Many obscure features are noted, such as the fact that the objects listed after a Find has been performed are the real thing! Do not delete these objects. Also, if you have your desktop set to need a password on bootup, and then forget that password, there are directions on how to get yourself out of that mess. Chapter 5 discusses the objects on the desktop in elaborate detail. The whole WPS internal class hierarchy is given, and the most important ones are explained. A neat tip is given in this chapter: how to move templates. This is not obvious at all, but once you realize that all objects have a default drag-and-drop action that sometimes can be over-ridden, that the default action of the templates are a version of copy, and that the move augmentation key is shift, then it all comes together. This level of detail is standard throughout the book, and is immensely helpful for those of us that say "Yeah, ok, but why?" all the time. Another neat feature of the book is that little REXX scripts are given all the way to accomplish helpful tasks. These are all on the included CD-ROM, so that they don't have to be typed in. The obscure pages in the object settings notebooks finally seem more purposeful to me, even if I don't personally use them all. One of the strong points of the book is that all the way through it, there are little short-cuts for commonly performed actions. Chapter 6 explains how to customize the Workplace Shell. This includes explaining the PROTSHELL and RUNWORKPLACE statements in the config.sys. It also goes through the AUTOSTART and RESTARTOBJECT parameters. These parameters are fairly misunderstood by many people, both novices and self-proclaimed "experts", with which the Net abounds, but the record is set straight here. A neat little tip follows: try putting the line SET MENUSTYLE=SHORT in your config.sys. You can probably guess what it does, but what a relief from information overload on every pop-up menu the system presents you with. Many of the tips presented in earlier chapters are elaborated on here as well. There are a number of tips here on how to develop WPS objects under Warp. Some of the old tips are no longer relevant, since Warp now uses DSOM by default, which means that your objects will run separately from the WPS, and will no longer bring the system down, if they are buggy. Chapter 7 discusses command-line windows, but since I have done a lot of work with these, I will skip this chapter. The author likes 4OS2, but personally I prefer YAOS. It also has aliases, history, and so on, but is much more like the tcsh that I use at school. Chapter 8 is about REXX programming, and again, I will move over this, having already read a number of introductory REXX books. Virtual DOS machines in chapter 9, and Win-OS/2 in chapter 10 are not terribly interesting to me either. I firmly believe in supporting the OS/2 developer community, and I am not one of the people who runs NetScape for Windows, WordPerfect 5.1 for DOS or Word for Windows regularly. The current batch of OS/2 programs that do the same thing are close in functionality, and this is good enough for right now. Chapter 11 covers the video subsystem, and is written by Bill Bodin, the OS/2 Warp Video Team Lead and Video Architect for the WPS. His involvement with OS/2 video really shows in the coverage he gives to this topic. Like so many of the chapters, this chapter is in-depth, covers pretty well everything, and has a unique insight into the inner workings of OS/2. The pros and cons of 8514/A, SVGA, VGA, and so on are all given. For example, did you know that the mode command now supports column widths of 1 to 255 inclusive? Maybe 1 isn't terribly useful, but it is nice to know that you impose your own restrictions, as opposed to following theirs. Some alternative ways of installing video drivers are given for those of you who are not able to use the display driver installation utility. Chapter 12 speaks of fonts. I have never quite understood why screen fonts seem so crude in OS/2, but I am sure that this chapter has that information buried somewhere, although I was unable to find it. Everything I have ever printed from OS/2 has come out beautifully, but the fonts used on my screen are frequently poorly spaced, and choppy. I have seen a lot of other people complain about this phenomenon on Usenet newsgroups, but no-one has ever satisfactorily explained it. Installation and usage of both Adobe Type 1 and True Type fonts is explained. Printing is the topic of the next chapter. Again, I skimmed this quickly. I use the HP LaserJet 4 at school, so I just print to file, and am done. For this reason, there is nothing in particular I needed to know from this chapter. Chapter 14, on the other hand, discusses file systems. Having recently written a small file system, this topic was of particular interest to me. FAT is built into Warp, whereas HPFS in an installable file system. That much I knew. On the other hand, Warp still has to know something about HPFS in order to be able to boot from an HPFS drive, with no FAT around. I hadn't really thought about that before, but it does make sense. But, then how do other people write IFSs for Warp? It is not possible for someone to port JFS, for example, and then put a little code into Warp so that it can boot from a JFS disk. Yet another chicken and egg argument... All aspects of both file systems are explained, such as caches, long/short file names, EAs, and so on. The relevant parameters from config.sys are pointed out and explained. The various disk utilities that come with Warp are also outlined. A couple of neat ways of using EAs are given, such as how to store comments with a file, without changing the file. Multimedia is covered in chapter 15. The rationale behind developing it in the first place is given, and then everything from DIVE to TV is discussed. Several REXX scripts are given that demonstrate how to program for multimedia with REXX. In addition to this, the applets that come with MMOS2 are covered. The most popular audio cards are talked about, as well as some special applications, such as Video IN. There is even some discussion of the suitability and features of the various sound cards on the market today. Finally, a brief guide to video recording, multimedia and the Internet, CD-ROM drives, Pen for OS/2, speech technology, and error messages are discussed. Chapter 16 is an introduction to the Icon Editor, EPM, and the other applets. There is a thick chapter 17 on networking, and another on trouble shooting. The latter chapter includes sections on installation, problem prevention, failure recovery, and error logging. Chapter 19 covers the Internet. Unfortunately, this area has been moving so fast that some of the information is already out of date, although the book was only just published. Such is life. For example, The Web Explorer covered is version 0.91, whereas we now have 1.01, and the 950331 beta. Nonetheless, there is some good information in here, including a section on the much-maligned Ultimail Lite. PPP is also left out, since it was only released in December/January. The next update of this book will surely have a nice section on connecting to other providers, complete with scripts and information for both SLIP and PPP. Finally, another chapter for which I don't have much use, but one that is probably a god-send for many: Portable computing with OS/2. There is also an appendix about the CD-ROM that comes with the book. Unfortunately, the CD-ROM itself is a bit messy, but the coverage of the CD in the book is excellent paradoxically. /dev/EDM2/BookReview - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 7.4. Summary ═══ Summary There is a plethora of insight and inside knowledge in this book which simply can not be found anywhere else. This is one of the best books I have ever read about OS/2, and I have no qualms about giving it my mark of excellence, an A+. This is just one of those books that everyone who owns OS/2 should own. If IBM licensed it, and included it with every copy of OS/2 sold, they would probably save a bundle in support costs. Unfortunately, that is probably just a pipedream. The only possible drawback to this book is the relatively high price of 39.99US$/53.99CAN$, but rest assured that once you have paid for it, you will not regret. A "must buy" book. /dev/EDM2/BookReview - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 7.5. Books Reviewed ═══ Books Reviewed This list is getting rather long, so I will just add list the book reviewed from now on, and instead keep an index of books reviewed, and issues they are reviewed in. Here is this month's entry:  OS/2 Warp Unleashed, Moskowitz, Kerr, et al. - Sams Publishing. ISBN 0-672-30545-3. US$39.99, CAN$53.99 - OS/2 Users and Power Users - A+ This book has a wealth of hard-to-come-by information regarding just about every imaginable topic on OS/2. This book belongs in the OS/2 library of everyone. NOTES This list contains all books I have reviewed, so that you can find what you are looking for at a glance. I will be careful to rate books fairly. If I feel a need to adjust ratings, I will adjust all of them at the same time, and write a note explaining why I felt this necessary. Please note that books aimed at different audiences should only be compared with great care, if at all. I intend to concentrate on the strong points of the books I review, but I will point out any weaknesses in a constructive manner. LEGEND BOOK: The name of the book, and the author(s). PUBLISHING INFORMATION: Publishing company, ISBN, and approximate price. AUDIENCE: This is a description of the audience I think the book targets best. This is not intended as gospel, just a guideline for people not familiar with the book. MARK: My opinion of the success of the book's presentation, and how well it targets its audience. Technical content, accuracy, organization, readability, and quality of index all weigh heavily here, but the single most important item is how well the book covers what it says it covers. Many books try to cover too much, and get a lower mark as a result. A+ Ground-breaking, all-around outstanding book. A Excellent book. This is what I want to see happen a lot. A- Excellent book with minor flaws. B+ Very good book with minor flaws or omissions. B Good book with some flaws and omissions. B- Good book, but in need of improvement. C+ Mediocre book with some potential, but in need of some updating. C Mediocre book with some good sections, but badly in need of fixing. C- Mediocre book, little good material, desperately in need of an overhaul. D Don't buy this book unless you need it, and nothing else exists. F Don't buy this book. Period. COMMENTS: This is a very brief summary of the review proper. /dev/EDM2/BookReview - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 7.6. Index ═══ Index This Content Index is designed to let you find the book that covers the topics you need to learn about. It will eventually have a lot of categories, with each book being rated along each row. These tables will be quite large, and will continually grow, so please give me your feedback regarding what categories you would like to see, and which you don't. It may take me a while to flesh them out, so have a little patience. BOOK LEGEND ┌─────┬───────┬───────────────────────────────────────────────────────────────────┐ │Code │Issue │Title │ ├─────┼───────┼───────────────────────────────────────────────────────────────────┤ │RWP │2-3 │Real World Programming for OS/2 2.1 │ ├─────┼───────┼───────────────────────────────────────────────────────────────────┤ │LPE │2-4 │Learning to Program OS/2 2.0 Presentation Manager by Example │ ├─────┼───────┼───────────────────────────────────────────────────────────────────┤ │ODD │2-5 │Writing OS/2 2.1 Device Drivers in C │ ├─────┼───────┼───────────────────────────────────────────────────────────────────┤ │GPI │2-6 │OS/2 Presentation Manager GPI │ ├─────┼───────┼───────────────────────────────────────────────────────────────────┤ │TAO │2-7 │The Art of OS/2 2.1 C Programming │ ├─────┼───────┼───────────────────────────────────────────────────────────────────┤ │MOR │2-8 │Mastering OS/2 REXX │ ├─────┼───────┼───────────────────────────────────────────────────────────────────┤ │RSH │2-9 │REXX Reference Summary Handbook │ ├─────┼───────┼───────────────────────────────────────────────────────────────────┤ │ADO │2-10 │Application Development Using OS/2 REXX │ ├─────┼───────┼───────────────────────────────────────────────────────────────────┤ │PMP │2-11 │OS/2 Presentation Manager Programming │ ├─────┼───────┼───────────────────────────────────────────────────────────────────┤ │DOA │3-1 │Designing OS/2 Applications │ ├─────┼───────┼───────────────────────────────────────────────────────────────────┤ │OSP │3-2 │OS/2 Programming │ ├─────┼───────┼───────────────────────────────────────────────────────────────────┤ │TGO │3-4 │The GUI-OOUI War │ ├─────┼───────┼───────────────────────────────────────────────────────────────────┤ │OU │3-5 │OS/2 Warp Unleashed, Deluxe Edition │ └─────┴───────┴───────────────────────────────────────────────────────────────────┘ NOTE: books which cover the same material can look similar in this table, but be different in real life. The style of a book, for example, can not be seen from a quick table, so make sure that you follow up by reading the reviews of the books you find here. Finally, be sure that the books you are comparing are aimed at the same audiences. PM C BOOKS ┌─────┬─────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┐ │BOOK │MARK │Kernel │Device │VIO and│PM │GPI │Fonts │Print │ │ │ │Basics │Driver │AVIO │Intro │ │ │ │ ├─────┼─────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┤ │RWP │B+ │2 │0 │0 │4 │4 │4 │3 │ ├─────┼─────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┤ │PME │B- │1 │0 │0 │2 │2 │2 │0 │ ├─────┼─────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┤ │ODD │A │0 │5 │0 │0 │1 │0 │1 │ ├─────┼─────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┤ │GPI │C+ │0 │0 │0 │0 │5 │2 │3 │ ├─────┼─────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┤ │TAO │B+ │3 │2 │1 │4 │1 │2 │0 │ ├─────┼─────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┤ │PMP │A- │1 │0 │1 │5 │3 │4 │2 │ ├─────┼─────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┤ │OSP │B+ │2 │0 │0 │3 │2 │1 │0 │ └─────┴─────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┘ REXX BOOKS: ┌─────┬─────┬─────────┬─────────┬─────────┐ │BOOK │MARK │REXX │WPS │Reference│ │ │ │Intro │ │ │ ├─────┼─────┼─────────┼─────────┼─────────┤ │MOR │B │4 │0 │2 │ ├─────┼─────┼─────────┼─────────┼─────────┤ │RSH │A │1 │2 │5 │ ├─────┼─────┼─────────┼─────────┼─────────┤ │ADO │A- │3 │2 │4 │ └─────┴─────┴─────────┴─────────┴─────────┘ SYSTEM AND NON-PROGRAMMING BOOKS: ┌─────┬─────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┐ │BOOK │MARK │Kernel │Device │VIO and│PM │Thread │GPI │Fonts │Print │WPS │ │ │ │Basics │Driver │AVIO │ │ │ │ │ │ │ ├─────┼─────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┤ │DOA │A │4 │4 │2 │4 │5 │3 │2 │3 │0 │ ├─────┼─────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┤ │TGO │B │0 │0 │0 │2 │1 │0 │2 │1 │5 │ ├─────┼─────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┤ │OU │A+ │1 │4 │4 │5 │2 │5 │5 │5 │5 │ └─────┴─────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┘ RATINGS LEGEND: ┌─┬──────────────────────┐ │0│No coverage │ ├─┼──────────────────────┤ │1│Very light coverage │ ├─┼──────────────────────┤ │2│Introductory coverage │ ├─┼──────────────────────┤ │3│Good Coverage │ ├─┼──────────────────────┤ │4│In-depth coverage │ ├─┼──────────────────────┤ │5│Authoritative │ └─┴──────────────────────┘ /dev/EDM2/BookReview - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 7.7. Coming Up ═══ Coming Up I haven't yet picked out a book for next month, but I am going to look for an intermediate PM book. The following are some other books I intend to review, in no particular order:  OS/2 Presentation Manager GPI, 2nd edition, Winn  The Design of OS/2, 2nd Edition, Kogan and Deitel  Designing High Powered OS/2 Applications, Reich (tentative title) I am considering reviewing the IBM OS/2 Redbooks, since they are readily and cheaply available, and look like good reference. If anyone has a book they want to see reviewed, I will be happy to oblige. Just mail me and tell me. Publishers can send me books at the address on my personal page at the end of the magazine, and I will review all OS/2 development- related and advanced user books I receive. /dev/EDM2/BookReview - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 8. OOPS Avenue ═══ ═══ 8.1. Introduction ═══ OOPS Avenue Written by Gordon Zeglinski Using C++ In Dynamic Link Libraries Just when things start moving along, a new set of setbacks arise. If they weren't happening to me, they'd be quite comical. At any rate, we'll take a break from SOM/DSOM in this issue, and look at using C++ code from Dynamic Link Libraries (DLLs). Before looking at C++ specific issues, we start by looking at some DLL basics. OOPS Avenue - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 8.2. DLL Basics ═══ DLL Basics A DLL is a library of code and or resources that can be loaded and unloaded dynamically and shared between processes. Dynamic loading means that the library is loaded at run time rather than being statically bound to the application. When a DLL is loaded or unloaded, the operating system calls the function _DLL_InitTerm(). The following snippet is a skeleton _DLL_InitTerm() function: unsigned long _System _DLL_InitTerm(unsigned long hModule, unsigned long ulFlag){ switch (ulFlag) { case 0 : // do load initialization here break; case 1 : // do unload termination here break; } return 1UL; //return 0 if we want to indicate that the initialization code has failed } The above function (in its current form) is only useful for a resource DLL or a DLL that contains only assembly language functions. High level languages will require initialization and termination routines to be called. If we use C-Set++ as an example, the minimal _DLL_InitTerm() function would be as follows: unsigned long _System _DLL_InitTerm(unsigned long hModule, unsigned long ulFlag){ switch (ulFlag) { case 0: //initialize the C Run Time library if (_CRT_init() == -1) return 0UL; //for C++ code we call __ctordtorInit to create the global/static intances __ctordtorInit(); // do user load initialization here break; case 1: // do user unload termination here //for C++ code we call __ctordtorTerm to destroy static/global instances __ctordtorTerm(); //if the C Run Time library is STATICALLY linked, we call _CRT_term to free up the //resources used by the CRTL _CRT_term(); break; } return 1UL; //return 0 if we want to indicate that the initialization code has failed } Note: for compilers other than C-Set++, different run time library initialization/termination calls have to be made. Functions are exported from a DLL by either name or numerical offset. The standard way of exporting a function is to specify its name in the "EXPORTS" section of the definition file for the DLL. Most compilers also allow a short cut method of exporting functions. This is to declare them with a special keyword that causes the compiler to generate code that the linker will automatically export for you. In C-Set++ one uses the "_Export" keyword. This seems simple enough. However, there are numerous pitfalls as we shall soon see. OOPS Avenue - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 8.3. DLLs and C++ ═══ DLLs and C++ C++ mangles function names. This can increase the difficulty in exporting C++ functions. C-Set++ provides a utility called CPPFILT to extract mangled names so that they may be placed in the .DEF file. Alternatively, one can use the _Export keyword to have the compiler and linker work together to export the function. Let's define the class Foo so that it's member functions will be exported. The code to do this is presented below: struct FooBar{ int Y; } class _Export Foo{ public: Foo(); ~Foo(); int GetVar(){return FooVar;} void SetVar(int); FooBar* CreateFooBar(); protected: int FooVar; }; Note: Borland C++ allows one to use the _export keyword in a similar manner as one uses _Export in C-Set++. By placing the keyword _Export after the keyword class, the compiler automatically exports all non-inline functions. In this example Foo::Foo(), Foo::~Foo(), Foo::SetVar(int), and Foo::CreateFooBar() will all be exported. Foo::GetVar() will not be exported because it is implicitly defined as inline. Let's say that we have coded Foo's member functions so they can be exported. We compiled the code and linked it statically to the C run time library to form a DLL. We then link the resulting DLL to the following code snippet to produce an executable file. void main(){ Foo FooInst; FooBar *Bar; Bar=FooInst->CreateFooBar(); delete Bar; } Let's assume that Foo::CreateFooBar() is contained in the Foo DLL and is defined as follows: FooBar* Foo::CreateFooBar(){ return new FooBar; } We've now compiled and linked our code. Everything should be fine until we actually try running the code. In this scenario, we have 2 active C run time libraries. The first is bound to the Foo DLL and the second is bound to the executable. When the delete operator is evoked, the memory is freed from the local C RTL. This is where the problem will occur. The memory was allocated in the DLL's C RTL and an attempt to free it was made in the executable's CRTL. There are two ways of solving this problem. First, one can use another DLL that holds the C RTL and dynamically link this DLL to FOO DLL and the executable. Alternatively, one can create and export a new function in FOO DLL that is used to delete instances of FooBar. Creating a separate DLL for the C RTL is not without its pitfalls. The biggest pitfall is that OS/2 doesn't unload DLLs in reverse order. In fact, the order in which they are unloaded is somewhat unpredictable. This is a serious problem. What if the C RTL DLL is unloaded first? If this happens, it's most likely that when subsequent DLLs are unloaded they will trap or hang the process if they have any static or global object instances in them. One way around this is to use exit handlers at different priorities. These exit handlers call the __ctordtorTerm() function and keep track of the DLL's initialization state. Another method could be to use a counter variable in the C RTL DLL that keeps track of how many other DLLs are using it. This method would require the C RTL DLL to export a function for incrementing and decrementing this counter. These functions would have to be called each time a dependent DLL is loaded and unloaded. When the counter reaches 0, the C RTL is shutdown by calling _CRT_term. Both the counter method and the exit handler method require each DLL to use a custom _DLL_InitTerm function. OOPS Avenue - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 8.4. Using C++ DLLs Across Different Languages or Compilers ═══ Using C++ DLLs Across Different Languages or Compilers Because the name mangling scheme is not standardized, nor is the calling convention for that matter, it is impossible to use C++ functions "raw" across different compilers. To do this, one has to define C to C++ wrapper functions. The following snippet shows how to wrap the Foo class in C- Set++. extern "C"{ void* _System CreateFoo(); void _System DestroyFoo(void *_Foo); int _System FooGetVar(void *_Foo); void _System FooSetVar(void *_Foo, int V); void* _System FooCreateFooBar(void *_Foo); void _System DestroyFooBar(void *_FooBar); }; void* _System CreateFoo(){ return new Foo; } void _System DestroyFoo(void *_Foo){ delete ((Foo*)_Foo); } int _System FooGetVar(void *_Foo){ return ((Foo*)_Foo)->GetVar(); } void _System FooSetVar(void *_Foo, int V){ ((Foo*)_Foo)->SetVar(V); } void* _System FooCreateFooBar(void *_Foo){ return ((Foo*)_Foo)->CreateFooBar(); } void _System DestroyFooBar(void *_FooBar){ delete ((FooBar*)_FooBar); } The function names CreateFoo(), DestroyFoo(), etc. can be exported by placing them in the EXPORTS section of the DLLS module definition file. The following snippet illustrates how these wrapper functions can be used. void main(){ void *FooInst=CreateFoo(); FooSetVar(FooInst, 10); DestroyFoo(FooInst); } The above snippet can be the main routine for either a C or a C++ program. The problem of multiple C run time libraries is avoided in this case because all memory allocation and deallocation is handled in the DLL's wrapper functions. OOPS Avenue - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 8.5. Wrapping Things Up ═══ Wrapping Things Up That's it for another issue. We have seen several ways of using C++ code contained in a DLL. The problem of having multiple C runtime libraries within a single process along with the problem of DLL unloading has been examined. Several solutions to each of these problems have been presented here. OOPS Avenue - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 9. Introduction to PM Programming ═══ ═══ 9.1. Introduction ═══ Introduction to PM Programming Written by Larry Salomon, Jr. Introduction The purpose of this column is to provide the readers out there who are not familiar with PM application development the information necessary to satisfy their curiosity, educate themselves, and give them an advantage over the documentation supplied by IBM. Of course, much of this stuff could probably be found in one of the many books out there, but the problem with books in general is that they don't answer the questions you have after you read the book the first time through. I will gladly entertain feedback from the readers about what was "glossed over" or what was detailed well, what tangential topics need to be covered and what superfluous crap should have been removed. This feedback is essential in guaranteeing that you get what you pay for. It should be said that you must not depend solely on this column to teach you how to develop PM applications; instead, this should be viewed as a supplement to your other information storehouses (books, the network conferences, etc.). Because this column must take a general approach, there will be some topics that you would like to see discussed that really do not belong here. Specific questions can be directed to me via email and I will do my best to answer them in a timely fashion. Last Month Last month we began looking at the menu control, which we will continue with this month. Introduction to PM Programming - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 9.2. Basic Knowledge Required ═══ Basic Knowledge Required Before we may continue, we should take a brief look at the typical use of a menu control, from the programmer's perspective. This involves the definitions of the manifest constants, the menu template definition, the "loading" of the menu (you'll see why "loading" is in quotes in a minute), and the processing of the user's actions. We're taking this different approach to looking at the window class because - as we stated last month - the menu provides the most widely used functionality of any of the window classes; as such, you'll need to know how to use it because you'll be writing this code more times than any other. Manifest Constants Manifest constants - a.k.a. #define's - are a convenient language element which allow us to use a "name" in place of something else. For menus, the "something else" is a number which represents an identifier unique to that menu template. You'll find that in larger applications that the file containing these constants can grow quite large, so it would be helpful if, at a glance, you could determine the use of the constant from looking at its name. To accomplish this, I defined a naming convention. You can use this one or your own - it doesn't matter. What does matter is that you spend less time trying to find out how a constant is used and more time on actually using the constant. For menus, I use the following: Constant form What it represents M_id Menu or submenu identifier MI_id Menuitem identifier The constants that are used for resource definitions I keep in a separate file to allow me to make the file a dependancy of all files that use resources. This is simply a suggestion. Template Definition Under Windows, there is App Studio which allows you to define and maintain all of your resources. How come there isn't something like this for OS/2? Regardless, you have to design your menus "blind," meaning that you can't see the result until you actually use the menu you've built. As it is with writing applications, designing your menu layout should be the most important action; a confusing or deep (i.e. many pullrights) menu structure will often confuse the user and turn them away from using your application. Group the items in submenus in a logical fashion so that the user can "take a guess" at where to look when trying to execute a particular action. IBM's "Common User Access" (CUA) guidelines define a number of pulldown menus and a (non-inclusive) list of what they should contain. I've included a brief list of these (since I cannot find my CUA book ) below: Pulldown name Menu items contained therein File New, Open, Close, Save, Save as, etc. Edit Undo, Copy, Cut, Paste, etc. Help Help index, General help, Using help, Keys help Another important CUA note is the use of the ellipsis and bang punctuation marks on text. An ellipsis should be appended to menu item text when selecting the menu item results in a dialog being displayed. A bang should be appended to text belonging to items on the action bar (note that this therefore excludes pullrights and any popup menu items) when selecting them does not display a pulldown menu (i.e. when it is a leaf-node). These are used as feedback indicators to the user to alert them about the behavior of the application without having them find out "the hard way." Loading a Menu Loading a menu usually involves the setting of a flag only. No explicit loading is usually performed by the application code. (Popup menus are the exception to this rule, since the current CUA specification eliminates action bars.) In the call to WinCreateStdWindow(), simply include FCF_MENU on the set of flags passed (by reference) as the third parameter. #define RES_CLIENT 256 HWND hwndFrame; HWND hwndClient; ULONG ulCreate=FCF_SYSMENU | FCF_TITLEBAR | FCF_MINMAX | FCF_MENU | FCF_SHELLPOSITION | FCF_TASKLIST; hwndFrame=WinCreateStdWindow(HWND_DESKTOP, WS_VISIBLE, &ulCreate, CLS_CLIENT, "Test application" 0, NULLHANDLE, RES_CLIENT, &hwndClient); It has been stated before in this column (I hope) that, when using FCF_MENU, FCF_ICON, or FCF_ACCELTABLE that the eighth parameter must specify the resource identifier of the resources corresponding to these flags. Since we specified FCF_MENU, we should have a menu template defined that begins with: MENU RES_CLIENT : If we do not have this defined, the WinCreateStdWindow() function will fail. A useful note here is that, as each component of the window specified in ulCreate is successfully created, the corresponding bit in ulCreate is set to 0. So, the components that were not created successfully can be determined by examining ulCreate after the function fails. Processing the User's Requests Now we learn how to utilize the menu in our application code. There are usually two messages that we will be interested in when coding our application: WM_INITMENU and WM_COMMAND. Note that, if any MENUITEMs have the MIS_SYSCOMMAND or MIS_HELP style, we will need to add the WM_SYSCOMMAND and WM_HELP messages to this list, but for the moment we'll assume that we have none with these menu item styles. WM_INITMENU is sent to the client window whenever a menu or submenu is about to be used by the user, e.g. when the user presses F10 to get to the action bar, when the user clicks on a pulldown menu to display the pulldown, etc. Intercepting this message allows us to dynamically enable or disable menu items according to the state of our application. For example, if the user selects the "Print" menuitem, you might want to disable the "Exit" menuitem until printing is completed. We will see later how to change menuitem attributes. SHORT1FROMMP(mpParm1) specifies the identifier of the menu. If the actionbar is the cause of the message, this will be FID_MENU; otherwise, it will be the identifier you specified on the SUBMENU statement in the menu template. HWNDFROMMP(mpParm2) is the handle of the window corresponding to the identifier. You should know that pulldown and pullright menus are actually separate windows which belong to something called an "object" window when not in use. When you click on a pulldown, the appropriate window is retrieved from the object window and displayed on the desktop. This window handle is needed to send messages to the menu. WM_COMMAND, WM_SYSCOMMAND, and WM_HELP are all sent whenever the user selects a menuitem with the corresponding style. This is your indicator that the user wants something done and that you are to process this request. SHORT1FROMMP(mpParm1) specifies the identifier of the menuitem selected. SHORT1FROMMP(mpParm2) specifies the source of the message and will always be CMDSRC_MENU when this message is sent via a menu control. SHORT2FROMMP(mpParm2) is TRUE if this message was sent as the result of a mouse action or FALSE if sent as the result of a keyboard action. Introduction to PM Programming - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 9.3. Next Month ═══ Next Month Next month we'll start to look at a sample application that uses the menu control, and we'll begin to look at the MM_ message family which allows you to interact with the menu control. As always, feedback will be enjoyed immensely; send any comments, suggestions, etc. to os2man@panix.com. Introduction to PM Programming - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 10. Contributors to this Issue ═══ Are You a Potential Author? We are always looking for (new) authors. If you have a topic about which you would like to write, send a brief description of the topic electronically to any of the editors, whose addresses are listed below, by the 15th of the month before the month in which your article will appear. This alerts us that you will be sending an article so that we can plan the issue layout accordingly. After you have done this, get the latest copy of the Article Submission Guidelines from hobbes.nmsu.edu in the /os2/newsltr directory. (The file is artsub.zip.) The completed text of your article should be sent to us no later than five days prior to the last day of the month; any articles received after that time may be pushed to the next issue. The editors can be reached at the following email addresses:  Larry Salomon - os2man@panix.com (Internet).  Carsten Whimster - bcrwhims@undergrad.math.uwaterloo.ca (Internet). The following people contributed to this issue in one form or another (in alphabetical order):  Larry Salomon, Jr.  Carsten Whimster  Johan Wikman  Gordon Zeglinski  Network distributors Contributors - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 10.1. Larry Salomon, Jr. ═══ Larry Salomon Jr. Larry Salomon Jr. has been developing OS/2 applications since version 1.1 in 1989. He has written numerous applications, including the Scramble applet that was included in OS/2 versions 2.0-2.11, and the I-Brow, Magnify, and Screen Capture trio that has been distributed on numerous CD-ROMs. Larry is also the coauthor of the successful book, The Art of OS/2 2.1 C Programming (Wiley-QED). Finally, he is the CEO/President of IQPac Inc. which is responsible for the publication of EDM/2 and he is a frequent contributor to the publication. Larry can be reached electronically via the Internet at os2man@panix.com. Contributors - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 10.2. Carsten Whimster ═══ Carsten Whimster Carsten is an undergraduate Computer Science student at the University of Waterloo. He is currently in fourth year, and enjoying it immensely. He uses Watcom C/C++ 10.0a and Watcom VX-REXX 2.0b. Carsten is the author of some commandline utilities, POV-Panel/2, and soon to come POVEd. He is also a TEAM-OS/2 member, and has adopted a little computer store called The Data Store in Waterloo, Ontario. You may reach Carsten... ...via email: bcrwhims@undergrad.math.uwaterloo.ca - Internet ...World Wide Web homepage (incomplete; aren't they all): http://www.undergrad.math.uwaterloo.ca/~bcrwhims - WWW ...via snail mail: Carsten Whimster 318A Spruce Street Waterloo, Ontario Canada N2L 3M7 ...via phone (519) 886-2439 Contributors - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 10.3. Johan Wikman ═══ Johan Wikman Johan Wikman received his Master's degree in Computer Science with a thesis "Adding remote execution capability to operating systems". He has been programming for OS/2, using C++, ever since 1990. Currently he works for Nokia Telecommunications where he takes part in a project that developes network management software in a Unix environment. In his spare time, he continues working on RMX-OS2, a system that provides remote execution for OS/2. Johan can be reached electronically via the Internet at johan.wikman@ntc.nokia.com, or via ordinary mail: Johan Wikman Smedjeviksvagen 23 B 22 FI-00200 Helsinki FINLAND Contributors - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 10.4. Gordon Zeglinski ═══ Gordon Zeglinski Gordon Zeglinski is a freelance programmer/consultant who received his Master's degree in Mechanical Engineering with a thesis on C++ sparse matrix objects. He has been programming in C++ for 6 years and also has a strong background in FORTRAN. He started developing OS/2 applications with version 2.0 . His current projects include a client/server communications program that utilitizes OS/2's features which has entered beta testing. Additionally, he is involved in the development of a "real-time" automated vehicle based on OS/2 and using C++ in which he does device driver development and designs the applications that comprise the control logic and user interface. He can be reached via the Internet at zeglins@cc.umanitoba.ca. Contributors - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 10.5. Network distributors ═══ Network Distributors These people are part of our distribution system to provide EDM/2 on networks other than the Internet. Their help to provide access to this magazine for others is voluntary and we appreciate them a lot!  Paul Hethmon (phethmon@utk.edu) - Compuserve  Gess Shankar (gess@knex.mind.org) - Internet  Jason B. Tiller (PeerGynt@aol.com) - America On-line  David Singer (singer@almaden.ibm.com) - IBM Internal  Andre Asselin (ASSELIN AT RALVM12) - IBM Internal If you would like to become a "network distributor", be sure to contact the editors so that we can give you the credit you deserve! Contributors - EDM/2 - May 1995 - Volume 3, Issue 5 ═══ 11. How Do I Get EDM/2? ═══ How Do I Get EDM/2? EDM/2 can be obtained in any of the following ways: On the Internet  All back issues are available via anonymous FTP from the following sites: - hobbes.nmsu.edu in the /os2/newsltr directory. - ftp.luth.se in the /pub/os2/programming/newsletter directory. - generalhq.pc.cc.cmu.edu in the /pub/newsletters/edm2 directory.  The EDM/2 mailing list. Send an empty message to edm2-info@knex.mind.org to receive a file containing (among other things) instructions for subscribing to EDM/2. This is a UUCP connection, so be patient please.  IBM's external gopher/WWW server in Almaden. The address is index.almaden.ibm.com and it is in the "Non-IBM-Originated" submenu of the "OS/2 Information" menu; the URL is "gopher://index.almaden.ibm.com/1nonibm/os2nonib.70". On Compuserve All back issues are available in the OS/2 Developers Forum 2. IBM Internal  IBM's internal gopher/WWW server in Almaden. The address is n6tfx.almaden.ibm.com and it is in the "Non-IBM-Originated Files" menu; the URL is "gopher://n6tfx.almaden.ibm.com/1!!nonibm/nonibm.70".  IBM's REQUEST command on all internal VM systems. Enter the VM command REQUEST LIST FROM ASSELIN AT RALVM12 and a list of the requestable packages will be sent to you; in this list are the names of the packages containing the EDM/2 issues. How do I Get EDM/2? - EDM/2 - May 1995 - Volume 3, Issue 5