Ask Dr. GUI #1

(Remember, he's not a real doctor.)

Bob Gunderson
Microsoft Developer Network Technology Group

Got a question? Dr. GUI's busy operating schedule won't allow him to answer all questions, but the Good Doctor will answer as many as he can on the next Microsoft Developer Network CD. Send your questions to:

Dr. GUI
Microsoft Developer Network
One Microsoft Way
Redmond, WA 98052-6399

Fax: 206-936-7329, Attn: Developer Network
Internet: devnetwk@microsoft.com

Dear Dr. GUI:

Does Windows (WIN.COM) set the environment variable windir? Some copies of Windows apparently put this in lowercase, some in uppercase.

I'm trying to write a batch file to install a utility in the user's Windows directory, and I'd like to take advantage of the windir variable, but MS-DOS doesn't recognize it. %windir% evaluates to nothing at run time. If I do set windir=n, a second variable is created in all caps—thus:

windir=C:\WINDOWS

WINDIR=C:\WINDOWS

So, I have a couple of questions:

Why does Windows put this in the environment in a form that is inaccessible to the user?

How can I make the value of windir accessible?

Dr. GUI replies:

Originally, windir was a private little hack that allowed WIN.COM and KERNEL.EXE to communicate. The Windows directory is the directory in which WIN.COM lives. The Windows system directory is the directory in which KERNEL.EXE lives. When an MS-DOS program is loaded, MS-DOS (or to be more accurate, COMMAND.COM) appends the directory in which the program was found to the end of the environment strings. WIN.COM directly modifies its copy of the environment, tacking windir in front of that directory and rearranging NULL values as appropriate, which saves space. windir was not meant to be used generically to determine whether Microsoft Windows is running. In fact, it did not occur to us that it would be used in that fashion. That it is not uppercase was perhaps an oversight. KERNEL.EXE always parses these strings itself and was unaware that batch files can deal with them only in uppercase.

Some other useful facts about windir:

It is always lowercase, never uppercase.

It is only set if Windows is running.

For backward compatibility, it will always be lowercase.

To check windir in a batch file, you'll have to write a program to check it directly and run this program in the batch file.

Dear Dr. GUI:

On page 10-5 of the Microsoft Windows version 3.0 Software Development Kit (SDK) documentation, in the Guide to Programming manual under section 10.5, "Reopening Files," the following statement appears:

"When a file is reopened, the file pointer marking the current position in the file is moved to the same position it was in just before the file was closed."

This statement is wrong. This documentation error has been around since version 2.03 of Windows. Please remove this statement from future releases of the SDK documentation.

Dr. GUI replies:

You are right! In fact, the statement has never been true.

Dear Dr. GUI:

I'd like to install a font (assuming it's not already installed) during setup processing. I've tried to simply copy the font file to the system directory and then add a line to the [fonts] section of WIN.INI. Assuming the user reboots Windows, is that all there is to it? What if the user doesn't reboot? It would be nice if this did not disturb the user's session.

Dr. GUI replies:

At initialization time, Microsoft Windows calls AddFontResource for each font file listed in the [fonts] section of WIN.INI. If you want the font available without rebooting, make this call yourself. Be careful though. You may need to add a different font based on the aspect ratio of the current display. This method will also work in Windows version 3.1 with TrueType fonts.

Dear Dr. GUI:

I know the documentation says not to delete stock objects, but it is darned inconvenient to save a bit to indicate if an object is stock. I have many instances of code like the following:

if ((hobj = alloc an object) == NULL)

hobj = GetStockObject(stock object);

// Use object

DeleteObject(hobj);

My question is:

Is it really a no-no to delete a stock object ? Windows versions 3.0 and 3.1 both seem to handle it OK.

Also, the documentation says that GetStockObject can fail. Can it really fail if I ask for a legitimate stock object?

Dr. GUI replies:

It is absolutely safe to delete stock objects. GDI checks for this specific case and ignores the request if the object is a stock object.

