═══ 1. About this FAQ ═══ This FAQ provides answers to the questions that VisualAge C++ customers ask most often about the product, how it works, and how to use it. When you encounter a problem using VisualAge C++, look here first for the solution. You can search for the information you need based on the symptoms of your problem, or on the type of task you were trying to do when the problem occurred. For a list of other information that you might also find helpful, see Other Information You Might Find Helpful. If the questions included here do not describe your problem, or if the answers do not solve it, refer to What If I Still Have Questions?. Before you begin to use this information, it would be helpful to understand how to navigate through it. You can use the Table of Contents and Index facility to locate topics and the Search facility to search the text of this document. You can use hypertext links to acquire related information on the current topic. Hypertext links appear in a different color (which you can customize using the OS/2 Scheme Palette). For example, here is a link to another panel: Communicating Your Comments to IBM. By double-clicking on the text of the link or by pressing Enter on a highlighted link, you will open a panel of related information. When you open a panel, the first link has the focus; to shift the focus to other links, use the Tab key. You should also understand:  How to Use the Contents  How to Obtain Additional Information  How to Use Action Bar Choices  How to Cut and Paste Examples ═══ 1.1. Notices ═══ Copyright International Business Machines Corporation, 1995. All rights reserved. Note to U.S. Government Users - Documentation related to restricted rights - Use, duplication, or disclosure is subject to restrictions set forth in GSA ADP Schedule Contract with IBM Corp. First Edition, May 1995. This edition applies to Version 3.0 of IBM VisualAge C++ for OS/2 (30H1664, 30H1665, 30H1666) and to all subsequent releases and modifications until otherwise indicated in new editions. Make sure you are using the correct edition for the level of the product. This publication could include technical inaccuracies or typographical errors. Changes are periodically made to the information herein; any such changes will be reported in subsequent revisions. Requests for publications and for technical information about IBM products should be made to your IBM Authorized Dealer or your IBM Marketing Representative. When you send information to IBM, you grant IBM a nonexclusive right to use or distribute the information in any ways it believes appropriate without incurring any obligation to you. Any reference to an IBM licensed program in this publication is not intended to state or imply that only IBM's licensed program may be used. Any functionally equivalent product, program, or service that does not infringe any of IBM's intellectual property rights may be used instead of the IBM product, program, or service. Evaluation and verification of operation in conjunction with other products, except those expressly designated by IBM, is the user's responsibility. IBM may have patents or pending patent applications covering subject matter in this document. The furnishing of this document does not give you any license to these patents. You can send license inquiries, in writing, to the IBM Director of Licensing. IBM Corporation, 500 Columbus Avenue, Thornwood, NY, 10594, USA. This publication contains examples of data and reports used in daily business operations. To illustrate them as completely as possible, the examples include the names of individuals, companies, brands, and products. All of these names are fictitious and any similarity to the names and addresses used by an actual business enterprise is entirely coincidental. ═══ 1.2. Trademarks and Service Marks ═══ The following terms used in this publication are trademarks or service marks of IBM Corporation in the United States or other countries: AIX C/2 C Set/2 C Set ++ IBM Open Class OS/2 OS/2 Warp Presentation Manager VisualAge WorkFrame Workplace Shell Windows is a trademark of Microsoft Corporation. Other company, product, and service names, which may be denoted by a double asterisk(**), may be trademarks or service marks of others. ═══ 1.3. How to Use the Contents ═══ When the Contents window first appears, some topics have a plus (+) sign beside them. The plus sign indicates that additional topics are available. To expand the Contents if you are using a mouse, click on the plus sign. If you are using the keyboard, use the Up or Down Arrow key to highlight the topic, and press the plus (+) key. For example, How to Use the Contents has a plus sign beside it. To see additional topics for that heading, click on the plus sign or highlight that topic and press the plus (+) key. To view a topic, double-click on the topic (or press the Up or Down Arrow key to highlight the topic, and then press the Enter key). ═══ 1.4. How to Obtain Additional Information ═══ After you select a topic, the information for that topic appears in a window. Highlighted words or phrases indicate that additional information is available. Certain words and phrases are highlighted in a different color from the surrounding text. These are called hypertext terms. If you are using a mouse, double-click on the highlighted word. If you are using a keyboard, press the Tab key to move to the highlighted word, and then press the Enter key. Additional information then appears in a window. ═══ 1.5. How to Use Action Bar Choices ═══ Several choices are available for managing the information presented in this document. There are three menus on the action bar: the Services menu, the Options menu, and the Help menu. The actions that are selectable from the Services menu operate on the active window currently displayed on the screen. These actions include the following: Placing Bookmarks You can set a placeholder so you can retrieve information of interest to you. Searching for Information You can find occurrences of a word or phrase in the current topic, selected topics, or all topics. Printing Information You can print one or more topics. You can also print a set of topics by first marking the topics in the Contents list. Copying Information to a File You can copy a topic that you are viewing to the System Clipboard or to a file that you can edit. This method is particularly useful for copying syntax definitions and program samples into the application that you are developing. Using the actions that are selectable from the Options menu, you can change the way your Contents list is displayed. To expand the Contents and show all levels for all topics, choose Expand all from the Options pull-down. You can also press the Ctrl and * keys together. The actions that are selectable from the Help menu allow you to select different types of help information. For information about any of the menu choices, highlight the choice in the menu and press F1. ═══ 1.5.1. Placing Bookmarks ═══ When you place a bookmark on a topic, it is added to a list of bookmarks you have previously set. You can view the list, and you can remove one or all bookmarks from the list. If you have not set any bookmarks, the list is empty. To set a bookmark, do the following: 1. Select a topic from the Contents. 2. When that topic appears, select the Bookmark option from the Services menu. 3. If you want to change the name used for the bookmark, type the new name in the field. 4. Click on the Place radio button (or press the Up or Down Arrow key to select it). 5. Click on OK (or select it and press Enter). The bookmark is then added to the bookmark list. ═══ 1.5.2. Searching for Information ═══ You can specify a word or phrase to be searched. You can also limit the search to a set of topics by first marking the topics in the Contents list. To search for a word or phrase in all topics, do the following: 1. Select the Search option from the Services menu. 2. Type the word or words to be searched for. 3. Click on All sections (or press the Up or Down Arrow keys to select it). 4. Click on Search (or select it and press Enter) to begin the search. 5. The list of topics where the word or phrase appears is displayed. ═══ 1.5.3. Printing Information ═══ You can print one or more topics, the index, or the table of contents. Make sure that your printer is connected to the serial port, configured correctly, and ready for input. To print: 1. Select Print from the Services pull-down. 2. Select what you want to print. Note that the This section and Marked sections choices are only available if you are viewing a topic or if you have marked topics, respectively. To mark topics in the table of contents, press the Ctrl key and click on the topics, or use the arrow keys. 3. Select Print to print what you've chosen on your printer. ═══ 1.5.4. Copying Information to a File ═══ You can copy a topic that you are viewing in two ways:  Copy copies the topic that you are viewing into the System Clipboard. If you are using a Presentation Manager (PM) editor (for example, the Enhanced Editor) that copies or cuts (or both) to the System Clipboard, and pastes to the System Clipboard, you can easily add the copied information to your program source module.  Copy to file copies the topic that you are viewing into a temporary file named TEXT.TMP. You can later edit that file by using any editor. TEXT.TMP is placed in the directory where your viewable document resides. To copy a topic, do the following: 1. Expand the Contents list and select a topic. 2. When the topic appears, select Copy to file from the Services menu. 3. The system puts the text pertaining to that topic into the temporary file TEXT.TMP. ═══ 1.6. How to Cut and Paste Examples ═══ You can copy examples (or information) from this FAQ to compile, link, and run them, or to paste them into your own code. To copy an example or information: 1. Make the topic you want to copy the active window. 2. From the Services menu, select Copy to file. The text in that topic is placed in the temporary file TEXT.TMP, in the same directory as this reference. 3. You can then modify or use TEXT.TMP as you want. ═══ 1.7. Other Information You Might Find Helpful ═══ This product provides a number of online guides and references that we hope you'll find helpful as you develop applications. This information includes User's Guides, References, and How Do I help that gives you specific instructions for performing common tasks. You can get to this online information from the Information folder inside the main product folder. You can also get to it from the Help menu in any of the components of the product. ═══ 1.8. Communicating Your Comments to IBM ═══ If there is something you like, or dislike, about this book, please let us know. You can use one of the methods listed below to send your comments to IBM. Please be sure to include the complete title of the publication that you are commenting on. The comments you send should only pertain to the information in this document and its presentation. To request additional publications or to ask questions or make comments about the functions of IBM products or systems, you should talk to your IBM representative or you authorized IBM remarketer. When you send comments to IBM, you grant IBM a nonexclusive right to use or distribute your comments in any way it believes appropriate without incurring any obligation to you. You can send your comments to IBM in the following ways:  By mail to the following address: IBM Canada Ltd. Laboratory Information Development 2G/345/1150/TOR 1150 EGLINTON AVENUE EAST NORTH YORK, ONTARIO CANADA M3C 1H7  By FAX to the following number: - United States and Canada: (416) 448-6161 - Other countries (+1) 416-448-6161  By electronic mail to one of the following IDs. Be sure to include your entire network address if you wish to get a reply. - Internet: torrcf@vnet.ibm.com - IBMLink: toribm(torrcf) - IBM/PROFS: torolab4(torrcf) - IBMMAIL: ibmmail(caibmwt9) ═══ 2. General Questions about VisualAge C++ ═══ This section answers some general questions about VisualAge C++.  How Do I Get Code Fixes for VisualAge C++?  What Operating Systems Does It Support?  What Are the Minimum Hardware Requirements?  How Do I Rebuild My VisualAge C++ Desktop Folders?  Can I Get the Class Library Source Code?  What Documentation Exists for the User Interface Classes?  Can I Mix Object Files from Different Compilers?  What Are the Library Naming Conventions?  What Does Mangling Mean? ═══ 2.1. How Do I Get Code Fixes for VisualAge C++? ═══ Question: I think I have found a problem with the compiler or one of the tools. How do I get a fix for this problem? Answer: Fixes for problems with VisualAge C++ components are provided in corrective service diskettes (CSDs). Note: CSDs are not really diskettes. However, you can create diskettes from them. You can also apply the fixes in other ways. CSDs are available from CompuServe (GO OS2DF1), from the IBM PC Company Bulletin Board System (919-517-0001), and from ftp://software.watson.ibm.com/pub/cset. CSDs are cumulative; you only need to apply the most recent one to get all the updates that have been made. If you apply the CSDs and your problem persists, report your problem to VisualAge C++ Service and Support. For instructions on how to report problems, see What If I Still Have Questions?. ═══ 2.2. What Operating Systems Does It Support? ═══ Question: Can VisualAge C++ generate code for DOS or Windows? What other operating systems does it support? Answer: VisualAge C++ generates only 32-bit code for OS/2 2.11 and higher, including the OS/2 Warp family. It does not generate DOS, Windows, or 16-bit code. VisualAge C++ runs only under OS/2 2.11 and higher. C Set ++ and its class libraries are also available for the AIX and Solaris** operating systems. ═══ 2.3. What Are the Minimum Hardware Requirements? ═══ Question: What are the minimum hardware requirements for VisualAge C++? Answer: The minimum requirements are: Processor 386; 486 is recommended. If you have a 386 processor, a 387 math coprocessor is also recommended. Memory (RAM) 8M for C development (12M recommended) 12M for C++ development (16M recommended) 16M for visual C++ development (24M recommended) Note: In some cases, you may be able to run VisualAge C++ with less RAM than stated, but the performance will suffer. Disk Space Approximately 170MB for a complete installation (110MB for the tools, 60MB for sample programs and documentation). When you install VisualAge C++, the installation program tells you how much space is required for individual components. Swap Space 10MB for C development 30MB for C++ development ═══ 2.4. How Do I Rebuild My VisualAge C++ Desktop Folders? ═══ Question: How can I rebuild my desktop icons for VisualAge C++ without reinstalling? Answer: Run the CPPDESK command from the main VisualAge C++ directory (IBMCPP, if you used the defaults). ═══ 2.5. Can I Get the Class Library Source Code? ═══ Question: Can I get the source code for the class libraries? How? Answer: The source code for the IBM Open Class Library is available as a separate product. You can order it the same way you ordered VisualAge C++. Its part number (order number) is 30H1667. Corrective service is available for the source code as well as for VisualAge C++, so you can maintain the source and executable files at the same level. (For information on getting corrective service, see How Do I Get Code Fixes for VisualAge C++?.) ═══ 2.6. What Documentation Exists for the User Interface Classes? ═══ Question: What documentation is there for the User Interface classes? Answer: The Open Class Library User's Guide and Open Class Library Reference that ship with VisualAge C++ document the entire IBM Open Class Library, including the User Interface classes. You can find these books in the VisualAge C++ Information folder. The most complete separate publication is OS/2 C++ Class Library: Power GUI Programming with C Set ++, by Kevin Leong, William Law, Robert Love, Hiroshi Tsuji, and Bruce Olson. The publisher is John Wiley and Sons, Inc., and the ISBN number is 0-471-13117-2. (Note that until recently, the publisher was Van Nostrand Reinhold and the ISBN number 0-442-01795-2.) The book comes with a diskette full of samples. The authors are architects and leading developers of the User Interface class library. Note: This book was written based on Version 2.1 of the class library. It does not include information about new classes and features that have been added to VisualAge C++ Version 3.0. Some technical articles have also been published in "OS/2 Developer", including an article on INoteBook in the January/February 1994 edition, and two articles that discuss creating new control classes in the September/October 1994 edition. These articles are also based on Version 2.1. IBM also offers educational courses for VisualAge C++ and the User Interface class library. For more information about current course offerings, in North America, contact IBM Education and Training at 1-800-IBM-TEAC (1-800-426-8322). Outside of North America, contact your local IBM office or supplier. Note: The 1-800 number for Canada is being changed to 1-800-IBM-TEAC. If you call from Canada and this number is not yet in service, call the previous number, 1-800-661-2131. ═══ 2.7. Can I Mix Object Files from Different Compilers? ═══ Question: Can I mix object (.OBJ) files compiled with an earlier version of C Set ++ with VisualAge C++ .OBJ files? What about .OBJ files from other compilers, such as Borland? Answer: You can link .OBJ files created with C Set ++ Version 2.0 or 2.1 with VisualAge C++ .OBJ files. (Note that this does not apply to .OBJ files created with a beta version of VisualAge C++.) You cannot link .OBJ files created using different compilers. You must recompile your source files using the same compiler. However, you can call between executable modules (EXEs and DLLs) compiled with different compilers or different versions of a compiler, providing:  The modules do not share a runtime environment.  The calling conventions match.  Only C or extern "C" functions are called. For example, if you have a VisualAge C++ executable and a Borland DLL, and the above conditions are met, you can then call code from the Borland DLL, import and export data from it, and so on. You cannot directly call C++ functions compiled with a different compiler because each compiler makes different assumptions about runtime environment and uses different algorithms for mangling C++ names. To call a C++ function, you must declare it as extern "C". If you are linking with C Set ++ V2.x object files that use templates, you must compile your VisualAge C++ source files using the /Gk option and link using the /OLDCPP option to ensure the templates resolve correctly. (If you use icc to invoke the linker, specify /Gk /Tdp to pass the /OLDCPP linker option.) You must specify these options because template resolution methods changed for VisualAge C++. Note: You can mix C and C++ object modules from the same compiler and use a common runtime DLL. ═══ 2.8. What Are the Library Naming Conventions? ═══ Question: What are the naming conventions used for the static and dynamic runtime and class libraries? Answer: The naming conventions for the libraries have changed for VisualAge C++ Version 3.0. They now indicate the product version and platform, as well as a new product prefix. You do not have to specify libraries at link time. The compiler imbeds information in your .OBJ files about the runtime and class libraries to link with, based on the compiler options you specify and the #pragma library directives in the header files you include. The new convention for the C runtime libraries (which also include the I/O Stream class library) is: CPPpivvt where: p Is the platform or operating system. i Is the library identifier. vv Is the version number. t Is the type of library The resulting library names are: ─────────────────────────────────────────────────────────────────────────── Character Significance Position ─────────────────────────────────────────────────────────────────────────── 1-3 4 5 6-7 8 ─────────────────────────────────────────────────────────────────────────── CPP Product prefix ─────────────────────────────────────────────────────────────────────────── O OS/2 platform ─────────────────────────────────────────────────────────────────────────── S Single-thread environment ─────────────────────────────────────────────────────────────────────────── M Multithread environment ─────────────────────────────────────────────────────────────────────────── N No runtime environment (subsystem) ─────────────────────────────────────────────────────────────────────────── 30 Version number (3.0) ─────────────────────────────────────────────────────────────────────────── I Import library ─────────────────────────────────────────────────────────────────────────── O Object library (contains initialization routines) ─────────────────────────────────────────────────────────────────────────── Statically bound library (no eighth letter) The new convention for the class libraries, as well as for the Performance Analyzer libraries, .OBJ, and device driver, is: CPPpiivt where: p Is the platform or operating system. ii Is the library identifier. v Is the version number. t Is the type of library. The resulting library, .OBJ, and device driver names are: ─────────────────────────────────────────────────────────────────────────── Character Significance Position ─────────────────────────────────────────────────────────────────────────── 1-3 4 5-6 7 8 ─────────────────────────────────────────────────────────────────────────── CPP Product prefix ─────────────────────────────────────────────────────────────────────────── O OS/2 platform ─────────────────────────────────────────────────────────────────────────── DI Data Access IDL library ─────────────────────────────────────────────────────────────────────────── DS Data Access static SQL library ─────────────────────────────────────────────────────────────────────────── OB Open Class Base library (Collection, Data Types) ─────────────────────────────────────────────────────────────────────────── OC Open Class library (single import library) ─────────────────────────────────────────────────────────────────────────── OD Open Class DDE library ─────────────────────────────────────────────────────────────────────────── OM Open Class Multimedia library ─────────────────────────────────────────────────────────────────────────── OR Open Class resource DLLs ─────────────────────────────────────────────────────────────────────────── OU Open Class User Interface base library ─────────────────────────────────────────────────────────────────────────── OV Open Class Visual library ─────────────────────────────────────────────────────────────────────────── OX Complex library ─────────────────────────────────────────────────────────────────────────── OY Complex multithread library ─────────────────────────────────────────────────────────────────────────── PA Performance Analyzer ─────────────────────────────────────────────────────────────────────────── SQ Data Access resource DLLs ─────────────────────────────────────────────────────────────────────────── TR Integration DLL ─────────────────────────────────────────────────────────────────────────── Vn Open Class Visual sample library n ─────────────────────────────────────────────────────────────────────────── 3 Version number 3.0 ─────────────────────────────────────────────────────────────────────────── I Import library ─────────────────────────────────────────────────────────────────────────── O Object library (for building import libraries) ─────────────────────────────────────────────────────────────────────────── Statically bound library (no eighth letter) ─────────────────────────────────────────────────────────────────────────── U U.S. English version of resource library ─────────────────────────────────────────────────────────────────────────── J Japanese version of resource library ─────────────────────────────────────────────────────────────────────────── K Korean version of resource library ─────────────────────────────────────────────────────────────────────────── T Taiwanese version of resource library ─────────────────────────────────────────────────────────────────────────── P Simplified Chinese version of resource library ═══ 2.9. What Does Mangling Mean? ═══ Question: I have seen a number of references to mangling in the C++ information. What does it mean? Answer: When a C++ compiler compiles a program, it encodes all class, member, and function names, as well as certain other identifiers, to include type and scoping information. This is called mangling. Different compilers use different mangling schemes. The linker uses the encoded or mangled name to ensure type-safe linkage. These mangled names are used in the object files and in the final executable file. Tools or code that references identifiers in these files must use the mangled names, not the original names used in the source code. To decode or demangle the names for VisualAge C++, you can use the functions defined in or the CPPFILT utility. For more information about demangling, see "Demangling (Decoding) C++ Function Names" in the Programming Guide. For information about CPPFILT, see the User's Guide. ═══ 3. Using Documentation and Help ═══ This section answers questions about the online and hardcopy information and the online help:  How Do I Close the How Do I Panel?  Where Is the Chapter on Compiler Options? ═══ 3.1. How Do I Close the How Do I Panel? ═══ Question: How do I close the How Do I panel? When I click on the system icon, there is no Close choice. Answer: To close the How Do I panel, press F3. You can also click mouse button 2 on the window to bring up a context menu for it. From the context menu, select Exit. The other help functions (such as Search and Print) are also available from this menu. ═══ 3.2. Where Is the Chapter on Compiler Options? ═══ Question: There used to be a chapter on compiler options in the Programming Guide, but I can't find it now. Where is it? Answer: This chapter has been moved to the User's Guide, along with other information about how to use the compiler and VisualAge C++ tools. ═══ 4. Using WorkFrame ═══ This section provides answers to commonly-asked questions about the VisualAge C++ integrated development environment, WorkFrame.  How Do I Create a WorkFrame Project?  How Do I Create a New Source File in a Project?  How Do I Change What a Double-Click Does in My Project?  How Can I Share Projects over a LAN?  How Do I Copy a Project or Profile to Diskette?  How Do I Set the Default Action for a Class?  Why Doesn't Double-Clicking on Error Messages Work?  Can I Inherit Environments from Multiple Projects?  Why Is My Project's Inherited Environment Not What I Expect?  Where Are Options for Inherited Actions Stored?  Where Are My Migrated Profiles? ═══ 4.1. How Do I Create a WorkFrame Project? ═══ Question: How do I create a WorkFrame project? Answer: The easiest way to create a project is to use Project Smarts or the WorkFrame Project template in the OS/2 Templates folder. If you already have files that you want to put in a project, or if you are familiar with WorkFrame and want to build a project from scratch, use the WorkFrame Project template. Open the Templates folder, select the WorkFrame Project template, and drag it onto your desktop. If you are not familiar with WorkFrame, or if you're unsure how to set up a certain type of project (a PM application, for example), use Project Smarts. Project Smarts is a catalog of project skeletons for common types of applications. Double-click on the Project Smarts object in the VisualAge C++ folder to display the catalog. Select the type of project you want to create, then select the Create button. ═══ 4.2. How Do I Create a New Source File in a Project? ═══ Question: I have a project, but I want to add a new source file to it. How do I do that? Answer: Drag a data file template (from the Templates folder) onto the project. You can also create a project-scoped EDIT action in the Actions Profile with no options, so that it brings up the editor with no file loaded. You can also use the command line to copy a file or invoke the editor on a new file in the directory pointed to by the project. ═══ 4.3. How Do I Change What a Double-Click Does in My Project? ═══ Question: How do I change the default action associated with double-clicking on files in my project? Answer: To change the default double-click action, you need to change the priority of the project's actions in the Tools Setup. The priority is a numeric value from 0 to 99, with 99 being the highest priority, specified on the Support page of the action's Settings notebook. Actions in the list of actions are sorted in order of priority; the higher it appears in the list, the higher its priority. When you double-click on a file, WorkFrame identifies all actions that match that file name, and performs the action with the highest priority. Only actions that are valid for that type of source file are considered. For example, if you set the VisualAge C++ Compiler action so that the only valid source files are .cpp files, the Compiler will never be invoked by double-clicking on any other type of file, regardless of the Compiler action's priority. A file's default action is always listed first on its pop-up menu. (Pop-up menu items are sorted by priority.) To change the default action for a file, select Change from the action's pop-up menu to display its Settings notebook, and then change the priority value on the Support page. Make sure the action you want to use is valid for the type of file. Note: If a file or object has no WorkFrame actions associated with it, or if it a WorkFrame project, double-clicking on it performs the default Workplace Shell Open action. ═══ 4.4. How Can I Share Projects over a LAN? ═══ Question: How can I share my WorkFrame projects with others over a LAN? Answer: You can easily share projects over a LAN by locating the project file itself on the LAN, and then shadowing it to the client desktops. Your project source files can reside on the LAN, and you can copy project WorkPlace Shell objects over the LAN (by dragging the project onto a remote Drives folder and then dragging it out again onto the target machine). Ensure that your target machine is set up similar to your original machine (for example, your source is in the same location; inherited projects, if any, also exist; the executable files are present; and so on). If you are sharing code over the LAN, you may find a source-control product such as CMVC for OS/2 helpful. ═══ 4.5. How Do I Copy a Project or Profile to Diskette? ═══ Question: I want to move my projects and profiles to a second machine that is not connected to the LAN. How do I copy my projects and profiles to a diskette, and then to the new machine? Answer: There are two methods for copying projects and profiles to diskette: 1. Drag the project or profile icon from your desktop and drop in onto the icon for your diskette drive (make sure you have a diskette in the drive). On the target machine, insert the diskette, double-click on the icon for the diskette drive, and drag the project or profile icon to the desktop. 2. Compress the project or profile file using a compression program that preserves extended file attributes (such as the public domain zip and unzip programs). Project files and profiles are found in the \DESKTOP directory on your boot drive. Copy the compressed files onto the diskette, and then uncompress the files under the \DESKTOP directory on the target machine. ═══ 4.6. How Do I Set the Default Action for a Class? ═══ Question: How do I set the default action for a class (such as COMPILE)? Answer: The default action for a class is simply the action that has the highest priority within that class. The priority is a numeric value from 0 to 99, with 99 being the highest priority, specified on the Support page of the action's Settings notebook. Actions in the list of actions are sorted in order of priority; the higher it appears in the list, the higher its priority. To make an action the default, change the priority value in its Settings notebook so that the value is higher than the other actions in that class. (To display the Settings notebook, select Change from the action's pop-up menu.) Note that the default action for a class also depends on the type of file that it is invoked on. For example, suppose the Resource Compile action has the highest priority among COMPILE actions. Resource Compile is only valid for .rc files. If you invoke COMPILE on a .cpp file, WorkFrame skips the Resource Compile action because it is not valid for the type of file. WorkFrame performs the action with the highest priority that is valid for a .cpp file. In most cases, the default action for a class is not important. However, it is important for the EDIT class. When you double-click on error messages in the monitor box, or invoke the editor from one of the other tools, the default Edit action (and default editor) is used. ═══ 4.7. Why Doesn't Double-Clicking on Error Messages Work? ═══ Question: I thought that double-clicking on an error in the monitor window was supposed to launch the editor and load the source file that has the error. Why doesn't this work for me? Answer:  Make sure the error message was generated for a source file. For error messages generated by tools like the linker, double-clicking on the message does not work because you cannot edit the object file for which the message was generated.  If you added or changed the action that generated the message, and it uses the WorkFrame default action support DLL, ensure the error template is specified correctly.  If the error message uses unqualified file names, the editor may not be able to load the file. If this is the case, go to your Project view and edit the file from there.  If the messages were generated by the Make action, and the make file you used was not generated by WorkFrame (or you edited the file), the make file may not contain information the WorkFrame needs to determine what tool should parse the errors. WorkFrame puts special tags before each OS/2 command in the make file to indicate the tool to call. If you use the Build action instead of Make, this problem does not occur. If you don't want to use Build, either regenerate your make file using WorkFrame, or add the tags to your own make file. The tags have the format: @echo WF::class::action where class is the action class (such as COMPILE), and action is the action name (such as VisualAge C++). ═══ 4.8. Can I Inherit Environments from Multiple Projects? ═══ Question: Can I inherit environments from multiple projects? Answer: Yes. Specify the list of projects in the Inheritance page of the project's Settings notebook. ═══ 4.9. Why Is My Project's Inherited Environment Not What I Expect? ═══ Question: My project inherits from more than one project. One of the actions, variables, or types is not what I expected. Why? Answer: When you inherit from multiple projects, conflicts can occur between the different environments. The rules that WorkFrame uses to resolve these conflicts are described in the WorkFrame section of the User's Guide. ═══ 4.10. Where Are Options for Inherited Actions Stored? ═══ Question: When I inherit an action from another project's environment, are the options copied from the base action stored locally with my new project? Or do they remain with the project where the action was originally defined? Answer: You have a choice. By default, the options remain with the base project, but you can also copy the options and store them locally. You can change the options of an inherited action, but then you will be changing the options for other projects that also inherit the action. If the options are stored locally, you can decide to use the inherited options instead by deleting the local options. (Select Options->Delete from the action's pop-up menu.) ═══ 4.11. Where Are My Migrated Profiles? ═══ Question: I just migrated my projects and profiles from WorkFrame/2 V2.1. I found the new projects, but I cannot find the profiles. Did the migration for the profiles fail? Answer: In VisualAge C++ V3.0, WorkFrame projects and profiles are merged. Your migrated profiles have been merged into your migrated projects. ═══ 5. Creating Applications with the Visual Builder ═══ This section answers questions you may have about using the Visual Builder to construct parts and applications.  How Can I Determine Which Connections Are Executed?  How Can I Tell What Parameters an Action Can Take?  How Do I Support Different Display Resolutions?  Do I Have to Hardcode Limits for Fields?  Why Are My Container Columns Invisible?  Why Can't I Select Parts in the Group Box?  How Should I Update a Composite Part?  Why Can't I Tear Off a Nonvisual Part's Attribute?  Why Doesn't My Drop-Down Combo Box Drop Down?  Why Does My Part Get Compiler Errors?  Why Does My Part Get Unresolved External Link Errors? ═══ 5.1. How Can I Determine Which Connections Are Executed? ═══ Question: My application doesn't run as I expected. Is there a way to determine which connections are being executed? Answer: Yes. The Visual Builder generates trace macros in its connection classes to indicate when a connection has been triggered. To view this information: 1. When you compile the generated source code for your parts, define the IC_TRACE_DEVELOP macro. 2. Enable tracing for the class libraries by entering SET ICLUI TRACE=ON (You could also set this variable in your CONFIG.SYS file.) 3. Indicate where the trace information should be sent by setting the ICLUI TRACETO environment variable. For example: SET ICLUI TRACETO=STDERR sends the information to stderr, which you can then redirect to a file. For more details on tracing, see the ITrace class information in the Open Class Library Reference. Once you have enabled tracing, look for the following possible problems:  Initializing attribute-to-attribute connections can execute other connections that are dependent on the target attribute.  A connection involving an attribute as the source might not have been executed because the attribute does not have an event identification defined for it.  A connection involving an attribute might not have been executed because the attribute-changed event happens only when the attribute's value changes. For example, setting the value of a Boolean attribute to true when the current value is already true might not execute the attribute-changed event. Another example is when the attribute is a pointer to an object and the pointer does not change but the contents of the object does. If your application must execute an event when the attribute's set member function is called, add a notification event.  If your application is getting an old attribute value instead of a new one, the attribute's set member function might be executing its attribute-changed event before the value actually changes. Make sure you update the attribute's value before you signal the attribute-changed event.  Whenever an action executes an event, the event happens before the actionResult value is set for the action. For example, Object Factory parts have a new action that instantiates an object of the type defined for the factory part. The new action starts an event called newEvent before the result of new is returned, so actionResult still contains the old value. ═══ 5.2. How Can I Tell What Parameters an Action Can Take? ═══ Question: I have an incomplete connection. How can I tell what parameters an action takes and what default values the parameters can be initialized to? Answer: Select the target part that the action is defined for. From its context menu, select Browse feature implementations. The Browse feature implementation window shows the full signature of the action, including its return type. You can then see what parameters an action supports, the type of each parameter, and any default values. ═══ 5.3. How Do I Support Different Display Resolutions? ═══ Question: How should I build my application views to ensure they display correctly on different display resolutions and monitors (VGA, SVGA, and so on)? Answer: Build your views using a multicell canvas (IMultiCellCanvas) as the client instead of a canvas (ICanvas). ═══ 5.4. Do I Have to Hardcode Limits for Fields? ═══ Question: I initialize many parts in my views through the parts' settings editors. How can I avoid hardcoding values for fields, such as the limit field for the entry field part? Answer: Create a separate file for your application and use #define directives to define macros for your field lengths. For example, for an entry field for names, you might define a macro NAME_LENGTH to 50. You would then specify #NAME_LENGTH for the entry field's Limit field. To ensure that your view will compile, add the name of the file that contains the #define directives to the Required include files field in the Class Editor. ═══ 5.5. Why Are My Container Columns Invisible? ═══ Question: Some of the container columns that I dropped on a container aren't visible. Why? Answer: When you build a container using the Composition Editor, the Visual Builder shows only those columns that fit within the container. To access the rest of the columns, either resize the container to a larger size, or select Tabbing and Depth Order from the context menu of the container. From the Tabbing and Depth Order window, you can open the settings notebook of any of the container columns listed by double-clicking on their names. You can delete a column by selecting Delete from the context menu of a column. Also, if you define several container columns and then change the view type to something other than showDetailsView, your columns may become invisible. You can always enable your users to switch the view type of a container at run time (for example, from a menu bar). ═══ 5.6. Why Can't I Select Parts in the Group Box? ═══ Question: I dropped a group box or outline box and sized it so that it contains other visual parts that I added to my view. Now I can't select the visual parts that the group box or outline box contains. Why? Answer: The visual parts inside the group or outline box are siblings of the box, not children of it. Because you created the box after the visual parts it contains, the depth order of the box is lower than those of the contained visual parts. To change the depth order, select Tabbing and Depth Order from the context menu of the parent of these parts (that is, the canvas that contains them). Then select and drag the group box or outline box and drop it so that it appears before all of the visual parts you want it to contain. To avoid this problem, create the group box or outline box first and then place the other parts on top of it. ═══ 5.7. How Should I Update a Composite Part? ═══ Question I am editing a visual part, and need to update one of the composite parts it contains. What is the easiest way to update the composite part? Answer: 1. Edit the part you want to change. (You can leave the first edit session open.) 2. Update the composite part, save it, and close the second edit session. Note that the changes will not appear in your first edit session until you close and reopen the session. ═══ 5.8. Why Can't I Tear Off a Nonvisual Part's Attribute? ═══ Question: I created a nonvisual part with an attribute that is a pointer to a collection object. I want to tear off this attribute to make connections to some of its actions, but I can't. Why not? Answer: Collections are implemented using C++ template classes. When you tear off an attribute, Visual Builder creates a variable part and sets it to the type of the torn-off attribute. However, variables that represent template-based classes are not currently supported. To avoid the need to tear off attributes, implement actions to work with the collection attribute directly in your nonvisual part. ═══ 5.9. Why Doesn't My Drop-Down Combo Box Drop Down? ═══ Question: Why doesn't my drop-down combo box drop down? Answer: Make sure a default height is specified on the Size/Position settings page. You must set the height to a value higher than 0. ═══ 5.10. Why Does My Part Get Compiler Errors? ═══ Question: I constructed my application using the Composition Editor, but when I try to compile it, the compiler generates errors. Shouldn't any constructed part compile without error? Answer: The Composition Editor does not prevent you from making connections that will not compile correctly. You must ensure you match types correctly on attribute-to-attribute connections and parameter connections. The easiest way to avoid incompatible-type errors is to browse feature implementations using the Composition Editor's contextual menu. ═══ 5.11. Why Does My Part Get Unresolved External Link Errors? ═══ Question: When I compile and link my part, the linker generates unresolved external errors. Why? Answer: This could be caused by one of the following:  Visual Builder uses template classes to implement variable parts, collections, collection-view list boxes, and collection-view combo boxes. When you compile, information required to resolve the templates is put into the TEMPINC subdirectory (under the directory where your part code resides). If you made changes to any template-based parts, delete the contents of TEMPINC before you recompile to ensure that you are not using the old template information.  If you compiled your nonvisual parts separately to create a DLL, you must link the .LIB file for that DLL into your application to resolve references to the nonvisual parts. You can either: - In the Class Editor, add the .LIB file name for each nonvisual part contained in the DLL. - Put a #pragma library directive in the header file for your nonvisual parts. This directive tells the compiler and linker to link in the library specified. #pragma library is described in the Language Reference. ═══ 6. Editing with the VisualAge C++ Editor ═══ This section answers questions about how to use the VisualAge C++ Editor.  How Can I Make the Editor Behave Like My Favorite Editor?  What Is the Gray Area Shown at the Top of My File?  Why Do I Have Multiple Copies of the Error Log?  Can I Extract the Text in a Marked Block?  How Do I Put the Contents of a Line in a Macro?  How Do I Issue an OS/2 Command from a Macro?  How Do I Debug a REXX Macro?  How Do I Unload the Editor from Memory?  How Can I Get File or Quit to Invoke My Macro? ═══ 6.1. How Can I Make the Editor Behave Like My Favorite Editor? ═══ Question: How can I make the Editor behave like my favourite editor and other editors on the market (such as Brief)?. Answer: In the Options menu, the Key behavior choice cascades to a menu of commonly used editors. Select from this menu to make the Editor behave like the selected editor. This affects only the current document in the current edit session. To save the behavior for all documents across sessions, select Save key behavior from the Key behavior cascade. To customize the behavior of any subset of keys, use the Customize choice from the Options menu. ═══ 6.2. What Is the Gray Area Shown at the Top of My File? ═══ Question: What is the gray area shown at the top of my file, and why is it a different size for different files? Answer: An edit window is created based on the size and position of the last window size and position (even from another session). The area in which the file is displayed is fixed until you change it. If the file loaded into the window is not large enough to fill the edit area, the gray area fills the remaining space. As the file size increases, the gray area diminishes. When the file fills the window and scrolling is required, the gray area disappears. ═══ 6.3. Why Do I Have Multiple Copies of the Error Log? ═══ Question: I start the editor from the command line. In every directory where I start it, it creates a new error log file. Why? How do I specify one location for this file? Answer: The editor uses the environment variable TMPDIR to determine where to create the error log. Make sure you have specified a valid drive and directory for this variable. If you want to specify only a drive, you must follow it with a backslash, for example: SET TMPDIR=f:/ ═══ 6.4. Can I Extract the Text in a Marked Block? ═══ Question: I have a block of marked text. Can I extract or query the text in the block? Answer: There is no direct way to extract or query the text marked by the current block. However, you can write a macro to extract the text. You need to: 1. Determine what document has the selected block of text (BLOCKDOC). 2. Determine the BLOCKTYPE. 3. Determine what you need to extract, based on the BLOCKTYPE. For example, for a RECTANGLE: a. Find the columns spanned by the rectangle (BLOCKSTART and BLOCKEND). b. Determine how many elements the block crosses (BLOCKLENGTH). 4. Position the cursor on the first included element (BLOCK FIND). 5. Extract the content of the element. 6. Throw away the characters not included. 7. Move to the next element and extract its content. 8. Repeat the above two steps (6 and 7) until finished. Your macro might contain the following statements: QUERY BLOCKTYPE QUERY BLOCKDOC BLOCK FIND BLOCK FIND END QUERY BLOCKSTART QUERY BLOCKLENGTH QUERY BLOCKEND EXTRACT CONTENT ═══ 6.5. How Do I Put the Contents of a Line in a Macro? ═══ Question: How do I put the contents of a line or editor variable in a macro? Answer: Use the extract command. The command extract content puts the content of the current line into the REXX variable content. You can also use the command extract content into x to extract the line's content into the variable x. ═══ 6.6. How Do I Issue an OS/2 Command from a Macro? ═══ Question: How do I issue an OS/2 command from a REXX macro? Answer: Use the REXX ADDRESS command. For details on how to use this command, see the REXX Information in the OS/2 Information folder. ═══ 6.7. How Do I Debug a REXX Macro? ═══ Question: One of my editor macros fails with an error code. How can I debug it to find out what's wrong? Answer: To debug a REXX macro, use the REXX trace utility and the editor's Macro Log (from the Window menu). If you put TRACE ALL as the first statement in your REXX macro, the Macro Log displays every REXX statement executed. If you put TRACE ?ALL as the first statement, the editor prompts you with the REXX Trace Read dialog for each statement, in addition to displaying them in the Macro Log. ═══ 6.8. How Do I Unload the Editor from Memory? ═══ Question: I noticed that after I close my editor session, the editor remains loaded in memory for some time afterwards. Why does this happen, and how can I unload it from memory? Answer: The editor remains in memory so that startup is faster when you edit your next file. Closing the editor from the OS/2 Window list only closes the document being edited. To close the editor itself and unload it from memory, use the command: lxpm /cm exit or select Close editor from the Windows menu. There is also an editor parameter, TIMOUT, that you can query and set from the command line, from an editor macro, or from a program. The default setting is five minutes; five minutes after the last edit window is closed, the editor closes itself. If you set it to 0, the editor closes immediately after the last edit window is closed, regardless of whether you select Close Editor or not. See the Editor Command Reference for more details on setting and querying parameters. ═══ 6.9. How Can I Get File or Quit to Invoke My Macro? ═══ Question: When I save a file or quit out of the editor, I want my own macro to be invoked first before the editor saves the file or exits. Can I do this? Answer: Yes. The easiest way is to define a synonym for these functions. For example, if your macro is called mymacro.lx: SYNONYM.QUIT mymacro quit SYNONUM.QQUIT mymacro qquit SYNONYM.SAVE mymacro save (Note that the File action is the Save and Quit actions put together.) Then in your mymacro.lx, issue the command 'LXN' parm at the end of your processing. Alternatively, you could define the following synonym: SYNONYM.QUIT MULT; mymacro quit ; LXN QUIT which does not require you to add the extra command to mymacro.lx. ═══ 7. Coding in C/C++ ═══ This section addresses questions you may have in creating your source files, including the correct use of operators, functions, and programming techniques.  How Does malloc Allocate Memory?  How Do Signal and Exception Handling Work in DLLs?  How Do I Export C++ Members from a DLL?  Can I Export an Empty Class?  How Do I Use C Objects in C++ Programs?  How Do I Call C++ Functions from C Code?  How Can I Share a Data Segment?  Should I Use Automatic Local Variables or new?  Can I Throw an Exception in One Thread and Catch It in Another?  What Are the Equivalent Calling Conventions in C++?  What Calling Convention Should I Use for Multiple Languages?  Can I Use Library Extensions at the ANSI/ISO Language Level?  Should I Use DosCreateThread or _beginthread?  Can I Use C Functions on a File Opened with DosOpen?  Can I Change the Parent's Environment from a Child Process?  How Can I Perform Port Input/Output?  Can Signals Be Raised Across Processes?  What Should I Call in _DLL_InitTerm? ═══ 7.1. How Does malloc Allocate Memory? ═══ Question: How does malloc allocate memory internally, and how much overhead does it require? Should I use malloc or DosAllocMem? Answer: malloc adds 8 bytes to the requested size (16 bytes if you are using debug memory management), and then rounds the resulting size to the next multiple of 16 bytes. Of this, at most 16 bytes is used by malloc and the other memory management functions for internal information. Note: Do not code your program to rely on the underlying implementation of any function. The implementation could change, which would then cause your program to fail. In most cases, there is no benefit to calling DosAllocMem over malloc. In previous releases of C Set ++, we recommended you call DosAllocMem for large blocks of memory; however, with the changes to runtime memory management in this release, malloc is equally efficient with large blocks. ═══ 7.2. How Do Signal and Exception Handling Work in DLLs? ═══ Question: How do signal handling and exception handling work in DLLs? Answer: You must clearly differentiate between OS/2 exception handlers and C signal handlers. In VisualAge C++, the #pragma handler directive registers the OS/2 exception handler. The signal function registers a C signal handler. To correctly generate signals from OS/2 exceptions, VisualAge C++ must have its own OS/2 exception handler registered. To automatically register C signal handlers, use the signal function. The default C exception handler _Exception converts OS/2 exceptions into C signals, and calls C signal handlers that are registered by signal. _Exception is automatically registered for the main function; you must explicitly register it for every DLL entry point using #pragma handler. You can also use #pragma handler to register your own exception handler for a function. For example, to register handler as the exception handler for blivet, specify #pragma(blivet, handler). Note: For more information on signal and exception handling, see the Programming Guide. ═══ 7.3. How Do I Export C++ Members from a DLL? ═══ Question: How do I export C++ member functions from a DLL? The _Export keyword is not valid in the function prototype. Answer: Specify the _Export keyword in either the class definition or the function definition, as in the following example: class A { void _Export fun1(); }; void _Export A::fun1() { return; } ═══ 7.4. Can I Export an Empty Class? ═══ Question: Can I export an empty class and use it somewhere else? Answer: You can only export member functions, static data members, and instances of classes. ═══ 7.5. How Do I Use C Objects in C++ Programs? ═══ Question: I am having a problem linking my C++ code with C objects. Why does the linker say that there is an unresolved external reference to the C objects? Answer: You must tell the C++ compiler that an external name uses C naming conventions by using extern "C" when you declare the function. Otherwise, the C++ compiler mangles the name. (For an explanation of what "mangles" means, see What Does Mangling Mean?.) Note that using the keywords for calling conventions (_System, _Optlink, and so on) in your function definition is equivalent to defining the function as extern "C". ═══ 7.6. How Do I Call C++ Functions from C Code? ═══ Question: I know that I can call C functions from a C++ file using extern "C" declarations, but how do I call a C++ routine in a .CPP file from a C file? Answer: Define the C++ function as extern "C" in your C++ code, and call it from the C code. Note that C functions compiled with the C compiler cannot directly call C++ member functions. To get around this, add an extern "C" function in your C++ code that calls your C++ member function. Then call that extra function from your C code. ═══ 7.7. How Can I Share a Data Segment? ═══ Question: I want multiple copies of my DLL to share a single data segment. How do I do that? Answer: To share a data segment: 1. Use #pragma data_seg to name the data segment you want to share. 2. Specify DATA MULTIPLE NONSHARED in the .DEF file for your DLL. 3. Put a SEGMENTS statement in the .DEF file specifying that the named segment is SINGLE SHARED. 4. If you need to initialize data structures in the shared segment, you will need to add to your DLL initialization routine. Remember that this routine is run for each instance of your DLL, so you will need to ensure your data structures are only initialized once. For more information about DLL initialization, see "Building DLLs" in the Programming Guide. ═══ 7.8. Should I Use Automatic Local Variables or new? ═══ Question: When should I use automatic local variables, and when should I use the new operator to create variables? Answer: When you can, use automatic variables. Because they are automatically destroyed when the block in which they were declared ends, you do not have to delete them and the stack space is returned. For example, a loop counter would typically be an automatic variable. However, if you need the variable to remain in existence outside that block, use new. (Alternatively, you could declare the variable as static or extern.) Remember that automatic variables use stack space. If you are limited to a small stack, which is common when you write threads, you may need to use new and delete for large variables that would typically be automatic. ═══ 7.9. Can I Throw an Exception in One Thread and Catch It in Another? ═══ Question: Can I throw an exception in one thread and catch it in another? Answer: No. You can only catch exceptions that are thrown from your own thread of execution. If you try to throw an exception from one thread to another, the exception will not be caught, and your application will end. ═══ 7.10. What Are the Equivalent Calling Conventions in C++? ═══ Question: I know about the VisualAge C++ calling conventions and keywords for C programs. Are the conventions the same in C++? How do I use them? Answer: The calling conventions in C++ are almost the same as in C. The difference is in the default convention. By default, C functions have _Optlink linkage; C++ functions have C++ linkage. C++ linkage behaves like _Optlink, but the two conventions are not equivalent; C++ linkage also includes name mangling and overloading. Using the C linkage keywords declares that function as a C function. Be careful that you don't use linkage keywords for member functions. You can also use the following qualifiers in your function declarations, listed with their equivalent keywords: C++ Qualifier Corresponding C Calling Convention extern "C" Default C linkage (_Optlink unless you change it to _System) extern "C++" _Optlink with name mangling, overloading extern "SYSTEM" _System extern "OPTLINK" _Optlink extern "PASCAL" _Pascal extern "FAR16 CDECL" _Far16 _Cdecl extern "FAR16 PASCAL" _Far16 _Pascal extern "FAR16 FASTCALL" _Far16 _Fastcall ═══ 7.11. What Calling Convention Should I Use for Multiple Languages? ═══ Question: I have some functions in a DLL that I want to call from applications written in other languages like Pascal and COBOL. What calling convention should I use for these functions? Answer: All compilers on OS/2 have to support system linkage (usually referred to in VisualAge C++ as _System) so they can call OS/2 APIs. If you are creating a general-purpose DLL, you should code all 32-bit entry points with the _System calling convention. Other compilers do not support VisualAge C++'s _Optlink convention. If you have any 16-bit entry points, you should use the _Far16 _Pascal calling convention for them. ═══ 7.12. Can I Use Library Extensions at the ANSI/ISO Language Level? ═══ Question: I have to compile my program with the language level set to ANSI to set the __STDC__ macro and ensure ANSI/ISO conformance. However, I also want to use some of the VisualAge C++ library functions that are not part of the ANSI/ISO standard, like _beginthread. Can I do this? Answer: You cannot use the VisualAge C++ library extensions and the ANSI/ISO language level together. However, you can define the __EXTENDED__ macro on the compiler invocation command to include the definitions for the library extensions when you have the language level set to ANSI/ISO. The compiler will generate a warning, but should work correctly. Defining the macro this way still provides ANSI/ISO conformance checking (type checking, for example), but allows your program to use non-standard library functions. ═══ 7.13. Should I Use DosCreateThread or _beginthread? ═══ Question: Can I use VisualAge C++ runtime functions from a thread created by DosCreateThread rather than by _beginthread? Answer: Yes, but there are extra steps you need to take to use DosCreateThread that you do not need to use _beginthread. To use DosCreateThread to create threads that can use the VisualAge C++ runtime: 1. Put a #pragma handler directive in your code for the thread function so that the VisualAge C++ exception handler is registered for the thread. 2. If you are using floating-point math, call _fpreset at the beginning of the thread function. This call ensures that the floating-point chip is in the correct state for the thread. 3. Terminate the thread with _endthread so that the thread-specific storage is freed correctly. VisualAge C++ does not create thread-specific storage until the first function that requires it is called. Note: Do not use DosExit to end your threads; it does not correctly terminate the runtime, which can cause problems when you start another thread. When you use _beginthread, you do not have to follow these steps. It performs the required initialization and termination actions for you. If you are writing C++ code, you can also use the IThread class from the User Interface Class Library. See How Do I Start A Thread? for more details. Note: If you are using the subsystem library (with the /Rn compiler option), the functions _beginthread, _endthread, and _fpreset are not available. You must use DosCreateThread to create a new thread, and you must write your own initialization, termination, and exception handling code. ═══ 7.14. Can I Use C Functions on a File Opened with DosOpen? ═══ Question: I want to use DosOpen to open files so I can specify file inheritance information, but can I then use C library functions (like fread) on those files? Answer: After you open the file with DosOpen, use _fdopen to convert your OS/2 file handle to a C stream pointer. You can then use the C stream library functions on that file. Note that you must then close that file with fclose. You can also accomplish the same thing by opening the file with a C function (like fopen), and then setting the inheritance bit with DosSetFHState(fileno(file)). However, this does not work if you have other requirements, like sharing or caching, the DosSetFHState cannot handle. ═══ 7.15. Can I Change the Parent's Environment from a Child Process? ═══ Question: Can my child process change the environment settings for its parent process and have them remain in effect? Answer: No. However, you can code the child process to write out a .CMD file, and then call that file from the invoking process to pick up the environment changes. ═══ 7.16. How Can I Perform Port Input/Output? ═══ Question: I want to perform port input and output. Are there functions that can read to and write from ports? Answer: VisualAge C++ includes the functions inp, inpd, inpw, outp, outpd, and outpw that you can use to do port input and output. However, you can only use these functions in code that runs at ring zero (namely, device drivers). 32-bit application code cannot perform direct I/O. If your 32-bit application requires direct port I/O, you need to create a 16-bit input/output parameter list (IOPL) segment. (IOPL does not apply to 32-bit segments.) The functions that perform the direct I/O must reside in this segment. One way to create the segment is include the port I/O functions from C/2 in a 16-bit DLL. However, it may be more efficient to link them directly to your 32-bit code or even to write the functions directly in assembler. ═══ 7.17. Can Signals Be Raised Across Processes? ═══ Question: Can signals be raised asynchronously (process to process)? Answer: The only signals that can be raised in another process are the following: Signal OS/2 API to use SIGTERM DosKillProcess SIGINT DosSendSignalException SIGBREAK DosSendSignalException This is a limitation of 32-bit OS/2. ═══ 7.18. What Should I Call in _DLL_InitTerm? ═══ Question: I am writing my own _DLL_InitTerm function. What should I call in it to correctly initialize and terminate the runtime environment? Answer: What you need to call depends on the runtime library you use, and on how you link to it (statically or dynamically). If you use the single- or multithread library: If your DLL statically links to the runtime library, you must call the following functions in the order shown: _CRT_init Initializes runtime environment. __ctordtorInit Initializes static C++ objects. __ctordtorTerm Terminates static C++ objects. _CRT_term Terminates the runtime environment. If your DLL dynamically links to the runtime, you don't need to (and shouldn't) call _CRT_init and _CRT_term. The runtime library DLL initializes and terminates itself. If you use the subsystem library If your DLL statically links to the subsystem runtime, call the following functions in the order shown: _rmem_init Initializes the runtime memory allocation functions. Note: If your DLL uses tiled memory, call _tmem_init instead of _rmem_init to initialize the functions for tiled memory. __ctordtorInit Initializes static C++ objects. __ctordtorTerm Terminates static C++ objects. _rmem_term Terminates the runtime memory allocation functions. Note: If your DLL uses tiled memory, call _tmem_term instead of _rmem_term to terminate the functions for tiled memory. If your DLL dynamically links to the runtime library, you don't need to (and shouldn't) call _rmem_init and _rmem_term (or _tmem_init and _tmem_term). The runtime library DLL initializes and terminates the functions itself. ═══ 8. Coding with the Data Type Classes ═══ This section discusses questions you might have about the IString, IDate, and ITime classes.  What Is the Maximum Length of an IString?  How Do I Convert an IString to char* ?  Can an IString Contain Null Characters?  How Should I Handle Date and Time in a Container? ═══ 8.1. What Is the Maximum Length of an IString? ═══ Question: Is there a limit on the size of an IString? What is the maximum length? Answer: The theoretical maximum size of an IString object is UINT_MAX - 1 (UINT_MAX is defined in ). In practical terms, the limit of IString size is the size of the largest memory block that can be allocated on your system. ═══ 8.2. How Do I Convert an IString to char* ? ═══ Question: How do you access the actual string contained in an object of type IString, and how do you convert between an IString and a regular char* string? Answer: IString provides an operator char*, so you can cast an IString to a char* either implicitly or explicitly. IString also provides a constructor that accepts a char* argument, so you could code something like: IString foo("testing"); foo = "this is a test"; You should treat the char* value as const char*, because changes you make using char* can corrupt the IString object. IString does not convert to a const char* for backwards compatibility. ═══ 8.3. Can an IString Contain Null Characters? ═══ Question: Can an IString contain a null character other than the terminating null character? Answer: Yes, it can. For example, you can set an IString to "Static text\0More text after NULL". If you cast that IString to a char*: char* pChar = (char*)nullString; // nullString is the IString object pChar will also point to "Static text\0More text after NULL". However, if you pass pChar to functions that expect a terminating null character, such as the C library string functions, those functions will interpret the null as the terminating null character for the string and stop reading after "Static text". You can use the indexOf and subString functions of IString to extract the null-terminated strings: int nullIndex; IString str("This is a string\0Hi there\0Last One\0", 35); int startPos = 1; while ( nullIndex = str.indexOf('\0', startPos) ) { cout << str.subString(startPos, nullIndex - startPos) << endl; startPos = nullIndex + 1; } ═══ 9. Coding with the Collection Classes ═══ This section answers questions you may have about using the Collection class library.  Why Does elementAt Not Work Correctly for My Cursor?  Why Do I Get Errors in ?  Why Do I Get an Error Returning the Key Element?  Why Do I Get Unexpected Results When I Add a Key?  Why Do I Get Linker Errors about Undefined Key Functions?  Why Is My Exception Output Traced When the Exception Is Caught?  Why Do I Get an Error about Undeclared Element Types?  Why Do I Get Compiler Errors about Multiply-Defined Symbols?  Why Do I Get Linker Errors for Multiply-Defined Symbols?  Why Do I Get Messages about Needing Constructors?  Why Do I Get Unresolved External References for Symbols I Don't Use? ═══ 9.1. Why Does elementAt Not Work Correctly for My Cursor? ═══ Question: When I use cursors, I get unexpected results. For example, elementAt sometimes fails for the cursor or returns an unexpected element. Answer: You have used an undefined cursor. Cursors become undefined when an element is added to or removed from the collection. You must rebuild your cursor with an appropriate operation (for example, locate()) before you use it again. Rebuilding is especially important for removing all elements with a given property from a collection. Elements cannot be removed by coding a cursor iteration. Use the removeAll() function that takes a predicate function as its argument. For more information about cursors, see the Open Class Library User's Guide. ═══ 9.2. Why Do I Get Errors in ? ═══ Question: I get several different compiler error messages about undefined keys, unmatched hash types, and operators that aren't allowed. All the errors point to . Why do I get these errors? Answer: Compiler error messages indicating a problem in are related to the element and key-type functions that you must define for your elements. These functions depend on the collection and implementation variant you are using. The compilation errors you describe occur when the key() function, the hash() function, operator==, or operator< are required for your elements, but are defined with the wrong interface or not defined at all. Whether arguments are defined as const is significant. Compiler messages do not always point directly to the incorrect function. For example, a compare function with non-const arguments results in the compilation error: The "<" operator is not allowed between "const ..". Verify which element and key-type functions are required for the implementation variant of the collection you are using. You can find this information for each collection in the Open Class Library Reference in the section pertaining to the collection under the heading "Template Arguments and Required Functions". For more information about element and key-type functions, see the Open Class Library User's Guide. Note that the same problem may be produced if function declarations and definitions are not correctly separated between .h files and .cpp files. This situation is described in detail in Why Do I Get an Error about Undeclared Element Types?. ═══ 9.3. Why Do I Get an Error Returning the Key Element? ═══ Question: When I try to return the key value, I get a compiler message warning about the use of a local variable or temporary in a return expression. Why? Answer: Your global-name-space function key() returns the key by value instead of by reference. A temporary variable is created for the key within the operator-class function key. The operator class function key returns the key by reference. Returning a reference to a temporary variable causes unpredictable results. The key function must return a reference and must also take a reference argument. If the key function calls other functions to access the key, it must call those functions with a reference to the object as an argument, and those functions must return a reference to the key. Verify that the global name-space function key correctly returns a key const& instead of key. For more information on element and key-type functions, see the Open Class Library User's Guide. ═══ 9.4. Why Do I Get Unexpected Results When I Add a Key? ═══ Question: I am trying to add an element into a unique key collection (a key set or map). I am sure that the collection does not already contain an element with this key. However, I either get the IKeyAlreadyExistsException, or the element is not added and the cursor is positioned to a different element. Why? Answer: Your global-name-space function key() returns the key by value instead of by reference. If you compile with a higher warning level, you will get compiler error messages about this as described in Why Do I Get an Error Returning the Key Element?. A temporary variable is created for the key within the operator-class function key. The operator class function key returns the key by reference. Returning a reference to a temporary variable causes unpredictable results. The key function must return a reference and must also take a reference argument. If the key function calls other functions to access the key, it must call those functions with a reference to the object as an argument, and those functions must return a reference to the key. Verify that the global name-space function key correctly returns a key const& instead of key. For more information on element and key-type functions, see the Open Class Library User's Guide. ═══ 9.5. Why Do I Get Linker Errors about Undefined Key Functions? ═══ Question: When I link my object files, I get an error message that a key function is undefined. Why? Answer: You are using a collection class that requires the element class to provide a key, and you chose to use the method of using a global key() function. You are using collection class methods in a .CPP file, but the .H file with the same name as the .CPP file does not contain a declaration (prototype) of the global key function. While compiling the .CPP file, which uses methods of the collection class, the compiler has created or modified a temporary .CPP file in the TEMPINC directory. During the link step, this .CPP file is compiled to resolve references to template code. The error message you encounter refers to this compilation. The .CPP file in the TEMPINC directory contains include directives for the collection class template code. It also contains include directives for a .H file of the same name as the .CPP file that uses the collection class methods. The template code in requires that the global key() function be known at compilation time. The only file that is included at this time is the .H file with the same name as your .CPP file. The problem is that the .CPP file is not included at this time, so a definition or declaration of the global key() function in this file is not recognized by the compiler. You must declare the global key() function in the .H file with the same name as the .CPP file that uses the collection class methods. The definition of the global key() function should be in the .CPP file. If you are not sure which .H file is meant by the message, look in the .CPP file found in the TEMPINC directory. ═══ 9.6. Why Is My Exception Output Traced When the Exception Is Caught? ═══ Question: Why is my exception tracing output printed to stderr when the exception has already been caught? Answer: For each exception raised, the trace function write() of class IException::TraceFn is called and writes information about the raised exception to standard error. This trace function write() is called whether the related exception is caught or not. To suppress the trace output, provide your own IException::TraceFn::write() tracing function by subclassing IException::TraceFn and register the subclass with setTraceFunction(). For more information about exception tracing, see the Open Class Library User's Guide. ═══ 9.7. Why Do I Get an Error about Undeclared Element Types? ═══ Question: I am compiling a file that uses templates and I am getting a compiler error that an element or one of its required element functions is not declared. Why? Answer: The element type or element function is defined locally to the .cpp file that contains the template instantiation with the element type as its argument. The prelink phase is executed only by using the header files. Therefore, your declaration local to a .cpp file is not recognized and causes these compilation errors. Move the corresponding declarations to a separate header file and include the header file from the .cpp file. ═══ 9.8. Why Do I Get Compiler Errors about Multiply-Defined Symbols? ═══ Question: Why do I get compiler errors about symbols being defined multiple times? Answer: The template instantiation needs to include the type declarations it received as arguments. Your header files containing type declarations used in template classes may automatically be included several times. Protect your header files against multiple inclusion by using the following preprocessor macros at the beginning and end of your header files: #ifndef _MYHEADER_H_ #define _MYHEADER_H_ 1 . . . #endif Where _MYHEADER_H_ is an identifier unique to each header file and representing the header file's name. ═══ 9.9. Why Do I Get Linker Errors for Multiply-Defined Symbols? ═══ Question: Why do I get linker errors about symbols being defined multiple times? Answer: The template instantiation needs to include the type declarations it received as arguments. Your header files containing type declarations used in template classes might automatically be included several times. Verify that you did not define functions in the header files that declare types used in templates. If you did, you must move them from the header file into a separate .cpp file or make them inline. ═══ 9.10. Why Do I Get Messages about Needing Constructors? ═══ Question: Why does the compiler say that I need a constructor for my template class? Answer: Compiler error messages indicating a problem with constructors for a collection are typically related to the constructors defined for your element. Here the default constructor for the element is missing. Define the default constructor for the element class. For more information about element and key-type functions, see the Open Class Library User's Guide. The element and key-type functions required for each collection are listed for each collection type in the sections entitled "Template Arguments and Required Functions". ═══ 9.11. Why Do I Get Unresolved External References for Symbols I Don't Use? ═══ Question: When I link my program, I get errors about unresolved external references for symbols that I don't use or know about. Why? Answer: A possible reason for unresolved external references during linking is that template code cannot be correctly resolved. To ensure templates resolve correctly, invoke the linker using icc with the /Tdp option. ═══ 10. Coding with User Interface Classes ═══ This section answers questions about the User Interface class library and how to use it. Because of the scope of the User Interface library, this section is broken down into the following topics:  Base Window and Events  Handlers  Frame Windows  Canvas Classes  Container Control  Text and Button Controls  Listbox and Combobox Controls  Notebook Control  Other Controls  Menus  Creating Help with IHelpWindow  Application and Resources  Threads  Fonts and Colors  Font and File Dialogs  Direct Manipulation (Drag and Drop)  Dynamic Data Exchange (DDE)  Exceptions  Internationalization  Extending the User Interface Class Library  Graphics and Drawing ═══ 10.1. Base Window and Events ═══ This section discusses general window and event topics:  What Does IWindow::setAutoDeleteObject Do?  How Should I Number Window IDs?  What is the Difference between Sending and Posting Messages?  How Do I Access the Value of IHandle Objects?  How Do Owner and Parent Relationships Work? ═══ 10.1.1. What Does IWindow::setAutoDeleteObject Do? ═══ Question: What does setAutoDeleteObject do, and when should I use it? Is it only for IWindow instances created by operator new, or for all instances? Answer: The setAutoDeleteObject function is only for windows created with new. It causes code in the User Interface dispatcher to call delete for the C++ object some time after the underlying presentation system window has been destroyed. In general, if you create something with the new operator, you must also delete it. This is true of IWindow objects also. However, you can use setAutoDeleteObject( true ) to have the User Interface class library delete the object for you if it is difficult to keep track of the IWindow object you created. One place where setAutoDeleteObject is useful is the creation of a modeless secondary or child frame window. When you use setAutoDeleteObject(true), do not call delete yourself for the object. ═══ 10.1.2. How Should I Number Window IDs? ═══ Question: What numbering scheme shoud I use for window IDs? Do they have to start from a specific number? Is there an upper limit? Answer: The safest way to program is to use window IDs that are unique on a per-frame window basis. You can then easily provide unique contextual help for all windows and identify the window under the mouse from a WM_CONTROLPOINTER message. If you don't use unique IDs, you'll need to know how you call functions like IWindow::windowWithId and functions that call it (such as IControlEvent::controlWindow). PM reserves some window IDs for windows that it creates, making it more difficult to ensure that your window IDs are unique. For client windows, you should always use a window ID of 0x8008 (preferably by using either the constant FID_CLIENT from or IC_FRAME_CLIENT_ID from ). If you don't, IFrameWindow::setClient changes the value of the ID to this, meaning that any code that relied on the value you set will not work correctly. Avoid using all other FID_* window IDs from for child windows of a frame. Although window IDs in PM APIs are unsigned long types, PM only honors the low word. Therefore, keep your window IDs below 65535. ═══ 10.1.3. What is the Difference between Sending and Posting Messages? ═══ Question: What is the difference between sending a message and posting one? Answer: When a message is sent (sendEvent), the sender is blocked until the receiver processes the message and returns. If the sender and receiver are on the same thread, a direct function call is made to the receiver. If the sender and receiver are on different threads, the message is placed on a private area of the receiver's message queue. In either case, the sender is blocked until the receiver processes the message and returns. When a message is posted, the message is placed on the receiver's message queue. The poster's thread then continues without waiting for a reply. ═══ 10.1.4. How Do I Access the Value of IHandle Objects? ═══ Question: How do I use the Value operator from the IHandle class to get the handle value from an IMessageQueueHandle object? Answer: In PM, Value is defined in a typedef to be an unsigned long value. Therefore, operator Value is really operator unsigned long, so you can use an IHandle or IMessageQueueHandle object as an unsigned long and not have to call any functions. In general, the IHandle-derived classes contain conversion operators that define conversions to the underlying system object. Usually you can use the IHandle objects in system calls where the corresponding system type is expected. In PM, the underlying type for HWND, HMQ, and most of the other handle objects is unsigned long. In the Motif version, the underlying types are typically pointers to an X or Motif structure (such as Widget, which is really a _WidgetRec*). ═══ 10.1.5. How Do Owner and Parent Relationships Work? ═══ Question: As a new programmer in the PM environment, I still don't understand the concepts of parent and owner as they relate to window controls. Can you please explain? Answer: The most important thing to remember is that parent and child relates to the display hierarchy of the windows in your application, and owner and owned relates to the control hierarchy. The display hierarchy deals with things like clipping and Z-order control. The control hierarchy deals with messages going between the owned and owner windows. The parent window is normally used for window location. For example, most frame windows have a parent of the desktop so that they can appear anywhere on the desktop. If a window is a child of another winodw, then it is only visible inside the parent window. (That is, a window is clipped to its parent.) Many messages are passed up the owner chain for processing. In PM, changes to presentation parameters (such as color or font) are passed down the owner chain. When you move an owner window, all of the windows that it owns are usually moved as well. A window with the desktop as parent and owner is considered a primary window. A window with the desktop as parent but with another window as owner is considered a secondary window. A window whose parent and owner are another window or windows is considered a child window. ═══ 10.2. Handlers ═══ This section discusses handlers, including common problems that can be solved using a handler.  How Do I Change the Mouse Pointer?  How Do I Capture Mouse Movement?  How Do I Send and Handle User-Defined Messages?  Which Handler Should I Use?  How Do I Find the IWindow from a Handler?  How Do I Replace Events?  How Are Keystrokes Processed? ═══ 10.2.1. How Do I Change the Mouse Pointer? ═══ Question: I want to change the pointer icon while the pointer is over my application window. How do I do this? Answer: Use the IMouseHandler class or the IFrameWindow::setMousePointer function. ═══ 10.2.2. How Do I Capture Mouse Movement? ═══ Question: I need to be aware of any mouse movement that occurs while the mouse pointer is on top of my application window. Is there a way to be informed about mouse movements? Answer: Yes. VisualAge C++ Open Class Library Version 3.0 includes a handler to capture mouse movements. Derive from IMouseHandler and override the mouseMoved function to be informed of mouse movements. ═══ 10.2.3. How Do I Send and Handle User-Defined Messages? ═══ Question: How do I dispatch and capture my own user-defined messages to an event handler? How do I create a handler to capture the message? Answer: When you create a handler and call its handleEventsFor function, all messages dispatched to the handler's windows are sent to the handler's dispatchHandlerEvent function. To handle messages differently, override this function and capture whatever user or system messages you want to handle. You must also return true or false to indicate whether other handlers should receive the message, and call setResult on the event passed to dispatchHandlerEvent if you need to pass information back to PM or to the sender of the event. To dispatch a message to a window:  Use the postEvent or sendEvent functions of IWindow or IWindowHandle to post or send an event to a specific window.  Use IMessageQueueHandle::postEvent to post an event to a specific message queue. For example, if you have attached your handler to an IWindow window, send messages to it with: mywindow.sendEvent( USER_MESSAGE ) This sends USER_MESSAGE to mywindow, which then causes your handler to run. Your dispatchHandlerEvent function should look for USER_MESSAGE and do whatever it needs to with it. One case where a user message handler is useful is to process the messages sent to an object window (IObjectWindow). Object windows are useful in cases where you have application-defined events that may take a long time to process. Because object windows are invisible and never get the input focus, you will not block user input going to other windows while you process these events. To use an object window, create a separate thread using IThread and in it: 1. Create an IObjectWindow, 2. Create a user message handler, 3. Attach this handler to the object window, 4. Call IThread::current().processMsgs(). Your main thread simply posts WM_USER events to the object window. You need to somehow pass back a handle for the object window to your main thread and possibly pass in a window handle to the object window so it can communicate back. You can use a user handler on the main thread to accept communications from the object window. For an example of a WM_USER message handler, click below: Example For more details on handlers, see the Open Class Library User's Guide. The HELLO6 sample program also has an example of a user-defined handler that handles WM_TIMER messages. ═══ Example of WM_USER Message Handler ═══ Here is an example of a WM_USER message handler that you could add to a frame window. When you want to post a message to the frame window, you do something like: #define WM_DATA_IS_READY WM_USER + 100 myFrameWindow->postEvent(WM_DATA_IS_READY ); The handler would look like this: ----------------------------- iusrhdr.hpp ----------------------- #ifndef _IUSERHDR_ #define _IUSERHDR_ #include #include #pragma pack(4) class IUserHandler : public IHandler { public: IUserHandler ( ); virtual ~IUserHandler ( ); virtual Boolean dispatchHandlerEvent ( IEvent& event ); protected: virtual Boolean user ( IEvent& event ) = 0; }; #pragma pack() #endif --------------------------- iusrhdr.cpp ---------------------------- #define INCL_WINMESSAGEMGR #include #include IUserHandler :: IUserHandler ( ) : IHandler ( ) { } IUserHandler :: ~IUserHandler ( ) { } Boolean IUserHandler :: dispatchHandlerEvent ( IEvent & evt ) { Boolean bRc = false; switch (evt.eventId()) { case WM_DATA_IS_READY: { bRc = user(evt); break; } default: break; } /* endswitch */ return bRc; } ═══ 10.2.4. Which Handler Should I Use? ═══ Question: How do I decide which handler class to use for a particular event? Is there a cross reference of handler classes and the messages they process? Answer: There is a table in the Open Class Library User's Guide chapter on handlers and events that lists some of the events and the handlers they are dispatched to. The VisualAge C++ documentation does not include a cross-reference between the handler classes and the system (PM) messages they process. OS/2 C++ Class Library: Power GUI Programming with C Set ++ also contains a listing of the handler classes and recommendations on where you might use them, as well as a handler-to-PM-message cross-reference. (Note that this book does not include information about additions to Version 3.0 of the library.) If you have the Open Class Library Source Code product, you can determine the messages processed by a handler by examining the dispatchHandlerEvents function implementation. However, do not depend on this being constant; future library enhancements may alter the message set handled by a handler class. ═══ 10.2.5. How Do I Find the IWindow from a Handler? ═══ Question: If more than one control is attached to the the same event handler, how do I find the IWindow object relating to the current event? Answer: When in a handler callback function, event.window() returns the window the message was dispatched to. If the event was not an IControlEvent, event.window() returns the window itself. For an IControlEvent, event.window() returns the owner of the control. You can then call IControlEvent::controlWindow to get the control window pointer. Once you have the window pointer, you can call IWindow::id to find the window's ID. You can also get the ID of a control in an event derived from IControlEvent by calling controlId. ═══ 10.2.6. How Do I Replace Events? ═══ Question: How do I replace an event that comes to my application with a different event? Answer: Typically you would implement a handler to replace the event. Detect the specific event you want to replace, and then send or post the replacement event or message to the window. Return a value of true to indicate that the original event should not be passed on to any more handlers or to any window procedures. However, any time you do this, you need to ensure that you don't cause an endless loop. You may need to add a check to your handler to detect that an event is not to be replaced again or reposted. Alternatively, when you detect the event to replace, you can build your replacement event, then pass it to IWindow::defaultProcedure, and return a value of true. The difference is that the replacement event won't come back through your handler, although it will also bypass all previous and subsequent handlers. Therefore, any other handlers attached to your window cannot process the replacement event. You can also call IWindow::dispatchRemainingHandlers to bypass handlers that have already been called, so that only subsequent handlers (and optionally, IWindow::defaultProcedure) are called. ═══ 10.2.7. How Are Keystrokes Processed? ═══ Question: How are keystrokes processed? Answer: The normal processing for keystrokes is that the window (control) with the input focus has the first opportunity to process the key. If that window doesn't process it, then the key is passed up to its owner window. The owner window can then process the key. If not processed, it again goes up the owner chain. The cycle continues until the keystroke is processed or until no windows are left in the owner chain. You need to consider this behavior when using an IKeyboardHandler in your application. For example, IMultiLineEdit processes PageUp and PageDown to scroll the data. If you want the IMultiLineEdit to ignore these keys, create a keyboard handler that provides an implementation for the virtualKeyPress function. In the handler, look specifically for IKeyboardEvent::pageUp and IKeyboardEvent::pageDown virtual keys. You can route these keystrokes to the owner window of the MLE by calling: event.window()->owner()->sendEvent( event ); Then return true to indicate that PageUp or PageDown should not be given to the MLE to process. For all other cases, return false. This will have the effect of disabling the MLE scrolling. You could use such a setup when the MLE's owner is an IViewPort that will be handling the scrolling. ═══ 10.3. Frame Windows ═══ This section discusses frame windows, dialog templates, and related topics.  How Do I Set the Title of a Frame Window?  How Can I Intercept a Request to Close a Frame Window?  What Is the IFrameWindow Client Window?  Why Is the Client ID Changed?  How Do I Set Up an IFrameWindow within an IFrameWindow?  Can I Use a Frame as a Client Window?  Can I Have Automatic Resizing for Dialogs?  Why Doesn't My Control Defined in a DLG Resource Work?  Should I Use Canvas Classes or Dialog Templates?  Can I Set the Minimum Size of an IFrameWindow?  How Do I Access the Scroll Bars on an IFrameWindow?  Why Does My Dialog Overwrite the Parent Notebook?  Why Doesn't My Application End when the Frame is Closed?  How Do I Exit IApplication::current().run() ? ═══ 10.3.1. How Do I Set the Title of a Frame Window? ═══ Question: How can my application read and set the titlebar text of an existing IFrameWindow object? Answer: Use the ITitle class. Create an ITitle object from the IFrameWindow, and then use objectText() and setObjectText(), or text() and setText(). to read and change the frame window's title, respectively. ═══ 10.3.2. How Can I Intercept a Request to Close a Frame Window? ═══ Question: When the user closes my application with the system menu, I need to intercept the close request so I can display a confirmation message. How can I do this? Answer: The command you want to intercept is SC_CLOSE. You can use the PM constant SC_CLOSE or the ISystemMenu::idClose constant. To use SC_CLOSE, derive a handler from ICommandHandler and provide a systemCommand function similar to this: Boolean MyCommandHandler::systemCommand(ICommandEvent& cmdEvt) { if ( cmdEvt.commandId() == SC_CLOSE ) { // ask for confirmation from user ... if ( response != IMessageBox::yes ) return true; // let it close } return false; // don't close } Attach this command handler to your IFrameWindow. Another way to do this is to attach your own IFrameHandler to your frame window, providing your own implementation of the virtual closed() function. This function will be called when the frame window is closed. (It keys off a WM_CLOSE, WM_SYSCOMMAND, or SC_CLOSE command). ═══ 10.3.3. What Is the IFrameWindow Client Window? ═══ Question: I want to create a window with a number of child controls in it. I thought of using canvases, but don't know what to use for a client window. What should I do? Answer: You can make almost any control the client of your frame. The client window is just the window that fills up the client area (the portion of the frame not occupied by frame controls). It can be any type of control; canvases are especially useful if you want your child controls to behave like dialogs (with Tab key support and so on). ═══ 10.3.4. Why Is the Client ID Changed? ═══ Question: I have a dialog that I create specifying an ID of 0x11FE. My dialog has an ISelectHandler attached to it. When my enter() method is called, the controlId() method returns 0x8008. What is happening? Answer: The ID 0x8008 is the value of the constant FID_CLIENT (and IC_FRAME_CLIENT_ID). The frame forces its client to have that ID so it knows the window is the client window. If you use a different value, IFrameWindow::setClient changes the window ID for you. The client window's ID must remain FID_CLIENT, or the frame won't format correctly or correctly dispatch events to the client window. You can obtain the FID_CLIENT definition from . IC_FRAME_CLIENT_ID is defined in . ═══ 10.3.5. How Do I Set Up an IFrameWindow within an IFrameWindow? ═══ Question: I have created a primary window using IFrameWindow. I also created a second IFrameWindow as a child of the primary window, using the primary window as both its parent and owner. How do I prevent the child frame from being positioned over the menu bar of my primary window? Answer: You need to add a client area window to the parent frame, and make the client the parent of the child frame. This will also keep the child frame from overwriting the borders of the parent frame. ═══ 10.3.6. Can I Use a Frame as a Client Window? ═══ Question: Can I use a frame window as the client window of another frame? I tried to do this, but saw strange results. Answer: A frame window does not work well as a client window because of certain aspects of PM's frame-window procedure. A PM frame window sends itself certain messages like WM_FORMATFRAME and WM_UPDATEFRAME, and expects to receive these messages only from itself. It also forwards these messages to its client window (as part of the frame-client message protocol). If the client window is a frame window, the client expects these messages to come only from itself. When it receives them from the parent frame window instead, the resulting processing can give unexpected results. Accelerator keys and WM_HELP messages are also likely to be processed by the client frame, rather than its parent, which might not be what you expect. ═══ 10.3.7. Can I Have Automatic Resizing for Dialogs? ═══ Question: If I use the Dialog Editor to build my dialog and then create an IFrameWindow from it, will the controls reflow when the main window is sized? Can I make the dialog fill the whole client area in the main window? Answer: If you use a dialog template to lay out the controls, there is no "reflowing" when you size the frame. The dialog will fill the whole frame. The dialog controls will be sized and positioned as you specified in the dialog template. Automatic sizing is only done on the client window of an IFrameWindow. You can use the canvas classes to construct windows with dialog behavior that are also resized when the frame is resized. Typically there is no client window in dialogs that are generated using the Dialog Editor. However, if your dialog template does happen to have a window with an ID of FID_CLIENT (0x8008), IFrameWindow::client will return it regardless of whether you've called setClient. You still have to use one of the User Interface classes as a wrapper for your client window so that there is an IWindow pointer to return to you. You can use IFrameWindow::clientHandle to get back a window handle even if you haven't wrappered the client window with a User Interface class. ═══ 10.3.8. Why Doesn't My Control Defined in a DLG Resource Work? ═══ Question: I want to create a combo box that is read-only. I instantiated an IComboBox::Style class, set it to dropDownListType, and passed it to the combo box constructor, but the user can still change the entry field part of the combo box without using the list box control. What should I do? Answer: The combo box is defined in a PM dialog template. The first constructor creates a new combo box control, which is 0-sized, so you can't see it. Your code is acting on this 0-sized combo box, not the visible one created by the dialog template. The second constructor correctly wraps the combo box created by the dialog box. Because the type of the combo box can only be specified when the control is created, you must change how the combo box is defined in the dialog template. Both constructors create an IComboBox C++ object. The first also creates a combo box control. The second requires an existing control that will be represented by the IComboBox object. ═══ 10.3.9. Should I Use Canvas Classes or Dialog Templates? ═══ Question: I am creating some PM dialogs. Should I use the Dialog Editor to create dialog templates (.DLG files), or should I create them inline in code using the canvas classes? Answer: If you are targeting your application only for OS/2 in a relatively homogeneous environment, the Dialog Editor and Resource Compiler provide a quick way to create the dialogs. However, many applications written today require that you consider support for portabilility, multiple monitor resolutions, fonts, and national language. When these factors come into play, maintaining the dialog resources becomes a major undertaking. Using the canvas classes may prove more effective in cases where one or more of these factors are important. The canvas classes provide layout support that can dynamically change based on language, fonts, and so on. You lay your controls out on a canvas aligned relative to one another in a row and column type gridding, and the class handles the rest. Take a look at the Hello World sample (HELLO6 in particular) for an example of how this works. It shows an example of the same application using different language resources. By bringing each one up, you can see how the same code will handle the language changes. You can also drop fonts onto the canvas, and you will see how it handles that as well. The portability of dialog resources is also very limited. The canvas classes are fully supported in the Motif version of the User Interface classes (shipped with C Set ++ for AIX), but dialog templates are not supported at all. Refer to the chapter on Creating Dialogs in the Open Class Library User's Guide. ═══ 10.3.10. Can I Set the Minimum Size of an IFrameWindow? ═══ Question: I have a frame window with a client window that is an instance of IMultiCellCanvas. Thus it has a minimum size. When I resize the frame window with the mouse, it is resized but the client does not become smaller than its minimum size, so part of the client window is not visible. Is there a way to force a frame window not to become smaller than the minimum size of its client window? Answer: There is no direct support in the library for making the frame window behave in this way. You may be able to control this behavior by creating a handler that processes the WM_QUERYTRACKINFO message to limit resizing using the mouse. If you are initially sizing the frame, you can query the minimum size of the client window and use the size obtained to size the frame using the IFrameWindow::moveSizeToClient function. ═══ 10.3.11. How Do I Access the Scroll Bars on an IFrameWindow? ═══ Question: If I create a frame window with the IFrameWindow::horizontalScroll and IFrameWindow::verticalScroll styles, are there any ways to access these scrollbars? Answer: In PM, you can construct IScrollBar objects for them as follows: IScrollBar* vert = new IScrollBar( FID_VERTSCROLL, &frameWindow ); IScrollBar* horz = new IScrollBar( FID_HORZSCROLL, &frameWindow ); (The window IDs, FID_VERTSCROLL and FID_HORZSCROLL are defined in .) If you do the above, you have to provide the scrolling support yourself. You can use the IScrollBar and IScrollHandler classes to provide this support. Alternatively, you can use an IViewPort as your client window, in place of the frame scroll bars. Generally IViewPort does not require you to provide any code for it to scroll a window. ═══ 10.3.12. Why Does My Dialog Overwrite the Parent Notebook? ═══ Question: I'm adding a dialog page to a notebook. When I run my code, the dialog pops up on the screen overwriting parts of the notebook. When I turn to that notebook page, the dialog appears normally. What am I doing wrong? Answer: Be sure that your dialog does not have a WS_VISIBLE style. If this style is set, the dialog will be drawn over its parent (the notebook) when it is loaded. ═══ 10.3.13. Why Doesn't My Application End when the Frame is Closed? ═══ Question: I used to end my application by calling IFrameWindow::close, but after I added an IObjectWindow, it only closes my frame window and leaves the executable still running. The last things I added were my frame window but leaves .EXE still running. What is happening? Answer: The problem you are seeing is probably caused by the object window. When you create an instance of IObjectWindow, the library flags this window as a primary window (just like a frame window that is a child of the desktop). IApplication::current().run() will not complete processing until all primary windows have been closed. Try adding a call to setParent for the object window; this should cause the library to remove the primary flag for the window. myObjectWindow->setParent(IWindow::objectWindow()); ═══ 10.3.14. How Do I Exit IApplication::current().run() ? ═══ Question: I have a simple application that calls IApplication::current().run(). I want to override the command function of ICommandHandler so that when the user selects an exit button, the application is terminated. What is the correct way to terminate my main window if I want more actions to be executed when the user chooses the exit button? Answer: Call IFrameWindow::close when the application is to be terminated. This function closes the frame and, if it is the primary frame window, exits the event loop. Exiting the event loop causes IApplication::current().run() to return to the caller. Note that this technique can also be applied to IFrameWindow objects that are running on secondary threads. When an IFrameWindow is not involved, such as when you are using an IObjectWindow, you can call IThread::stopProcessingMsgs to exit the event loop. ═══ 10.4. Canvas Classes ═══ This section answers questions about the canvas classes: ICanvas, ISetCanvas, IMultiCellCanvas, ISplitCanvas, and IViewPort.  Can I Resize an IViewPort Child Window?  How Do I Center a Control on a Canvas?  How Do Tabbing and Cursor Movement Work?  Why Does My Canvas Work with new But Not with Solid Objects?  How Do I Get a Fixed Column Size for an IMultiCellCanvas?  Does Changing the Minimum Size Affect Control Behavior?  How Do I Draw Separator Lines in an IMultiCellCanvas?  Why Doesn't removeFromCell Remove the Window Text?  How Many Windows Does IViewPort Have?  How Do I Align an IComboBox in an IMultiCellCanvas? ═══ 10.4.1. Can I Resize an IViewPort Child Window? ═══ Question: I want to create an IViewPort that resizes the child window when the size of the viewport is greater than the minimum size of the child. When one of the dimensions for the viewport is greater than the corresponding dimensions of the child window's minumum size, the scroll bar is removed and the child is resized along that dimension. When the viewport's dimension is less than the child window's minumum size, the scroll bar must reappear. How can I do this? Answer: The following code creates a resize handler that you can attach to any viewport to perform the function you describe. The code is for a viewport that scrolls a multicell canvas, but you could also use it for other types of windows. Further discussion of this and other canvas tricks be found in chapter 15 of OS/2 C++ Class Library: Power GUI Programming with C Set ++. A complete program using code similar to that below is on the disk that comes with that book. class SizeHandlerForViewPortWithMultiCell : public IResizeHandler { public: virtual SizeHandlerForViewPortWithMultiCell &handleEventsFor ( IViewPort* viewport ), &stopHandlingEventsFor ( IViewPort* viewport ); protected: virtual Boolean windowResize ( IResizeEvent& event ); private: virtual IHandler &handleEventsFor ( IWindow* window ), &stopHandlingEventsFor ( IWindow* window ); }; Boolean SizeHandlerForViewPortWithMultiCell :: windowResize ( IResizeEvent& event ) { IViewPort* viewport = (IViewPort*)event.window(); IMultiCellCanvas* canvas = (IMultiCellCanvas*)IWindow::windowWithHandle ( viewport->viewWindow() ); if (canvas) { ISize newSize = event.newSize(); ISize minChildSize = canvas->minimumSize(); ISize childSize = canvas->size(); ISize newChildSize = newSize; // Grow to viewport size. if (newSize.width() < minChildSize.width()) { // Don't shrink child smaller than its minimum size. newChildSize.setWidth( minChildSize.width() ); } // (Let the viewport scroll it.) else if (newSize.height() < minChildSize.height()) { // Allow room for vertical scroll bar if needed. newChildSize.setWidth( newSize.width() - viewport->verticalScrollBar()->size().width() ); if (newChildSize.width() < minChildSize.width()) { // Don't shrink child smaller than its minimum size. newChildSize.setWidth( minChildSize.width() ); } // (Let the viewport scroll it.) } if (newSize.height() < minChildSize.height()) { // Don't shrink child smaller than its minimum size. newChildSize.setHeight( minChildSize.height() ); } // (Let the viewport scroll it.) else if (newSize.width() < minChildSize.width()) { // Allow room for horizontal scroll bar if needed. newChildSize.setHeight( newSize.height() - viewport->horizontalScrollBar()->size().height() ); if (newChildSize.height() < minChildSize.height()) { // Don't shrink child smaller than its minimum size. newChildSize.setHeight( minChildSize.height() ); } // (Let the viewport scroll it.) } if (newChildSize != childSize) { // Need to resize the child multi-cell canvas. canvas->sizeTo( newChildSize ); } } return false; // Let viewport also process this. } SizeHandlerForViewPortWithMultiCell& SizeHandlerForViewPortWithMultiCell :: handleEventsFor ( IViewPort* viewport ) { IASSERTPARM( viewport != 0 ); IResizeHandler::handleEventsFor( viewport ); IMultiCellCanvas* canvas = (IMultiCellCanvas*)IWindow::windowWithHandle ( viewport->viewWindow() ); if (canvas) // Don't leave canvas with 0 size. { // Have viewport deal with real size. canvas->sizeTo( ISize( 1, 1 )); } return *this; } SizeHandlerForViewPortWithMultiCell& SizeHandlerForViewPortWithMultiCell :: stopHandlingEventsFor ( IViewPort* viewport ) { IASSERTPARM( viewport != 0 ); IResizeHandler::stopHandlingEventsFor( viewport ); return *this; } IHandler& SizeHandlerForViewPortWithMultiCell :: handleEventsFor ( IWindow* window ) { // Private to hide version in IHandler. window; ITHROWLIBRARYERROR( IC_MEMBER_ACCESS_ERROR, IErrorInfo::invalidRequest, IException::recoverable ); return *this; } IHandler& SizeHandlerForViewPortWithMultiCell :: stopHandlingEventsFor ( IWindow* window ) { // Private to hide version in IHandler. window; ITHROWLIBRARYERROR( IC_MEMBER_ACCESS_ERROR, IErrorInfo::invalidRequest, IException::recoverable ); return *this; } ═══ 10.4.2. How Do I Center a Control on a Canvas? ═══ Question: I have a single push button on an ISetCanvas. I then put the ISetCanvas on an IMultiCellCanvas. How do I center the button horizontally in the cell? Answer: You can use a multicell canvas to center a control. Add the control (for example, a set canvas or single pushbutton) to cell 2, 2. Then mark rows 1 and 3, and columns 1 and 3 as expandable. Because canvas objects can be nested, you can apply this technique to center a small control in a row containing another large control. ═══ 10.4.3. How Do Tabbing and Cursor Movement Work? ═══ Question: Can the enableTabStop and enableGroup functions work on buttons or entry fields not owned by the same window? Do these functions effect how the tab key works in moving the cursor between entry fields? Answer: Tabbing and cursor-arrowing work as they do in PM. You can only tab to windows with the tabStop style. You can only use arrow keys to move around in a group. A group is defined by giving the first window in the group the group style. The group ends when the next group is found. All windows in a group must also have the same parent window. Tabbing and arrowing are supported by frame windows and canvases. The control with the keyboard focus must be owned by a frame or canvas (so that frame or canvas can get the tab or arrow key not processed by the control). The control must also be a child window of the same frame or canvas (so the frame or canvas can enumerate child windows to find where to move the input focus to). You can also tab from a control in one canvas to a sibling window of the canvas or to a child control of a sibling canvas. ═══ 10.4.4. Why Does My Canvas Work with new But Not with Solid Objects? ═══ Question: I can construct children of canvas classes using new, but when I try to construct them as solid objects as shown in the Open Class Library User's Guide, I get a SYS3170 or SYS3175 error message. Why? Answer: A typical problem of using solid objects rather than pointers for data members (child windows) of a class is getting the wrong order of construction. Parent and owner windows must be constructed before their child windows and windows they own. The order of their construction is dictated by their order in the class declaration (.hpp file), rather than how they appear in the constructor's initializer list (the .cpp file). ═══ 10.4.5. How Do I Get a Fixed Column Size for an IMultiCellCanvas? ═══ Question: I have a 2-column IMultiCellCanvas that I dynamically build; the contents are also created dynamically. The first column is an IStaticText with a style of default and workbreak. I want this text to split to multiple lines if it is longer than 20 characters. What should I do? Answer: IMultiCellCanvas uses the minimum size of the child window to determine the size that a column or row can be decreased to. Using setColumnWidth will not allow the column to be made smaller than that amount. If no minimum size value exists for a window, IMultiCellCanvas calls calcMinimumSize for that window to obtain the minimum size. For IStaticText, calcMinimumSize returns the larger of the text length in pixels or the text limit times the average character width. To do what you want, you need to set the minimum size of the IStaticText window yourself, before IMultiCellCanvas queries for the minimum size. For an example that shows how to do this, click below: Example Debugging gridlines were also added to the example because they are useful for altering window sizes. ═══ Example of Fixed Column Size in IMultiCell Canvas ═══ /**********************************************************************/ /* Sample program */ /**********************************************************************/ //Include IBM UI class headers: #include //IApplication Class #include //Include IFrameWindow Class Header #include #include #include #define IDC_WINDOW 0x1000 #define IDC_TEST 0x1001 #define IDC_CONTROL1 0x1002 #define IDC_CONTROL2 0x1003 /**********************************************************************/ /* main - Application entry point */ /**********************************************************************/ void main() { /********************************************************************/ /* Create a main frame window on the desktop */ /********************************************************************/ IFrameWindow mainWindow( "Sample MCV", IDC_WINDOW ); /********************************************************************/ /* Create a multicell canvas with the frame as owner & parent */ /* */ /********************************************************************/ IMultiCellCanvas testMCV( IDC_TEST, &mainWindow, &mainWindow, IRectangle(), IMultiCellCanvas::gridLines | IMultiCellCanvas::dragLines | IWindow::visible ); /********************************************************************/ /* Create 2 controls for placing into the multicell */ /********************************************************************/ IStaticText *stControl1 = new IStaticText ( IDC_CONTROL1, &testMCV, &testMCV, IRectangle(), IStaticText::left | IStaticText::top | IStaticText::wordBreak | IStaticText::fillBackground | IWindow::visible); IEntryField *efControl2 = new IEntryField( IDC_CONTROL2, &testMCV, &testMCV, IRectangle(), IEntryField::defaultStyle() ); /********************************************************************/ /* Set the text of each of these controls after setting minimum */ /* size of the static text field */ /********************************************************************/ stControl1->setMinimumSize( ISize( 70, 20 )); stControl1->setText("Control #1 test ... this one wraps"); efControl2->setText("Control 2"); /********************************************************************/ /* Now place them into the multi-cell and adjust columns */ /********************************************************************/ testMCV.addToCell( stControl1, 2,2, 1,1); testMCV.addToCell( efControl2, 4,2, 1,1); testMCV.setColumnWidth( 2, 20, false); testMCV.setColumnWidth( 4, 80, true); testMCV.setRowHeight( 2, 20, true); testMCV.refresh(); /********************************************************************/ /* Make the created text field the client of the frame window, */ /* change focus to the frame and show it. */ /********************************************************************/ mainWindow.setClient(&testMCV); mainWindow.setFocus(); mainWindow.show(); /********************************************************************/ /* Run the application as built */ /********************************************************************/ IApplication::current().run(); return; } ═══ 10.4.6. Does Changing the Minimum Size Affect Control Behavior? ═══ Question: If I use minimumSize to get the minimum size of a control, and then set the minimum size of this same control to that value, is the control's behavior affected? Answer: Yes. Querying the minimumSize and then setting this value as the minimum size does change the behavior. Once the minimumSize has been set, that value is always used; for example, if the font or text limit changes, the minimumSize does not change. If the minimumSize is not set, changes to the control (such as font or text limit) may cause the control to have a different minimum size. You can call IWindow::resetMinimumSize to cause a window to discard the effects of any previous calls to IWindow::setMinimumSize. ═══ 10.4.7. How Do I Draw Separator Lines in an IMultiCellCanvas? ═══ Question: How do I draw a horizontal separator line between cells of a multicell canvas? Is there a control that can be used across all the cells of the canvas? Answer: You could use an IStaticText and set it to the background color of the separator you want and a small minimum size. The height of the minimum size would be used as the thickness of a horizontal line. The minimum width would not be important because the length of the line would be stretched by the columns it occupies. This solution does require that you place IStaticText objects in each of the cells of the IMultiCellCanvas where you want separators to appear. ═══ 10.4.8. Why Doesn't removeFromCell Remove the Window Text? ═══ Question: I am using IMultiCellCanvas::removeFromCell to remove windows from my multicell canvas that were added using IMultiCellCanvas::addToCell. However, the window text associated with the removed window remains visible in the multicell canvas, even after I call IWindow::refresh. Why? Answer: The library does not delete the child window after you call removeFromCell. As a result, the child window still exists, but is never resized or repositioned when the layout of the canvas changes. For this reason, if you are not adding the child window to a different cell, you should either delete it, hide it, or change its parent to the object window so it does not remain in the canvas. Note that keyboard navigation can also be affected by the child window. ═══ 10.4.9. How Many Windows Does IViewPort Have? ═══ Question: Are there more windows to an IViewPort than just a canvas and some scroll bars? Answer: Yes. A viewport has an additional window that is not obvious. Basically, it is an ICanvas that clips your view window so it does not overwrite the scrollbars. (The viewport makes the parent window of your view window the view rectangle.) The window IDs of these child windows are defined in . ═══ 10.4.10. How Do I Align an IComboBox in an IMultiCellCanvas? ═══ Question: I have a multicell canvas with a DropDownList combo box between two entry fields, but the combo box does not align correctly. I want the combo box to occupy a single line, and when I open it, to cover the second entry field. Instead, it takes extra lines. How can I get the box to take less space until it's opened? Answer: For a drop-down or read-only-drop-down combo box, you should have the combo box only occupy one row. This is the row that its entry field and drop-down button will appear in. The size of the drop-down list should be controlled with the IComboBox::setMinimumRows function. The list box will appear over rows below the combo box, and could even stretch below the bottom of the multi-cell canvas. ═══ 10.5. Container Control ═══ This section discusses the IContainerControl class and related classes.  Why Aren't Objects Sorted in Icon View?  How Do I Insert a Container Object as the First Object?  What Does extendedSelection Mean?  What Happens to Child Objects when an Object is Removed?  How Many Objects Can I Have in a Container?  Can I Use Small Icons in an IContainerControl?  Why Doesn't My Pop-Up Menu Work?  How Can I Wrap Text in Columns or Column Headings?  How Should I Handle Date and Time in a Container?  How Do I Access Members of a Derived IContainerObject?  Why Doesn't My Container Display?  Can I Display Items in Tree View on User Request?  Why Are All Container Objects Deleted from a Tree View?  Can I Have Two Instances of the Same Object in a Container? ═══ 10.5.1. Why Aren't Objects Sorted in Icon View? ═══ Question: I have a sort function for a container that works correctly when the container is in tree or details view. However, when it is in icon view, the container is not refreshed until arrangeIconView is called or the view type is changed. Do I have to call arrangeIconView after sorting an icon view? If so, why? Answer: By default, the arrangement of the icon view is independent of the order of the objects in the container. This is to preserve any user arrangement of the objects through direct manipulation or any positioning that might have been done on insertion. If you always want the icon view to be sorted, you can either specify the IContainerControl::autoPosition style for the container or call arrangeIconView after the sort. ═══ 10.5.2. How Do I Insert a Container Object as the First Object? ═══ Question: I want to be able to add IContainerObjects to my container. I know I can use addObject and addObjectAfter to add IContainerObjects to my container after a specified object. How do I add an object as the first object in the container? Answer: To add an object at the beginning of the container, specify 0 as the second argument to addObjectAfter or addObjectsAfter. For example: addObjectAfter(newObject, 0); ═══ 10.5.3. What Does extendedSelection Mean? ═══ Question: What does the IContainerControl::Style extendedSelection mean? Answer: Extended selection is an enhanced form of single selection you can use to select discontinuous ranges of objects. For example, you use extended selection when you select objects in an OS/2 Workplace Shell folder. Extended selection works like single selection unless you use shift or control keys. The control key allows discontinuous selection like multiple selection. The shift key allows range selection as an editor would. If the user of your container should only be able to select one object, use single selection. If the user would typically select many objects, use multiple selection. If the user would typically select one object, but could optionally select more, use extended selection. ═══ 10.5.4. What Happens to Child Objects when an Object is Removed? ═══ Question: In my tree view container, I have an object with two child objects. If I remove the object, the two child objects are also removed. If I delete the object, what happens to the child objects? Answer: When you remove or delete an object from a container in tree view, all of its child objects are also removed or deleted. ═══ 10.5.5. How Many Objects Can I Have in a Container? ═══ Question: Is there a limit on the number of container objects I can add to a container? Answer: IContainerControl imposes no limit other than that set by the underlying container control. In the OS/2 implementation, the maximum number of records is limited by the amount of memory in the computer. The container control does not limit the number of records that a container can have. In the Motif implementation, there is a limit of approximately 1050 records that can be displayed in a one-dimensional view such as details or tree. (The reason is that position values in the X Intrinsics use a signed 16-bit value.) To work around this limitation, limit the number of objects displayed at any one time. ═══ 10.5.6. Can I Use Small Icons in an IContainerControl? ═══ Question: For my container's tree icon view, can I use small icons like the small size OS/2 desktop icons? Answer: Version 3.0 supports a new style, IContainerControl::miniIcons, that uses mini-icons. These are similar to the Workplace Shell small icons. Also refer to the function IContainerControl::showMiniIcons. ═══ 10.5.7. Why Doesn't My Pop-Up Menu Work? ═══ Question: Why doesn't the pop-up menu for my container work correctly? Answer: Here are some tips on pop-up menus:  When you create a menu handler class for a container, derive it from ICnrMenuHandler instead of IMenuHandler.  Remember that an object is not selected by the manipulation mouse button (right mouse-button click). What is shown visually is source emphasis. You must keep track of the pop-up menu object yourself. Store the pointer to the object returned by popupMenuObject, and use this pointer to process a pop-up menu event for that object.  In your command handler, make sure you differentiate between commands invoked from the pop-up menu and those invoked from the menu bar. A pop-up menu should use the popupMenuObject; a menu-bar menu should use selected objects. You can use different command IDs for the pop-up and menu bar.  You can also create a pop-up menu for the container itself if the pop-up is requested when the pointer is not over an object. In this case, popupMenuObject returns 0. ═══ 10.5.8. How Can I Wrap Text in Columns or Column Headings? ═══ Question: Can I make the text in my container columns wrap to the next line? Can I create container column headings that are more than one line? Answer: Yes to both questions. Insert new-line characters in the text where you want line breaks. For the column heading, you can use \x0a in a STRINGTABLE resource. For example: TXT_HEAD1 "Line1\x0aLine2" Then when you build the container: col->setHeadingText (TXT_HEAD1); The following alternative also works: col->setHeadingText ("Line1\nLine2"); To wrap the text in the column, put a new-line character (\n) in the string where you want it to wrap to the next line. For example: Column1 Column2 Column3 Textdata\n something\n for this\n goes here\n goes \n for test here The text string for the fourth column would be: "Textdata\nfor this\ngoes \nhere" ═══ 10.5.9. How Should I Handle Date and Time in a Container? ═══ Question: How should I use IDate and ITime to correctly handle date and time values in a container? Answer: The natural data type for a date in a container is a CDATE, defined in . Declare your date data member as a CDATE object. When you need to perform arithmetic operations on the value, construct an IDate object from the CDATE type. You can also convert from IDate to CDATE using the asCDATE function. For time, declare your member as a CTIME object, and construct an ITime object when you need to perform operations on the value. Alternatively, you can store the date and time information in the container as strings, and then convert the values using the asString functions of IDate and ITime. ═══ 10.5.10. How Do I Access Members of a Derived IContainerObject? ═══ Question: The IContainerControl functions to access objects all return IContainerObject pointers. How do I access functions defined in a class derived from IContainerObject? Answer: Cast the IContainerObject pointer to a pointer to your object. For example: MyObject* object = (MyObject*)container.objectAt(1); object->myFunction(); ═══ 10.5.11. Why Doesn't My Container Display? ═══ Question: I'm trying to use a group box to provide a border for a container, but the container is not being drawn. Why? Answer: Containers will not paint over a sibling window that overlaps and lies on top of the container, even if the sibling window does not paint that portion of the screen (such as group boxes and outline boxes, which do not paint their interiors, and combination boxes, which do not paint where their drop-down list boxes will be displayed). Create the container first, and then the group box, so that the container lies on top of the group box. ═══ 10.5.12. Can I Display Items in Tree View on User Request? ═══ Question: I want to partially fill my tree view container and indicate that more items exist, then display those items only when the user selects that branch of the tree. How can I do this? Answer: You can get a list of the top-level objects in the tree and insert each one into the container using cnr->addObject(topLevelObject); To provide a visual indication (such as a plus sign) that the tree can be expanded, add a dummy child object to each of the expandable top-level objects. For example: cnr->addObject(dummy, topLevelObject); To be able to expand the tree with real objects when the user selects it, add a container handler (ICnrHandler) to the container and override the IContainerObject::handleTreeExpanded member function in your container object class. This member function is called by the ICnrHandler whenever an object is expanded. In this function, delete the dummy object and add the real objects. Once you have expanded the tree, set a flag so you don't expand it again. Return FALSE and let the default action occur if the level is already expanded. The object is really already expanded when handleTreeExpanded is called. However, the technique above works because the container is repainted when you add or remove objects. ═══ 10.5.13. Why Are All Container Objects Deleted from a Tree View? ═══ Question: When I call deleteSelectedObjects for a tree view of a container, it deletes all objects, not just the selected one. I also noticed that the Select All and Deselect All menu choices for tree views are disabled. Why? Answer: The behavior you are seeing is a restriction on the PM container control. In a tree view, only one object can be selected at any time and there must be one object selected. That is the reason you can't select or deselect all objects in a tree view. The deleteSelectedObjects function deletes the currently selected object. Because there must always be a selected object in a tree view, the next object in the tree view gets selected. The deleteSelectedObjects function then detects another selected object and deletes it, causing the next object to be selected. The cycle continues until all objects are deleted. ═══ 10.5.14. Can I Have Two Instances of the Same Object in a Container? ═══ Question: Can I add the same IContainerObject to an IContainerControl twice? Answer: No, you cannot add the same container object twice. PM maintains pointers to place the record in the container relative to other records, and does not allow the same record to be added twice. Instead, use the IContainerObject copy constructor to create another instance of your container object, and add that to the container. ═══ 10.6. Text and Button Controls ═══ This section discusses ITextControl and classes derived from it.  How Do I Limit the Character Type for an IEntryField?  How Do I Search for Text in an IMultiLineEdit Control?  Can I Append to a File using an IMultiLineEdit Control?  How Does IRadioButton::selectedIndex Work?  Why Can't I Disable Refreshing for an IMultiLineEdit Control? ═══ 10.6.1. How Do I Limit the Character Type for an IEntryField? ═══ Question: I want to have a numeric-only entry field, a time-format entry field (hh:mm), and a date format entry field (mm/dd/yy). How can I do this? Answer: For a numeric-only entry field, create an IKeyboardHandler. In characterKeyPress, check for the characters 0 through 9. If the value is other than those characters, return TRUE and the non-numeric input will be ignored. Depending on how your user interface works, you may also want to override the entry field's paste and setText functions so they cannot be used to enter non-numeric text. To create a formatted field such as time, date, or currency, you need an IEditHandler in addition to the IKeyboardHandler. The IEditHandler is used to format the string correctly each time an acceptable character is received. An uppercase-only entry field is similar to the numeric entry field, except that you want to change the lowercase keyboard events to uppercase ones. To do this, discard the lowercase keyboard event by calling setResult(true) for the event and returning TRUE from the characterKeyPress function. Then construct a new keyboard event with the appropriate uppercase value and send the new event to the entry field. Your keyboard handler will also be called for this new event, so ensure it ignores valid values; otherwise you may enter a loop. For example: Boolean MyHandler :: characterKeyPress(IKeyboardEvent& keyEvt) { char cTest = keyEvt.character(); if ( islower(cTest) ) { cTest &= '\xDF'; //make upper case keyEvt.window()->postEvent(IWindow::character, keyEvt.parameter1(), cTest); return true; } return false; } ═══ 10.6.2. How Do I Search for Text in an IMultiLineEdit Control? ═══ Question: Is there a find or search function for locating text in an IMultiLineEdit control? Answer: For a forward text search, use the text function to copy the IMultiLineEdit content, beginning at the line where the cursor is, to an IString object. Then search the resulting IString to find the index where the string is found. Use the index from the IString search to highlight the found string in the IMultiLineEdit control. ═══ 10.6.3. Can I Append to a File using an IMultiLineEdit Control? ═══ Question: Is there a way to append to an existing file using the exportToFile function or some other function of the IMultiLineEdit control? Answer: The exportToFile function opens the file for writing and will overwrite your existing file. Instead, try the following: IString alltext = mle.selectRange().selectedText(); Then append alltext to the file yourself. ═══ 10.6.4. How Does IRadioButton::selectedIndex Work? ═══ Question: The IRadioButton::selectedIndex function returns incorrect values when there are controls other than radio buttons in the window. Why? Answer: The index includes all windows that are of the group, not just radio buttons. The group should include only radio buttons to get the expected results. ═══ 10.6.5. Why Can't I Disable Refreshing for an IMultiLineEdit Control? ═══ Question: I call disableRefresh to disable refreshing for my IMultiLineEdit control. Then when I add lines, the refresh still occurs. Why? Answer: The User Interface classes use MLM_IMPORT to add text to the multiline edit field (MLE). MLM_IMPORT requires that a null character be the last character added, so the class library adds this extra character to the MLE along with your text. The library then calls MLM_DELETE to delete the extra null character, and MLM_DELETE sends an MLM_ENABLEREFRESH message to the MLE. As a result, refresh is always enabled when you add text. Even if a handler prevents MLM_ENABLEREFRSH from going to the MLE after a delete operation, the screen text is updated. To prevent the MLE from showing updates as they are made, call the IWindow virtual function enableUpdate with the appropriate Boolean value. For example: mle.IWindow::enableUpdate(false); // add text to mle here mle.IWindow::enableUpdate(true); ═══ 10.7. Listbox and Combobox Controls ═══ This section discusses topics related to the list controls IListBox and IComboBox.  Why Don't I See Events from My IComboBox?  What Is the Limit on the Amount of Data in a Listbox?  Can I Determine the Height of a Listbox Item? ═══ 10.7.1. Why Don't I See Events from My IComboBox? ═══ Question: I defined a class that inherits from IComboBox and IKeyboardHandler to intercept keystrokes within the entry field of the combo box. It seems the IKeyboardHandler is never invoked even though the constructor calls handleEventsFor(this). What is the problem? Answer: The combo box is causing you problems because it is a composite control, meaning that it is implemented with child controls: a child entry field and listbox. The keystrokes you don't see are being processed by the child entry field. To process these keystrokes, you need to construct an IEntryField for this child entry field, and attach the keyboard handler to this window. For example: IComboBox comboBox( ... ); IEntryField childEF( CBID_EDIT, &comboBox ); //CBID_EDIT is in pmwin.h MyKeyboardHandler keyhdr; keyhdr.handleEventsFor( &childEF ); ═══ 10.7.2. What Is the Limit on the Amount of Data in a Listbox? ═══ Question: I have a listbox that could contain a large number of elements. Is there a limit to how many elements I can add? Answer: In OS/2 2.x, there is a limit of 64K of heap space per frame window for all controls. The list box data is allocated out of this space. The limitation is really on the amount of data, not the number of elements. In OS/2 Warp, the limit is 32K entries. One possible solution is to use a container in details view instead of the listbox. ═══ 10.7.3. Can I Determine the Height of a Listbox Item? ═══ Question: Is there a way to determine the cell height or the number of displayable items for a given listbox? Answer: No. PM does not make available either the height of a row or the number that are currently visible in a listbox. ═══ 10.8. Notebook Control ═══ This section discusses topics related to the INoteBook class.  How Do I Change the Appearance of INotebook Tabs?  Why Isn't My IPageHandler::drawTab Function Called?  How Can I Display Page Numbers in a Notebook?  How Can I Get the Correct Size for a Notebook? ═══ 10.8.1. How Do I Change the Appearance of INotebook Tabs? ═══ Question: What can I do to change the appearance of INotebook tabs? Answer: To change the size of the notebook tabs, use INotebook::setMajorTabSize and INotebook::setMinorTabSize. Note, however, that all tabs in a notebook must be the same size. You can control the location of the tabs with the INotebook::setOrientation function and through INotebook styles. To change the color of all the tabs in a notebook, use INotebook::setColor. If you want to change the color of some but not all tabs (for example, as an indicator that a page has been changed), you must draw the tabs yourself using IPageHandler::drawTab. You also have to draw the tabs yourself if you want to have multiline tab text. ═══ 10.8.2. Why Isn't My IPageHandler::drawTab Function Called? ═══ Question: The drawTab function for my IPageHandler is not called. Why? Answer: If you call either setTabText or setTabBitmap for a page, the PM notebook control draws the tab and does not call your drawTab function. This behavior is determined by PM. To ensure your drawTab function is called, do not call either setTabText or setTabBitmap for the page. ═══ 10.8.3. How Can I Display Page Numbers in a Notebook? ═══ Question: How can I display page numbers on my notebook pages, such as Page 1 of 3, Page 2 of 3, and so on? Answer: You can use INotebook::setStatusText to implement page numbers as status text on the notebook page. If you want the page numbers aligned to one side, use INotebook::setStatusTextAlignment as well. You will need to specify the statusTextOn attribute when you construct the IPageSettings object that you use to insert pages. ═══ 10.8.4. How Can I Get the Correct Size for a Notebook? ═══ Question: I have a notebook that contains a multicell canvas with an expandable margin. When I request the minimum notebook size after the pages are loaded, but before the canvas is filled, I get unusual values from INotebook::pageSize and INotebook::notebookSize.. A sizeTo of the frame window creates a 1x.75 inch window. What am I doing wrong? Answer: When you call INotebook::pageSize, the size of the notebook is still (0, 0), so the size needed by a page window to fit into a notebook would have to be even smaller. When you call INotebook::notebookSize, the page window is still sized to (0, 0), and that is the reason your notebook has such a small size. After adding page windows to your notebook, you can size the frame based on the size of the notebook client window: frame.moveSizeToClient( IRectangle( IFrameWindow::nextShellRect() .bottomLeft(), notebook.minimumSize() ); ═══ 10.9. Other Controls ═══ This section discusses topics related to the IMessageBox, ISpinButton, and ISlider classes.  Why Don't I See Events from My ISpinButton?  Why Does My IProgressIndicator Not Display Correctly?  Can I Use IMessageBox without a Main Application Window?  What Is an IMessageBox Modal To? ═══ 10.9.1. Why Don't I See Events from My ISpinButton? ═══ Question: I have a class that inherits from ISpinButton and IKeyboardHandler, and overrides the key function to intercept characters other than 0-9 and '.'. But when I enter a character, it appears in the entry field part of the control before the key function runs. Also, the key event that I receive is not a character (isCharacter returns FALSE). Why does this happen? Answer: The spin button is a composite control with an entry-field child window. You need to attach the keyboard handler to the spin button's entry-field child window. The spin button itself receives only the keystrokes that the entry field doesn't process (like noncharacter keys and key releases). To attach the keyboard handler to the entry field, you must first construct an IEntryField for the entry field. In OS/2 2.0 and above, you can take advantage of the fact that the spin button creates its child entry field with the same window ID that it uses, for example: unsigned long ID = spinButton->id(); IEntryField* spinEntry = new IEntryField( id, spinButton ); ═══ 10.9.2. Why Does My IProgressIndicator Not Display Correctly? ═══ Question: When I add an IProgressIndicator to a multicell canvas and then size the window to its minimum size, the tick text associated with the progress indicator scale is clipped at both ends of the scale. Why? Answer: The calcMinimumSize for IProgressIndicator (and ISlider) returns (30,100) or (100,30), depending on the shaft orientation. Depending on your application and the styles you are using, you may need to call setMinimumSize to establish a different size for the control. ═══ 10.9.3. Can I Use IMessageBox without a Main Application Window? ═══ Question: I need to display a message box for my application before my main window is created. How can I do that? Answer: You can use IMessageBox and pass 0 for the window owner. Note, however, that without a window owner, you cannot provide help for the message box. ═══ 10.9.4. What Is an IMessageBox Modal To? ═══ Question: I have an application with two frame windows. When I create an IMessageBox for frame window 1 with the applicationModal style, the message box is not modal with respect to frame window 2. Why? Answer: A message box is only application-modal to its owner. In your example, the message box is modal relative to frame window 1, and your other frame windows can receive input focus. ═══ 10.10. Menus ═══ This section discusses menus, including pop-up or context menus.  Why Don't the IMenuItem Functions Affect My Menu?  Where Should I Call setAutoDeleteObject for Pop-up Menus?  How Can I Change the Font of a Pop-up Menu? ═══ 10.10.1. Why Don't the IMenuItem Functions Affect My Menu? ═══ Question: I can use IMenu::enableItem and IMenu::disableItem to enable and disable my menu items, but IMenuItem::setDisabled and IMenuItem::setSelectable do not work. Why? Answer: The key point to remember about the IMenuItem functions is that they do not affect the menu unless the IMenu add or set functions are called with the instance of the menu item. To set the styles or attributes of an existing menu item, you must use the IMenu functions such as enableItem and checkItem. You can also manipulate an IMenuItem object that represents a menu item within the existing menu, and then call IMenu::setItem to replace the styles or attributes of that item with those you specify for the IMenuItem object. ═══ 10.10.2. Where Should I Call setAutoDeleteObject for Pop-up Menus? ═══ Question: I create a pop-up menu with makePopUpMenu. To delete it correctly, can I call setAutoDeleteObject from makePopUpMenu, or do I have to override menuEnded and call setAutoDeleteObject from there? Answer: If you want the library to delete your pop-up menu, call setAutoDeleteObject in makePopUpMenu. Note that to do this, you must have created the pop-up menu with operator new. ═══ 10.10.3. How Can I Change the Font of a Pop-up Menu? ═══ Question: When I change the font in a container, the pop-up menus for objects in the container inherit the font change. How can I change the fonts of the pop-up menus? Answer: Fonts are inherited from the owner chain. If you don't want the pop-up menu to inherit from the container, change the owner of the pop-up menu from the container to the frame window. ═══ 10.11. Creating Help with IHelpWindow ═══ This section discusses providing application help using IHelpWindow and other classes.  How Do I Create Help for Child or Secondary Windows?  How Does IMessageBox Help Work?  How Do I Customize Help for IFileDialog? ═══ 10.11.1. How Do I Create Help for Child or Secondary Windows? ═══ Question: I have two help subtables, one for my main window and one for a dialog. An IHelpWindow is associated with the main window and an IHelpHandler is attached to the main window. The help works for the main window, but not for the dialog. How do I fix this? Answer: If your dialog is a secondary window (its parent window is the desktop), then Help Manager should be able to locate the correct help subtable for its contextual and/or general help. However, you should use IHelpWindow::associateWindow to associate the IHelpWindow used by the main window with your secondary dialog. Then when you dismiss the help window, focus returns to the dialog instead of the primary window, and when you dismiss the dialog, the help window is automatically dismissed. Once you add the associateWindow, you will also need to add the same help handler to the dialog because all help notification messages will be sent to the associated window. If your dialog is a child window, use the IHelpWindow::setActiveWindow function to identify the child frame window as the active help window. Without this call, Help Manager will use the wrong frame window when searching the help tables. ═══ 10.11.2. How Does IMessageBox Help Work? ═══ Question: I am trying to display a message box with a help button on it. The IMessageBox documentation says, if you have helpID, the help button will display, but it does not say anything about the help file to load the help from. What does IMessageBox do? Answer: IMessageBox uses the same IHelpWindow that its owner window uses and consequently the same help library file. ═══ 10.12. Application and Resources ═══ This section discusses general application topics and how to use resources and and resource libraries.  How Should I Use IResourceLock and ISemaphoreHandle?  What Does IDynamicLinkLibrary Do If the DLL Is Not Found?  How Do I Get the Anchor Block (HAB) of a Thread?  Which Classes Can I Use in a Non-GUI Application?  Can I Dynamically Load Resources from a DLL?  How Can I Specify a New-Line Character in a Resource File?  How Does the Library Choose between Multiple Icons?  Can I Dynamically Load Bitmaps and Icons That Aren't Resources?  How Do I Load a String Resource into an IString? ═══ 10.12.1. How Should I Use IResourceLock and ISemaphoreHandle? ═══ Question: How should I use ISemaphoreHandle and IResourceLock? Answer: The easiest way to lock resources is to use the IResourceLock class. Create a static instance of IPrivateResource or ISharedResource. When you need to lock the resource, create a local instance of IResourceLock that exists during the time you need the resource to be locked. For example: static IPrivateResource collectionResource; // ... { // set lock IResourceLock reslock(collectionResource); // ... do serialized tasks here } // lock freed on scope exit and destruction of reslock object In OS/2, IResourceLock uses mutex semaphores to implement the locking. The User Interface class library does not support direct user of OS/2 event semaphores, but you can use ISemaphoreHandle as an alias for the PM HEV and HMTX types on the OS/2 API calls. ═══ 10.12.2. What Does IDynamicLinkLibrary Do If the DLL Is Not Found? ═══ Question: What does the IDynamicLinkLibrary constructor do if the DLL specified in the input character string cannot be found? Answer: The IDynamicLinkLibrary class will throw an IAccessError exception if it cannot find and load the DLL specified. ═══ 10.12.3. How Do I Get the Anchor Block (HAB) of a Thread? ═══ Question: How can I determine the anchor block handle (HAB) of the current thread? Answer: You can determine the anchor block handle with the following function call: IAnchorBlockHandle hab = IThread::current().anchorBlock(); ═══ 10.12.4. Which Classes Can I Use in a Non-GUI Application? ═══ Question: Are there any User Interface classes that I can use in a non-GUI application? Answer: For applications that do not have a graphical interface, you can use the following classes from the User Interface class library:  IBase  IVBase  ITrace  IErrorInfo and derived classes (except IGUIErrorInfo and IXLibErrorInfo)  IString, I0String and related classes  IDate  ITime  IBitFlag  IPoint and derived classes  IRectangle  IResource and IResourceLock  IProcedureAddress  IDynamicLinkLibrary  IApplication and ICurrentApplication (non-GUI functions only)  IThread and ICurrentThread (non-GUI functions only) The other libraries in IBM Open Class (I/O Stream, Complex, Collection, Database Access, and Data Type and Exception) are also available to all C++ applications. ═══ 10.12.5. Can I Dynamically Load Resources from a DLL? ═══ Question: Is there a way I can load resources (like icons) and dialogs dynamically from a DLL without having to use setUserResourceLibrary and setResourceLibary? Answer: Most of the functions in the library that use resource IDs accept them in the form of an IResourceId. If you provide an unsigned long value for this argument, the resource is loaded from the default resource library set by ICurrentApplication::setUserResourceLibrary. However, you can explicitly create the IResourceId to refer to a specific resource library. For example, textctl.setText( IResourceId( 1000, IDynamicLinkLibrary("mydll") ) ); would set the text to the value of resource ID 1000 from mydll.dll. ═══ 10.12.6. How Can I Specify a New-Line Character in a Resource File? ═══ Question: When I call myResLib.loadString for my message, it doesn't remove the new-line (\n) characters from the message string in my string table. Should I specify new-line characters a different way? Answer: This is the usual behavior of the PM WinLoadString function, which is used to load the string. To get around it, use \x0d instead of \n in the string in the resource file. ═══ 10.12.7. How Does the Library Choose between Multiple Icons? ═══ Question: When there is more than one version or size of a bitmap or icon, what rules are used to choose which one is displayed? Answer: PM chooses the icon or bitmap whose format most closely matches the target display device. When you design your icons and bitmaps, you should provide and test formats for each anticipated target display device. ═══ 10.12.8. Can I Dynamically Load Bitmaps and Icons That Aren't Resources? ═══ Question: Can I dynamically load and display a bitmap or icon file dynamically without binding it as a resource? Answer: You can use the PM function WinLoadFileIcon to load icons. In Version 3.0 of the class library, you can also use the IGBitmap class to dynamically load bitmaps and icons, as well as other image file types such as .GIF. The DMSAMP.CPP file in the DRAG2 sample (shipped with VisualAge C++) contains code that loads a bitmap from an icon. ═══ 10.12.9. How Do I Load a String Resource into an IString? ═══ Question: How do I load a string resource into an IString object? Answer: Use the IApplication::current().userResourceLibrary().loadString function and pass it the ID of the string resource. For example: IString string; string = IApplication::current().userResourceLibrary().loadString(id); ═══ 10.13. Threads ═══ This section discusses how to use the IThread class and related topics.  Is There an Example of Using IThread and IThreadFn?  How Do I Start A Thread?  When Is a PM Message Queue Allocated for a Thread?  How Do I Open a Window on a Secondary Thread?  Can I Create Child Windows in Another Thread? ═══ 10.13.1. Is There an Example of Using IThread and IThreadFn? ═══ Question: Is there an example that shows how to use IThread and IThreadFn? Answer: The MLE sample program shipped with VisualAge C++ shows a simple example using of IThread and IThreadFn. In addition, OS/2 C++ Class Library: Power GUI Programming with C Set ++ contains extensive discussion and examples for IThread and related functions. For more information about this book, see What Documentation Exists for the User Interface Classes?. ═══ 10.13.2. How Do I Start A Thread? ═══ Question: I want to start an asynchronous thread using a function and an unsigned long argument. How do I start it? Answer: There are several ways to start a thread, depending on what you want to use it for. First decide what code you want to run in the separate thread. 1. If the code matches any of the following, you can use the IThread::start function directly: a. A given member function of a given object (with return type void and no arguments): // MyClass has member function void foo() IThread thread; thread.start( new IThreadMemberFn< MyClass >( *this, foo ) ) b. A _System function accepting an unsigned long argument (that you would start with DosCreateThread in C): // non-member function declared as void _System foo( unsigned long arg); IThread thread; thread.start( foo ); c. An _Optlink function accepting a void* argument (that you would start with _beginthread in C): // non-member function declared as void _Optlink foo( void* arg); IThread thread; thread.start( foo ); 2. If the code you want to run does not match the above descriptions, you must write a "wrapper" using one of the following two techniques: a. Derive from IThreadFn and implement run to call the desired member function of your object. This may be difficult because you typically need to know what additional arguments to pass, and this technique provides no way to address that requirement. However, it can be more syntactically elegant: aThread.start(this) instead of aThread.start( new IThreadMemberFn(*this, T::foobar) ). Also, the lifetime of this must transcend the lifetime of the thread. b. Derive a new class from IThreadFn. Add data members for arguments to the function you want to run, including the object that a member function might be invoked on. Add a constructor that accepts references to the arguments as its arguments. Finally, implement run to dispatch the function you want with the provided arguments. The MLE sample program shipped with VisualAge C++ provides an example of this technique. You could alternatively implement the function you want to run within MyThreadFn::run. The IThreadFn object is reference-counted so that it can be automatically deleted when the thread terminates. As a result, if you use IThreadFn or a derived class, you must allocate the IThreadFn using the new operator and you must not delete it. ═══ 10.13.3. When Is a PM Message Queue Allocated for a Thread? ═══ Question: When I create a thread with IThread, does PM automatically allocate a message queue for it? If not, when and how does a message queue get allocated? Answer: When you start your thread, if you set the second argument (autoInitPM) to TRUE, it automatically calls initializePM, which in turn calls WinInitialize and WinCreateMsgQueue to create a message queue. If you set autoInitPM to FALSE, the thread initializes PM when it first creates a frame window (or certain other types of window such as IFileDialog). It does not create a message queue. If your thread will be sending PM messages, it requires a message queue. Because many User Interface class library functions interact with controls by sending messages, it is recommended you create a message queue for your thread if it deals with any IWindow objects. ═══ 10.13.4. How Do I Open a Window on a Secondary Thread? ═══ Question: My application starts a secondary thread that instantiates a window (inherited from IFrameWindow) to run in that thread. However, after the window is constructed, the function in the second thread ends and therefore ends the thread. The window only appears momentarily. How can I prevent the thread from ending before the user closes the window for that thread? Answer: If you open a window on a thread, you must process the PM window messages for that thread's message queue. The message processing loop keeps your thread running while simultaneously handling the messages sent to your window. You can call IThread::current().processMsgs to enter an event loop for the thread. This is the same as calling IApplication::current().run in your main thread. The event loop will normally be terminated when the last primary window on the thread is closed. Another alternative is to show the frame window modally by using showModally instead of show. For a frame without an owner, showModally is equivalent to showing the frame using show followed by the message processing loop. ═══ 10.13.5. Can I Create Child Windows in Another Thread? ═══ Question: Can I create the child windows of the frame window in another thread? Answer: No. PM window parentage and ownership cannot cross thread boundaries. ═══ 10.14. Fonts and Colors ═══ This section discusses the usage of the IFont and IColor classes.  How Can I Detect Font or Color Changes in a Window?  How Can I Save Font Information?  Why Do I Get an Incorrect Font? ═══ 10.14.1. How Can I Detect Font or Color Changes in a Window? ═══ Question: Is there a way to determine when the font or color of a window has changed as the result of a drag/drop operation? Answer: When the font or color changes, the IWindow dispatcher calls the virtual function IWindow::setLayoutDistorted. The first argument to this function is the set of flags that are being turned on (values defined in the IWindow::Layout enumeration). The second argument is the set of flags being turned off. This function is the basic notification vehicle for telling a canvas that the look of one of its child windows has changed. To detect font changes, you can provide your own implementation of setLayoutDistorted in a derived class. Check the first argument, which is IWindow::fontChanged for a font change and IWindow::colorChanged for a color change, and then call the base class version of setLayoutDistorted, passing the original arguments. ═══ 10.14.2. How Can I Save Font Information? ═══ Question: Is there a way to save the current font and font attributes from an IFont object into a .INI file, so that it can be recalled the next time the program is started? Answer: The font face name and size should uniquely identify the font you are using. You may also want to save the emphasis information so that the font will look identical in the future. The HELLO6 sample program shipped with VisualAge C++ gives an example of how to save and restore the font facename and size to a profile. Look at the readProfile and updateProfile member functions in that sample. ═══ 10.14.3. Why Do I Get an Incorrect Font? ═══ Question: I want to use Helvetica bold font with point size 8. However, I always get the Courier font instead. Why? Answer: Make sure you specify the font name as the first parameter of the IFont constructor, and that it is in the correct case. Because font names in OS/2 are case-sensitive, "Helvetica" uses the correct Helvetica font, but "helvetica" does not. If the font cannot be found, the class library defaults to Courier. ═══ 10.15. Font and File Dialogs ═══ This section discusses the classes IFontDialog and IFileDialog as well as the related classes.  How Do I Customize Help for IFileDialog?  Why Doesn't IFileDialog Find the Files?  Can I Customize the File and Font Dialogs? ═══ 10.15.1. How Do I Customize Help for IFileDialog? ═══ Question: I have set the helpButton style on a IFileDialog. How do I show my help for the button? Do I have to trap the WM_HELP message? What is the ID of the Help button? Answer: The simplest approach for contextual help support in the file dialog is to create a HELPTABLE and HELPSUBTABLE in a resource file. Use the IDs that are defined by PM for the various fields in the subtable to identify the contextual help panels. The control IDs for the file dialog are in after the following: /*----------------------------------------- /* File Dialog - dialog and control IDs /*----------------------------------------- #define DID_FILE_DIALOG 256 You can use a similar approach for IFontDialog help. The font dialog control IDs are also in after the following: /**********************************************************************/ /* font dialog and control id's */ /**********************************************************************/ #define DID_FONT_DIALOG 300 You can also read about the IDs in the Presentation Manager Guide and Reference. ═══ 10.15.2. Why Doesn't IFileDialog Find the Files? ═══ Question: I created an instance of IFileDialog and used setInitialFileType to set the type to "*.CPP". There are files with that extension in the directory, but the Files list box never contains any files. Why? Answer: The setInitialFileType specifies an extended attribute type, not the file extension. Allow the EA type to default to all file types, and use setFileName("*.CPP") instead of setInitialFileType. ═══ 10.15.3. Can I Customize the File and Font Dialogs? ═══ Question: I want to alter the field prompts on the file dialog, but IFileDialog only supports changes to the title. Can I change the prompts? Answer: You can specify your own dialog template using IFileDialog::Settings::setDialogTemplate or IFontDialog::Settings::setDialogTemplate. The &pmref. describes how to define this dialog template. You do have to use specific constants for control IDs when defining your dialog template. These are also described in the &pmref.. ═══ 10.16. Direct Manipulation (Drag and Drop) ═══ This section discusses the IDM classes and related problems with drag and drop operations.  How Do I Control Drag and Drop between Containers?  Can I Add Drag and Drop Support to an IComboBox? ═══ 10.16.1. How Do I Control Drag and Drop between Containers? ═══ Question: I have two containers with different objects. How can I control what objects can be dragged from one container to the other, or from one object to another? What classes or functions should I use? Answer: By default, you can move any container object to any container window in your application. To restrict or enhance this level of function, you need to write your own IDMCnrItem-derived class. In your constructors, set the attributes of your derived item class (type, operations, and rendering mechanisms and formats). You must also create a corresponding IDMItemProvider that works with your item class. In most cases, you can use the IDMItemProviderFor class template to generate that class. The basic framework moves the container objects to the container controls for you. If the action of dragging and/or dropping requires some application-specific action to take place, you must also override the targetDrop function. You may find the Drag and Drop samples (DRAG1 through DRAG4) useful in demonstrating how to enable this support. ═══ 10.16.2. Can I Add Drag and Drop Support to an IComboBox? ═══ Question: I tried to add drag and drop support for an IComboBox control, but it didn't work. Are there restrictions on direct manipulation of combo boxes? Answer: IComboBox is a composite control. You have to use PM to enumerate the children of the combo box for its contained list box and entry field, depending on the support you require. Use the obtained PM window handles to create an IListbox and IEntryfield. Then you can add drag and drop support to the new class objects. ISpinButton requires similar support; you must add drag support to the entry field contained in the spin button. ═══ 10.17. Dynamic Data Exchange (DDE) ═══ This section discusses the Dynamic Data Exchange (DDE) classes.  What DDE Formats Are Supported? ═══ 10.17.1. What DDE Formats Are Supported? ═══ Question: Is there a list of valid DDE data formats and what they represent? Answer: The information on the IDDE class in the Open Class Library Reference defines a number of IDDE:Format values. These values represent the predefined DDE formats defined in the header file . (The formats in the header file all begin with SZFMT_). Applications can also define their own DDE formats. If you want to use a format specific to a particular application, check the documentation for the application you want to communicate with for a description of the format. ═══ 10.18. Exceptions ═══ This section discusses exceptions and exception handling.  What Happens to Exceptions on Secondary Threads?  Why Does My Program Just Exit? ═══ 10.18.1. What Happens to Exceptions on Secondary Threads? ═══ Question: I throw an exception in a secondary thread that I started. What happens if I do not catch that exception in the function I started on that thread? Answer: If you started the thread with either _beginthread or the IThread class, the exception is handled as though it were the main thread: an uncaught exception on a thread calls terminate, which then calls abort. To avoid termination, put a try/catch block around the code in the secondary thread. If you started the thread with DosCreateThread and did not register the C++ OS/2 exception handler, the C++ exception is treated as though it never occurred. ═══ 10.18.2. Why Does My Program Just Exit? ═══ Question: When I run the program I wrote with the User Interface classes, it simply ends. Why? Answer: What is probably happening is that an exception is being thrown and not being caught, causing the program to terminate. To learn more about the exception, use the User Interface library trace facility. Before running your program, enter: SET ICLUI TRACE=ON SET ICLUI TRACETO=STDERR (You could also set this in your CONFIG.SYS file.) When you run your program, redirect stderr to a file, for example: myprog 2> myprog.out After the program ends, look at myprog.out to see if exceptions were logged. For more information about the ITrace class, see the Open Class Library Reference. ═══ 10.19. Internationalization ═══ This section discusses internationalization and national language support topics.  Are There Restrictions on National Language Support?  Can I Change Text on Message Box Buttons? ═══ 10.19.1. Are There Restrictions on National Language Support? ═══ Question: Are there limitations of the national language support provided in the User Interface class library? If so, what are they? Answer: The known limitations of the class library support for national language enablement are:  There is no multiple code page support for IFont.  The following functions do not account for double-byte characters (DBCS): - IString::isLike - IFont::textWidth - IFont::charWidth - IFont::textLines  All messages retrieved from the message file are in English. (In the Japanese version of VisualAge C++, they are in Japanese.) These messages are used primarily for exception text for library-specific errors; the class library retrieves other error messages from OS/2 and PM. If you want to translate the message file into another language, the source is available in the Class Library Source Code product. However, the end user would not see this text unless you display exception text in message boxes, or do something similar. ═══ 10.19.2. Can I Change Text on Message Box Buttons? ═══ Question: I'm developing a bilingual application. I need the buttons to say OUI/NON instead of YES/NO. Is there a way to change the text on IMessageBox buttons? Answer: The PM message box does not support this, and displays the buttons using the language that was specified during system setup. However, you could replace the message box with a modal IFrameWindow with an ISetCanvas containing the buttons as a frame extension. Then you could easily change the text. ═══ 10.20. Extending the User Interface Class Library ═══ This section discusses how you can extend the User Interface class library.  How Can I Create a New IWindow Class?  Can I Use PM Window Words in My Application? ═══ 10.20.1. How Can I Create a New IWindow Class? ═══ Question: I want to create a new IWindow class that is not based on one of the existing IWindow or derived classes. The new class must work with a new handler class that processes new message types. What should I do? Answer: Assuming you have a unique PM window class, you need to create the windows and interact with them using C++: 1. Derive from IWindow. 2. Add constructors for your window class. The constructor arguments should correspond roughly to the values your PM window class takes for its style and ctldata parameters to WinCreateWindow or WM_CREATE. 3. In your constructor, or in a common intialization function called from each of your constructors, construct the appropriate arguments and call WinCreateWindow or IWindow::create. Your constructors should use the default constructor to construct the IWindow base class. 4. After you have the hwnd value returned by WinCreateWindow or IWindow::create, call IWindow::startHandlingEventsFor. 5. At this point, you can add handlers to your window. If you choose to implement your window procedure as an IHandler, add the handler here. You can have a separate handler for each window, or one that works for all windows. If you choose the latter, use a static member of your IWindow-derived class to point to the shared handler (refer to IFrameWindow for an example). Note that, if your PM window class includes handler functions in its window procedure, you don't need a handler. 6. Add member functions to your derived IWindow class that correspond to the window messages your PM window responds to. If your window class is a conventional PM class, you can implement those functions by sending the appropriate PM message (IEvent). 7. If your window class is tailorable, meaning it sends notifications or allows users to override the message handling, declare an IHandler-derived class with an appropriate implementation of dispatchHandlerEvent. Typically, you would add virtual functions for the notifications and events that can be handled. You can also choose to derive specialized IEvent classes specific to the events required. 8. If you are implementing the base window behavior as a handler (as mentioned in step 5) rather than a PM window procedure, separate that handler from the one described at step 7. The latter should ideally have empty implementations of all virtual functions. Users inherit the built-in behavior of your primary handler in the base window by the chaining done by the IWindow dispatching, not through C++ inheritance. The inheritance diagram would look like: This method is recommended because you are adding your handler automatically at construction; if a user derives from the class without knowing this, he or she could invoke your handler's code a second time by calling Inherited::virtFunc. 9. Override inherited IWindow functions as required for your window. Often you do not need to override any functions. If you are interested in additional information, the September/October 1994 issue of OS/2 Developer magazine also has several articles relating to this topic. ═══ 10.20.2. Can I Use PM Window Words in My Application? ═══ Question: Does the User Interface class library use the window words provided in PM controls? Can I use the user words in my application, or will that cause a conflict? Answer: The User Interface class library does not use the QWL_USER value. Currently, it does not register any new window classes either. You can use the window words as described in the Presentation Manager Guide and Reference. ═══ 10.21. Graphics and Drawing ═══ This section discusses using graphics and customized drawing with the User Interface classes.  Are There Graphics Classes?  How Can I Implement Customized Graphics? ═══ 10.21.1. Are There Graphics Classes? ═══ Question: Which class implements the Gpi functions such as GpiLine, GpiPolyline, and GpiBox? Answer: The latest version of the User Interface class library now includes a set of 2-D graphics classes. See the Open Class Library User's Guide and Open Class Library Reference for more details on these classes and how to use them. ═══ 10.21.2. How Can I Implement Customized Graphics? ═══ Question: Can I put graphics in the client area of a window? How? Answer: Yes, you can. Use the samples 2D-BMAP, 2D-DRAW, and BIGCPP as a starting point. The HELLO6 sample also provides some example code that may be helpful. (All of these samples are included with VisualAge C++.) ═══ 11. Creating Portable Applications ═══ This section discusses questions you might have about the portability of the applications you create with VisualAge C++.  What Operating Systems Does It Support?  What Are General Guidelines for Creating Portable Applications?  Are There Guidelines for Portable User Interface Applications? ═══ 11.1. What Are General Guidelines for Creating Portable Applications? ═══ Question: I am writing an application that I want to be able to compile and run on other platforms. Are there any guidelines I should follow? Answer: To ensure the greatest degree of portability between operating system platforms, use only language and library extensions that are defined in current language standards such as ANSI/ISO and POSIX. If you are using VisualAge C++ on the other platforms, there may be extensions that are common. Refer to the language and library references. If you use language elements that are implementation-defined (as described in the appendix about implementation-defined values in the Language Reference), try to code them in a way that will require less effort to port to platforms with different implementations. For example, in you require a variable to be exactly 32 bits in length, don't use unsigned int or unsigned long. A better solution is to use a typedef to create a new type synonym, and then use that new type name instead. Then when you port to a different platform, you only have to modify the typedef. If you are creating a C++ program, another way to increase portability is to use the class libraries. Because they provide a layer of abstraction above the operating system, the code you write can be less system-specific. The VisualAge C++ class libraries are currently available on the OS/2, AIX, and Solaris operating systems. Note: Some classes in the class libraries differ slightly from platform to platform, particularly the User Interface classes which must encapsulate very different system interfaces. The Open Class Library Reference includes portability information on each function, and platform-specific notes where applicable. For tips on creating portable applications with the User Interface class library, see Are There Guidelines for Portable User Interface Applications?. ═══ 11.2. Are There Guidelines for Portable User Interface Applications? ═══ Question: I am writing an application that uses the User Interface classes, and I want to port it to other platforms. Are there any guidelines I should follow? Answer: The User Interface class library was designed to be as portable as possible to all supported environments, which currently include OS/2 (PM), AIX (Motif), and Solaris (Motif). Most of the classes are supported in all of the environments, but there are some unavoidable system differences. When you design your application for portability, you should consider the following differences that exist between the PM and Motif versions of the library:  Motif does not support DLGTEMPLATE resources or the ability to construct an IFrameWindow from a DLGTEMPLATE resource. Use the ICanvas classes to create portable dialogs.  Motif does not support Dynamic Data Exchange (the IDDE* classes).  The current Motif version does not support direct manipulation (IDM* classes).  Motif does not support the owner-draw classes (IDrawItemEvent and derived classes) or the handlers that process owner-draw events. To do customized drawing in Motif, write a handler class to capture the appropriate X events and use the X functions to do the drawing.  Motif does not support the IDynamicLinkLibrary class. To change the resource library being used, you must change the locale (the LANG environment variable and related settings) in the calling environment.  Motif does not support ISystemBitmapHandle or ISystemPointerHandle, or the IBitmapControl and IIconControl constructors that use ISystemBitmapHandle::Identifier and ISystemPointerHandle::Identifier as parameters.  Because Motif does not provide a scheme palette, the IGUIColor class returns hard-coded color values. It is probably best to avoid color manipulation where possible and use the default values provided by the user in the OS/2 scheme palette or X resource database.  Under Motif, IThread does not support the creation or starting of new threads.  Use ISetCanvas to create group boxes instead of the IGroupBox class for better portability. There are other classes and functions that are not provided in all environments or have no effect in some environments. Refer to the Open Class Library Reference for details on the support for each class and function. VisualAge C++ also provides macros you can use to conditionally include platform-dependent code. In the PM environment (VisualAge C++ for OS/2), the IC_PM macro is defined. In the Motif environment (C Set ++ for AIX and Solaris), the IC_MOTIF macro is defined. ═══ 12. Compiling and Compiler Errors ═══ This section includes questions about using the compiler, as well as questions about compiler errors and why they occur.  Can I Use an Object File in Both an EXE and a DLL?  How Can I Improve Compiler Performance?  Can the Compiler Generate Assembler Listings?  Can I Compile C Files as C++ Files?  Why Doesn't My _beginthread Function Have _Optlink Linkage by Default?  Why Can't I Increment a Cast Value?  Why Are My Structures Incomplete or Mismatched?  How Should I Compile DB2 Applications?  Can I Inherit Static Data Members?  How Does the /Sn Option Enable DBCS Support?  Why Does My Part Get Compiler Errors? ═══ 12.1. Can I Use an Object File in Both an EXE and a DLL? ═══ Question: How do I use VisualAge C++ to compile a library .OBJ file that can be linked into either an .EXE or a .DLL file? Answer: To compile objects that can be linked into an .EXE or DLL file, use the /Ge+ option. These objects must not contain a main function. To link in the correct startup code for a .EXE, compile the source file that contains the main function using the /Ge+ option. To link in the correct startup code for a .DLL, compile one of the source files, preferably the one that contains your _DLL_InitTerm function, with the /Ge- option. /Ge+generates a reference to _exeentry only if a main function is seen. /Ge- always generates a reference to _dllentry. ═══ 12.2. How Can I Improve Compiler Performance? ═══ Question: What can I do to get faster compile times? Answer: You can speed up some compilations by doing one or all of the following:  Check the settings of your environment variables (LIBPATH, PATH, INCLUDE, and so on). If the VisualAge C++ directories are at the end of the paths, the compiler or linker must search through all of the other directories before it finds the required DLLs, header files, and so on.  Ensure you preload the compiler with the /Tl option (it is preloaded by default). This option keeps the compiler components loaded in memory.  If you are statically linking to the VisualAge C++ libraries, try dynamic linking.  Use precompiled header files. See the User's Guide for details on how to structure your files so the compiler can take full advantage of the precompiled headers.  Turn off all optimizations.  If you are using the HPFS file system, set the IFS statement in your CONFIG.SYS file to: IFS=c:\os2\hpfs.ifs /cache:2048 /crecl:64 This allows OS/2 to keep frequently-used files in memory between compilations, which can greatly reduce the amount of time it requires the compiler to process them. Note: If you experience performance problems and have LAN Requester 4.0 installed, doublecheck this statement in your CONFIG.SYS. Some users have reported that LAN Requester 4.0 changes these settings and severely degrades performance.  Add more RAM to your system. In a memory-constrained environment, OS/2 spends more time swapping data to disk, which increases compilation times tremendously. Additional RAM allows the compiler and its data to remain in memory, which can make your builds run more quickly.  Define a virtual disk (VDISK or RAM disk) with the OS/2 VDISK device driver. It creates a disk from your system's RAM. For example, placing this statement in your CONFIG.SYS file: DEVICE=C:\OS2\VDISK.SYS 2048,,256 allocates a virtual disk of 2M with 256 directory entries. You can then copy all the library and header files you need for compiling and linking to this disk and set your LIB and INCLUDE variables to point to it before any other directory. Because these files are already in memory, your compile and link times will improve. (Do not put the compiler executable files there; the compiler preloads them into memory by default, so you would then have two copies in memory.) However, there are drawbacks to using a VDISK. The data on it is lost when you reboot. Also, memory allocated for a VDISK is unswappable memory space, meaning it cannot be used as normal memory space by applications. Because C++ compilations can require a lot of memory, putting memory into a VDISK could cause excessive memory paging and slow down your compilation. If you have a large VDISK, it may be to your advantage to eliminate it or make it smaller to provide more available memory to the compiler  Minimize dependencies between source files so that changes made to one file don't require you to recompile all your files.  For C++ programs specifically: - Create abstract objects that contain only public methods and make your real objects descendents of those objects. This approach reduces the amount of total source code in compiles that do not need to refer to the real objects. - In .HPP files, only include those files you need. For example, if all that the .HPP file for class b needs to know about class a is that class b contains a pointer to a class a-type object, put class a;, (rather than #include "a.hpp") in B.HPP. Note: This approach would still allow you to put #include "a.hpp" in B.CPP. ═══ 12.3. Can the Compiler Generate Assembler Listings? ═══ Question: Is there a compiler option that provides instruction offsets in conjunction with the assembler code? Answer: There is no compiler option that provides instruction offsets with assembler code. You can usually process the output of the /Fa+ option with a 32-bit assembler. However, because of the nature of some assemblers, output of /Fa+ that is processed with an assembler may not be identical to the compiler output. Also, certain problems, such as variables that have the same name as assembler statements, may prevent the code from assembling. You can also link with the /L option to get line offsets. refdi=xlat. Use the VisualAge C++ Debugger to look at the actual object code. ═══ 12.4. Can I Compile C Files as C++ Files? ═══ Question: How do I compile an application written in C as if it were C++ on the VisualAge C++ compiler? I have tried to do this, but I get syntax errors in some header files. Answer: VisualAge C++ allows you to mix your C- and C++-compiled modules freely as long as you use extern "C" { ... } with any prototypes of functions that are called by C code or are compiled as C code. Note that there are some language elements, such as #pragma linkage directives, that are valid in C but not in C++. If your C source files contain these language elements, you cannot compile them as C++. For complete information on C and C++ compatibility, see the Language Reference. ═══ 12.5. Why Doesn't My _beginthread Function Have _Optlink Linkage by Default? ═══ Question: I am using _beginthread to start a thread, and passing func as the function to run in that thread. I declared func as static void func(void). The compiler says that it cannot convert func to _Optlink. I thought that _Optlink was the default calling convention. Why am I getting this error? Answer: If you are compiling a C program, _Optlink is the default calling convention. Make sure you did not change the default with one of the /M compiler options. If you are compiling a C++ program, the default calling convention is a bit different. C++ functions have C++ linkage. They use the _Optlink model of parameter passing, but they also have mangled names and can be overloaded. You need to tell the compiler that func is a C function by declaring it as extern "C", extern "OPTLINK", or _Optlink. For example, instead of declaring your C++ function as: void cpp_func(void *); use one of the following declarations: extern "OPTLINK" void cpp_func(void *); void _Optlink cpp_func(void *); extern "C" void cpp_func(void *); // if _Optlink is the default // calling convention (/Mp option) You cannot specify a member function for _beginthread. ═══ 12.6. Why Can't I Increment a Cast Value? ═══ Question: Why does the compiler generate an error message for the statement ((long *)fred)++ ? Answer: There are two problems with this expression. First, the operand of the increment operator (++) must be an lvalue. Because a cast does not produce an lvalue, the statement above is not valid and the compiler generates an error for it. Operators that must have lvalue operands include the increment and decrement operators ++ and --, as well as the simple and compound assignment operators. The following statement yields and increments an lvalue: (*(long **)&fred)++ For more information on casting and lvalues, see the Language Reference. The other and more important problem is that you are trying to reference a value using a type other than the one it was defined with. According to the ANSI/ISO standard, you can only reference an object using its declared type (with some minor changes such as qualifiers) or a char *. The example statement may or may not work, but is not guaranteed to work. Change your code to reference the value either using its declared type or a char *. ═══ 12.7. Why Are My Structures Incomplete or Mismatched? ═══ Question: The compiler says that the argument I am passing to a function doesn't match its prototype, but both have the same structure type: int func(struct st *); struct st {int s1;} ss; func(&ss); Why? Answer: A structure declaration must appear before any function prototype statements that use that structure type. GIven the above declaration for func: int func(struct st *); a b At point (a), a new scope is opened for the identifiers that are introduced in the parameter list. At point (b), this scope is closed. Every identifier that appears betweeen (a) and (b) is local to that scope, and nothing outside is allowed to look inside. The declaration of struct st occurs outside the scope of the function declaration for func. Therefore, this struct st is different from the struct st is the function prototype. As a result, struct st is incomplete when it is introduced in the parameter list of func, and can never be completed because its scope is closed to everything outside. Only a void pointer could be passed to func. To solve this problem, declare the structure before you use it in the the function prototype. For example: struct st; int f(struct st *); struct st {int s1;} ss; f(&ss); Declaring the tag at file scope causes all subsequent occurrences of struct st to resolve to the same file scope identifier, and everything works correctly. ═══ 12.8. How Should I Compile DB2 Applications? ═══ Question: What compiler options do I need to set to create a DB2 application using VisualAge C++? Answer: The easiest way to build DB2 applications is to use Data Access Builder to create classes to access the DB2/2 database, and code your program in C++. Using these classes may eliminate the need to call DB2/2 APIs directly. You can then use whatever compiler options are appropriate. If you are calling DB2/2 APIs directly, compile with the /DES32TO16 option (defines ES32TO16) and link with the SQL_DYN.LIB library. You also need to add the SQLLIB directory to the INCLUDE and LIB paths in your CONFIG.SYS file. ═══ 12.9. Can I Inherit Static Data Members? ═══ Question: The following code gives very different warnings and errors when compiled with different compilers: class a { public: static int sm; }; int a::sm = 1; class b: public a { }; int b::sm = 2; I want all classes that inherit from a to have a member sm that has a unique value for the class. However, VisualAge C++ generates the following error message: 13 |int b::sm = 2; test.cpp(13:1) : error EDC3079: "sm" is not a member of "b". Does this mean that static members are not inherited in C++? Answer: There is only one instance of a static member called sm in your program, and it belongs to class a. Class b does not contain a separate copy of sm, it inherits a::sm. Because class b does not contain its own copy of sm, it does not allow: int b::sm = 2; However, used in an expression, a::sm and b::sm refer to the same piece of storage. If you want class b to have its own copy of sm, declare it as a static member, which then hides a::sm. ═══ 12.10. How Does the /Sn Option Enable DBCS Support? ═══ Question: According to the compiler documentation, the /Sn compiler option allows you to use the double-byte character set (DBCS). What does that mean? Answer: Use /Sn to tell the compiler whether you use double-byte characters in your source code. It specifies whether characters in the DBCS first-byte range should be interpreted as single-byte characters (/Sn-) or as the first half of a double-byte character (/Sn+). DBCS support is always included in the runtime library and VisualAge C++-generated code, regardless of compiler options. If you are interested in DBCS support because you want to translate or tailor your applications for other countries or languages, see the information on locales and internationalization, beginning with "Introduction to Locale" in the Programming Guide. ═══ 13. Linking and Linker Errors ═══ The questions in this section deal with how to link your program and with errors that occur at link time.  Which Libraries Should I Link To?  How Can I Improve Linker Performance?  Why Are My Symbols Defined More than Once?  When Should I Use the /NOD Option?  Why Do I Get Unresolved External Errors?  Why Does errno Cause an Undefined External Error?  Why Does Inlining Cause Unresolved External Errors?  Why Does My Part Get Unresolved External Link Errors?  Why Is My virtual-fcn-table-ptr Unresolved?  Why Does /Ti Cause Unresolved External References? ═══ 13.1. Which Libraries Should I Link To? ═══ Question: What libraries should I use to link? Answer: You do not have to specify any libraries for the link step. Information about the required libraries, including the IBM Open Class library files, is imbedded in your .OBJ files by the compiler according to the compiler options you use and the #pragma library statements in the header files you include. If you mix objects compiled with different runtime options (for example, one multithread and the other single-thread), the linker generates duplicate definition errors, as described in Why Are My Symbols Defined More than Once?. There are compiler and linker options to exclude or ignore the information about the default libraries (/Gn and /NOD), but we do not recommend you use them. If you specify these options, you must then explicitly specify the names of all required libraries to the linker. If the libraries specified are incorrect, your program may not link or run correctly. For details on the names of the library files, see What Are the Library Naming Conventions?. ═══ 13.2. How Can I Improve Linker Performance? ═══ Question: What can I do to get faster link times? Answer: To improve linker performance:  Invoke the linker using icc to preload it in memory.  Use the /NOEXEPACK option.  Use the /NODEBUG option if you don't need debug information. If you do need debug information, use /DBGPACK.  Do not use the /NOE or /SEG options unless a linker error message tells you to.  Convert old .LIB files to the new format: ILIB /CONV /NOBR xxx.LIB; or for import libraries: ILIB /CONV /NOBR /NOE xxx.LIB;  Do not use the /OPTFUNC option. To improve your application load time:  Use the /EXEPACK option, or /EXEPACK if you are targetting only OS/2 Warp systems.  Use the /OPTFUNC option. ═══ 13.3. Why Are My Symbols Defined More than Once? ═══ Question: When I link my object modules, the linker says that some symbols are defined more than once. The symbols are not in my source code, so I think they're in the runtime library. Why do I get this error? Answer: You are probably linking objects that were compiled with incompatible compiler options. For example, if you compile one module as a multithread program (/Gm+) and another as single-thread (/Gm-), each will include a reference to a different runtime library. At link time, both libraries are linked in, resulting in symbols being defined more than once. Recompile your objects using the same compiler options. ═══ 13.4. When Should I Use the /NOD Option? ═══ Question: When should I use the /NOD linker option? Answer: We do not recommend the /NOD option for general use. It tells the linker to ignore the library information included in the object modules being linked and to link in only the libraries you explicitly specify in the linker command. You must then make sure that the libraries you link in match the libraries that the object modules were compiled for. For example, if the objects were compiled as multithread objects (/Gm+ compiler option) and you use /NOD to override the specified library and link them with the single-thread library, the linker will have unresolved references. However, if you have created your own runtime libraries, you need to use /NOD and specify the names of your libraries and OS2386.LIB. You can use /NOD in other situations, but be careful that you specify the correct libraries. Note also that using the /Gn+ compiler option suppresses the default library information in the object module itself. Using this compiler option has the same effect as using the /NOD linker option; you must then explicitly specify the correct libraries in the linker command. ═══ 13.5. Why Do I Get Unresolved External Errors? ═══ Question: Why is the linker generating unresolved external errors? Answer: The linker probably cannot find the libraries it needs to construct the executable module. Make sure that you specify all necessary libraries when you invoke the linker. The correct VisualAge C++ libraries are linked in by default, unless you use the /Gn compiler option or the /NOD linker option. Avoid using the /Gn compiler option and /NOD linker option. The /Gn option suppresses information about the default libraries from the linker; the /NOD option causes the linker to ignore the default libraries. When you use these options, you must specify on the command line all libraries you use, both directly and indirectly. For details about what libraries you need to include, see Which Libraries Should I Link To?. This error can also occur if you do not specify your functions and variables using consistent case. For example, if you define a function as UPPERCASE, but then call it as uppercase. Because the C language is case sensitive, the linker is also case sensitive by default. You can specify the /IGNORECASE linker option to make it case insensitive, but because the VisualAge C++ libraries and most other C code are case sensitive, you may encounter more problems at a later time. Change your code so that the case of the function name is the same in the definition and in the call. Another possibility is that you are compiling code with templates using the /Ft+ option. When you use this method of handling templates, additional source files are created in your TEMPINC directory. Before you link, you must compile these files as well, and any additional files that are created when you compile the TEMPINC files. For this reason, use icc to invoke the linker for you to ensure that all TEMPINC files are compiled before the link step. For more details on templates and how to use them, see the sections on templates in the Language Reference and the Programming Guide. ═══ 13.6. Why Does errno Cause an Undefined External Error? ═══ Question: Why do I get an undefined external error for errno? Answer: When you use the single-thread library, there is only one instance of errno, and it is defined as an extern int. However, when you use the multithread library, there is a separate instance of errno for each thread, so it is defined differently than it is in the single-thread library. Ensure you:  Always include . It defines errno appropriately for either the single-thread or multithread library (depending on the compiler option you specify).  Compile and link all modules with the same library. If you compile module A with the single-thread library and module B with the multithread library, linking both together with either library generates an unresolved external error. ═══ 13.7. Why Does Inlining Cause Unresolved External Errors? ═══ Question: I am trying to inline one of my functions, but I keep getting an "external not found" error in another module. Why? Answer: Inline functions are only available in the source file in which they are defined. (They are conceptually similar to static functions with regard to scoping.) If you have defined the inline function in one module, it is not available to your other modules, the reason you are getting the "unresolved external". Inlining means you want the compiler to inline the code if possible rather than linking the functions together. The compiler can't inline code that is not available until your modules are linked together. You may want to put your inline function in a .H file and include the .H file in both source files that need the function. Alternatively, you could use the intermediate code linker to link the intermediate files together. ═══ 13.8. Why Is My virtual-fcn-table-ptr Unresolved? ═══ Question: When I link C++ code, I get the following linker error: X::virtual-fcn-table-ptr: undefined symbol What am I doing wrong? Answer: The missing virtual-fcn-table for a class is a compiler-generated data structure that is used to implement virtual function calls for that class. The compiler has to determine which compilation unit it should generate each table in. Often, the compiler generates the table in the compilation that contains your definition of a selected virtual function. If you never define that function, the table is never generated, and this linker error results. If you recompile your code with the /Wvft+ option, the compiler will produce a message similar to: EDC3281: informational The virtual function table for X will be defined where X::foo() is defined. You will find that the function X::foo() has not been defined anywhere. ═══ 13.9. Why Does /Ti Cause Unresolved External References? ═══ Question: When I compile the following program with debug information on: class X { protected: int getI() {return i;} static int i; }; main(int argc, char *argv[]) { X x; return 0; } why does it generate the following linker error? filename.obj(filename.CPP) : error L2029: 'X::i' : unresolved external Answer: When you compile with debug information on, the compiler generates an external reference to X::i so that you can see the value of X::i from any other compilation units that make up your application. If the compiler didn't do this, you would only be able to view the contents of X::i while you are in the source view for the compilation unit in which X::i was defined (where the storage was allocated). The unresolved reference is a result of no storage being allocated for the static data member; it was declared but it was not defined. The compiler does not know if the storage has been allocated in another compilation unit so it can't check if it should put out the reference or not. Therefore, it always puts out references to static data members. Add the following statement, which allocates storage for the data member: int X::i = ; If you never reference the static class variable, the amount of storage available for it is irrelevant. Your program generates the same results whether or not it exists. The same is true for unreferenced class methods. One of the reasons that external references are not included when they are not required is that unreferenced data items may consume page space or address space, but they do not increase the size of the working set, except for a slight effect they may have on page tables. ═══ 14. Runtime Errors ═══ This section contains possible answers to questions that you might have when you run a program. Refer to this section if your program ends abnormally or behaves unexpectedly.  Why Does My DLL Work Incorrectly?  Why Does Adding Entry Points Change Existing Ones?  Why Does My DLL End Abnormally When I Call It?  Can I Allocate and Free Memory across Modules?  Should I Link My DLL to the VisualAge C++ Runtime Library?  Why Don't My String and Memory Functions Work in DLLs?  Why Are My Classes Initialized Multiple Times?  Why Is _DLL_InitTerm Causing Errors?  Why Does a Destructor Exception End My Program?  Why Does a Destructor Corrupt the Object?  Why Does Calling a Destructor Explicitly Cause Errors?  Why Do I Get an Access Violation from the Runtime DLL?  Why Can't I Open a File?  Why Does Optimization Cause My Program to Fail?  Why Doesn't My Old C Code Work?  Why Does One Function Alter Information from Another?  Why Do My Window Procedures End Abnormally?  Why Does Printing a _Seg16 Pointer Trap?  What Is __EDCThunkProlog and Why Does It Trap?  Why Does printf Work Incorrectly?  Why Doesn't printf Output Display?  Why Does scanf Behave Unexpectedly?  Why Do Library Functions and APIs Not Work?  Why Do Macros with Increment Operators Work Incorrectly?  Why Doesn't My Macro Resolve Correctly?  Why Don't My Threads Work Correctly?  Why Do My Statements Have No Effect?  Why Do Increment Operators Produce Unexpected Results?  Why Do I Get a SYS2070 Error?  Why Does My PM Application Disappear?  Why Does My Program Work Incorrectly and Inconsistently?  Where Is the Message File?  Why Doesn't My Application End when the Frame is Closed?  Why Does My Program Just Exit? ═══ 14.1. Why Does My DLL Work Incorrectly? ═══ Question: Why doesn't my DLL work correctly? Answer: You may be mixing objects compiled with the /Ge+ and /Ge- compiler options in the same DLL. The VisualAge C++ libraries provide different initialization routines for executable modules and DLLs. To ensure that the correct initialization routine is run, use the /Ge+ option when you create an executable module and the /Ge- option when you create a DLL. When you link your files, you can override the /Ge option you specified at compile time. See the User's Guide for more information on how to do this. See the Programming Guide for more information on DLLs in general. ═══ 14.2. Why Does Adding Entry Points Change Existing Ones? ═══ Question: I have an existing DLL that exports a number of functions and works correctly. However, when I add more entry points (export additional functions), the DLL no longer works as it did. How can I add entry points without affecting existing ones? Answer: When you add to your DLL, you are probably changing the ordinal numbers associated with your existing entry points. A DLL contains a table that maps names to entry points. An ordinal is an index into this table. The OS/2 loader can resolve entry points by either name or ordinal, depending on what is available. Resolving by ordinal is faster. If you build an import library (.LIB) with ILIB using a .DEF file that does not contain ordinals, the library only provides the loader with names for the entry points. If you create the library from the DLL, both names and ordinals are included, and calls to the DLL are resolved by ordinal. If you do not explicitly associate an ordinal with an entry point, the ordinal could change when you rebuild the DLL. You then need to rebuild all executables and DLLs that reference that DLL or library. To avoid any confusion, you should define both a name and an ordinal for each entry point in one of the following ways: 1. Use #pragma export to specify the name and ordinal for each export. If you do not change the #pragma directives when you rebuild your DLL, the files currently using that DLL do not require rebuilding. 2. List the exports in the .DEF file by name and ordinal. Again, if you do not change the names or ordinals of existing entry points when you rebuild the DLL, the files currently using that DLL do not require relinking. The advantage of using a .DEF file instead of #pragma export is that your exports are defined in one place. Alternatively, include the statement OLD in your .DEF file. This statement tells the linker to maintain the ordinal assignments as used in a previous version of the DLL. If you are making only a few changes to a DLL that is mostly complete, this method is quicker than defining ordinals for all entry points. See the section on Linking in the User's Guide for more information on the OLD statement. ═══ 14.3. Why Does My DLL End Abnormally When I Call It? ═══ Question: My DLL ends abnormally when a second process tries to call it. Why? Answer: It could be that both processes are trying to use the same memory for library data, but only one of them has access. Make sure that you include the following statements in your module definition (.DEF) file:  LIBRARY INITINSTANCE TERMINSTANCE This statement identifies the executable file as a DLL. INITINSTANCE specifies that the _DLL_InitTerm function is called the first time the DLL is loaded for each process that accesses the DLL. TERMINSTANCE specifies that _DLL_InitTerm is called the last time the DLL is loaded for each process that accesses the DLL.  DATA MULTIPLE NONSHARED This statement defines the default attributes for data segments within the DLL. MULTIPLE specifies that there is a separate copy of the data segment for each process that accesses the DLL. NONSHARED specifies that the data segment is not shared by other processes. If you need to share memory between two instances of your DLL, use #pragma data_seg to put the data to be shared in a named data segment. Then put a SEGMENTS statement in the .DEF file for your DLL and specify that segment after it. For more information on DLLs, see Building Dynamic Link Libraries in the Programming Guide. ═══ 14.4. Can I Allocate and Free Memory across Modules? ═══ Question: I allocate memory in one DLL, using new or malloc, and then free it in another DLL. Sometimes this works, but other times it causes my application to trap. Why? Answer: In most cases, you can allocate memory in one module (executable or DLL) and free it in another. (Note that this has changed from the previous release of C Set ++.) However, both modules must be linked to the same copy of the library code. ═══ 14.5. Should I Link My DLL to the VisualAge C++ Runtime Library? ═══ Question: I'm creating a DLL. Do I need to link it to the VisualAge C++ runtime library? Answer: If you are not making any runtime calls out of your DLL, you don't have to do anything else and don't have to link to the runtime library. If you do use the runtime, you have to specify the /Gd+ option when you compile. Otherwise, the VisualAge C++ runtime environment may not be initialized correctly. You will probably need some kind of runtime support because you probably have a static constructor and/or destructor in your DLL. Make sure that you call the __ctordtorInit and __ctordtorTerm routines as described in the Programming Guide. ═══ 14.6. Why Don't My String and Memory Functions Work in DLLs? ═══ Question: I have a problem with the strupr, stricmp, and memicmp library functions when they are used in a DLL. They work if only a single copy of the DLL is loaded, but they return incorrect values when they are used by any additional processes that load another copy of the DLL. The strupr function always returns a zero-length string, and stricmp and memicmp always return equal values for the two strings or blocks of memory. These functions do not exhibit this behavior when they are used in an .EXE file, and multiple copies of the .EXE are running. What is wrong? Answer: Make sure you include LIBRARY libname INITINSTANCE TERMINSTANCE (where libname is the name of your library) in your .DEF file when you create the DLL, ═══ 14.7. Why Are My Classes Initialized Multiple Times? ═══ Question: In my program, my class constructor is called each time my DLL is loaded (called by an executable file), but built-in types are initialized only once. For example: class MYCL .... // global int i1=4711; // only on 1st loading MYCL i2(4711); // each time I specified INITINSTANCE for the DLL. Why does this happen? Answer: Because you are building a shared DLL, you should use INITGLOBAL, not INITINSTANCE. When you specify INITINSTANCE, the library initialization routine is called for each process that uses the DLL. When you use INITGLOBAL, the routine is only called once, when the DLL is loaded. The initialization is calling __ctordtorInit each time, and that is what is initializing your variable each time. Important When you use INITGLOBAL, you must ensure that your DLL does not statically link to the VisualAge C++ runtime library. The VisualAge C++ runtime library must use INITINSTANCE to correctly initialize the runtime environment. Making the runtime library INITGLOBAL could lead to problems as described in Why Does My DLL End Abnormally When I Call It?. ═══ 14.8. Why Is _DLL_InitTerm Causing Errors? ═══ Question: I have some questions about _DLL_InitTerm: 1. Why does my program trap when my _DLL_InitTerm calls printf? 2. The Programming Guide says to register a clean-up routine with DosExitList if I require runtime calls and the runtime library is dynamically linked. I'm using /Gd+ to dynamically link to the runtime, but _DLL_InitTerm is called with ulFlag!=0. Why? 3. The Programming Guide says that I need to call _CRT_Term only if the runtime is statically linked. Does this also apply to __ctordtorTerm? Answer: 1. Your program traps because _DLL_InitTerm has already terminated the runtime environment, so you cannot call any runtime functions (like printf). 2. Specifying TERMINSTANCE in your .DEF file tells OS/2 to call your code when the DLL is terminated or unloaded. 3. You must always call __ctordtorTerm. ═══ 14.9. Why Does a Destructor Exception End My Program? ═══ Question: If an exception is thrown in a constructor of a derived class, and the destructor of the base class also throws an exception, the program terminates immediately. It is not even possible to do an output redirection in this case. Why? Answer: If a destructor that is called during exception handling itself throws an exception, the terminate function is called. This is the way C++ handles double exceptions. If you have not registered your own terminate routine (by using set_terminate), the program is aborted by default. ═══ 14.10. Why Does a Destructor Corrupt the Object? ═══ Question: Why does a destructor corrupt the address of an object in the following example? main(int argc, char *argv[]) { SharedEventSemaphore *phev = new SharedEventSemaphore; phev->~SharedEventSemaphore(); delete phev; return(0); } Answer: According to ANSI, if you call a destructor twice for the same object, the behavior is undefined. In the example, you call the destructor once explicitly, and again as part of the delete processing. Rewrite your program so that the destructor is called only once. ═══ 14.11. Why Does Calling a Destructor Explicitly Cause Errors? ═══ Question: It appears that an explicit call to a destructor sometimes generates an error, and sometimes creates and destroys an object in one step. It never simply calls the destructor. When the destructor is called with a this-> statement, it works properly. Why? Answer: You should not explicitly call destructors on objects. You can use a delete this; statement that calls the destructor, and then free the memory used by the object. Note: This should only be done on dynamically allocated objects, and not in the destructor itself. ═══ 14.12. Why Do I Get an Access Violation from the Runtime DLL? ═══ Question: My program runs fine until the very end of main, and then I get an access violation from the runtime library (DDE4MBS.DLL) after termination. What causes this and how can I fix it? Answer: This behavior is typically caused by problems in the heap, such as writing to unallocated or freed memory. To find the problem: 1. Compile your program with the /Tm option to enable the debug versions of the memory management functions. 2. Call _heapmin immediately before the end of main (or before the function call that ends the program). If your problems are in a DLL, call _heapmin in your _DLL_InitTerm function. For more information about debugging your heap problems, see "Debugging Your Heaps" in the Programming Guide. ═══ 14.13. Why Can't I Open a File? ═══ Question: I can edit a file, but I cannot open it. Why? Answer: Make sure that, if you store the file name in a constant string, you use a double backslash (\\) to represent a backslash (\). In C, a backslash is an escape character for inserting a character that you normally cannot type. For example, because \t is the tab character, the following string: char filename[] = "c:\directory\test.c" has an actual value of "c:directory est.c" To enter the file name correctly, convert the string to the following: char filename[] = "c:\\directory\\test.c" ═══ 14.14. Why Does Optimization Cause My Program to Fail? ═══ Question: My program runs correctly when I create it with no optimization (/O-), but when I add optimization, it fails or traps. Why? Answer: There could be a problem with the optimizer, but often, optimization reveals an underlying problem in your source code. For example, given the following code: int bruce() { int dave[8]; char stephan; int vij[6]; . . . vij[6] = 0; // writes past end of array . . . return dave[0]; } With no optimization (/O-), the statement vij[6] = 0; causes stephan to be overwritten. This may or may not cause a runtime error, depending on how stephan is used after this statement. With optimization (/O+), the order of local variables on the stack changes, and the same statement causes dave[0] to be overwritten. Because bruce actually returns this value, the problem is more likely to appear. Using the optimizer is a good way to further test your source code. ═══ 14.15. Why Doesn't My Old C Code Work? ═══ Question: Some of the old C code that I have recently started to use does not seem to work correctly. It seems that the parameters to a function are not being received correctly. What is happening? Answer: Make sure that you do not mix functions defined under the old C standard (KRx) with functions defined under the ANSI standard. You can still define functions according to the KRx standard, but you cannot mix prototyped and unprototyped function definitions because of the difference in conversions. Note: The C++ language requires that all functions have ANSI-style prototypes. There are important differences between the standards in the way that functions are defined and processed by the compiler. Under the old standard, you define a function as follows: int my_function( variable1, variable2, variable3 ) int variable1; float variable2; short variable3; { . : } To make passing of parameters easier, variables of type char or short are converted to type int, and variables of type float are converted to double. The ANSI standard formally defines the function using a function prototype. With the prototype definition, you explicitly state the number and types of parameters each function receives. The corresponding ANSI definition of the function above is: /* function prototype */ int my_function( int variable1, float variable2, short variable3 ); /* function declaration */ int my_function( int variable1, float variable2, short variable3 ) { . : } The ANSI standard also allows functions with a variable number of parameters, specified by following the fixed parameter list with an ellipsis (...). Under this standard, fixed parameters of type char are converted to int, optional parameters of type char and short are converted to int, and optional parameters of type float are converted to double. It is best to convert the function definitions to the ANSI standard. Prototyping your functions as described in the ANSI standard makes your code more portable. Defining a full prototype also gives the compiler and optimizer complete information about the types and sizes of the parameters. As a result, the compiler does not have to perform conversions to widened types or generate eyecatcher instructions for the function. ═══ 14.16. Why Does One Function Alter Information from Another? ═══ Question: Why is information that I generate by calling one function being altered after I call a second function? Answer: You may be returning the address of a local variable. If you call a function from within your program, do not rely on any of its local data after it returns. For example, given the following function: void a( void ) { int *x; x = b(); /* x points to a variable local to b() */ . : c(); . : printf( "%p", &x ); /* try to access x after c() has been called */ } The int variable that x points to may not exist after function c is called, causing an error on the printf statement. Local data is stored temporarily on the stack, which may be used by the operating system. If the operating system or another function needs some of the stack space, it is likely that the original data will be overwritten. The cause of this problem can be difficult to isolate, because the demand for stack space is random and unpredictable. To avoid this problem, declare the variables in the calling function or as global variables. ═══ 14.17. Why Do My Window Procedures End Abnormally? ═══ Question: Why do my window procedures end abnormally? Answer: Make sure that you prototype your window procedures to use the _System calling convention. You can do this by including the appropriate system header file from the Toolkit. You should also ensure your window procedures include the EXPENTRY keyword, as described in the Toolkit documentation. The VisualAge C++ compiler uses the _Optlink calling convention by default, which is not compatible with the _System calling convention used by the OS/2 system to call window procedures. OS/2 APIs use _System linkage; _Optlink is used for VisualAge C++ library functions. It is easiest to use the _System keyword to give individual functions _System linkage. For more information on the calling conventions, see the Programming Guide. ═══ 14.18. Why Does Printing a _Seg16 Pointer Trap? ═══ Question: I have a char * _Seg16 variable in my application. When I use it with string functions like strupr, it works fine. However, when I try to print it with printf, my program traps. Why? Answer: VisualAge C++ converts _Seg16 pointers to flat pointers depending on the function prototype. The string functions always take char * variables, so the conversion is done for you. However, because printf has a variable argument list and can take any type, there is no prototype to convert the pointer. To force the conversion, explicitly cast the _Seg16 pointer to the type you want to print. For example, to print char * _Seg16 myptr as a string, code: printf("%s\n", (char *)myptr); ═══ 14.19. What Is __EDCThunkProlog and Why Does It Trap? ═══ Question: I have an application that uses both 16-bit and 32-bit modules. When I run it, it traps in __EDCThunkProlog. What is this function, and why does it cause the trap? Answer: __EDCThunkProlog is part of the VisualAge C++ runtime and is called in the prolog of any 32-bit function that calls a 16-bit function. It ensures that the 32-bit function's parameters and local variables, as well as the 16-bit function's parameters and the 16-bit stack, do not cross a 64K boundary on the stack. If it traps, it usually indicates that you have insufficient stack to run the 16-bit function correctly. Check the value specified in your #pragma stack16 statement (the default value is 4K). This value specifies how much stack __EDCThunkProlog reserves from your program's stack for your 16-bit functions. If you set this value too low, your 16-bit functions may not have enough stack to run correctly. Because the 16-bit stack is allocated from your program's stack, you may also need to increase your program's stack size. ═══ 14.20. Why Does printf Work Incorrectly? ═══ Question: When I call printf, why does it print the wrong thing or cause my program to end abnormally? Answer: Make sure that the parameters you list in your format string match the parameters you are actually passing to the function. Possible problems include:  Passing a parameter of a different type than you have declared. For example, the printf function in the following code fragment expects a string variable, but is passed a variable of type int: int a; printf( "%s", a ); The correct printf call should read: printf( "%d", a );  Passing a parameter of a different size than you have declared. For example, the format string in the following code fragment indicates that three variables of type int are expected, but the variables passed are of type long, int, and short: long l; int i; short s; printf( "%d %d %d", l, i, s ); Because it reads in the bytes from storage, this call could have unexpected results. The correct printf call should read: printf( "%ld %d %hd", l, i, s );  Passing a parameter by reference instead of by value. For example, in the following code fragment, the printf function expects a variable of type int, but is passed the address of an int variable: int a; printf( "%d", &a ); The correct printf call should read: printf( "%d", a ); ═══ 14.21. Why Doesn't printf Output Display? ═══ Question: How do I display a printf prompt on the screen before the program waits for an answer from scanf? Answer: Because printf is line-buffered, its output appears only after a new-line character or when the buffer is flushed. You can do one of three things: 1. Include a new-line character at the end of every printf statement (for example, printf("This will display immediately.\n"); 2. Put a fflush(stdout); statement after any output to stdout that precedes input from stdin. It is usually a good idea to fflush at every point where you switch between stdout and stdin. 3. Change the buffering mode to unbuffered by calling setbuf or setvbuf at the beginning of your program after any fopen or freopen statements, but before any output to stdout: setbuf(stdout, NULL); setvbuf(stdout, NULL, _IONBF, 0); Note that this last solution can make your program run more slowly. In C++ programs, output from cout and cin is automatically displayed. ═══ 14.22. Why Does scanf Behave Unexpectedly? ═══ Question: Why doesn't scanf behave as I expect it to? Sometimes it does not wait for input, does not convert all input, or goes into an infinite loop. Answer: scanf works on streams of characters, not lines of input. It reads the characters from the specified input stream and formats them according to the conversion rules that you specify. Here are some guidelines to follow when reading character input:  Use scanf for machine-generated input only.  Use a combination of the fgets and sscanf functions for user input. Note: Do not substitute the gets function for fgets. If you use gets, it is possible to overwrite the character array used to store the input, and cause memory problems. With fgets, you control the number of characters that the user can input.  Check the return count from the scanf functions to see how many fields were processed.  Read the descriptions of the various formats carefully. Some formats skip leading white space (for example, %d and %f) and others do not (for example, %c). Remember to include the new-line character.  Remember that the scanf conversion characters are different from the printf conversion characters. The following examples show how scanf works and illustrate some possible problems. All of the examples assume that the input is coming from the user.  The following statement reads an integer from the user: scanf( "%d", &myint ) The program waits for you to enter a string of characters. If you enter: 25\n the function reads the digits 2 and 5 and stops when it reads the first non-decimal digit, the new-line character (\n).  If you instead enter: 13 74\n the function reads the digits 1 and 3 and stops when it reads the blank, which is the first non-decimal digit. The %d conversion skips any leading whitespace characters such as a blank, the tab character (\t), and the new-line character. The 74\n remains in the input stream. Because the input stream is not empty, the next call to scanf will read directly from the stream and will not wait for user input.  It is possible to enter an infinite loop with a combination of scanf and unexpected user input. Here is an example: The following code fragment reads a set of numbers until a negative number is entered. answer = 0; i = 0; while (answer >= 0) { scanf( "%d" , &answer ); myarray[i] = answer; i++; } If you enter: 123XYZ\n the first call to scanf reads 123 as a valid integer and stops at the X, leaving XYZ\n in the input stream. Because the input stream is not empty, the next call to scanf tries to read an integer from the stream. Because X is not an integer, scanf never progresses through the input stream, and you do not have the opportunity to enter new data. The result is an infinite loop. For more information on scanf, see the C Library Reference ═══ 14.23. Why Do Library Functions and APIs Not Work? ═══ Question: When I call a library function or OS/2 API, it does not work or it causes my program to end abnormally. Why? Answer: Make sure that you use the #include preprocessor directive to include the header file that contains the prototype statement for the library function or API. Also make sure that you are using the correct calling convention. Use _Optlink to call library functions. Use _System to call OS/2 APIs. Include the appropriate header files to ensure that the functions and APIs you use are prototyped correctly. If you change the default calling convention to _System (with the /Ms compiler option), you must include header files for all library functions you use. Note: Use the /Wpro compiler option to warn about unprototyped functions. This option is set by default. ═══ 14.24. Why Do Macros with Increment Operators Work Incorrectly? ═══ Question: A statement in my C program behaves strangely. It deals with a combination of a macro and increment operators. What is the problem? Answer: When you use a macro, make sure that you know how it will be expanded. If you define a macro that repeats the input argument, problems might occur in combination with increment (++) and decrement (--) operators. As an example, given the following macro toupp: #define toupp(c) islower(c) ? _toupper(c) : (c) The following statement is intended to copy every character of source into dest: while (*dest++ = toupp(*source++)); After the toupp macro is expanded, the actual statement that is executed is: while (*dest++ = islower(*source++) ? _toupper(*source++) : (*source++)); This increments source twice each time the loop is done, an unintended result. ═══ 14.25. Why Doesn't My Macro Resolve Correctly? ═══ I have defined a macro, but it does not always produce the correct answer. Why? Answer: If your macro expands to an expression, make sure that you use parentheses when you define the macro. You may get unexpected results if you use the macro in the same statement as other operators. The precedence rules of the other operators may interfere with the macro definition. For example, given the following code: #define DOUBLE(x) x+x y = DOUBLE(2)+1; /* assigns 5 to y */ z = DOUBLE(2)*3; /* assigns 8 to z */ The last statement evaluates to 8 rather than to 12 because it expands to z = 2 + 2 * 3. To prevent this problem, use parentheses when you define a macro. For example, the above macro would give the expected results if it were defined as: #define DOUBLE(x) ((x) + (x)) ═══ 14.26. Why Don't My Threads Work Correctly? ═══ Question: In my program, why do threads other than thread one not work correctly? Answer: Ensure that:  You use the /Gs- compiler option to generate stack probes (which is the default).  You use the /Gm compiler option to link with the multithread libraries.  If you started a thread with the DosCreateThread API, you call _endthread to end the thread and perform the necessary termination actions. If you used _beginthread to start the thread, _endthread is called implicitly when the thread ends. ═══ 14.27. Why Do My Statements Have No Effect? ═══ Question: I have a statement or a group of statements that does not seem to do anything. Answer: Make sure that you are not missing an end to a comment (*/). In the following example, an ending comment is omitted, causing a statement to be skipped during the compilation: /* This comment has an incorrect terminator *\ ... here = (is > some) ? important : code; ... /* This comment "accidentally" terminates the previous comment */ code = begins + working / fine->again; A similar problem can occur with macro definitions. For example: #define something(important) // An important macro \ this->gets(ignored) \ this->does(too) Because of the line continuation character (\), the lines are spliced together before the comment is processed. As a result, the second and third lines of the macro become part of the comment. For this reason, avoid using // comments in multi-line macro definitions. The syntax highlighting in the VisualAge Editor can help you find this type of problem. ═══ 14.28. Why Do Increment Operators Produce Unexpected Results? ═══ Question: A section of my code is not producing the expected results. The statements use the ++ and -- operators. Answer: Make sure that your statements do not depend on side effects of the ++ and -- operators. For example, because the result of the following statement depends on when the ++ operator is evaluated and when the assignment is done, it may produce inconsistent results: s[i++] = t[i]; The order of these operations depends on the compiler being used and possibly on the optimization requirements. One compiler may compute the source address first (the right-hand side of the statement), while another may compute the target address first (the left-hand side). The same problem often occurs with function parameters. For example, given : printf("%d %d %d\n", i++, i++, i++); The parameters can be evaluated and passed in any order, so any code that depends on a particular order of evaluation will most likely fail. To produce consistent results, if you use the ++ or -- operators on a variable within an expression, make sure that the variable appears only once within the expression. ═══ 14.29. Why Do I Get a SYS2070 Error? ═══ Questions: When I try to run my program, it does not run and the operating system generates a SYS2070 error. Why? Answer: The program could not access an external reference. The linker may not have been able to resolve all of the external references in your program. Make sure you use the /NOI linker option to preserve the case of external names when you link your program. This is the default. Do not use the /IGNORECASE linker option. See Why Do I Get Unresolved External Errors? for different causes of unresolved external references. ═══ 14.30. Why Does My PM Application Disappear? ═══ Question: Why does my PM application disappear without generating any messages? Answer: An exception has been generated and handled by an exception handler that has terminated the program. A machine-state dump is sent to stderr, but because the PM interface directs the stderr stream to a null output device, you do not see the error messages. Use the _set_crt_msg_handle function to redirect stderr to a file. You will then be able to see the runtime messages, including exception messages and machine-state information. Alternatively, you can write your own exception handler to intercept the exception and handle it however you want. ═══ 14.31. Why Does My Program Work Incorrectly and Inconsistently? ═══ Question: My program does not work properly. Sometimes adding or removing statements changes how the program terminates or may even solve the problem temporarily. Using a debugger changes the symptoms. What is the problem? Answer: There are several possible solutions to your problem:  Make sure that you are calling functions correctly. If a parameter is missing, the program may replace the parameter with arbitrary data to complete the function call. The VisualAge C++ compiler checks for missing parameters if you define your functions using function prototypes. Note: Use the /Wpro compiler option to warn about missing prototypes. This option is set by default. If the incorrect type of parameter is used, the function misreads the parameter list.  Make sure that there is not a semicolon at the end of a for, do, or while statement. When there is only one statement in the body of a loop, it is common to code the loop in the following style: for (... ; ... ; ...) statement; It is also a common error to accidentally add a semicolon to the end of the first line. For example: for (i = 0; i < SOMENUMBER; i++); d[i] = 0; The semicolon at the end of the for statement ends the body of the loop. The second line, which is the intended body of the loop, is executed only once. To catch problems like this, use the /Weff compiler diagnostic option to locate code that has no effect. It is also a good idea to enclose the loop statements in braces ({ }).  Ensure that strings are terminated by a null byte. When you initialize a string, you must include space for the null byte. For example, char str1[3] = "ab" /* allocates 'a', 'b', '\0' */ char str2[3] = "abc" /* allocates 'a', 'b', 'c'; no '\0' */ When you use a string, do not overwrite the null byte. Because the null byte is used to indicate where the string terminates, a string without the null byte can cause memory problems. If you use a function such as strcpy on a string without the terminating character, portions of the memory following the string may be overwritten, causing problems with the current program or programs that are using that memory space. Problems could appear immediately or only after the program is run several times. Note: Use debug memory management (/Tm option) to help find memory corruption problems.  Check your function return types. The compiler assumes that a function declared without a return type returns an int. This could cause problems if your program is expecting a different return type. Prototype your functions to avoid this problem.  If you declare an array in one file and reference it in another file using extern, make sure that the extern statement has the same form as the declaration statement. For example, the following declarations are not equivalent: /* File 1: Global Data definitions */ char x[100]; /* File 2: Using the global data */ extern char *x; In the second file, the compiler generates code that assumes that the address at x contains the address of the actual array. The correct definition in File 2 is: /* File 2: Using the global data */ extern char x[];  Ensure that you are not referencing beyond the last element of an array. The first entry of an array is found at index 0 (for example, array[0]). If you declare an array of size n, the array starts at element 0 and ends at element n-1. The following code fragment references beyond the last array element: char stuff[10]; int i; . . . for (i = 0; i <= 10; i++) { /* test should have been i < 10 */ stuff[i] = ' '; } Referencing beyond the last element in the array may overwrite memory locations and cause problems with variable data, functions, or the entire program.  Ensure you use the malloc and free library functions correctly. The malloc function returns a pointer to an area of memory that is at least as large as you request. The free function releases memory previously allocated by malloc. Make sure that only pointers returned by the malloc function are passed to the free function. To keep track of what memory is available, malloc stores information in a section of memory adjacent to the pointer that it returns. The free function uses this information to return the allocated space to the list of available memory. The free function does not check the pointer that it receives. If free receives a pointer that was not set by malloc, memory problems can occur. For example, other programs may get unauthorized access to your data areas, program code, or parts of the operating system. Also make sure that you do not use memory outside of the memory allocated by malloc. To help you find possible problems with these functions, use the debug memory management functions. Memory management is described in the Programming Guide; for descriptions of individual functions, see the C Library Reference ═══ 14.32. Where Is the Message File? ═══ Question: When I run my application on another machine, I get the message "Message file not found". Why? Answer: The VisualAge C++ runtime cannot find the message file that contains the runtime messages it needs. To solve this problem, do one of the following:  Copy the runtime message files you need to a directory in the DPATH of the machine running the program. The runtime message files are: DDE4.MSG C runtime library and I/O Stream and Complex class libraries. IBMCRERR.MSG Regular expressions. DDE4C01E.MSG Collection class libraries. CPPOOC3U.MSG User Interface class libraries. DAXCLS.MSG Database Access class libraries.  Use MSGBIND to bind the messages to your application's own runtime. (You must do this if you will be shipping your application to other people.) For more information about how to use MSGBIND and what messages you need, see the section on MSGBIND in the User's Guide. ═══ 15. Debugging with the Debugger ═══ This section answers questions you may have while debugging your program with the debugger.  When Should I Use Synchronous and Asynchronous Debugging Modes?  Can I Debug 16-Bit Code?  Can I Debug Optimized Code?  Why Do I Get A DosStartSession Error?  How Does the Debugger Locate Source Files?  Why Can't the Debugger Find the Source File?  How Do I Debug A DLL Called by A REXX Program?  How Do I Debug a WorkPlace Shell DLL?  Why Can't I Typecast in the Monitor Expression Dialog?  How Do I Debug SQC Files?  Can I Run Performance Analyzer and the Debugger at the Same Time? ═══ 15.1. When Should I Use Synchronous and Asynchronous Debugging Modes? ═══ Question: When should I run the debugger in synchronous mode, and when should I run it in asynchronous mode? What is the difference? Answer: The difference between the two modes appears when the program you are debugging is stopped, such as at a breakpoint. In asynchronous mode, you can interact with non-debugger applications, such as an editor. This is the usual mode for running the debugger. However, the debugger then interferes with the normal flow of messages to the program you are debugging. The debugger responds to the PM messages of your program in a default manner, so that it does not lock the PM input queue. If you need to handle your program's messages in a specific way (other than the default), or if the order of input messages is important, run in synchronous mode. For example, if your application uses dynamic data exchange (DDE), you should run in synchronous mode. You will not be able to use other applications while the debugger is running, but you will capture all the messages to and from your application. ═══ 15.2. Can I Debug 16-Bit Code? ═══ Question: Can I use the VisualAge C++ debugger to debug my 16-bit code? Answer: The VisualAge C++ debugger recognizes both IBM and Codeview debugger data, so you can use it to debug both the 32-bit and 16-bit parts of your application. ═══ 15.3. Can I Debug Optimized Code? ═══ Question: Can I debug code that has been optimized? Answer: Yes, but you will not be able to use all debugger features. Because some variables are placed in registers, you cannot monitor variables accurately. The register and storage windows are both accurate. To debug optimized code most efficiently, turn off the instruction scheduling optimization (/Os-) when you compile, and use a mixed source and assembly view to navigate through your code while using the register and storage windows. ═══ 15.4. Why Do I Get A DosStartSession Error? ═══ Question: Why do I get a DosStartSession error when I start the debugger? Answer: The debugger starts your program using DosStartSession in trace mode. DosStartSession does much more checking when trace mode is on, causing some programs that seem to run correctly outside the debugger to fail under the debugger. The most common errors and causes are: Return Code Cause 2 A DLL referenced by the application could not be found. 127 A function in a DLL could not be found (usually because of a missing EXPORTS line in a .DEF file). 182 An ordinal is invalid (usually caused by picking up a different level of the DLL than was used to create the import library you linked to). ═══ 15.5. How Does the Debugger Locate Source Files? ═══ Question: How does the debugger locate the source files? Answer: The debugger looks in the following places in the following order: 1. The name stored in the .EXE file (from the icc command line) 2. The directory containing the .EXE file 3. The PMDPATH environment variable 4. The current directory ═══ 15.6. Why Can't the Debugger Find the Source File? ═══ Question: Why is the debugger unable to find a source file unless the path and file name are given in the Source Filename Incorrect dialog box? Answer: If you recompiled any of the code since the last time you used the debugger, it may be unable to locate the source file. If you compile in one directory and run in another, you should either use the PMDPATH environment variable or put a qualified path on the ICC compile line, For example, specify: icc ./test.c rather than: ICC test.c If your ICC command lines are created by the WorkFrame MAKEMAKE utility, check the make file to see if it is using .\source or source.c on the command line. ═══ 15.7. How Do I Debug A DLL Called by A REXX Program? ═══ Question: How do I debug a DLL called by a REXX program? Answer: 1. Start the debugger with the command: IPMD CMD.EXE /C your Rexx.cmd 2. When the debugger displays the code for CMD.EXE, set a load-type breakpoint to stop at the DLL. 3. Select Run from the menu. 4. At the pop-up indicating your DLL has loaded, open the desired parts and set breakpoints. Use deferred breakpoints to have them set in your next debugger session. 5. Select Run again. ═══ 15.8. How Do I Debug a WorkPlace Shell DLL? ═══ Question: How do I debug a WorkPlace Shell DLL? Answer: 1. In your CONFIG.SYS file, add the following line: SET RUNWORKPLACE=C:\OS2\CMD.EXE 2. Reboot your machine. 3. In the initial OS/2 window, enter IPMD C:\os2\pmshell 4. Set a Load-type breakpoint for the DLL containing the WPS program. ═══ 15.9. Why Can't I Typecast in the Monitor Expression Dialog? ═══ Question: Why doesn't my typecasting work in the Monitor Expression Dialog? Answer: Complex user typecasting is only supported if you build your application with the C++ runtime library. If you are compiling a C program, use the /Tdp compiler option to compile it as a C++ program and include the C++ runtime support. ═══ 15.10. How Do I Debug SQC Files? ═══ Question: I am trying to debug some SQC files, but the debugger seems to get lost in them. Is there a special option that I need to use? Answer: The problem originates in the SQL precompiler. To assist with error messages when you're compiling your .SQC file, the precompiler brackets all the code it adds to the .C with #line macros. Unfortunately, the #line macros can cause debuggers to get confused. To avoid this problem, tell the precompiler not to create the #line macros. You can do this by modifying the option in your project's SQLPREP action, or by setting the /# option in your SQLPREP statement. (If you're using SDK/2, add NOLINEMACRO to your DB2 PREP statement instead.) ═══ 16. Tracing with Performance Analyzer ═══ This section answers questions you may have about using the Performance Analyzer. Note: In previous versions of C Set ++, the Performance Analyzer was called EXTRA.  Why Can't Performance Analyzer Show My Executable or Object Files?  Why Can't Performance Analyzer Trace OS/2 API Calls?  Why Are There No Events in My Trace File?  Can Performance Analyzer Analyze Child Processes?  Why Can't I Open My Trace File?  Why Can't I View Execution Density or Time Line?  Why Doesn't the Trace File Show All Functions?  How Can I Make My Trace File Smaller?  Why Is My Trace File Incomplete?  Why Do I Get Dashes in the Statistical Summary?  Why Are Many Events Displayed as the Same String?  Why Is the User Event Information Unreadable?  Why Is the Annotate Choice Disabled?  Why Is the Pattern Recognition Choice Disabled?  Why Is the Include Functions Choice Unavailable?  Why Aren't All Events Displayed?  Why Doesn't the Dynamic Call Graph Display?  Why Can't Performance Analyzer Trace My DLL?  Why Can't I Manipulate the Focus in the Overview Window?  Why Can't I See the Function Names?  Why Can't I See My Annotations?  Can I Run Performance Analyzer and the Debugger at the Same Time? ═══ 16.1. Why Can't Performance Analyzer Show My Executable or Object Files? ═══ Question: Sometimes my executable files don't appear in the trace generation window. Other times, my executable file is shown in black, but Performance Analyzer cannot show the object files. Why? Answer: You must compile and link your application with the correct options before you can use Performance Analyzer. To compile your application, use the following options: /Gh Includes the profile hooks that allow Performance Analyzer to monitor your executable. /Ti Includes the debugging information in the compiled object (.OBJ) file. To link your application, use the /DE option to include the debugging information in the final executable (EXE or DLL file). You must also link CPPOPA3.OBJ into your executable file. ═══ 16.2. Why Can't Performance Analyzer Trace OS/2 API Calls? ═══ Question: Why can't Performance Analyzer trace my calls to the OS/2 APIs? Answer: To trace calls to the OS/2 APIs, specify the Performance Analyzer library for the APIs immediately before the OS/2 libraries in your link statement:  For Dos APIs, use _DOSCALL.LIB.  For Win APIs, use _PMWIN.LIB.  For Gpi APIs, use _PMGPI.LIB. Each of these libraries also has an associated DLL. The order of the libraries in the link statement is critical. If the Performance Analyzer libraries do not immediately precede the OS/2 libraries in the link statement, Performance Analyzer may not interpret or trace the API calls. ═══ 16.3. Why Are There No Events in My Trace File? ═══ Question: Why are there no events logged in my trace file? Answer: Several things could have happened:  All functions were disabled in the Trace Generation window from the Edit menu. See the online help for the Trace Generation Window for more information.  The application needs more time to run. Use the Timeout Control choice in the Trace Generation window to control how long your application can run.  If you set a trigger on a function, the triggered function may not have been reached.  Your program was not compiled and linked with the correct options. (See Why Can't Performance Analyzer Show My Executable or Object Files? for a list of the options). ═══ 16.4. Can Performance Analyzer Analyze Child Processes? ═══ Question: Can Performance Analyzer analyze child processes in a multiprocess application? Answer: No, Performance Analyzer does not provide this support. ═══ 16.5. Why Can't I Open My Trace File? ═══ Question: I can't open a trace file from the command line. I know I successfully created it; why can't I open it? Answer: Make sure you are typing the correct command: To Display Enter Statistical Summary icsperf /ss file.ext Call Nesting icsperf /cn file.ext Time Line icsperf /tl file.ext Execution Density icsperf /ed file.ext Dynamic Call Graph icsperf /cg file.ext For more information, read the online help for the Trace Analysis Selection window. ═══ 16.6. Why Can't I View Execution Density or Time Line? ═══ Question: Why can't I open the Execution Density or Time Line diagram? Answer: If you chose to disable the time stamps from the Trace Generation window, these diagrams do not display. To view these diagrams, go to the Trace Generation window and from the Options menu, make sure that Time stamp events has a check mark. Then create the trace file again. ═══ 16.7. Why Doesn't the Trace File Show All Functions? ═══ Question: Why doesn't the trace file show all the functions? Answer: One of three things may have occurred: 1. A trigger may have been set from the Trace Generation window. Read the online help for Set Trigger for more information. 2. The call depth may be set to null or a low number. Read the online help for Set Call Depth for more information. 3. Some of the functions may have been disabled before the trace. Read the online help for the Trace Generation window for more information. ═══ 16.8. How Can I Make My Trace File Smaller? ═══ Question: My trace file is very large. How can I make it smaller? Answer: To limit the trace file information to a more manageable number of events, you can:  Use triggers to have a specific function start the trace. You can set triggers from the Trace Generation window.  Add calls to PerfStart() and PerfStop() to your code, to specify to Performance Analyzer when to begin and end tracing.  Disable functions, object files, or executables so they will not be traced. You can do this from the Trace Generation window before you start the trace.  Limit the tracing to specific threads or specific call depths per thread by using Call Depth from the Options menu in the Trace Generation window. If you set the call depth to 0 for a given thread, that thread will not be traced. ═══ 16.9. Why Is My Trace File Incomplete? ═══ Question: My trace file is missing some early events. Why? Answer: Your buffer may be set to overwrite the older events. See the online help for the Buffer Control Action window for information on how to change the buffer. ═══ 16.10. Why Do I Get Dashes in the Statistical Summary? ═══ Question: In the Statistical Summary display, why do I see "---" next to a function name? Answer: When you traced your application, there was not enough timing information available for Performance Analyzer to analyze the function. You need to allow more time for the function to run. Use the Timeout Control in the Trace Generation window to control how much time is allowed. ═══ 16.11. Why Are Many Events Displayed as the Same String? ═══ Question: In the Statistical Summary and Call Nesting diagrams, why are multiple different user events incorrectly displayed as the same string? Answer: Make sure that the string you are passing to the PERF entry point is an ASCIIZ string. The string must exist in storage when the trace buffer containing the reference is written to disk. Also make sure that you have included the correct prototypes for the PERF entry point. For C programs, the prototype is: void PERF(PSZ string); For C++ applications, the prototype is: extern "C" { void PERF(PSZ string); } ═══ 16.12. Why Is the User Event Information Unreadable? ═══ Question: In the Statistical Summary and Call Nesting diagrams, why do user events display with unreadable information? Answer: Make sure that the string you are passing to the PERF entry point is an ASCIIZ string. The string must exist in storage when the trace buffer containing the reference is written to disk. Also make sure that you have included the correct prototypes for the PERF entry point. For C programs, the prototype is: void PERF(PSZ string); For C++ applications, the prototype is: extern "C" { void PERF(PSZ string); } ═══ 16.13. Why Is the Annotate Choice Disabled? ═══ Question: In the Call Nesting diagram, why is the Annotate choice disabled? Answer: When the Pattern Recognition choice is enabled, the Annotate choice is not available. Disable pattern recognition before selecting Annotate. ═══ 16.14. Why Is the Pattern Recognition Choice Disabled? ═══ Question: In the Call Nesting diagram, why is the Pattern Recognition choice disabled? Answer: You must select a single thread using the Include threads option from the View menu: 1. Select the View menu. 2. Select Include threads. 3. Select the thread you want to view, and then select the Use pattern recognition option. If the trace file is large, pattern recognition is not available. If this is your problem, the Include threads menu item is disabled (grayed out). See How Can I Make My Trace File Smaller? for ways to reduce the size of your trace file. ═══ 16.15. Why Is the Include Functions Choice Unavailable? ═══ Question: In the Call Nesting diagram, why isn't the Include functions choice from the View menu available? Answer: This choice is only available when all threads are displayed. If you have enabled a specific thread from the Include threads menu (also from the View menu), you must deselect the pattern recognition checkbox and select the All Threads choice before you can use Include functions. ═══ 16.16. Why Aren't All Events Displayed? ═══ Question: Why isn't the Execution Density diagram displaying all of the events? Answer: Check your settings in the diagram: 1. Check which functions are included in the display by selecting Include functions from the View menu. 2. Check which threads are included in the display by selecting Include threads from the View menu. ═══ 16.17. Why Doesn't the Dynamic Call Graph Display? ═══ Question: Why doesn't the Dynamic Call Graph display? Answer: If your trace file contains only one function per thread, no dynamic calls are made and therefore the Dynamic Call Graph does not display. ═══ 16.18. Why Can't Performance Analyzer Trace My DLL? ═══ Question: Why can't Performance Analyzer trace my dynamically-loaded DLLs? Answer: Make sure you compiled the DLLs with the correct options (/Gh to enable profiling and /Ti to generate debug information), and linked using the /DE option to include debug information in the DLL. You must also link CPPOP3A.OBJ into your DLL. Note: Previous versions of C Set ++ did not support tracing dynamically-loaded DLLs. ═══ 16.19. Why Can't I Manipulate the Focus in the Overview Window? ═══ Question: In the Dynamic Call Graph, why can't I manipulate the grey box in the Overview window to change the focus? Answer: Increase the size of the Overview window. (Press mouse button 1 on the corner of the window and drag it.) ═══ 16.20. Why Can't I See the Function Names? ═══ Question: In the Dynamic Call Graph and Time Line diagram, why can't I see the names of the functions? Answer: You need to increase the size of the diagram. Use Zoom In to magnify the function names. ═══ 16.21. Why Can't I See My Annotations? ═══ Question: In the Call Nesting diagram, why can't I see my annotations? Answer: You need to disable the Pattern Recognition to see the annotations. ═══ 16.22. Can I Run Performance Analyzer and the Debugger at the Same Time? ═══ Question: Can I run Performance Analyzer and the debugger at the same time? Answer: No. Because of operating-system limitations, you cannot run Performance Analyzer and the debugger at the same time. ═══ 17. Browsing Your Program ═══ This section answers questions about using the VisualAge C++ Browser to understand your program.  Will the Browser Work with Old .BRS or .PDB Files?  What is QuickBrowse and Why Should I Use It?  Why Doesn't QuickBrowse Show Class Template Instances?  What Is the Difference Between /Fb and /Fb*?  What Are the .PD* Files?  What Does the Hold Button Do?  What Do the Blue Initials in Front of the Names Mean?  Can I Browse SOM Classes?  Can I Browse Programs Written in Other Languages?  What Does Merge Do and Why Should I Use It?  Why Can't I Edit Definitions for Library Member Functions?  Why Are Some Pop-Up Menu Items Disabled?  Why Doesn't Show Documentation Work with My Own Classes?  What Does the Order Menu Choice Do?  Where Is the Source View of My Functions?  Why Is the Text in the Graph So Small?  Why Is QuickBrowse Not Always Available?  How Do I Zoom Into a Small Area on a Graph?  What Are Anonymous Types?  What are Compiler-Generated Constructors and Destructors?  What Does 'n' Instances Mean?  What do Print Client and Print Zone Mean?  What does Weighting Do in the Graph Window?  In What Format Are the Graph Pictures Saved? ═══ 17.1. Will the Browser Work with Old .BRS or .PDB Files? ═══ Question: Will the browser work with my old .BRS files from other versions of VisualAge C++, or with my .PDB files from C Set ++ for AIX? Answer: No, you can only use the browser with .PDB files generated by this version of the VisualAge C++ compiler. Unless you want to continue using the old browser, you can delete the .BRS files. ═══ 17.2. What is QuickBrowse and Why Should I Use It? ═══ Question: What is QuickBrowse, why would I want to use it, and how can I make use of it? How is it different from compiling with the Generate Browser information (/Fb) option? Answer: Use QuickBrowse to quickly obtain and browse type information for code for which there is no compiler-generated (/Fb) browser information. Use QuickBrowse because it is faster than compiling your code, and because you may be able to browse files that do not compile. (Because QuickBrowse parses your code and ignores function bodies, as long as the type information is complete and valid, you can browse it with QuickBrowse.) For example, QuickBrowse is well-suited for:  Understanding class library interfaces.  Browsing code you are porting to OS/2 from another platform.  Understanding an existing code base.  Aiding in the design of new code. For more information on how to accomplish these tasks with QuickBrowse, see the Browser How Do I help. To use QuickBrowse, invoke the browser on your project. If compiler-generated browser information is not available, the browser displays a window to tell you the information is missing. From this window, you can choose to invoke QuickBrowse for the files where data is missing. While QuickBrowse runs, messages appear in the project's monitor, just as if you were doing a Build. Although QuickBrowse is faster than compiling, compiler-generated browser information is more complete than that generated by QuickBrowse. Because QuickBrowse disregards function bodies, any information in that function body, including information about calls and thrown exception types, is not included. If you need this information, compile your source files to generate browser information. Note: QuickBrowse is only available when you invoke the browser on a VisualAge C++ project. If you start the browser from the desktop icon or command line, QuickBrowse is not available. ═══ 17.3. Why Doesn't QuickBrowse Show Class Template Instances? ═══ Question: When I use QuickBrowse, class template instances do not appear in any list of classes, and listing the instantiations for the templates gives no results. Why? Answer: When you QuickBrowse source files, any class template instances at file scope appear as global variables. For example, given the following source code: template class foo { T* bar; }; foo boo; QuickBrowse produces one class named foo and one global variable boo of type foo. ═══ 17.4. What Is the Difference Between /Fb and /Fb*? ═══ Question: What is the difference between the compiler options to generate browser information (/Fb) and generate all browser information (/Fb*)? Answer: The difference between the two options relates to how much browser information is generated from system header files (specified in angle brackets < > instead of double quotation marks). The generate browser information (/Fb) option discards much non-type information from system header files:  Non-member function declarations are not included in the .PDB file, including those C and OS/2 header files. Any friendship granted to any such omitted functions will not be recorded for a class. For example, given the following declarations: // in int foobar(void); // in "bar.h" class bar { friend int foobar(void); } This friendship will not be included in the list of friends of class bar.  No global variable declared or defined in the system header files is included in the .PDB file. This includes variables of an instantiated template type.  Class member functions declarations will be emitted, but their inline definitions, if any, will not appear in the .PDB file.  Non-inline function definitions will not be emitted in the .PDB file. When you choose to generate all browser information (/Fb*), the above information is included. Note: In general, you should only use /Fb to generate browser information. The compiler generates a message if you should use /Fb* instead. ═══ 17.5. What Are the .PD* Files? ═══ Question: What are the .PD* files? Answer: The .PDB files are output by the C++ compiler. There is one for every OBJ file. When you browse a target file (.EXE, .DLL, or .LIB), the individual .PDB files (for the .OBJ files contained in the target) are loaded into memory by the browser. A copy of this digested data is quickly saved when you stop browsing the target file, and is quickly loaded when you browse the target file again. The extension of the saved file is based upon the extension of the target file: data for a .EXE file is saved in a .PDE file, DLL data in a .PDD file, and LIB data in a .PDL file. Manually loading one or more .PDB file or performing a Merge operation is analogous to grouping a set of .OBJ files together into a single .LIB file, so the saved file version in this case is a .PDL file. ═══ 17.6. What Does the Hold Button Do? ═══ Question: What does the Hold button do? Answer: Typically, the results of any one action will overlay the contents of the first window of the right type for the action (for example, a List All Files action changes the content of the first List window). However, there may be times when you want to maintain the contents of that window, for example, to keep the list of all classes available as you look at the contents of different classes. When you push the Hold button, the contents of the window are maintained as they are and the results of any action you perform are placed in a separate window. ═══ 17.7. What Do the Blue Initials in Front of the Names Mean? ═══ Question: What do the blue letters in front of names mean? Answer: The letters are shorthand icons for common C++ attributes: V virtual S static C const P pure virtual E enum You can choose to see the attribute icons only, the full text only, or both, using the Styles page in the List Window Settings Notebook. ═══ 17.8. Can I Browse SOM Classes? ═══ Question: Can I browse SOM classes? Answer: Yes and no. You can browse SOM classes compiled with the Direct-to-Som (DTS) feature of the VisualAge C++ compiler. You cannot browse SOM classes defined in IDL unless you generate C++ bindings for these classes, and then compile them with the DTS compiler support. ═══ 17.9. Can I Browse Programs Written in Other Languages? ═══ Question: Can I use the browser to browse programs written in other languages like Object-Oriented REXX, Object-Oriented Cobol, or C? Answer: No. While some similarity exists between C++ and these other OO languages, this is a C++ class browser. You can browse C files to some extent by compiling them as C++ files. However, the data you obtain has limited value because there are no classes in the C language. (Structures and unions are similar to C++ classes, except that they only contain data members). File and function information will be present, however. ═══ 17.10. What Does Merge Do and Why Should I Use It? ═══ Question: What does Merge do? Why would I want to use it? Answer: When you browse a target program (executable file, DLL, or library), you only see those classes, functions, and files that were actually used in the program. You do not see related objects. For example, assume that you have written a small program using the User Interface library, and it contains an IFrameWindow, a menu bar, and some static text. Next you want to add push buttons and a bitmap onto your window. You can merge the User Interface Library data (all of it) with your program's data, and see all the interface facts about all the classes in the User Interface library. Also, many programs are written as a .EXE file and one or more DLLs. If you browse the .EXE file, you only see the data from that file. You can merge in the data from the DLLs to see the whole program's information. ═══ 17.11. Why Can't I Edit Definitions for Library Member Functions? ═══ Question: Why can't I edit the definitions for members of the Open Class library that are listed in the Load menu. Why not? Answer: The browser differentiates between declarations and definitions of objects. The Edit Definition action edits the file where the object is defined. A function is defined where it is implemented. If you don't have the body of the function in any of the files that make up the target that you are browsing (for instance it may a function from a library), the browser cannot edit the function. Editing the file containing the declaration for a function whose full prototype is displayed in the browser would not give any more useful information than you already have. Also, a function's declaration can appear in multiple files, and may be (subtly) different in each location. Only your Build process will help you find all problems with mismatched function prototypes. ═══ 17.12. Why Are Some Pop-Up Menu Items Disabled? ═══ Question: Why are some items greyed out (disabled) on pop-up menus? Answer: The browser prechecks the contents of its database to see if certain actions are valid. For objects without definitions, it greys out the Edit Definition action. For classes that have no base class, it greys out both Graph All Base and Derived Classes and Graph All Base Classes actions. The Expand Typedef action only appears on type objects that are typedefs. The prechecks are done when it is possible to do so quickly. Some actions require the entire database to be searched for answers. An example of this is the List Friendships action. This particular action is not prechecked and if invoked on a class that has not been identified as the friend of any other class, you will see the "No results were found" response. ═══ 17.13. Why Doesn't Show Documentation Work with My Own Classes? ═══ Question: Show documentation does not work with my own classes or functions. Why not? Answer: The VisualAge C++ Open Class Library provides special files that enable Show documentation to display details about particular classes and members. To enable Show documentation for other classes, including your own, you must provide: 1. An online document in .INF format that describes the classes. For information on creating online documentation, see the IPF Guide and Reference. 2. An index file that lists the classes or members along with information about where they are described. For information on creating index files, see the documentation for KwikINF in the User's Guide. ═══ 17.14. What Does the Order Menu Choice Do? ═══ Question: What does the Order menu choice do? Why is it sometimes greyed out (disabled)? Answer: The Order choice is enabled only when you are looking at the results of a List Members with Inheritance action. The browser displays all the members of a class, and all the members that that class inherits from its base classes. The default order, Class, presents the name of the selected class first, followed by its base classes. When you expand the + on one of these classes, you see public, protected, and private; these are the Access levels. If you expand the + on one of these access levels, you see constructors/destructors, functions, and so on; these are the Type levels. If you are interested in the public functions of a class (and all the other classes through inheritance), you can use a different order to group the functions that are spread throughout the window. If you sort by Access order, the items are grouped according to public, protected, and private. If you sort the information by its Type, you see grouped together all the functions, all the variables, and so on. Experiment with the ordering to find out what best suits your needs. ═══ 17.15. Where Is the Source View of My Functions? ═══ Question: Where is the source view of my functions? Answer: You can see the source for any program object by displaying the pop-up menu for the object, and selecting the Edit Definition action. This starts your editor on the file containing the definition of the object. This menu option may be greyed out if the definition of the object is not available; see Why Can't I Edit Definitions for Library Member Functions? for more details. Other C++ and Smalltalk browsers contain a small edit window which is linked to other list boxes etc. in their browser. This has a number of drawbacks:  The small edit windows introduce yet another editor that you may not want to use. The VisualAge C++ browser uses your choice of editor, the same one that you use all the time.  The edit window is small. With small windows you spend more time scrolling and resizing than you do understanding.  Programming with C++ is not the same as Smalltalk. In Smalltalk, all of your program's classes and methods are stored in some environment-aware database, not the file system. When you program in C++, you are still using files, and frankly the performance of opening a new file, and locating a line just because the user changed focus or scrolled up with the arrow keys is slow. ═══ 17.16. Why Is the Text in the Graph So Small? ═══ Question: Why can't I read the text in nodes in the drawn graph? Why does the text get smaller when I zoom in? Answer: One problem with drawing an arbitrary graph on a fixed size window is that as more and more items are displayed in the graph, the size of the items becomes so small that any associated label is too small to read. That is what is happening here. The type of font used in the window can also make it worse. The text does not really grow smaller when you zoom in; it just seems to. If the font used in the labels in the graph is a bitmap font, then the Graph Window is limited to the available sizes in that font. The text does not shrink, but the box around the text grows larger. If the font used is an Outline (or Vector) font, then the Graph Window is not limited to a small set of available sizes, but can draw the text in a size that fits the containing box. Outline fonts are marginally slower in performance than Bitmap fonts, so a large graph with a lot of node text would draw more slowly when an Outline font is used. ═══ 17.17. Why Is QuickBrowse Not Always Available? ═══ Question: Sometimes I can't use QuickBrowse. Why? Answer: The QuickBrowse feature of the browser is not available unless you are browsing a project. If you started the browser from the command line, or by double-clicking on its desktop icon, then you are not browsing a project, and QuickBrowse is not available. ═══ 17.18. How Do I Zoom Into a Small Area on a Graph? ═══ Question: How do I zoom into a small area on a Graph? Answer: You can do this in one of two ways: 1. Use the mouse to select an area to zoom to. Move the mouse to the corner of where you want to zoom, hold down mouse button 1, and drag the mouse to the opposite corner of the area. A small box appears between the first point and the mouse pointer. Release the mouse button, press mouse button 2 to display the pop-up menu, and select Zoom in. 2. Select a node, and then either press Ctrl-C, or use mouse button 2 to display the pop-up menu, and select the Center action. This centers the picture on that point or node. Then zoom in using one of:  The zoom slider control on the left side of the window  The Zoom in or Max Zoom in actions from either the View menu or from the window background pop-up menu.  The Ctrl and + keys (equivalent to Zoom in).  The Alt and + keys (equivalent to Max Zoom in). ═══ 17.19. What Are Anonymous Types? ═══ Question: Sometimes I see types called [anonymous]. What are they? Answer: Both the C and C++ languages have the concept of anonymous, or unnamed, structures, unions and enumerated types. For example, each of the types below is anonymous: struct { int number; int code; char *name; } record; union { int a; char b; }; enum { red, yellow, green } color_1, color_2; ═══ 17.20. What are Compiler-Generated Constructors and Destructors? ═══ Question: What are compiler-generated constructors and destructors, and why do I see them in my browser files? Answer: When you define a class, but do not define your own default constructor, copy constructor, or destructor, the compiler will implicitly generate one for you. They are included to inform you that they are there. ═══ 17.21. What Does 'n' Instances Mean? ═══ Question: What does ('n' Instances) mean? Answer: When you use multiple inheritance, and two (or more) of your base classes inherit from a common class, you end up with an inheritance hierarchy like one of the following: Nonvirtual inheritance was used by B and C when they inherited from A, but virtual inheritance was used by F and G when they inherited from E. An object of type D will contain two copies of the data contained in an object of type A. An object of type H will contain only one copy of the data contained in an object of type E. To highlight this non-diamond shape inheritance structure to you, the browser indicates when a derived class contains more than one copy of the data from a base class. ═══ 17.22. What do Print Client and Print Zone Mean? ═══ Question: What do Print Client and Print Zone mean in the Graph Window? Answer: A client is the client area in the graph window, meaning the part of the graph that you can see on your screen in the window. (What is shown in the window is not necessarily the entire graph.) Print Client prints the contents of this client area on a single page. A zone is a rectangular area that you select in the graph. Print Zone prints the contents of a zone on a single page. ═══ 17.23. What does Weighting Do in the Graph Window? ═══ Question: What does Weighting (from the View menu) do in the Graph Window? Answer: Different graph layouts look better one way (for particular graph contents) than others. For instance, some wide graphs with wide nodes look best when shown horizontally, while narrower graphs look best when shown vertically. Also, graphs can be drawn with all roots of a tree at an equal height, all leaves at an equal height, or something somewhere in between. Consider the following three layouts for the same graph: You can choose to lay out a graph differently so that you can better understand its contents. You may find this helpful in some situations. ═══ 17.24. In What Format Are the Graph Pictures Saved? ═══ Question: In what format are the Graph pictures saved? Answer: The pictures are saved as OS/2 bitmap (.BMP) files. To specify the size of the bitmap to save, see the Bitmap page of the Graph settings notebook. ═══ 18. Preparing to Ship Your Application ═══ This section answers questions you may have as you prepare to ship your application to other customers.  Which VisualAge C++ DLLs Can I Ship with My Product?  Should I Build My Own C Runtime DLL?  Can I Rebuild the User Interface Class Library DLLs?  Should I Bind Runtime Messages to My Application?  Can I Use Multiple C Runtime Libraries?  How Do I Imbed a Copyright Statement? ═══ 18.1. Which VisualAge C++ DLLs Can I Ship with My Product? ═══ Question: Which VisualAge C++ DLLs can I ship with my product? Answer: You can ship any or all of the following DLLs with your product, provided you rebuild them with another name or rename them using DLLRNAME: CPPOM30.DLL CPPON30.DLL CPPOS30.DLL CPPOOU3.DLL CPPOOD3.DLL CPPOOM3.DLL CPPOOB3.DLL CPPOOC3.DLL CPPOOR3.DLL CPPOOV3.DLL CPPODS3.DLL CPPODI3.DLL CPPOOR3U.DLL CPPOOR3J.DLL CPPOOR3K.DLL CPPOOR3T.DLL CPPOOR3P.DLL Refer to the License Information booklet for details on shipping VisualAge C++ libraries. Note that if you use the class library resource DLLs, in addition to renaming them, you must call IApplication::current().setResourceLibrary() in your application, specifying the new DLL name. You can also create your own runtime libraries and export the C library functions from them. See the Programming Guide for more information on creating your own runtime libraries. ═══ 18.2. Should I Build My Own C Runtime DLL? ═══ Question: Should I build my own C runtime DLL to ship with my program? How do I do it? Answer: If you own a whole application that contains multiple .DLL and .EXE files, you should create a runtime DLL with only the functions your application requires and have all your components dynamically link to that DLL. The advantages are: 1. Your application is smaller. 2. Your application is faster, because only one copy of the runtime environment needs to be created for each process. 3. The working set is smaller for the same reason as in Should I Build My Own C Runtime DLL?. 4. You can allocate memory in one module and free it in another. 5. You can open files in one module, read and write in another module, and close them in another module. 6. All exceptions are handled by one common runtime environment. If you do not own the whole application and you need to supply a standalone DLL, you should statically bind the C runtime to your DLL. Make sure that you use the #pragma handler on all the entry points to the DLL, to ensure that any exceptions that occur within your DLL are handled. However, you lose the advantages mentioned in points 4 and 5 above. VisualAge C++ includes the .DEF files that are used to build the runtime DLLs shipped with it. Do not be concerned about internal functions, because the linker links in everything it needs to resolve all external references. To make your own runtime DLL: 1. Rename the appropriate .DEF files that are provided in the LIB subdirectory where you installed VisualAge C++. For example, to create a multithreading DLL, rename CPPOM30.DLL to MYCRTDLL.DEF. Remember to change the DLL name on the LIBRARY line of the .DEF file. 2. Remove the STUB line from the .DEF file. Remove unwanted functions from the .DEF file. Remember not to delete anything that is followed by a **** comment: these variables and functions are always required. 3. Create an empty source file, for example, MYCRTDLL.C. 4. Compile and link the files as follows: icc /Ge- MYCRTDLL.C MYCRTDLL.DEF and use one of the following options: /Gm, /Gm-, or /Rn, according to what type of DLL you want to build. 5. Build the import library as follows: IMPLIB /NOI MYCRTDLL.LIB MYCRTDLL.DEF 6. Use the ILIB utility to add the necessary real objects to the import library, as follows: ILIB MYCRTDLL.LIB +CPPOx30.LIB where x is M, S, or N, according to whether you want a multithread, single-thread, or no runtime environment. 7. Compile your .EXE or .DLL files with the /Gn option. When you link, specify your own libraries, including MYCRTDLL.LIB and OS2386.LIB. If you want to rebuild the class library DLLs, see Can I Rebuild the User Interface Class Library DLLs?. ═══ 18.3. Can I Rebuild the User Interface Class Library DLLs? ═══ Question: I need to ship the User Interface class library DLLs with my application, but they are very large. Can I rebuild them so I use only what I need? Answer: Yes. To rebuild the User Interface class library DLLs: 1. Change to the ICLUIDLL directory under your install directory. 2. Extract the object files you need from the Open Class static library (in the LIB directory), using GETOBJS. For example: GETOBJS ..\LIB\CPPOOC30.LIB 3. Identify the .OBJ files you need for the classes you use. If your application uses any of the classes in a given .OBJ file, you need that file. Use the cross-reference tables in the Open Class Library Reference to determine which .OBJ files implement which classes. (The tables list the .HPP files, but also apply for the .OBJ files.) Note that some classes are implemented in multiple .OBJ files, which are numbered sequentially (for example, IRESLIB.OBJ, IRESLIB1.OBJ, and so on); for these classes, you must include all of these .OBJ files or none of them. 4. Edit the .RSP files and remove the lines for the .OBJ files you don't need. (The .RSP files are in the ICLUIDLL directory.) 5. Edit the .DEF files and remove the export entries for the .OBJ files you don't need. (The .DEF files are in the ICLUIDLL directory.) 6. Delete the .OBJ files you don't need from the ICLUIDLL directory. 7. Link the remaining .OBJ files, using the .RSP and .DEF files. For more information on the Open Class library and how to rebuild the library DLLs, see the Open Class Library User's Guide For information on building your own runtime library DLLs, see Should I Build My Own C Runtime DLL?. ═══ 18.4. Should I Bind Runtime Messages to My Application? ═══ Question: Should I bind the runtime message files to my application? What message files do I need? Answer: It depends. You do not need to bind them if your application runs only on machines where VisualAge C++ is installed, or if all of the following are true:  You do not use the assert, perror, or strerror functions.  You did not compile with the /Tm option to enable the debug memory management functions.  You do not want compiler-generated trap information.  You do not use any of the class library runtime messages. If you are shipping your application to other machines that may not have VisualAge C++ installed, you should probably bind the runtime messages into the application. You can use MSGBIND to do this. MSGBIND is described in the User's Guide. The message files you may need are: DDE4.MSG Messages for the C runtime library and for the I/O Stream and Complex class libraries. IBMCRERR.MSG Messages for regular expressions. DDE4C01E.MSG Messages for the Collection class libraries. CPPOOC3U.MSG Messages for the User Interface class libraries. DAXCLS.MSG Messages for the Database Access class libraries. The message files are in the HELP subdirectory under your main install directory. For information on specifying what messages to bind, refer to the MSGBIND section in the User's Guide. ═══ 18.5. Can I Use Multiple C Runtime Libraries? ═══ Question: I don't know which runtime libraries other people will use with my DLL. Is there any way I can use multiple runtimes? Answer: You can have more than one C runtime (and its associated environment) in an .EXE and its associated DLLs. However, the following restrictions apply: 1. You can read from and write to a stream opened in another library environment, but you cannot use freopen or fclose on it. 2. fcloseall, fflushall, and similar functions work only for streams opened in the environment in which they were called. 3. Signal handling is separate for each environment. 4. When you move from one environment to another, the entry point to the destination environment must have a #pragma handler statement to ensure that exception and signal handling functions correctly. 5. If a thread executes in several runtime environments, it may not completely clean up its thread-specific storage when it terminates. Normally this is not a problem, but can generate strange results if you depend on everything being set to default values when you start a thread (signal handlers, random number seeds, and so on). 6. Each environment has its own errno and _doserrno values. 7. The strtok, putenv, and getenv functions only work within a library environment. ═══ 18.6. How Do I Imbed a Copyright Statement? ═══ Question: What is the best way to imbed a copyright statement at the beginning of an executable file or DLL? Answer: Compile a small DOS application with the copyright statement in it, and use it as the DOS stub. It is put right at the beginning of the .EXE module. This ensures that the copyright is near the beginning of the file, and also sets up the executable so it can issue appropriate statements if it is run under DOS by mistake. The example that follows is similar to the stub used by VisualAge C++, and is based on assembler. You can create your stub using any DOS assembler or compiler. ;******* Data Segment (copyright statement) DSEG SEGMENT PARA PUBLIC 'DATA' __COPYRIGHT DB 0dh,0ah DB 'IBM(R) VisualAge(TM) C++ for OS/2(R), Version 3', 0dh,0ah DB '(C) Copyright IBM Corp. 1991, 1995.', 0dh,0ah DB ' - Licensed Material - Program-Property of IBM - All Rights Reserved.', 0dh,0ah DB 0dh,0ah DB 'This program will not run in DOS mode' DB 0dh,0ah DB 24h ; End marker for DOS call DB 1ah ; EOF marker DSEG ENDS ;****** Code segment (Assembler stub to print copyright in DOS) CSEG SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG,SS:STACK ;Already set by dos loader ENTPT PROC FAR ;entry point from DOS PUSH DS ;set up the stack to have XOR AX,AX ; the double word vector so the PUSH AX ; far return will go back to DOS MOV AX,DSEG ;set up addressability to MOV DS,AX ; the data segment ASSUME DS:DSEG ;tell assembler what I just did ;****** Call DOS to print the copyright notice MOV DX,OFFSET DS:__COPYRIGHT MOV AH,9 INT 21h RET ;Return to DOS ENTPT ENDP CSEG ENDS ;****** Stack segment STACK SEGMENT PARA STACK 'STACK' DB 512 DUP(?) ;512 Byte Stack STACK ENDS ;****** Mark the entry point END ENTPT ═══ 19. What If I Still Have Questions? ═══ Question: I've looked through this FAQ, and I can't find the answer to my problem. What do I do now? Answer: Contact VisualAge C++ Service and Support. This section tells you how. Telephone support in the USA and Canada VisualAge C++ has a free 60-day Getting Started period (GSP) to help you with installation, usage, and how-to problems. Call 1-800-237-5511. The 60-day period starts on the date of your first call, and is offered 9-5 (in your time zone), Monday through Friday. If you need service outside of these hours during the GSP, service charges apply. Call 1-800-237-5511 for details. After your 60-day GSP, we offer a wide menu of service options tailored to a full spectrum of customers needs. Again, call 1-800-237-5511 for details. Quite often, you may not need technical support, for example when you have questions about CSD levels and availability, registration cards, or beta tests. In recognition of these questions, our automated response system contains a wealth of useful information. You can also receive much of that information by fax. The response system is available 24 hours a day, 7 days a week. Call 1-800-668-2853. (Outside of North America, you can access the same system at 416-448-4363.) Telephone support outside North America For local country support, please contact your IBM branch for details. Electronic support - worldwide You can also contact VisualAge C++ Support electronically. There is no charge for this service, other than what you normally pay for your electronic access.  Compuserve (OS2DF1)  INTERNET - va_cpp@vnet.ibm.com - workframe@vnet.ibm.com for WorkFrame-specific questions  IBMLink/ServiceLink - VA C++ forum - ETR (electronic reporting of problems)  TalkLink - VA C++ forum - ETR (electronic reporting of problems) If you frequent the Internet, you can also contact other knowledgeable VisualAge C++ users on the USENET newsgroups. (VisualAge C++ discussions often appear in the comp.os.os2.programmer hierarchy.) Other defect reporting channels in the USA and Canada You can also report possible VisualAge C++ code-related problems using any of the following methods: FAX 1-800-426-8602 MAIL IBM Corp Personal Systems Support Family 11400 Burnet Road Internal Zip 2901 AUSTIN, TX 78758 ELECTRONIC CompuServe - 76711,611 Other defect reporting channels outside the USA and Canada Other IBM countries offer mail-in and fax support. Please contact your IBM branch for details.