Michael Geary
The Microsoft WindowsÔ graphical environment Version 3.0 is here! The new version of Windows1 presents a revamped memory management system and scores of other improvements for both users and developers. Let's take a quick tour of Version 3.0 from a user's perspective, and then we'll get into some programming details. You will notice one difference even before you open the box. There's now one version of Windows; Windows/286Ô and Windows/386Ô have been merged so the same version works on all machines. (As in the past, Windows quietly supports 8088/8086 machines, but runs well only on the fastest of them.)
Once the box is open, the first thing you have to do is run SETUP. The Windows 2.x SETUP program confronted the user with screen after screen of questions about every detail of installation. Now, SETUP begins with a few character-mode screens containing only the most critical information (about your display, mouse, keyboard, network): enough to get Windows up and running. Then a graphics screen comes up and the remainder of SETUP runs as a Windows application to complete the installation. The improved look of 3.0 is already apparent here, at least on a VGA or 8514/a monitor. The buttons are three-dimensional, there are color icons, and the proportional system font is used everywhere.
The graphical part of SETUP has an advice window at the bottom of the screen that always has a suggestion about what to do next. Full help is always available too, via the F1 key or a click on a question mark icon. The help system is a spiffed-up version of the hypertext help introduced in Microsoft Word for Windows Version 1.0. Key phrases in the help text for each topic are underlined; they lead to other help topics when you click them. When you click on words and phrases with dotted underlines, a glossary window pops up with the appropriate definition. The best part is that this help system is used by all the mini-applications shipped with Windows, and can also be incorporated into your own Windows applications.
When SETUP wants to update your CONFIG.SYS and AUTOEXEC.BAT files, it doesn't just change them without your input as so many install programs do. It offers you three choices: to update them automatically, to save the edited versions under different names, or to edit them interactively in a little dual-file editing window that shows the old and new versions.
The old SETUP had its own routine to install printers, which was completely different from how you would install them later using Control Panel. The new SETUP actually runs Control Panel in a guided session-that advice window is still there-so you are already learning about the tools you will be using later on. In the same way, SETUP runs Notepad to view the various README files instead of using the funky file viewer in the old SETUP. The idea here is to use the Windows tools instead of special purpose tools, so you have immediate practice in using Windows.
The biggest change in SETUP doesn't become apparent until some time after you have installed Windows 3.0. Every Windows 2.1 user who has changed their display card or mouse knows what a pain that is-you have to reinstall Windows from scratch. Many developers used the "slow boot" method of installing Windows to make it easier to change drivers, but this method wasn't really available to end users. All that has changed. Now Windows always installs itself with the drivers in separate files, and there is a version of SETUP you can run inside Windows to change your display, mouse, keyboard, or network drivers (see Figure 1). If you ask for a driver that isn't on your hard disk, SETUP will ask you for the appropriate diskette and copy it to the hard disk. For drivers that have already been copied to the hard disk, it will just switch back and forth using those copies. This gives users the ability to change their Windows hardware configuration painlessly.
After Windows is fully set up, it can run in one of three modes: real mode, standard mode, or 386 enhanced mode. Real mode is the "traditional" way of running Windows with all the limitations it always had. Standard mode and 386 enhanced mode are where things get exciting: both exploit the protected mode of the 286 and 386 processors. This permits Windows applications to access all the physical memory in the machine. The 386 enhanced mode goes even further; it provides virtual memory by swapping 4Kb pages out to disk. It's neat to open up the About box the first time and see that you have perhaps twice as much available memory as actually exists in your machine. Windows by default runs in the best configuration it can. Unless other protected-mode software prevents it, this usually means 386 enhanced mode on a 386 or standard mode on a 286. You can always select a lesser mode if you need to by running WIN /S for standard mode or WIN /R for real mode. The most common reason for doing this would be to run some older Windows application that doesn't work in protected mode; WIN /R allows most older applications to run.
One surprise in installing the new Windows is that it doesn't have the PIF directory with dozens of PIFs (Program Information Files) any more. PIFs are not used as extensively as in the past: Windows will now run just about any non-Windows application without a PIF. Part of what made this possible is that Windows no longer attempts to run a non-Windows application in a window, except in 386 enhanced mode. In real or standard mode, the only way to run a non-Windows application is to switch it to a full screen. This is no great loss, since so few non-Windows applications worked in a window anyway. Besides eliminating the need for the "Directly modifies screen" option in the PIF Editor, this change means that most applications can now run with the same default settings founds in _DEFAULT.PIF. PIFs are now used more to fine-tune settings for improved performance rather than to make it possible for non-Windows applications to run at all. Each PIF contains settings for real, standard and 386 enhanced mode. In 386 enhanced mode, the advanced options in PIFEDIT.EXE (see Figure 2) permit you to customize the application's behavior as much as you want. Non-Windows applications in 386 enhanced mode run a lot faster these days and seem to be more reliable.
There are two things that SETUP doesn't do for you that it really should. One is set up a permanent swap file for 386 enhanced mode. If you're running Windows on a 386 and haven't done this yet, run SWAPFILE now. Setting up a permanent swap file of contiguous disk space does wonders for Windows' performance. The other thing SETUP could do is put a WIN command at the end of AUTOEXEC.BAT. Pretty radical, I know, but this is the first version of Windows where it's really feasible to make it your primary environment.
Program Manager
If any feature of the old Windows has received more well-deserved criticism than the old SETUP, it is the MS-DOS Executive. The good news is that the MS-DOS Executive is no longer the primary shell used by Windows. Now, Windows starts up in the Program Manager, an icon-based program starter. Instead of presenting just a list of whatever files happen to be in the current directory, the Program Manager's main window uses the Multiple Document Interface (MDI) to present several program group windows in its main window (see Figure 3). The program groups contain icons for applications or documents. The Program Manager is able to look inside Windows executables to find their icons; you can either use an application's usual icon or substitute one from any other file.
SETUP creates an initial set of these program groups, which contains the standard applications that ship with Windows. It also optionally scans your hard disks looking for other applications and creates groups for them. Later you can add or remove icons, rearrange the groups, and so on. The Program Manager is easy to customize within its limitations: all it does is let you manually start programs, one by one. It would be nice to be able to put together a group of programs that work together and somehow just say "Start this group." Ideally, you would even be able to save the state of several running programs so you could restart them later in the same screen arrangement with the same documents open. Third parties have filled in this gap in Windows; for example, hDC FirstApps includes a program called Work Sets, which saves the states of multiple applications and lets you restart them as a group.
Perhaps a more serious omission in the Program Manager is the inability to specify an initial directory for an application. The Program Manager provides for only a single pathname with a program, usually the directory containing the program itself. You can use this pathname to specify a directory other than the one the program is in, but only if the program is in your PATH. Windows does let you specify separate program and initial directories, but only via PIFs for non-Windows applications!
File Manager
Unlike the MS-DOS Executive, the Program Manager has no file-management facilities. These facilities are now in a separate program, File Manager. The Windows File Manager is nearly identical to the OS/2 1.2 File Manager; it uses MDI to allow several views of your file system. The File Manager first shows one directory tree window that displays the first level directories of the current drive. This window lets you expand and collapse any level of the directory tree to see the directories you want (see Figure 4). To look at files, double-click (or press Enter) on a directory; another window will open up. Once you get to that window, all the usual file operations are available: copy, rename, delete, and so on. These operations work the same way as in the MS-DOS Executive. However, to copy or move files now, you can just grab them with the mouse and drag them into a new directory.
The File Manager is a good program for manipulating files, but it does have some annoying design flaws inherited from OS/2's File Manager. The worst is having only one directory tree. If you want to view the tree of a different drive, you can't open up a new tree window, you've got to switch drives on the single tree window. In the process you lose track of which directories were expanded and collapsed in the previous drive. Working with multiple drives in the File Manager is an exercise in frustration. For DOS diehards like me, the best bet may be to open a COMMAND.COM window and use good old DOS commands. Especially in 386 enhanced mode, DOS command windows are really handy. (A tip here-make yourself a COMMAND.PIF file and set the Display Usage option to Windowed, so COMMAND.COM will start up in a window instead of a full screen.) Even the MS-DOS Executive is still available. It's in your Windows directory, little changed from previous versions except for its use of lowercase in filenames. I still use the MS-DOS Executive more often than I'd expect. It's not as fancy as File Manager but it starts up a lot faster, since it doesn't have to read through all your directories. So a File Manager fan I'm not: your mileage may vary.
The third new feature of the Windows shell is the Task List. You can open this window by typing Ctrl-Esc or double-clicking on the screen background. It's a simple list of all the current top-level application windows, with buttons at the bottom to switch to a window, terminate a program, arrange the icons at the bottom of the screen, or cascade or tile all application windows. The tiling feature is handy, but it would be better if options were provided to push window borders off the screen and to tile by rows instead of columns. I prefer tiling in rows, because if you have two application windows on a typical 640x480 screen, it's often more useful to put one above the other rather than side by side.
Enhanced Look
Windows 3.0 applications look different from Version 2.1 applications. The proportional system font gives them more readable text in their menus, title bars, and dialog boxes, and the screen is a little more colorful because the standard EGA and VGA drivers have been upgraded from eight colors to sixteen. The Video 7 VGA and 8514/a drivers provide 256 colors at a time, out of a much larger palette of available colors. Also, icons are now full-color instead of black-and-white.
Three-dimensional push buttons, scroll bars, and minimize/maximize icons contribute to the enhanced appearance. But it does seem a little strange to see the system menu icon, flat as always, after all the other three-dimensional features. Also, in some cases, the three-dimensional look was implemented in a rather cumbersome way.
For the scroll bars and min/max icons, there are actually two bitmap resources for each item-one for the normal state and one for the pressed state. It's quite easy to produce a good three-dimensional button effect without two different bitmaps. That's how push buttons are implemented: you create a bitmap that includes just the "surface" of the button, not the edges. Surround the bitmap with lighter pixels on the top and left and darker pixels on the bottom and right. To "push" the button, slide the "surface" down and to the right and redraw some darker pixels on the top and left. Although it takes some work to write this routine, you have your three-dimensional effect working consistently, using any bitmap you like. I'd recommend this approach for your own programs rather than the two-bitmap approach.
New and Improved Applications
Most of the mini-applications packaged with Windows have been improved; some were rewritten entirely. No one will be too sorry to learn that Paint is gone, replaced by the much better Windows Paintbrush from ZSoft. Paintbrush isn't without flaws. The Zoom In feature is absurd for small images-the zoomed image is crammed into the same tiny space as the original image, leaving hardly any room to edit the pixels. And don't even try to paste in a full screen image captured with the PrtSc key-it will get clipped to the current window size. But Paintbrush supports color and larger images, and it can create BMP files as well as its own PCX format. I'll take it over Paint any day.
The old Terminal is also gone, replaced by a new terminal program from Future Soft. It's a much more capable program, with modem command strings that can be customized, definable function keys, VT100Ô emulation, and XMODEM and Kermit file transfers. My little Hewlett-Packard 48SX calculator happens to speak Kermit too, so I plugged it into the new Terminal and they happily sent files back and forth. The old Terminal wasn't good for much, but it did have one feature that I really liked-the scrollback buffer would save up to 999 lines. The new Terminal only allows up to 399 lines of scrollback. Another curiosity is that the Modem Commands dialog box lets you specify a string to put the modem into auto-answer mode, but there is no command anywhere to let you use this string. If you want to answer a call, you'll most likely have to type in a Hayes "AT" command, like ATS0=1, yourself.
Control Panel has been redone, and everything is more logically organized. In particular, setting up printers is done far more sensibly. You can set them up from one dialog box instead of having to bounce back and forth between the Printers and Connections dialogs as in the old Control Panel. Setting up screen colors is simpler, too. You can choose from a dozen predefined color schemes, or you can customize each screen element (see Figure 5, and the sidebar "WIN.INI Color Settings").
There is now a keyboard speed setting in Control Panel, but anyone who has written or used other programs to control keyboard speed will be surprised to see there's only one speed setting. PC keyboards have two settings: initial delay and repeat rate. Control Panel unfortunately always sets the initial delay to be somewhat on the slow side. To solve this, remove the KeyboardSpeed= line in your WIN.INI file, which will disable the Windows keyboard speed setting. Then simply put your favorite key speed setting program in your AUTOEXEC.BAT. It's best if this is the kind of program that just sets the speed in the keyboard hardware, not a program that stays resident and uses timer interrupts and such. Doing this lets Windows leave your speed setting alone. If you ever click open the Keyboard dialog box in Control Panel, Windows will go back to its own setting-even if you Cancel the dialog box.
Other new mini-applications include Recorder, a program that uses Windows' journaling hooks to record and play back keyboard and mouse activity. It's reasonably easy to use and very useful for recording quick shortcuts and longer sessions for testing or demos. Print Manager replaces the old Print Spooler and provides more control over the print jobs. Notepad now supports files of almost 64Kb. Clipboard has several new options, including the ability to save and load files containing the clipboard contents. Calculator can now switch between being a full-fledged scientific/programmer's calculator and a simple "four-function" model. And not to be left out, Clock now has an appropriately hard-to-read digital option. Finally, there's a dangerously addictive Solitaire program with terrific graphics (see Figure 6).
Developing in Windows
The big news in Windows 3.0 for developers is of course protected mode. Windows applications are finally free of many of the memory constraints that bogged down previous versions of Windows. True, Windows 2.x, with its support for EMS and XMS, was an improvement over 1.03, but it did not solve The Problem: you could buy all the memory you wanted, but you couldn't get at it with a GlobalAlloc. Working with memory that's constantly moving has terrorized a generation of Windows programmers. Show me a Windows programmer who really likes GMEM_MOVEABLE and I'll show you someone who hasn't seen their first Invalid Global Heap.
In simplest terms, running in protected mode means that all extended memory is directly available through normal calls like GlobalAlloc and GlobalLock. Protected mode permits any application to allocate as much memory as it needs, up to the limit of physical memory, unlike EMS, where more applications can be run but each application is still subject to the 1Mb limit. And in 386 enhanced mode, you can go beyond the limit of physical memory to allocate virtual memory, which is swapped to disk when physical memory is overcommitted. Either way, the same memory management functions that a Windows application used in real mode now work in protected mode. In fact, normal well-behaved Windows code will run identically in both modes.
The strange thing is that the handle-based movable memory system is quite literally a simulation in real mode of protected-mode addressing. The protected mode of the 286 and 386 provides direct hardware support for the kind of memory management that Windows provides, without the hassles on the application programming side. If it had been planned this way from the start, and if (a big if) real mode support could be dropped, Windows programming could be a lot simpler. A lot of the extra memory management work you do in traditional Windows programming is simply to help Windows simulate protected mode in real mode. This happened, as they say, "for historical reasons."
Back in the days when 64Kb was more than enough memory, our CP/M and Apple II systems addressed it in the simplest way: linear physical addressing. Each byte of memory had a unique physical address, and you accessed memory directly by using its physical address: no mapping, no funny tricks (see Figure 7). Since the width of the address registers determines how much memory you can access, an 8080/Z80 machine with a 16-bit address could use 216, or 65,536 (64Kb) bytes of memory. Addresses 0 (0000H) and 65,535 (FFFFH) were indeed the first and last bytes of memory (assuming you could afford a full 64Kb). Ah, life was simple.
Then it was time to put the 8086 (and 8088) together, and the designers at Intel got clever. Too clever, some might say. They wanted to provide access to more than 64Kb, while at the same time sticking with 16-bit registers to keep things simple-simple for the chip, that is. So take a 16-bit register and shift it four bits, and you can address a full megabyte (220 bytes). The only problem is that you can only address in 16-byte increments. But add in another 16-bit register (not shifted) and you have our beloved 16:16 bit segmented addressing system (see Figure 8). The less said about this, the better.
Memory fragmentation is a big problem in most memory management schemes; memory compaction with movable blocks is one sure way to avoid fragmentation. But movable memory means that you can't use just conventional pointers-the memory you're pointing to might move (as every Windows programmer is painfully aware). Due to the relative difficulty of programming this kind of system, most memory management packages have opted for the fixed allocation approach even if it means suffering some fragmentation. The C run-time library functions malloc and free are a good example. They're easy to use, but you can't avoid fragmentation with these.
Graphical user interfaces gobble up memory compared to simple character-based programs, so both Windows and the Macintosh use handle-based movable memory systems. This permits developers to manage memory without incurring fragmentation and write larger programs with more consistent memory behavior. However, these two systems present the movable memory to the application programmer differently. Both have a table of master pointers, pointing to each movable block. When a block gets moved, its master pointer gets updated. The handle you get back when you allocate a block is in some form an index into this master pointer table. But from the application programmer's point of view, the Mac's method is more convenient by far: the handle for a movable block is literally a pointer to the master pointer for that block. All you have to do to get to a block's data is a double indirection instead of the single indirection you would use with a straight pointer. In C, you simply use ** instead of * in what otherwise looks like a normal pointer dereference. You have to be careful-the memory you are looking at is not locked down and could move if you somehow cause a compaction-but all in all it's a wonderfully convenient technique. There are Lock and Unlock calls for when you want to play it safe, but they don't need to be used all that often.
In Windows, on the other hand, you go through a lot more aggravation dealing with movable memory. You aren't allowed to do much with the handle you get back from GlobalAlloc directly. (For a fixed segment, of course, the return value from GlobalAlloc is actually the segment address, and you can directly construct a far pointer from that.) You have to pass the handle to GlobalLock or GlobalHandle to get a pointer to the data (see Figure 9). A global handle is in fact an offset into a table of master pointers, found in the ever-mysterious BurgerMaster segment. With near and far pointers to deal with, a simple double indirection may not have flown, but some kind of simple macro, or even a special C pointer type, could have provided direct access to movable data. The Windows designers chose, however, to make you go through the explicit GlobalLock call to get to the data. This has the advantage of always locking the data when you're using it, but I think anyone who has programmed both the Mac and Windows will agree that the Mac's double indirection is a lot easier to deal with.
GlobalLock is a pain, but there may have been method in Microsoft's madness. In protected mode the 286 uses mapped memory, not direct physical addressing. 16:16-bit real-mode segmented addressing uses a direct physical address calculation. In protected mode the segment becomes instead a selector, which is really an index into a table where the physical address can be found. Two tables, the Global Descriptor Table (GDT) and Local Descriptor Table (LDT), are used by the protected mode addressing hardware along with the segment selector to find the physical address (see Figure 10).
If this sounds a lot like BurgerMaster and GlobalLock to you, you're right. Just as Windows in real mode keeps BurgerMaster up to date with segment movement, it keeps the GDT and LDT up to date in protected mode. There is still a global heap with segments that move around, but application software doesn't have to deal with the segment movement. Each reference to memory goes through the addressing hardware, which does the GDT/LDT lookup and calculates the correct physical address. It's just like what GlobalLock does for you when it looks up the handle in BurgerMaster, but it happens behind the scenes.
This means that protected mode provides the benefits of movable memory without the drawbacks. How can Windows take advantage of it in a way that is reasonably transparent to existing code? First, when you call GlobalAlloc(GMEM_MOVEABLE), the "handle" you get back is actually a selector. You could go ahead and use this selector to access the memory, but if your code was written for real mode, it regards the handle as a "magic cookie:" an identifier and nothing more. You pass the handle to GlobalLock when you want the address, and GlobalLock returns the address-the same selector! (It's formatted into a far pointer, of course, and the low order bit of the selector is turned on. In the "handle" form of the selector, the low order bit was off. There is a reason for this, as we'll see.)
GlobalLock can get away with this because the protected-mode memory addressing hardware is doing its work. If a selector is automatically dereferenced through the GDT/LDT as needed, there isn't much left for GlobalLock to do but convert it into a far pointer. Even the locking aspect goes out the window. In real mode, a segment is locked to keep it from moving or being discarded. The GDT and LDT of protected mode make these operations redundant, because they indicate whether a segment is in memory at all as well as its physical address if it is. If you try to reference a segment that isn't in memory, the hardware traps that and Windows takes care of it. So there's not much need for a lock count, and GlobalLock's work is easy.
There's one case in real mode where GlobalLock is nearly this lazy. A GMEM_FIXED segment isn't going to move, so Windows doesn't bother with a BurgerMaster entry. When you call GlobalAlloc(GMEM_FIXED) in real mode, the "handle" you get back is in fact the actual segment address. All GlobalLock does with it is construct a far pointer with the segment and a zero offset. There is no low-order-bit twiddling as I mentioned above, because the "handle" already has the low order bit on. This is how GlobalLock figures out whether the "handle" you pass it is a BurgerMaster offset or a GMEM_FIXED segment. If the low order bit is off, it's a handle; on, it's a segment. That is, handles are even, segments are odd. Microsoft might prefer that you didn't know this, because you might write code that inadvertently or even-dare I say-intentionally depends on it, and they might want to change it in the future. So you didn't hear it from me. But for the time being, that is how it works. (I'm ignoring several other special cases that GlobalLock has to check.)
Because segments do not move around (at least their selectors don't change) in protected mode, all sorts of other simplifying assumptions are possible. Reload thunks, those little SAR/JMP sequences generated for movable/discardable far functions in real mode, go away completely. You need these in real mode because there has to be a fixed entry point, even for a movable function, so you can stash its address away in a pointer variable, and because there has to be a way to know when to load in a segment that's not in memory. The SAR/JMP changes to an INT 3FH for functions that aren't in memory; that INT 3FH is how Windows can magically load in a discarded code segment when it's called. But in protected mode, every segment looks like it's FIXED because it always has the same selector. Also, if you try to reference a segment that isn't in memory, the CPU will cause a Not Present exception interrupt, which tells Windows when to load in your segment. Reload thunks are just part of Windows' real-mode emulation of protected-mode memory management, so you won't find any if you're not in real mode.
You can play similar tricks yourself. If you know you are in protected mode, you don't have to lock and unlock segments all the time. You get a chunk of memory with GlobalAlloc and perform just a single GlobalLock call on it-or just make a pointer from the segment selector returned by a GlobalAlloc(GMEM_FIXED)-and keep using that pointer until you are finished with the memory. You won't be interfering with Windows memory management as you would be in real mode; remember that Windows can still move physical memory around all it likes and never tell you about it, as long as it keeps the GDT and LDT up to date. (There are functions, GlobalFix and GlobalPageLock, that actually lock down a segment in the physical linear address space, and they would of course cause the same fragmentation problems as always. Few programs should ever have to use these.)
This Can't Be Real!
Here's the bad news. Real mode is still with us. There's no mercy here. Just when we discover how nice it is for our applications to have enough memory to run and we don't have to worry about dangling far pointers, some user pops our balloon by starting Windows with a WIN /R command, or by running on an 8088 or 8086 (see Figure 11). This puts us back where we started, complete with all the hassles of EMS. Windows does run fairly well on a fast (10MHz or better) 8086 machine, but it can't run protected mode on one. So we still have real mode to kick around.
There are some improvements in Windows 3.0 for managing memory in real mode. GlobalLock and GlobalUnlock have a completely undeserved reputation for speed. But you don't have to use them as much-there's an alternative called the private handle table, which is a table in your DGROUP that contains a list of global segment addresses. A DefineHandleTable call tells Windows where your table is. When you allocate a GMEM_MOVEABLE segment, call GlobalHandle to get its current (unlocked) segment address, and put this segment address in an empty spot in your table. Internally in your code, you then use a private handle of your own, which is really an offset or a pointer into your table. You can define this private handle any way you want, as long as it gets you to a table entry. Whenever Windows moves a segment in memory, it scans all private handle tables looking for references to that segment and updates them. When you need to get to a segment in memory, use your private handle to get to the correct entry in your table, and you will find the current segment address. The private handle table really isn't anything new; it's been in Windows since Version 2.0, it's just not a secret anymore.
The one catch with the private handle table is, of course, that its addresses are the addresses of unlocked segments. Segments can still move around in real mode; therefore, a far pointer derived from the handle table has a lifetime shorter than a mayfly. In addition to the well-known situations in which memory can move, any far call to a discardable segment can also move memory. So don't hold onto one of these pointers for very long. To protect against this, I add a nested block in C with the segment pointer declared inside that block. The block is as short as possible and includes only the code that needs to use the pointer. Since the pointer variable name is local to the block, the compiler will give me an error message if I use this pointer farther down in my code after I might have made a far call. Now, alarms go off in my head when I see a pair of curly braces without some keyword above them. It looks like there is a missing statement above the opening brace. One solution is to add a new keyword to the C language, scope, that does nothing but look better than a blank line. Code then looks like this (assuming a SEGDEREF macro that takes a private handle and returns the current far pointer to a segment):
// define a no-op placeholder keyword
#define scope
o
o
o
// We are inside a function with a private handle
// variable "hfoo" that points to a segment
// containing a FOO structure, and we need to copy
// a couple of items out of the structure into
// local variables.
// The "scope" keyword does nothing, but clarifies
// the purpose of the extra curly braces.
scope
{
LPFOO lpfoo = SEGDEREF(hfoo);
item1 = lpfoo->item1;
item2 = lpfoo->item2;
}
// Now item1 and item2 have our data. The "lpfoo"
// pointer variable is safely undefined, so we
// can't accidentally use it after memory moves.
The private handle table works in both real and protected mode. In protected mode, Windows never bothers updating the table, but then the "segments" it gives you are selectors that aren't going to change anyway. At least that's true for segments of 64Kb or less. With segments greater than 64Kb, Windows may have to give you back a different selector on a GlobalReAlloc call, because it has to find a contiguous range of selectors to allow addressing the entire segment. The private handle table doesn't take this into account, but other than this, it works in either mode. If you want to be a Good Programmer, you write your code so it runs in both real and protected mode. For many programs, all you have to do is code as you always have for real mode, avoiding any violations of the protected-mode rules set forth in the Software Development Kit (SDK).
You start running into problems with large applications. When your programs get past a certain size, there's just no room left in real mode and you have to resort to drastic measures like supporting EMS or writing your own disk swapping routines. It's tempting just to switch to protected mode completely and avoid all that, but then you'd have to give up the 8088/8086 market.
The best approach is to support all memory modes whenever it's possible. If your application is small enough to run reasonably well in real mode, by all means code it so that it's compatible with both modes. Or if you already have code that uses EMS or swaps to disk to let you run a bigger app in real mode, keep that code around-it can still serve you in real mode even if it isn't needed in protected mode. A typical approach here is to try GlobalAlloc first, and if that fails try allocating EMS or swapping something else out to disk. Under 3.0, this gives you the best of both worlds. In real mode, it will work just like under 2.1. In protected mode, the GlobalAlloc will fail much less often because you have so much memory available. When it does, you would skip EMS because it isn't available in protected mode and your disk swapping code would kick in.
But if your app is bursting at the seams in real mode, if you're facing months of tuning for real-mode performance to be acceptable, if you're having to write memory management code instead of writing your application, maybe it's time to switch to protected-mode-only programming. You won't run on 8086/8088 machines, but an application that large probably wouldn't run very well on them anyway. If you're really backed into a corner with real mode, go ahead and require protected mode. But be warned: if you are lucky enough to be able to write protected-mode-only code, you will never, ever want to go back to coding for real mode.
Even in protected mode, there are still places you can run out of memory long before global memory is exhausted. Your own local heap is one, of course, but you should also consider GDI's local heap and USER's local heap. USER's local heap usually runs out first. Every window created uses space in USER's heap, as does the text of each menu item and window title. You can see this in action if you have a lot of icons and program groups in Program Manager-when I added a bunch of icons to my original groups, the Free System Resources percentage displayed in Program Manager's About box dropped from close to 90 percent down to 65 percent. (The Free System Resources number is the percentage of free space in either USER's heap or GDI's heap, whichever is currently more crowded.) Those innocent little icons were eating up 25 percent of USER's available heap space!
Standard versus Enhanced Mode
Although standard mode and 386 enhanced mode look nearly identical from the point of view of a Windows application, they're implemented quite differently. In standard mode, a simple DOS extender, DOSX.EXE, handles switching between real and protected mode, selector allocation, and other services that Windows needs (see Figure 12). DOSX does not provide multitasking for non-Windows applications, nor does it create virtual machines-it has to be compatible with both 286 and 386. It does use a few 386-specific features like faster mode switching when it is on a 386.
The 386 enhanced mode is completely different. Instead of the simple DOSX.EXE, there's a Virtual Machine Manager that creates virtual machines and multitasks them, as well as a set of virtual device drivers needed for virtual machines (see Figure 13). This, of course, was developed from the Windows/386 code, but goes beyond it. In particular, Windows 3.0 virtual machines include not only the virtual 8086 environment that the 386 hardware provides, but also an optional protected-mode portion. The first virtual machine created-the System VM-has a protected-mode portion used for running Windows applications. Other virtual machines may have only an 8086 (DOS) portion, but they can just as easily have a protected-mode portion, allowing other kinds of protected-mode applications to run in 386 enhanced mode.
Proportional System Font
After protected mode, the change in Version 3.0 that may affect the most existing code is the switch to a proportional system font. At the very least, dialog boxes need to be redone to allow for changes in text string size. Any code that assumed a fixed-pitch system font-for example, code that gets the height and width of one character and multiplies that by the number of characters to calculate string length-will have to change. You must now use GetTextExtent or GetCharWidth to calculate string lengths. The Windows edit control code had to be completely rewritten for Version 3.0, largely because of the switch to the proportional font.
You may also have some code that knows the physical size of the dialog units used in specifying dialog coordinates. For example, I've worked on a couple of programs that created windows that acted a lot like dialog boxes. I kept track of window sizes in units directly proportional to the dialog unit size, to achieve the same modest degree of device-independence that dialog boxes have. I just did a GetTextMetrics on the system font and divided the height by eight and the width by four. I thought this might be OK for a proportional system, if TextMetric.tmAveWidth was used instead of TextMetric.tmMaxWidth. Not quite. Windows does a special calculation of the system font's average width (tmAveWidth obviously wasn't good enough). To get the system font average height and width, you have to call GetDialogBaseUnits, still dividing by eight and four if you want the dialog units.
Since you also can't use spaces to line up columns of text with a proportional font as you can with a fixed-pitch font, list boxes and edit controls now support tab stops. Send a LB_SETTABSTOPS or EM_SETTABSTOPS to specify the tab positions, then embed tab characters in your text to line up your columns. For text you are painting explicitly in your own code, use the new GetTabbedTextExtent and TabbedTextOut functions to support your own tab stops.
The proportional system font will cause extra coding pain for some applications, but for others it could save a huge amount of work. Because all the standard control classes now support the proportional system font, they support all fonts. (All bitmap fonts, anyway.) You can send a WM_SETFONT message to any control to select its display font and WM_GETFONT to find out a control's current font. If you were about to write your own edit class just because you needed to support proportional fonts, stop. The same is true for list boxes and the other classes. They will all work with any font you can name, making the standard controls useful in more cases than before.
GDI Goodies
One of the most visible changes in the new Windows is the greatly increased use of color, especially color bitmaps. The old Windows bitmap format was not quite good enough for providing this extra color. In the old format, a color bitmap was organized to match a specific display device; monochrome bitmaps were portable, but color bitmaps weren't, which is why you didn't see them very often.
The bitmap portability problem is solved in Windows 3.0 by the appropriately named Device Independent Bitmap (DIB). Instead of being tied to a specific display, the DIB is in a more general format that is portable across devices but can still be converted reasonably quickly into any particular display format. Each pixel in a DIB can be represented by 1 bit (monochrome), 4 bits (16-color EGA/VGA), 8 bits (256-color Super VGA/8514), or 24 bits (the software is ahead of the hardware for a change, at least for display adapters in common use). In a 24-bit-per-pixel DIB, the 3 bytes representing each pixel directly specify an RGB color value. When there are fewer than 24 bits per pixel, the bits representing each pixel are actually an index into a color table at the beginning of the bitmap. This color table provides the full RGB values, allowing a bitmap to be saved in a more compact format as long as it uses a small number of colors, even if those colors themselves might be composite.
DIBs are easy to create and use. When you save a file in Windows Paintbrush, the Options button gives you the choice of any of the four DIB formats as well as the PCX format. The four DIB formats use the BMP extension even though they are not in the old bitmap format; you can make them into resources and use LoadBitmap on them just like before. LoadBitmap converts the bitmap from DIB format to the appropriate display format when it loads it. Alternatively, there is a set of functions-CreateDIBitmap, CreateDIBPatternBrush, GetDIBits, SetDIBits-that can be used to create memory bitmaps and pattern brushes from DIBs directly. Another option is to skip the memory bitmap step completely and use SetDIBitsToDevice (StretchDIBits if stretching is required) to transfer the DIB to the destination device directly. This saves memory, but it can be slower for bitmaps that are painted repeatedly because the format must be converted each time the bitmap is drawn. Another memory saver is the compression in DIBs-there are several run-length encoding (RLE) methods used to save space.
The new multiple resolution icons and cursors are similar to DIBs, but implemented differently. When you create an icon or cursor using SDKPaint, you have the option of including multiple images for different display devices, tuning the images for each device. Icons can be in full color on color devices, but cursors are still limited to black-and-white. LoadCursor and LoadIcon select the best match for the current display out of the images you supply. As before, if there is not an exact match, the functions choose the best match they can find and scale it to fit the resolution.
It's convenient that DIB pixels can be lookups into a list of RGB color values, because that's exactly the capability many display cards have-there are many color choices, but only a fraction of them can be shown onscreen at one time. For example, 8514 and Super VGA monitors can display 256 simultaneous colors out of 262,144 possible choices. It's better to be able to specify which colors go into that usable palette; you can do a lot more with 256 colors when you get to pick them yourself. The only problem is that in Windows, you can run several applications at once. If your application changed the display palette, it would disturb every other application since they're all sharing one screen.
Palette Manager
The new Windows Palette Manager was designed for this. This facility mediates among applications that use many colors and need to modify the display palette. Applications can create their own logical palettes containing color values they need, then use those color values in normal GDI calls. The Palette Manager maintains a separate system palette with the current physical display colors. The active application gets first choice of those colors; any leftover colors are used for inactive applications. The Palette Manager matches the logical palettes with the available physical colors, giving inactive applications the closest matches it can find after the active application has taken all the colors it needs. To allow title bars, menus, and the like to be painted in their normal colors, 20 colors are reserved. That way, even if the active application takes all the colors it can get, the Windows screen will pretty much look correct. An application with a great many colors can, with a little extra work, even temporarily steal all 20 system colors, except black and white.
Using the Palette Manager is fairly easy. You create a logical palette with CreatePalette, which takes a list of RGB colors and transforms them into a GDI logical palette object. Select the palette into a device context with SelectPalette, and call RealizePalette to have the Palette Manager match your colors with the system palette and install the system colors it needs for you. Then you can use your palette in GDI functions like CreateSolidBrush by using specially coded color values. The color parameter to such functions is now called a COLORREF, and its high order byte tells what kind of color reference it is. A zero in the high order byte indicates that the color reference is an ordinary RGB color. You can still use a direct RGB color in Version 3.0 when you're working with the Palette Manager, but you will probably prefer to use the palette that you set up. If the high byte has a value of 1, the low order 16 bits of the COLORREF are an index into the list of colors in your palette. A device-independent bitmap can use palette colors by listing palette indices in its color table instead of RGB colors.
If you're running on a 24-bit color device, the palette system gets in the way. Why bother with a list of colors when the entire spectrum of RGB colors is available directly? The best method of managing colors would be to specify an RGB color for the 24-bit case but still be able to use your logical palette when on a palette device. There is a way to do that. When the high order byte of a COLORREF contains 2, the low 3 bytes are interpreted as a palette-relative RGB value. On a 24-bit system, this is used directly as a 24-bit RGB value. On a palette system, the Palette Manager matches this RGB value with your logical palette and uses that palette entry as if you had specified its index directly. Watch out for the high order byte when converting existing code! That byte was ignored in the past, but if you fiddle with a COLORREF-for example, performing a binary NOT to obtain the complement of a color-be sure to zero out the high order byte if the COLORREF is supposed to be an RGB color.
Odds and Ends
With the increased use of color, there's a problem in using the FloodFill function. FloodFill expects a border color parameter and fills outward in all directions until it encounters that color. This is fine for monochrome images, but in color you may have a border of several different colors and an area with a single color that you want to change to a different color. For color, use the ExtFloodFill function; it fills all pixels that match a given color, instead of those that don't match a border color. This function is like FloodFill but takes one more parameter specifying the fill mode: FLOODFILLBORDER, which makes the function work like FloodFill, or FLOODFILLSURFACE, which will perform the new filling method I just described. You can see FLOODFILLSURFACE used if you try the fill tool in Paintbrush.
There are of course new META_xxxxxx metafile records to support all the new GDI functions, including those for palette management and DIBs. A serious omission in Windows 2.1 meant that a metafile could create brushes, pens, and other objects but could not delete them, leading to nasty memory leaks as objects were created but not destroyed. This has been corrected. The new META_DELETEOBJECT record allows you to destroy these objects.
MDI
My favorite Version 3.0 API feature is one that users will never notice, but is a boon for developers: support for the MDI. For the last couple of years developers have been told to use MDI, but there was practically no Windows MDI support other than for child windows. Adding MDI support to an application could mean an additional thousand lines of code or more. Fortunately there's now clean, simple MDI support in the Windows API. There is one new predefined window class, MDIClient, four new functions, and a dozen new messages.
Setting up MDI is easy: create the outermost frame window as you normally would, then create a window of the MDIClient class in its client area. This window is the MDI workspace. It can fill up the frame window's client area or you can leave room for objects such as a status line (see Figure 4). To create each document window inside the MDI workspace, you send the MDIClient window a WM_MDICREATE message. Similarly, you destroy a document window by sending a WM_MDIDESTROY to the MDIClient window. Instead of calling DefWindowProc, the frame window calls DefFrameProc, and the document windows call DefMDIChildProc. The only thing remaining is to include a call to TranslateMDISysAccel in your main message loop. All the required MDI behavior is handled by the MDIClient window and the three new functions: even the window's menu is maintained properly by MDI. Anyone who has programmed MDI under 2.1, or more likely has left MDI support out of their application because of its difficulty, will be happy when they see that this support is built into Windows. (For a more detailed discussion of the Windows 3.0 MDI API, see "A New Multiple Document Interface API Simplifies MDI Application Development," p. 53-Ed.)
Control Class Enhancements
As I mentioned, the Edit control class now supports proportional fonts. Other things work now that never did before, too. Did you ever try the ES_CENTER or ES_RIGHT styles in previous releases? Text would show up in the wrong places and sometimes even would jump out of your way when you tried to click on it. In 3.0, these styles work correctly-not as entertaining perhaps, but certainly more useful.
New Edit control styles make it easy to do some things that before you had to subclass or worse: ES_UPPERCASE and ES_LOWERCASE force input to uppercase or lowercase; ES_PASSWORD prevents normal display of the edit text, displaying asterisks instead (see Figure 14). If you don't like the asterisk, you can pick a different character with an EM_SETPASSWORDCHAR message. Other new messages include the EM_SETTABSTOPS that I mentioned and EM_EMPTYUNDOBUFFER, which prevents a subsequent EM_UNDO or Alt-Backspace keystroke from having any effect (until the next undoable edit, of course).
ListBox Class
The ListBox class wasn't completely rewritten like Edit was, but it did receive more new features (see Figure 15). The first new feature is the owner draw facility. Items in a list box can now be anything you draw, instead of having to be text strings. The ListBox class still takes care of the keyboard and mouse interface, scrolling and keeping track of what lines are visible and it still sends you a simple WM_DRAWITEM message for each line to be painted. You can have the owner draw list box store a list of strings for you by using the LBS_HASSTRINGS style. Without this style, the list box will store 32 bits of data for each item. You can put what you want in them, including a far pointer to a string, a far pointer to any data structure of your choosing, or any kind of arbitrary 32-bit value. The LB_ADDITEM and LB_INSERTITEM messages take this 32-bit value in lParam in place of the normal string pointer.
Each WM_DRAWITEM includes this 32-bit value along with the other information it passes; you can also look at it with a LB_GETITEMDATA message or change it with LB_SETITEMDATA. These two messages work with any list box, not just owner draw list boxes. This is really handy when you want to associate some invisible data with each item, but you don't need anything fancier than strings drawn in the list box. Owner draw would be overkill; just do a LB_SETITEMDATA on an ordinary list box and you're all set.
The LBS_SORT style gets a little tricky with owner draw list boxes. If LBS_HASSTRINGS is set, it's just as easy as before-Windows will take care of the sorting on its own. But without LBS_HASSTRINGS, there's no way for Windows to determine the sort order. So when you send an LB_ADDSTRING message to insert an entry, Windows sends back one or more WM_COMPAREITEM messages to the list box's owner. Like the compare routine called back by the C run-time library's qsort function, this message lets you specify the sort ordering by comparing pairs of items.
Items in owner draw list boxes can all be the same height (as in a normal list box) or different heights. You select these items using the LBS_OWNERDRAWFIXED and LBS_OWNERDRAWVARIABLE window styles. The list box returns WM_MEASUREITEM messages to determine the size of the list items. A fixed-height list box sends one of these messages; a variable-height box sends one for each item.
List boxes can now be scrolled horizontally. To add a horizontal scroll bar to a normal (vertically scrolling) list box, just add the WS_HSCROLL style, then send a LB_SETHORIZONTALEXTENT message to specify the scroll range. This is a simple feature, but it should be useful to many developers-I had to code my own list box class last year only because the standard class wouldn't scroll horizontally.
You can also make a list box that's similar to the old MS-DOS Executive in "short view"-a horizontally scrolling block of items in rows and columns in which you can move the cursor in all four directions. Use the LBS_MULTICOLUMN style and send the list box an LB_SETCOLUMNWIDTH message to tell it how wide the columns are.
It's now easier to handle list box keyboard input. If you want to handle any of the list box's keystrokes, give it the LBS_WANTKEYBOARDINPUT style. The list box's owner is then sent WM_VKTOITEM and WM_CHARTOITEM messages, which correspond to WM_KEYDOWN and WM_CHAR messages but include the number of the item in the list box. You can then take the default action or modify it as needed. As part of your processing, you might want to use the new LB_FINDSTRING message to locate an item by text value and LB_SETTOPINDEX to scroll the list box to a specific location.
Multiple selection list boxes have changed, mostly for the better. If you used the LBS_MULTIPLESEL style in the past, the user always had to hold the Shift or Ctrl key down to perform a multiple selection. This was really annoying; you might take a couple of minutes carefully selecting various items in a list, but then one unshifted mouse click or cursor key would wipe out that selection. Some developers took a saner approach: they subclassed list boxes so that a mouse click toggled the selection on the clicked item without affecting any other selection, and cursor keys moved the focus rectangle as if the Ctrl key were down; the user pressed the space bar to toggle an item's selection. Unfortunately, this approach wasn't standard and whether the ease of use in this approach was worth the inconsistency was arguable. But now all LBS_MULTIPLESEL list boxes work as I just described.
In a variation of multiple selection list boxes, the LBS_EXTENDEDSEL style, an unshifted click or arrow key removes all previous selections, selecting the item clicked or moved to. Or a Ctrl-click adds individual items to a discontinuous selection. You can also select a continuous range using the Ctrl-arrow key or just by dragging the mouse across a range of items. Other than the mouse dragging, this is all nearly identical to the old LBS_MULTIPLESEL behavior. The idea is to make it easy to select a range of items at once with the keyboard or the mouse. It does get a little tricky if you want to make a discontinuous selection with the keyboard. You have to press Shift-F8 first to put the list box into a special selection mode. Then the keyboard works as it does when you use LBS_MULTIPLESEL, where arrow keys just move the selection cursor and the space bar sets or clears the selection on the current item.
I'm not sure why there are two different kinds of multiple selection list boxes. They have quite different user interfaces, and surely this can't help users become more comfortable using Windows. Some operations are easier with LBS_MULTIPLESEL, and others are easier with LBS_EXTENDEDSEL. I don't know which style a developer is supposed to use. I wonder why these two styles weren't combined into one style with consistent behavior.
Buttons
Push buttons have a very different appearance in 3.0, but no new APIs are needed to support it. The old calls and messages work with the new look-after all, the button still has just pushed and not pushed states; it only looks different. Check boxes and radio buttons are unchanged, except that they use the gray text system color instead of a dithered gray when disabled. Since they're not three-dimensional, they look curiously flat compared to push buttons. The only API change for buttons is the BS_OWNERDRAW style, which lets you draw your own buttons using techniques consistent with the owner draw list boxes and menus. (The old BS_USERBUTTON style and BN_PAINT message are no longer listed in the SDK documentation and are not recommended for new applications, but obviously they're still supported so existing applications can work.)
ComboBox Class
As its name implies, the new ComboBox control class combines the behavior of the existing Edit and ListBox controls. It links input between the two controls so that you can either type into the edit control or scroll through the list box, picking an entry that way (see Figure 5). The three kinds of combo boxes are determined by the window style bits. Least often used is the CBS_SIMPLE style, in which the list box is visible all the time and the edit and list windows are linked back and forth. Much more common is the CBS_DROPDOWN style, which has an edit control you can type into and the down arrow icon next to it, which opens up the drop-down list box. CBS_DROPDOWNLIST is also used frequently; it's like CBS_DROPDOWN except that the edit control is disabled (that is, you can't type into it but can change its value by hitting the up or down arrows or by opening up the list box).
A combo box, depending on its style, actually does create an edit control and temporarily a list box if needed, so it's no surprise that many of the same style options, messages, and notifications for edit and list controls are also used for combo boxes. The messages and styles aren't exactly the same-they've got CB_ and CBS_ prefixes in their names-but everything should be fairly familiar if you've worked with edit controls and list boxes. Whichever window is actually doing the work-the combo box itself, edit control, or list box-you normally exchange messages with the combo box only. Only two messages are unique to combo boxes, and they will probably be rarely used: CB_SHOWDROPDOWN, to open or close the drop-down list box explicitly, and CB_GETDROPPEDCONTROLRECT, to determine the drop-down list box dimensions. The only notification unique to combo boxes is CBN_DROPDOWN, which tells you when the drop-down list box is opened or closed.
Class and Window Architecture
Subclassing is a powerful technique used in many Windows applications. The most common method of subclassing a window is to call CreateWindow on an existing class and then use SetWindowLong to change the window function pointer. This is easy, but prevents you from intercepting the WM_NCCREATE or WM_CREATE messages, from adding additional cbWndExtra to your window, and from changing other class-specific information such as the icon. To avoid these problems, gather the WNDCLASS information for an existing window class, change what needs to be changed (such as the function address), and register a new class of your own with this new WNDCLASS information. Then you can create windows of this class that are immediately subclassed.
To get the WNDCLASS information you need, use the new GetClassInfo function. It will give you this information without your having to create a window. You can then save the window function pointer, tweak the WNDCLASS as needed, and call RegisterClass to create your new class.
One window that was especially difficult to subclass in Windows 2.x was the screen or desktop window. This window encompasses the entire screen background; all top-level windows (WS_OVERLAPPED and WS_POPUP) are really its children. Subclassing this window allows you to do things such as put up your own screen background. Windows 2.x tried to prevent you from getting to the screen window handle, but naturally there was a very sneaky way to get around this. (The call was HIWORD(GetWindowLong(GWW_PARENT-2)), if that's sneaky enough for you.) Sneaky tricks are no longer required-all you have to do in Version 3.0 is call GetDesktopWindow and it returns the screen window handle. In fact, the screen window handle is used by the desktop section of the Windows 3.0 Control Panel, allowing end users to use any bitmap as their screen background (see Figure 16). (Readers of WINDOWS.H will note the function GetDesktopHwnd. It's the same function with a different name, which carries on the tradition of GetCurrentTime and GetTickCount, also synonyms for one function.)
When you're finished with a window class, call UnregisterClass to get rid of it. This allows you to register temporary classes and unregister them at various times during your program. When a dynamic-link library (DLL) exits, its classes are destroyed-previously this happened automatically only for applications. If you do have a DLL that creates public window classes, be sure to put the new CS_GLOBALCLASS style on them or they will be private to the DLL.
Windows 3.0 has some new WS_ style bits. The problem is where to put them. The flWndStyle parameter to CreateWindow is more than full, in fact, it's overloaded. You may already have noticed that WS_MINIMIZEBOX and WS_MAXIMIZEBOX are the same bits as WS_GROUP and WS_TABSTOP. (This fact has been a minor nuisance in MDI programming under Version 2.1.) Since there was no place to put any more style bits, an entire new doubleword of extended style flags, with WS_EX_ prefixes, has been added. The CreateWindow function doesn't know about these flags, so a new CreateWindowEx function does. (And yes, CreateWindow now is just a call to CreateWindowEx with the extended style flags zeroed out.) There's plenty of room to grow, because only two of these flags are defined: WS_EX_DLGMODALFRAME, providing the new movable modal dialog style, and WS_EX_NOPARENTNOTIFY, which shuts off the new WM_PARENTNOTIFY message. It may seem silly to have to add another 32 bits of style flags and use only two of them, but Microsoft probably wanted to running out of bits again for a long time.
DeferWindowPos
One thing that's always been more difficult than it should be is positioning several windows at once and avoiding sloppy screen painting. You could just make some SetWindowPos calls, but it may be hard to avoid extra screen painting as you make the calls. Windows has always had internal functions to position a group of windows cleanly; these are now available to "the rest of us." Just replace each SetWindowPos call with DeferWindowPos, and bracket the whole thing with BeginDeferWindowPos and EndDeferWindowPos. After the EndDeferWindowPos call, Windows positions all the windows, optimizing the painting for the cleanest appearance. OS/2 Presentation Manager programmers may notice that it is similar to WinSetMultWindowPos. In fact, BeginDeferWindowPos creates and DeferWindowPos fills in a structure much like the SWP array used by WinSetMultWindowPos; EndDeferWindowPos processes this structure in the same way as the PM function. So it's really the same thing with two rather different APIs.
Menus
You can do much more with menus in 3.0. To create hierarchical menus with practically no work at all, use a POPUP statement instead of MENUITEM when you want a hierarchical menu, just like you do at the top menu level. After the POPUP, of course, is a BEGIN and END with a list of MENUITEMs in between for the nested menu items. The only thing different in Windows 3.0 is that you can nest POPUP statements instead of being limited to one level of POPUP menus. If I were converting a Version 2.1 application I'd probably put in hierarchical menus first, just because it would be fun and easy.
With a little more work, you can explicitly bring up a pop-up menu anywhere on the screen. Create a pop-up menu with a CreateMenu call (or just use GetSubMenu to grab one loaded from a resource) then call TrackPopupMenu to display the menu. TrackPopupMenu handles all the user's keyboard and mouse input for the menu and returns when the menu is closed. One of the parameters to TrackPopupMenu is the handle of an owner window, which receives the WM_COMMAND and other messages generated by the pop-up menu.
As with list boxes and combo boxes, there are now owner draw menus. You always could specify a bitmap for each menu item, but owner draw menus are more flexible than that. They work like the other owner draw controls-the application window gets WM_MEASUREITEM and WM_DRAWITEM messages if you put the MF_OWNERDRAW flag on a menu item. You can also change menu appearance by defining your own check mark bitmap. Instead of always displaying a check mark when the MF_CHECKED flag is on, Windows lets you associate individual checked and unchecked bitmaps with each menu item.
One minor improvement that makes things a little less confusing is that the multipurpose ChangeMenu function is replaced by specific functions for the individual functions it performed. The new functions are AppendMenu, DeleteMenu, InsertMenu, ModifyMenu, and RemoveMenu. ChangeMenu still exists, so you don't have to change existing code right away. Some existing code could be affected by a new system menu item, SC_TASKLIST, but this should be transparent as long as you don't assume how many system menu items there are.
With all the menu improvements, one omission surprised me: menus still do not support scrolling. In a number of other GUIs, if a menu has too many items to fit on the screen, the menu scrolls up and down to reveal all the items. Not so in Windows. A menu must fit completely on the screen or the user just won't be able to click on the hidden items. This is really unfortunate. For example, it means that a Windows application can never have a simple Font menu listing all available fonts. If there are too many fonts, the user won't be able to select some of them. (Yes, I know many Windows applications do have a Font menu like this, but unless they switch to a list box, they will be in trouble when there are many fonts installed.) Similarly, the Window menu in an MDI application cannot list all open windows. If there are too many, it adds a "More Windows" item to the bottom, which brings up a list box for the windows that don't fit in the menu. Elegant this isn't.
Dialog Box Changes
The dialog manager has been restructured. In Version 2.1 we pretty much had to use the standard dialog window class. Theoretically it was possible to create a dialog box using your own window class, but the standard dialog window class still had some of the functionality you needed. The only easy way to make things work was to use the standard class and then subclass it, and this approach had its own problems. All that special functionality has been moved out of the standard dialog class and into a new DefDlgProc function. Thus, you can use a window class of your own for a dialog box, calling DefDlgProc instead of DefWindowProc.
Another dialog problem that has been solved is how to pass parameters to a dialog function. The lpParam of CreateWindow is missing from the existing dialog creation functions, forcing programmers to use static variables and various kludges to get information into dialog functions. This doesn't seem like that much of a problem until you get to situations like multiple instances of a modeless dialog box; then it's a real nuisance. Now the dialog creation functions all have versions that include a long parameter like in CreateWindow. The odd part is that because there were four ways to create a dialog box (using DialogBox, DialogBoxIndirect, CreateDialog, or CreateDialogIndirect), there are now four new functions, one of each of the above with "Param" tacked on the end.
A few new dialog styles are worth noting. DS_MODALFRAME gives you the new movable modal dialog box; it corresponds to WS_EX_DLGMODALFRAME in CreateWindowEx. DS_NOIDLEMSG turns off those pesky WM_ENTERIDLE messages. If you're tired of not knowing what that message really does, you can get rid of it. Or just look it up in the documentation; it's documented now. Last, DS_SETFONT now specifies a font to be used by the controls in the dialog box. You can do the same thing with the WM_SETFONT message; it's just a convenient way to set the font for every control in the dialog. You're most likely to use DS_SETFONT in its guise as the FONT statement in an RC file; DS_SETFONT is the actual style bit you'd use in a CreateDialogIndirect call.
Message Box Changes
Message boxes have one new feature, the MB_TASKMODAL flag. This flag disables all other windows owned by your application while the message box is open. You might think that MB_APPLMODAL would do this, but no. MB_APPLMODAL, the default, simply disables the owner (also known as parent) window listed in the MessageBox call. But it doesn't disable any other top-level windows (WS_OVERLAPPED and WS_POPUP) owned by your application. If, for example, you had both a main application window and a modeless dialog box open, a MB_APPLMODAL message box would disable only the one listed as its owner. In many cases, they should both be disabled, and MB_TASKMODAL does that. Rather than relying on the owner window parameter, it simply disables all top-level applications owned by your task, and then reenables them when the MessageBox call exits. It also notes any windows that you may have already disabled, so that itdoesn't inadvertently enable them when it exits. They remain disabled as they should.
It's a shame this feature is only available for message boxes. Dialog boxes really need it, and for that matter ordinary windows do as well (for example, it would be useful in conjunction with a window that looks and acts like a modal dialog but is created with an explicit CreateWindow call). I've had to implement task-modal dialog boxes myself in my last two applications, and I'm sure other developers have also had to do this. It isn't hard, just one of those nuisances that you aren't even likely to realize you need until you find your modal dialog box only disables one of your top-level windows, not all of them.
New Printing Support
In the past, there hasn't been any easy way to maintain individual printer settings for different documents. You could call the DeviceMode printer driver function from your application, but that function keeps data in a device-dependent format. (It's usually stored in WIN.INI, but there is no guarantee of this, and exactly what gets stored is different for each printer.) Furthermore, in Windows 2.x, DeviceMode would always bring up the printer settings dialog box, which is not what you want if you just need to switch to a different setting. The ExtDeviceMode function now deals with all this in a more useful manner. The call has options to fetch and set the current printer settings, to change the WIN.INI defaults, and to bring up the printer settings dialog box. Note that the dialog box is optional, allowing you simply to change to a new printer setting saved with a document (or elsewhere). The settings are stored in a DEVMODE structure, which combines device-independent settings with device-dependent ones very cleanly. The first part of the structure contains many common printer settings: paper size and orientation, number of copies, resolution (either a device-independent high/medium/low/draft or DPI), and even input paper bin and duplex printing options. You can manipulate any of these settings in the DEVMODE structure directly. At the end of DEVMODE is a variable length device-dependent section, in which the driver keeps any additional information needed for a particular printer. Naturally, you shouldn't manipulate this section unless you're very cozy with a particular printer driver.
There are also two dozen new printer escapes. Most of these are specific to PostScript printers, thus beefing up Windows PostScript support. New escapes make it easier to print Encapsulated PostScript (EPS) files and to manipulate the transformation matrix used by PostScript printers.
Windows Flags
With all the different CPUs and memory modes Windows 3.0 supports, it would be a disaster if you had to puzzle out the current mode and CPU. Fortunately, a handy function called GetWinFlags returns a set of flags indicating your CPU type, Windows memory configuration, and presence or absence of a math coprocessor (see Figure 17). On the CPU side, flags distinguish among 8088/8086, 80186 (yes, there is such a processor), 80286, 80386, and 80486 processors. Additional flags tell whether Windows is running in real mode, standard mode, or 386 enhanced mode. If you just want to find out if you're in protected mode or not, there's a flag specifically for that. In real mode, there are additional flags identifying small- and large-frame EMS. Most Windows code doesn't need to know what CPU or what mode it is running in, but when you do need to know, GetWinFlags is the ticket. In assembly language, you can directly import the absolute symbol __WINFLAGS like this:
externA __WINFLAGS
globalD flMyWinFlags, __WINFLAGS
Files and Disks
In previous versions of Windows, there was a set of undocumented file I/O functions that everybody who read Charles Petzold's Programming Windows (Microsoft Press, 1988) knew about: _lopen, _lcreat, _llseek, _lread, _lwrite, and _lclose. These functions still exist, and they're now documented and supported. They are nothing fancy, just direct calls to the equivalent INT 21H DOS services.
OpenFile sports a few new options to support file sharing. It allows all the sharing modes that SHARE.EXE supports: compatibility, deny none, deny read, deny write, and exclusive. If you run out of file handles, you can now get more by calling SetHandleCount. Although OpenFile looks for files in the Windows and Windows system directories automatically, you may need to know those directory names, and a GetWindowsDirectory or GetSystemDirectory call will do the trick.
Strings Attached
As with the file functions, the "undocumented" string functions lstrcat, lstrcpy, and lstrlen are now supported and documented. One string function, lstrcmp, still exists in name, but has a new meaning in 3.0. In Windows 2.1, lstrcmp was not case-sensitive, unlike the C run-time library's case-sensitive strcmp function. In Windows 3.0, both kinds are available and the names match the C style: lstrcmp is now case-sensitive and lstrcmpi is not case-sensitive. Both these functions were rewritten to work better with international versions of Windows and to be faster. The C string compare functions have the advantage of not having to worry about different character sets, so they are faster than the Windows versions, but they don't handle international character sets at all. Because of this, it's better to use the Windows functions.
Since C's character classification functions don't work with international character sets either, Windows now has its own: IsCharAlpha, IsCharAlphaNumeric, IsCharLower, and IsCharUpper. Several string conversion functions now have variants that take a character array of a specified length instead of a zero-terminated string. The new ones are AnsiLowerBuff, AnsiToOemBuff, AnsiUpperBuff, and OemToAnsiBuff. Another new function, ToAscii, converts a virtual key code into an ANSI character code. Besides the virtual key code, you have to provide the scan code and a key state table as returned by GetKeyboardState-in other words, the same information Windows and the keyboard driver normally have on hand when they do this translation inside TranslateMessage to generate WM_CHAR messages.
The best new string functions are wsprintf and wvsprintf, which are replacements for C's sprintf and vsprintf. True, you can call sprintf and vsprintf from a Windows application, but they add bulk to your code, and don't work at all from DLLs. The Windows equivalents don't have these disadvantages, and are therefore very helpful for DLL developers. They don't support all the format specifiers from sprintf-floating point and pointer types are missing-but they do include %c, %d, %u, %x (and %X) in both short and long, together with the , #, 0, width, and precision prefixes. Of course %s is supported, but it expects a far string pointer regardless of your memory model, and there are no %F or %N prefixes to modify this. Just make sure to cast any near string pointers to (LPSTR). The only Windows functions that the C compiler won't let you do this with are wsprintf and wvsprintf, because there are no prototypes for these particular parameters. (The lpOutput and lpFormat parameters do receive the normal near-to-far casting, so it's best not to cast them. That way the C compiler can do a better job of diagnosing incorrect parameters.)
As for using wvsprintf, once you see one example you'll discover that this is the function you've been looking for for years. Many Windows programs include code something like this to display formatted text:
wsprintf( szBuf, "A string: %s", sz );
TextOut( hdc, x1, y1, szBuf, strlen(szBuf) );
wsprintf( szBuf, "Two numbers: %d %d", i, j );
TextOut( hdc, x2, y2, szBuf, strlen(szBuf) );
That's clumsy. It would be a lot nicer if you could just write something like this:
TextOutF( hdc, x1, y1, "A string: %s", sz );
TextOutF( hdc, x2, y2, "Two numbers: %d %d", i, j );
But of course you can't do that. How can you get those varying parameters at the end into a wsprintf call? It just can't be done. But that's exactly what wvsprintf (and vsprintf for normal C programs) is for-a building block for writing your own printf-style functions. The TextOutF function can be easily coded thus:
// Formatted printf-style TextOut
BOOL _far _cdecl TextOutF( HDC hdc, int x, int y, LPSTR lpszFormat, ... )
{
char szText[256];
int cbText;
cbText = wvsprintf( szText, lpszFormat,
(LPSTR)lpszFormat + sizeof(lpszFormat) );
return TextOut( hdc, x, y, szText, cbText );
}
As you can see, calling this one function is better than having to call both wsprintf and TextOut each time. Two caveats on this sample code: it assumes that the 256- character szText buffer is long enough, and it does not use the recommended va_start macro to access the varying parameters at the end. I didn't use va_start because that function is model-dependent and I wanted this code to be model-independent. It's hard to avoid making assumptions about the buffer size, but a good safeguard is always to use explicit precision specifications on strings in your format. For example, the format "A string: %.80s" would never overrun the buffer no matter how long the string is.
Process and Memory Support
In the past, Windows did not provide a straightforward way for one program to launch another. The DOS INT 21H/4BH Exec service has been used for this, but it's not really documented. Now, there's a convenient WinExec function. Just pass it a command-line argument and an nCmdShow parameter (which is passed to the target application's WinMain) and off you go. Another option is to call LoadModule. This function takes the same parameters as the Exec call in DOS-pointers to the filename and the Exec parameter block-so it's more complex to use. However, you can provide an environment string for the target app instead of having it receive a copy of the Windows environment.
Version 2.1 had a problem shutting down programs. Applications have always "known" when they were exiting, because they they themselves initiate the shutdown. (WM_QUIT doesn't magically shut down a Windows app. What it does is cause GetMessage to return FALSE, breaking out of a WinMain message loop. WinMain returns to its caller, which is the run-time support code linked into the application. That code, after cleaning up, executes a thoroughly conventional INT 21H/4CH to terminate the application.) DLLs have not had this luxury. They "knew" when they were started because their initialization function was called, but there wasn't any notification of exit. Under 3.0, a new function called WEP (Windows Exit Point) must be defined by each DLL. When Windows unloads the DLL, it calls WEP so the DLL can clean up after itself.
GetCurrentPDB is a new function that returns the segment address of the running application's DOS Program Segment Prefix (PSP; which Windows refers to as the Program Data Base). Little known fact: this information has always been available to WinMain, as the segment portion of the lpszCmdLine parameter. When Windows starts an application, it sets up a normal DOS PSP for it in a fixed nonbanked segment, complete with a weirdly formatted command-line tail at offset 80H, so that DOS will do things like maintain file handles for each application. A Windows application actually receives this PSP address in the ES register, just like a DOS application, and the run-time start-up code linked into the application that cleans up the command-line tail, converting it into the lpszCmdLine parameter. (It skips over leading blanks and zeros out the CR character at the end.) This cleanup is done in place, so the segment portion of lpszCmdLine is always the PSP address, and the offset portion is always 81H plus the number of leading blanks in the command tail.
Another new function, GetCodeInfo, provides several pieces of interesting information about code segments in currently running programs. You pass it either a function address or a module handle and segment number, and it returns a set of flags describing the segment, together with the segment's location in its executable file and both its length in the file and the memory size that should be allocated for it. Combined with GetModuleFileName, this gives you enough information to load a code segment from a Windows executable in some unusual way should you need to. That's assuming you don't mind doing all your own relocation fix ups on the segment, of course.
CodeView for Windows
Windows in real mode has been at best a badly cramped programming environment. The worst crowding occurred when you tried to squeeze in a debugger with Windows and an application. SYMDEB with its symbol tables took up more than enough space; Microsoft CodeView for Windows debugger (hereafter CVW) was even worse. CVW tried bravely to squeeze itself into memory and keep as much in EMS as possible, but as anyone who tried to use it with any large application knows, it just didn't cut it. You had to turn off symbols for much of the application to get it to fit at all and to avoid the dreaded "CVW Crawl." MagicCVW by NuMega did a good job of making Windows 2.1 CVW more usable, but now CVW itself runs in protected mode, which takes care of the memory problem nicely.
With more room, it was time to put some improvements into CVW. If you have Microsoft C Version 6.0, you know what the improvements are, so I won't go into great detail here (CVW is the C 6.0 CodeView with Windows extensions). If you didn't like the old CVW, this one will change your mind. If you did like the old one, you'll be thrilled. The new CVW is infinitely faster. I am not exaggerating. When I ran a large program in the old CVW, if I threw in a couple of Watch variables it could approach a speed of 1/c. The new CVW is so much faster, it can display a Locals window showing all the parameters and local variables of the current function. You can add Watch expressions and as many as two source windows and two memory display windows, all updating live, and the thing still doesn't slow down. It's not only faster, it's a more usable and powerful debugger all around.
CVW now starts up the right way. Instead of running CVW from DOS and having it start Windows, it works the other way around. CVW is now a real Windows application; you run it under Windows and it starts up and takes control of your application. Finally, you don't have to restart Windows each time you want to use CodeView!
The Windows extensions to CVW include (lh) and (gh) type casts, which dereference local and global memory handles into pointers, along with several Windows-specific commands. The most interesting are wwm (Windows Watch Message) and wbm (Windows Break Message). These commands set watchpoints and breakpoints, not just at particular locations, but on specific messages or classes of messages. This really helps with debugging window functions. There are other commands to display the local and global heaps and the list of modules, and to terminate the application being debugged.
One caveat: you can no longer run CVW on a serial debugging terminal. It must be on a second display on the same computer running Windows. For a classic bus machine, the usual setup is to run Windows on a color display and CVW on an auxiliary monochrome card and monitor. It's tough to find mono cards for a PS/2, so there the hot setup is to run Windows on an 8514/a and CVW on the VGA. Then CVW runs in color 50 line mode, which is handy.
Another Debugger
SYMDEB is still supported for real-mode debugging, and it works as well-ahem-as it always did. There's even a SYMDEB-style debugger for protected mode called WDEB386. Yes, you restart Windows each time, it's purely a command-line system, and it doesn't have any source code access at all. You'd use it if you didn't have a second display for CVW, or if you were debugging certain types of device drivers. Printer drivers are easily debugged under CVW, because they are just demand-loaded DLLs, but to debug any of the drivers that load during start-up, you have to use WDEB386. CVW won't work because it can't start until after all the start-up drivers are loaded. WDEB386 also has a few little things it will do that CVW won't, so it's worth keeping. But CVW is clearly the debugger of choice; it really makes debugging Windows applications easier.
Profiler
Previous versions of the SDK did not provide much in the way of performance measurement tools. Duncan Booth, a Windows programmer in England, helped fill the gap with a profiler called WinET (Windows Execution Timer). WinET uses a simple but effective technique: it hooks into a timer interrupt and records the current code segment (CS) and instruction pointer (IP) on timer ticks. After you leave Windows, it matches these up against specified SYM files and prints a report showing where the time was spent.
The Windows 3.0 SDK includes a profiler that works on the same principle as WinET, and it's better and easier to use. It supports real mode and 386 enhanced mode (but not standard mode), so you can profile any kind of Windows application. It's better to use 386 enhanced mode if you can, so that the memory the profiler consumes won't get in the way of your application.
To use the profiler, add ProfStart and ProfStop calls to your code, bracketing the sections of code you want profiled. (You wouldn't want the profiler on all the time-for example, you generally wouldn't want to leave it on across a GetMessage or PeekMessage call because other applications would then be running instead of yours.) To start Windows with the profiler installed, either add a driver line to SYSTEM.INI in 386 mode or start PROF.COM in real mode. When you run your program, the profiler samples the instruction pointer periodically when it is activated with ProfStart; when you shut down Windows or call ProfFinish, it writes this information out to a disk file. Then, just run SHOWHITS with the correct options and it will report where the CPU time was spent. SHOWHITS gets its name because it shows how often the instruction pointer "hit" each part of the code. The most frequently executed code is listed in a portion of the report appropriately titled "Top 10 Hits."
Swap Analyzer
The profiler gives one view of your program's execution; it's also useful to know something about calls across segment boundaries. In real mode, you want to find out which code segments are being swapped too often. While not often a problem in protected mode, too many far calls are, because they're very expensive compared to near calls. Since you'll want to know what far calls are being made, use the swap kernel, SKERNEL.EXE. Strangely, the swap kernel runs only in real mode, because it's a special version of the real-mode kernel that puts extra debugging code into the reload thunks it generates. Since there are no thunks in protected mode, this trick doesn't work there. To use swap kernel, put SwapRecording calls in your code to start and stop swap recording; after you exit Windows, run the Swap utility to view the results. An option in SwapRecording lets you specify the events to record: segment loads and discards only, or all far calls.
Other Tools
HeapWalker naturally had to be updated to work in protected mode, but real mode wasn't forgotten. Under large-frame EMS in Version 2.1, HeapWalker was only able to look at its own EMS bank. Other applications were essentially invisible because their code and data were in other banks. However, in Windows 3.0 real mode, a new EMSWalk menu now lists the other running applications so that you can view any of their EMS banks.
The Dialog Editor can now add new control classes and support them as fully as the built-in classes. If you write a DLL that registers a control class and provides a set of entry points and messages that is defined in the SDK, Dialog Editor can load it, add that control to its palette, and let you put the control in dialog boxes.(For more information on control classes, see "Extending the WindowsÔ 3.0 Interface with Installable Custom Controls," p. 29-Ed.)
SDKPaint improves upon the old Icon Editor by providing more drawing tools and support for color images as well as multiple-resolution cursors and icons (see Figure 18). As with Paintbrush, I'd like to see more flexible zooming. Optional grid lines would be a help too, but SDKPaint is still pretty good as is.
Don't overlook the new Recorder that is included with Windows 3.0 as a development tool. Simply by recording and playing back a macro, you can run your application and make sure that you don't break things that once worked.
The most fun of all the SDK tools has to be ZoomIn. Play with it for a couple of minutes and if you don't chuckle, see a doctor immediately. It has a practical use, too, but that is secondary to the amusement factor (see Figure 19).
New Libraries
There were several problems with the LIB files shipped in previous SDKs. Library functions were not compiled with the Windows prolog/epilog they needed and it was unclear which functions chould be used in DLLs or applications. The Windows import library ?LIBW.LIB came in different versions for each memory model-even though most of this library was common to all models. And just to keep things interesting, C 5.1 and the Windows SDK each offered several different naming conventions for the libraries. It sounded convenient-you could have either DOS, OS/2, or Windows libraries as the default-but it meant no one's MAKE files would be compatible with anyone else's.
C 6.0 and the Windows 3.0 SDK have standard names for all libraries, with an R suffix for DOS, P for OS/2, and W for Windows. You have to use explicit library names when linking files for Windows, but since you pretty much need to use a MAKE file anyway it's no big deal. Besides, anyone else will then be able to build your program without fiddling with the MAKE file. There is a single import library, LIBW.LIB. The small amount of model-specific code that was in the ?LIBW.LIB files is now in the ?LIBC?W.LIB files instead. A separate set of files for DLLs named ?DLLC?W.LIB contain only the C run-time routines that are safe to use in DLLs. And for applications and DLLs that don't use the C run-time library, there are new libraries that omit the routines but have the other start-up code needed.
Header Files
WINDOWS.H has been reorganized. It's in a more logical order, but more importantly, numerous coding errors are fixed. Functions that had been declared with only () for the argument list are declared as void, to avoid compiler warnings. Some functions with no return value had been erroneously declared to return int; these now have void returns. Far pointers are used in macros where needed, for example in MAKEPOINT. This macro used to work only with data in the default data segment in the small or medium model; now it works wherever the data is. And assembly-language programmers are no longer neglected stepchildren-WINDOWS.INC is up to date and complete.
DPMI
Most of protected mode's restrictions, such as segment limit checking, help more than they hurt. But one restriction can be onerous: you can't easily communicate between protected mode and real mode. If you have a DOS TSR that your application communicates with through a software interrupt or maybe a shared memory buffer, it's not going to work in protected mode. You can't use a real-mode address in protected mode and expect it to point to the same thing. The segment portion of the address is interpreted differently in the two modes. And you can't call just any real-mode interrupt from Windows protected mode. INT 21H calls and a few others such as NETBIOS calls are all right, but only because Windows handles them specially.
What's needed is some kind of interfacing service to hook up protected-mode and real-mode programs. The DOS Protected Mode Interface (DPMI) provides this sevice. DPMI is a set of protected-mode functions that allocate real-mode memory, simulate real-mode interrupts and function calls, intercept real-mode interrupt vectors, play games with selectors, and more. Using these calls, you can communicate with TSRs from protected mode just as you would in real mode. It's pretty straightforward, if your code is organized so that the actual communication with the TSR is done in just a few places. If various kinds of direct interfaces with the TSR are interspersed through your code, it will be tougher. (For more information about DPMI, see "Supporting Protected-Mode Applications in a DOS-based Environment," p. 92-Ed.)
DPMI calls use an INT 31H interrupt interface, not a Windows-style dynamic-link interface. You load registers and call INT 31H, just as you would make a BIOS or DOS call. DPMI works this way because its real purpose is not to provide an interface between Windows applications and real mode (that's a side benefit) but to give DOS extenders a way to work cleanly under Windows. DOS extenders, such as DOS16M by Rational Systems, let DOS applications run in protected mode to access extended memory. They switch as needed to protected mode for application code and real mode for DOS and other real-mode calls. Rational Systems and others were able to make this work under Windows/286 and DOS by switching modes on their own. Windows/286 did not know it was happening, because the DOS extender would always go back to real mode before returning control to Windows. This didn't work in Windows/386, where the non-Windows application using the DOS extender was actually running in a virtual machine, so the usual tricks didn't work.
Therefore, the INT 31H DPMI interface was implemented to provide the same services that DOS extenders must use internally to run their applications. The DOS extender can determine that it is running under a DPMI system-protected-mode Windows, or eventually other systems such as OS/2 and UNIX/XENIX-and make INT 31H DPMI calls instead of pulling its own mode switching and selector translation tricks. This is a real boon to users. To cite the classic case, Lotus 1-2-3 Version 3.1, which uses Rational Systems' latest DPMI-compatible DOS16M, can fully exploit Windows 3.0 extended memory in any memory mode. It's a bonus that Windows programmers can use INT 31H to solve the problem of communicating with real mode.
There is a little support for real-mode interfacing in Windows proper. You can allocate a block of memory in real-mode address space by calling GlobalDosAlloc and free it with GlobalDosFree. GlobalDosAlloc returns both a real-mode segment value and a Windows selector. This "selector" is equal to the segment value if Windows is running in real mode; in protected mode the two values are different. In either case, your Windows code can use the "selector" to address the memory, passing the segment value to any real-mode code. That brings us back to the problem of how to call real-mode code in the first place, pointing back toward the DPMI approach, since there are functions to simulate a real-mode interrupt or function call.
Even if you're not communicating with a TSR, you may need to break some other protected-mode rules. Normally, the 286/386/486 hardware prevents you from executing instructions from a data segment, and you can't write into a code segment. (There is another level of protection available, a code segment that you can't even read from. That's not used often because it is common to read from a code segment, especially in compiled C code.) This is a big problem in those situations where you literally do need to write your code or run your data. Any kind of interactive compiler or code generator would have to do this. Amazingly enough, even the Windows display drivers use self-generating code techniques. When the display driver receives a BitBlt (bit block transfer) call, it compiles a small subroutine on the stack, containing the customized loops that make up that particular BitBlt operation. Then it calls that subroutine to perform the actual transfer.
Windows makes it easy to "cheat" and accomplish these things. All you have to do is create a code selector and a data selector that point to the same physical memory. Then you can do anything you could do in real mode; you just have to remember to use the code selector when you're executing machine instructions and the data selector when you're writing to memory. You can always read from memory with either selector.
To get an executable code selector for a data segment, call AllocDStoCSAlias. Or you can create a selector that isn't associated with any memory by calling AllocSelector, then calling ChangeSelector any number of times to make this selector an alias for various other segments. Either way, when you are finished with the "alias" selector, call FreeSelector. One thing to watch out for : if Windows has to move the segment in question (remember, segments still can move around in the linear address space) the alias selector won't get updated and it will point nowhere. To be safe, lock down the segment in linear memory by calling GlobalFix before aliasing it, and call GlobalUnFix when you're done. Also, ChangeSelector was inadvertently omitted from the SDK import libraries, so if you use it you need to explicitly import it in your DEF file:
IMPORTS
CHANGESELECTOR = KERNEL.177
You may discover that you can use ChangeSelector to create a data alias for a code segment as well as a code alias for a data segment. Although this works, it isn't recommended because it won't be supported in future environments such as OS/2 2.0. Creating a code alias for a data segment is the preferred method and should be compatible with future products.
Besides, you wouldn't want to receive a Programmer General's Warning: Cheating with selectors can be hazardous to your code. If you have to talk with a hardware device, you should get or write a Windows DLL that supports the device instead of using DPMI to call a real-mode TSR. As for this business about writing into code segments or running code in data segments, you saw the warnings in the SDK: "Use of these functions violates preferred Windows programming practices." You probably also use GOTOs and write self-modifying Assembler code and break the 55 MPH speed limit too. Bad Programmer!
Conclusion
One of the running jokes about Windows has always been that it was fine for users, but you couldn't develop Windows applications inside Windows. Even under Windows/386, editing and compiling within Windows just wasn't worth the trouble. The 386 mode made the compiles too slow, and you had to restart Windows anyway if you wanted to use CodeView. It's a different story now. As I write this using a Windows-based word processor, I have a DOS window compiling a test program I'm working on, with my source code loaded into BRIEF in another window, while another DOS window copies some files to diskette, and in the background FAXit sends a fax I created as another word processor document. I'm finally getting my money's worth out of this 386 system! I don't have to shell out of BRIEF to run DOS commands; all I have to do is switch to a DOS window. Debugging is easy now that CodeView runs inside Windows.
The entire Windows SDK reference is now a Windows Help document, making it very convenient to develop inside Windows. The whole thing is cross-referenced so you can click your way through any topic. It's really nice. If you're looking at the description of BeginPaint and want to see what the fields in the PAINTSTRUCT are called, you don't have to go flipping to the back of the book. Just click on PAINTSTRUCT on the screen and you're there. Another click and you're back to reading about BeginPaint. You won't want to develop outside Windows after using this.
I can't believe I did this, but I just added a WIN line to the end of my AUTOEXEC.BAT file! I tried booting up a couple of times and I don't miss that old DOS prompt. No, I take that back. I use the DOS prompt all the time-it's still the easiest way to do many things. But it's even handier to have as many DOS prompts as I want, along with the SDK help and all the rest. I guess I'm just going to have to get used to being a Windows user.
WIN.INI Color Settings
The predefined Windows 3.0 color schemes are all too bright and have too many contrasts for my taste-they look like they were designed for snappy screen shots instead of daily use. With the high-intensity white window backgrounds, they're much brighter than typical DOS applications. I looked through Control Panel's predefined color schemes to see if any were easier on the eye. No, they all use the same bright white window background, so I used the Color Palette option to change the window background to the new light gray color. But disabled (grayed-out) check boxes and radio buttons also use the light gray color instead of a dithered gray, so they're invisible against the light gray window background. I discovered that Control Panel doesn't let you set this color nor several other system colors, including the highlight (selection) text and background colors. You have to edit WIN.INI to set any of these. (Also, since many Windows 3.0 controls are painted with pairs of fixed bitmaps representing the pushed and unpushed states, playing with the ButtonShadow and ButtonFace values has no effect on most three-dimensional controls.) It's also no longer correct to invert colors to indicate highlighting; you are supposed to use the highlight text and background colors instead.
Here's the color scheme I came up with after much experimenting. I think its low contrast is easier to look at than any of the standard schemes. Try pasting the following code into your WIN.INI. It may look rather dim at first, but I'll bet that's because you have been turning down the brightness on your display to compensate for the overly bright standard Windows colors. Set the brightness to the normal level that you use with DOS applications and this should be just about right.
[Colors]
; Ones that Control Panel knows about
ActiveBorder= 128 255 255
ActiveTitle= 0 128 64
AppWorkspace= 64 128 128
Background= 128 128 128
InactiveBorder= 192 192 192
InactiveTitle= 128 128 128
Menu= 0 128 128
MenuText= 0 0 0
Scrollbar= 191 191 191
TitleText= 255 255 255
Window= 192 192 192
WindowFrame= 0 0 0
WindowText= 0 0 0
; New ones that Control Panel knows not
;ButtonFace=
;ButtonShadow=
ButtonText= 0 0 0
GrayText= 128 128 128
Hilight= 128 0 0
HilightText= 255 255 0
The following is a similar scheme that I designed to help test for painting bugs. It uses nonstandard colors for most elements and avoids colors that are complements of each other, to try to aggravate any misuse of colors and make problems obvious.
[Colors]
; Ones that Control Panel knows about
ActiveBorder= 128 255 255
ActiveTitle= 0 128 64
AppWorkspace= 64 128 128
Background= 128 128 128
InactiveBorder= 192 192 192
InactiveTitle= 128 128 128
Menu= 0 128 128
MenuText= 0 0 0
Scrollbar= 129 129 129
TitleText= 0 0 0
Window= 192 192 192
WindowFrame= 0 0 128
WindowText= 128 0 0
; New ones that Control Panel knows not
;ButtonFace=
;ButtonShadow=
ButtonText= 0 0 0
GrayText= 128 128 128
Hilight= 0 0 255
HilightText= 255 255 0
Figure 17
Syntax: DWORD GetWinFlags()
This function returns a 32-bit value containing flags that specify the memory configuration under which Windows is running.
This function has no parameters.
The return value contains flags specifying the current memory configuration. These flags may be any of the following values: Value Meaning
WF_CPU086 System CPU is an 8086.
WF_CPU186 System CPU is an 80186.
WF_CPU286 System CPU is an 80286.
WF_CPU386 System CPU is an 80386.
WF_CPU486 System CPU is an 80486.
WF_ENHANCED Windows is running in 386 enhanced mode. The WF_PMODE flag is always set when WF_ENHANCED is set.
WF_LARGEFRAME Windows is running in EMS large-frame memory configuration
WF_PMODE Windows is running in protected-mode. This flag is always set when either WF_ENHANCED or WF_STANDARD is set.
WF_SMALLFRAME Windows is running in EMS small-frame memory configuration
WF_STANDARD Windows is running in standard mode. The WF_PMODE flag is always set when WF_STANDARD is set. If WF_PMODE is not set, Windows is running in real mode.