While GetStockObject never fails when asked for a legitimate stock object, selecting a stock object into a device context (DC) using SelectObject can fail. When you select an object, GDI realizes it and may need to allocate memory. If memory is tight, the SelectObject call can fail. Your application needs to be able to handle this failure.

Dear Dr. GUI:

Hi. I'm in need of a screen shot tool that can take shots of a window and can also capture open menus. I'd like to avoid drawing menu bars by hand.

Dr. GUI replies:

Actually, you don't need a special tool to do this. Starting with Windows version 3.0, you can use the PRINT SCREEN key to capture an image of the entire screen. This image is placed in the Clipboard. If all you want is the active window, use the ALT+PRINT SCREEN combination. Simply paste this into your favorite bitmap editor (such as Microsoft Paintbrush), and trim the image you want.

Using ALT+PRINT SCREEN to capture dropped menus may be a little tricky because the menu manager uses the ALT key. But if you follow these simple steps, you can do it easily:

1.Make the program active.

2.Hold down the ALT key.

3.Press the keyboard accelerator key for the menu desired (without releasing ALT).

4.Move the selection with the arrow keys (keeping ALT down).

5.Press the PRINT SCREEN key.

Dear Dr. GUI:

I have an application that prompts the user to save changes when it gets a WM_QUERYENDSESSION message. No problem so far. But if the user hasn't saved this document before, I have to bring up my Save As dialog box to prompt for a file name. The problem is that I have a drop-down combo box in this dialog box. When anyone tries to drop down the combo box at WM_QUERYENDSESSION time, the system hangs. What's happening?

Dr. GUI replies:

All windows created when WM_QUERYENDSESSION is being broadcast are created system modal. When a system modal window is up, all other applications are prevented from running to ensure that nothing happens while Windows asks all the applications if it is OK to shut down the system. Your problem is caused by a bug in the Windows version 3.0 drop-down combo box code. Windows was attempting to send a message to another application when the combo box was dropped. This hangs the system because the other application could not run at system modal time.

So, how do you get around this? What I would do, and what others have done, is to detect the system modal case while processing your WM_INITDIALOG message for the dialog box and then disable the combo box. You can easily detect if a system modal window is up by calling GetSysModalWindow. A nonzero return value tells you that you need to disable your combo box.

One more thing. This bug is fixed in Windows version 3.1, so you might want to add a version test and disable the combo box only in Windows version 3.0 (use GetVersion).

Dear Dr. GUI:

Do you know a method I can use to modify a list box string without having to use LB_DELETESTRING and then LB_INSERTSTRING? Deleting and inserting causes an unpleasant repaint of the entire list box, not to mention the "jump" that takes place between the delete and the insert messages.

Dr. GUI replies:

Although Microsoft Windows has no atomic call that deletes and inserts a string in a list box, you can prevent the list box from jumping around when you modify its contents. Take a look at the WM_SETREDRAW message. Sending this message to the list box with a FALSE value in wParam prevents the control from updating when you make changes. Passing a TRUE value reenables these updates. After you reenable updates, you have to force the control to redraw. In other words, follow these steps:

SendMessage(hWndListbox, WM_SETREDRAW, FALSE, 0) ;

/* Modify the control content here.*/

SendMessage(hWndListbox, WM_SETREDRAW, TRUE, 0) ;

InvalidateRect(hWndListbox, NULL, FALSE) ;

UpdateWindow(hWndListbox) ;

By the way, WM_SETREDRAW works on all standard controls, not only on list boxes.

Dear Dr. GUI:

I have a window in which I want to provide a customized icon for different instances of the window class instead of using the class icon. To show the customized icon, I respond to the WM_PAINT message and, if I am iconic (using IsIconic), I paint the icon with DrawIcon. This all works fine. But when someone clicks on my icon and drags it around the screen, the cursor becomes this ugly white square rather than an image of my icon. After reading the documentation, I now realize that I need to respond to the WM_QUERYDRAGICON message, but this message needs an hCursor rather than an hIcon. How do I convert an icon into an equivalent cursor?

Dr. GUI replies:

You don't have to convert anything! Simply pass the handle to your icon back from the WM_QUERYDRAGICON message, and Windows transforms it into a cursor for you. Pretty cool, huh?

Dear Dr. GUI:

While creating a custom font for my application using the Font Editor that comes with the Microsoft Windows SDK, I got confused by the font format selection in the Save As dialog box. What is the difference between version 2.0 format and version 3.0 format? When I save the font in version 3.0 format, it won't work unless I am running Windows on my 386 system. Windows refuses to load the font when running on my 286 system. If I save the font in version 2.0 format, it works just fine with Windows version 3.0 on both systems. What gives?

Dr. GUI replies:

Let me explain why you are confused. Font files consist of two main pieces, a font header and font bits. The font header is a large array of descriptors, with one descriptor for each character in the font. The font bits are the actual pixel bitmaps of each character and are all packed together. The version 2.0 font file format has a small problem: The offsets in the font header that point to the start of the font bits for each character are only 16 bits long. Therefore, the font bits can be at most 64K long. This is more than adequate for most fonts, but some large point size fonts and fonts for high-resolution displays need more than 64K of font bits. Microsoft Windows version 3.0 implemented a font file format that widened the font bit offsets to 32 bits. With this format, you could create and use huge fonts, but performance was a problem for two reasons—because of the need to do 32-bit math, and because of the huge number of bits that these fonts contained. To speed things up, 32-bit code was added to Windows to accommodate these fonts. Now you see the problem. This 32-bit code simply does not run on anything but a 386 (or higher) processor, which means that Windows does not load version 3.0 format font files unless it is running on a 386 (or higher) processor.

So, what does this all mean? Unless you absolutely need the added capabilities of the version 3.0 font file format, you should continue to use the version 2.0 format. Microsoft will continue to support the version 2.0 format.

Dear Dr. GUI:

My application contains several DLLs that may or may not exist when the application runs. I want to look at the return value from the LoadLibrary function to determine whether the DLL was loaded. The problem is that LoadLibrary displays an annoying message box if Windows cannot locate the DLL. How do I make LoadLibrary stop doing this? I don't want to add all the extra code to search for the DLL before I call LoadLibrary.

Dr. GUI replies:

This is your lucky day! An undocumented feature in Windows version 3.0 lets you do this very thing. (Don't ask why it was never documented; I don't know.) If you set the high bit of the parameter to the SetErrorMode function, Windows does not display the message box. This affects both the LoadLibrary and the LoadModule functions. In the version 3.1 WINDOWS.H file, a new constant, SEM_NOOPENFILEERRORBOX, is defined for this purpose.

Dear Dr. GUI:

My application needs to know the time when a key on the keyboard was pressed. I need this information to analyze typing rates. The value the GetMessageTime function returns seems tied to the time that the GetMessage function returned the message rather than to the time of the keyboard event. Because my application is "well behaved" and calls GetMessage to yield control to other applications, quite a bit of delay can occur between the time the key on the keyboard is pressed and the time my application sees the WM_CHAR message. What can I do?

Dr. GUI replies:

Time for a quick overview of Windows keyboard messages, queues, and events. When a key is pressed, the processor causes a keyboard interrupt, which the Windows keyboard driver handles. The driver does some processing, such as determining the virtual-key code, and calls the window manager. The window manager adds an entry to the system queue that describes the keyboard event. Among other information, it stores the time of the event in the system queue. The window manager then dismisses the interrupt, and processing continues. When your application next calls the GetMessage or the PeekMessage function, it sees the keyboard event and creates a message based on the event. Your problem is that the messages that the system queue keyboard events generate are limited to WM_KEYDOWN and WM_KEYUP (and the SYS versions of these messages). The TranslateMessage function generates the WM_CHAR message when it sees a WM_KEYDOWN message. The return value from GetMessageTime is synchronized with the last message returned from GetMessage or PeekMessage. So, if you look at this value after you get the WM_CHAR message, you see the time your application called TranslateMessage with the WM_KEYDOWN message—not exactly what you are looking for. Instead of watching the WM_CHAR messages, catch the WM_KEYDOWN messages. The value from GetMessageTime is the time of the interrupt event for this message.

Dear Dr. GUI:

The resource compiler keeps giving me messages like the following:

Non-Discardable segment 1 set to PRELOAD

What does this mean? Everything seems to work fine. Should I be concerned?

Dr. GUI replies:

A decision was made a long time ago that certain segment types in Windows applications should be marked PRELOAD. This simply means that the Windows loader automatically loads the segment when the application starts. The following segment types fall under this rule:

The code segment that contains the entry point

Any segment marked FIXED

All data segments

All nondiscardable code segments

The resource compiler checks all segments and verifies that any of the segment types listed above are also marked PRELOAD. If they aren't, the resource compiler automatically marks the segment and displays the message you describe. You can either live with the message or change your DEF file to prevent the resource compiler from complaining. You could change your CODE and DATA statements to read something like the following:

CODE MOVEABLE DISCARDABLE PRELOAD

DATA MOVEABLE MULTIPLE PRELOAD

These statements define the default segment style for all segments not explicitly mentioned in the SEGMENTS section of the file, so be careful. For applications of any significant size, you want to control the style explicitly on most segments (at least, for code segments). To do this, add the SEGMENTS section to your DEF file, and include the PRELOAD style on the segments that need to be marked PRELOAD. (Look in your favorite Windows manual for the format of the DEF file.)

Dear Dr. GUI:

I can't figure out the meaning of the lfOutPrecision field in the LOGFONT data structure. A bunch of constants in WINDOWS.H (OUT_CHARACTER_PRECIS and so on) are supposed to be used for this field, but they are equally nondescript. Can you shed some light on this?

Dr. GUI replies:

Ah, the wonders of fonts! In reality, this field means little. The font mapper uses it to distinguish between stroke fonts and all other fonts. For Windows version 3.0, set it to 0 if you are creating a LOGFONT structure. If you are using a LOGFONT structure obtained through EnumFonts, simply leave the field as it is.

In Windows version 3.1, this field gains new functionality. A number of new constants are defined in WINDOWS.H that let an application tell the font mapper what to do if a font name conflict arises among raster, stroke, TrueType, or device fonts. The value of this field determines which type of font is returned if names conflict.

By the way, the lfClipPrecision field is even less meaningful than the lfOutPrecision field.

Dear Dr. GUI:

I want to determine the "Free System Resources" number that Program Manager displays in its About dialog box. How do I do this? I've heard of an undocumented function called GetHeapSpaces that I can supposedly use to do this. Can you shed some light?

Dr. GUI replies:

The GetHeapSpaces function should have been documented but never was. It's a really useful function that returns information about a local heap. Given the module handle or the instance handle (either may be passed), it returns two values packed into a double word. The low word of the return value contains the number of bytes that are currently free in the module's local heap. The high word contains the maximum potential size, in bytes, of the heap, assuming the data segment in which it resides can grow to 64K. The high word value takes into account other things in the data segment, such as statics and stacks.

The following source (extracted from Program Manager source) computes the free system resource number that Program Manager reports in Windows version 3.0:

DWORD cbFree;

WORD wFreeK, wHeapK;

WORD wUserPercent, wGDIPercent, wSysPercent;

cbFree = GetHeapSpaces(GetModuleHandle("USER"));

wFreeK = LOWORD(cbFree) / 1024;

wHeapK = HIWORD(cbFree) / 1024;

wUserPercent = (wFreeK*100) / wHeapK;

cbFree = GetHeapSpaces(GetModuleHandle("GDI"));

wFreeK = LOWORD(cbFree) / 1024;

wHeapK = HIWORD(cbFree) / 1024;

wGDIPercent = (wFreeK*100) / wHeapK;

wSysPercent = min(wUserPercent, wGDIPercent);

A Windows version 3.1 function does this for you. The GetFreeSystemResources function takes a single parameter that determines the "resource" in which you are interested. Program Manager replaces all code listed above with a single call:

WORD wSysPercent;

wSysPercent = GetFreeSystemResources(0);