Questions & Answers - Windows

Q:

I'm doing a series of grouped CreateCompatibleDC, CreateBitmap and SelectObject calls to create many relatively large, concurrent bitmaps, followed by the proper symmetric series of SelectObject, DeleteObject and DeleteDC calls. Everything works fine in standard and 386Ô enhanced modes, but in real mode the behavior is erratic. Sometimes the CreateBitmap calls succeed and other times they fail, without any apparent pattern. I know it's a memory management problem, but I do not know specifically what it is and how to solve it.

A:

The sequencing of your calls sometimes causes problems. Your sequence, in abbreviated form without error checking, is:

auto HDC ahDC1, ahDC2;

auto HBITMAP ahBM1, ahBM2;

ahDC1 = CreateCompatibleDC(...);

ahBM1 = CreateBitmap(...);

ahBM1 = SelectObject(ahDC1, ahBM1);

ahDC2 = CreateCompatibleDC(...);

ahBM2 = CreateBitmap(...);

ahBM2 = SelectObject(ahDC2, ahBM2);

o

o

o

ahBM2 = SelectObject(ahDC2, ahBM2);

DeleteObject(ahBM2);

DeleteDC(ahDC2);

ahBM1 = SelectObject(ahDC1, ahBM1);

DeleteObject(ahBM1);

DeleteDC(ahDC1);

Whenever a large bitmap is created, it is a GMEM_MOVEABLE global memory object. Once that bitmap is selected into a DC, it becomes locked. Your problem is that you are "sandbarring," or fragmenting memory in real mode by selecting your bitmaps into DCs, thereby locking them, before you really need to. This makes it increasingly unlikely that there will be chunks of contiguous free memory large enough for creating subsequent large bitmaps. Because of the somewhat random state that your global heap is in at the time, the behavior is erratic.

Rearrange your code to read as follows:

ahDC1 = CreateCompatibleDC(...);

ahDC2 = CreateCompatibleDC(...);

ahBM1 = CreateBitmap(...);

ahBM2 = CreateBitmap(...);

ahBM1 = SelectObject(ahDC1, ahBM1);

ahBM2 = SelectObject(ahDC2, ahBM2);

o

o

o

ahBM2 = SelectObject(ahDC2, ahBM2);

ahBM1 = SelectObject(ahDC1, ahBM1);

DeleteObject(ahBM2);

DeleteObject(ahBM1);

DeleteDC(ahDC2);

DeleteDC(ahDC1);

You should probably also be doing a GlobalCompact((DWORD)-1L) before these calls to maximize the amount of memory in the global heap and allow WindowsÔ to clean it up. It is best to create your bitmaps (or any global memory object, for that matter) in order from largest to smallest memory requirement to increase the likelihood of getting all the global memory you need.

Q:

My company has quite a few old 8MHz IBM PC/ATs, on which we run Windows 3.0 in standard mode. We run a lot of DOS applications concurrently with Windows and then access them through the Alt+Tab key combination. Because of these DOS applications, we have set up large RAM disks for the temporary files. Our problem is that when we Alt+Tab to a DOS application, the first few keys subsequently hit get lost (that is, the application does not receive them). Is this a bug in Windows?

A:

This effect is due to the way that Windows displays the standard mode DOS applications to the user, and it is not a bug.

In standard mode, whenever you Alt+Tab to a DOS application from the Windows "screen group," Windows needs to save the contents of low memory (memory below 640Kb) to a hidden read-only file. This file, called ~WOA0000.TMP, is in your temporary subdirectory. While Windows is saving the contents of memory, the screen blanks out. After the memory is saved, Windows can then reload low memory with the DOS application. It does this from another hidden read-only file named ~WOAXXXX.TMP, where each X is a random hexadecimal digit. This file is basically a memory image of the DOS application and its video buffer as it existed when you last left DOS.

Windows next reloads the video buffer as it existed for the DOS application, then reloads the roughly 500Kb of low DOS memory for the application. Next Windows resets the current disk and directory for the DOS application. Finally, Windows resets all the interrupt vectors set by the DOS application and any TSRs that exist. As a result, you see the DOS application's screen before the application itself is loaded and able to react to any keyboard interrupts.

This is more pleasant visually, but it makes you think that something is wrong when the application does not immediately react to any keystrokes. All you can do is wait until the application is completely loaded and the interrupt vectors are reset.

When you Alt+Tab out of a DOS application, the reverse happens. There is a slight delay as the video buffer is saved. Windows then displays the "Switching..." message while it saves the low memory containing the DOS application. Then it reloads the next application in the Alt+Tab chain.

If you are running many DOS applications and Alt+Tab'ing between them, you can hold the Alt key down during the "Switching..." message, press Tab until you get to the DOS application name you want, then release the Alt key. This saves you from having to go back to the Program Manager or Task Manager to get to the next DOS application you need.

Q:

I'm writing an MDI-style application, where the focus may be on one of many windows. In a multiple-window application, what's the easiest way to check for messages across all windows without having to add code to each WndProc? I want to monitor and display the state of the Num Lock, Caps Lock and Scroll Lock keys, as is done in Microsoft Excel and Microsoft Word for Windows. I'd prefer to use an object-oriented method, so that I don't have similar code (or function calls) in numerous WndProcs. Microsoft Excel and WinWord seem to monitor both WM_MOUSEMOVEs and WM_KEYDOWN/WM_KEYUPs, but don't always show a changed key status unless they are the active application.

A:

The easiest way to check all your messages, no matter which MDI window has the focus, is to use a modified main GetMessage loop, as shown in the following example.

auto MSG aMsg;

while (GetMessage(&aMsg, 0, WM_NULL, WM_NULL))

{

switch (aMsg.message)

{

case WM_XXXX:

o

o

o

continue;

// do not Translate and Dispatch

case WM_XXXX:

o

o

o

break;

// do normal Translate and Dispatch

}

TranslateMessage(&aMsg);

DispatchMessage(&aMsg);

}

Although this will allow you to monitor all the messages that go through the normal application message queue, you will not see messages here that are sent directly to your various WndProcs. Many common Windows messages are sent directly to the WndProcs. As an example, a WM_ERASEBKGND is sent directly as a result of a WM_PAINT BeginPaint call, so you could not successfully switch on it in the above code.

For monitoring the Lock keys, consider setting a systemwide hook with SetWindowsHook. Unfortunately, this must be done in a DLL and is complicated. This is probably why Microsoft Excel and WinWord appear to monitor only those WM_MOUSEMOVEs and WM_KEYDOWN/WM_KEYUPs that come to them, and therefore do not accurately reflect the state of the Lock keys. Incidentally, when you study how these two applications operate, the Lock keys code they use appears to be very different.

The best way to monitor the Lock keys is to simply do a SetTimer for your main WndProc for, say, every 125 milliseconds. This will allow you to represent the state accurately, whether or not you are the active application. On the WM_TIMER message, see if the state of each key (VK_NUMLOCK, VK_CAPITAL or VK_SCROLLLOCK) has toggled with:

(GetKeyState & 0x00FF).

If it has, redisplay the state appropriately. (Note: VK_SCROLLLOCK should be defined as 91H, because that key code is currently undocumented and not in WINDOWS.H.)

Q:

I can easily prevent multiple copies of a Windows application from running by coding the application so that it checks the WinMain PrevInstance argument. How do you prevent multiple copies of a regular DOS application from running concurrently in Windows? My goal is to protect a dedicated file from being corrupted by accidentally running two instances of the DOS application.

A:

There is no direct way of doing this within Windows. You can, however, accomplish this indirectly by using a BAT file to test for the existence of a "semaphore file" before running your DOS application under Windows. A semaphore is a signaling apparatus; in other words, a fancy name for a flag.

Assume your DOS application is named DOSAPP.EXE and that it resides in your C:\DOSAPP subdirectory. Create a C:\DOSAPP\APP.BAT file with either Notepad or your editor as follows:

@echo off

c:

cd \dosapp

if exist %temp%\dosapp.sem goto sem

echo %0 >%temp%\dosapp.sem

dosapp %1 %2 %3 %4 %5 %6 %7 %8 %9

erase %temp%\dosapp.sem

goto end

:sem

echo DOSAPP is already running!

pause

:end

In the Program Manager, select the group that the application should be in or is in, choosing File New for the Program Group or File Properties on the Program Item. Instead of specifying C:\DOSAPP\DOSAPP.EXE in the Program Item Properties dialog box, set the Command Line to C:\DOSAPP\APP.BAT. In this example, you could also specify up to nine arguments (%1 to %9 in the BAT file) on the same Command Line.

The above BAT file assumes that you have set an environment variable in your AUTOEXEC.BAT named TEMP indicating a subdirectory where temporary files should go, although it will still work without the TEMP variable. Ideally the subdirectory would be on a RAM disk, created with the RAMDRIVE.SYS program that comes with Windows. If TEMP points to a subdirectory on your hard disk (say C:\TEMP), you should also include the following line after the SET TEMP=C:\TEMP as a "reboot" handler in AUTOEXEC.BAT:

if exist %temp%\*.sem erase %temp%\*.sem

The two lines in the BAT file that move you into C:\DOSAPP (c: and cd\dosapp) are necessary only if you do not put the BAT file in C:\DOSAPP. They ensure that the current subdirectory is set properly before DOSAPP.EXE is executed, just as Windows does if you had specified C:\DOSAPP\DOSAPP.EXE on the Command Line. When a path is specified on the Command Line, Windows changes into that subdirectory before executing the EXE or BAT file. If the BAT file is in the same subdirectory as the EXE file, it should not have the same filename since EXE files are executed before BAT files.

It would be more elegant to be able to use an environment variable as a semaphore instead of the file to block the second instance, but this will not work. Each EXE (Windows or DOS) or BAT file inherits the environment that exists at the time Windows is started. Setting an environment variable in the BAT file only applies to that specific invocation of the BAT file, so a second execution of the same BAT file would not "see" the environment variable set in the previous one. Unfortunately, the only common changeable "environment" in Windows is WIN.INI, and DOS sessions cannot access it.

Another way to protect your files from multiple updates, although not foolproof, is to execute the DOS SHARE.EXE program in AUTOEXEC.BAT. This recommendation is documented in one of the "read me" files that come with Windows, and is often overlooked.

Q:

What are the recommended default sizes for the predefined window/control classes (Button, ComboBox, Edit, ScrollBar and Static)? I want to make sure my controls adhere to the standard. Also, how would these sizes be determined at run time?

A:

The width and height of the predefined window control classes is based on an average character being 4 logical units wide and 8 logical units high. The actual values for a unit then depend on the resolution and aspect ratio of the display, as evidenced by the size of the default system font. Based on this relative concept and these values, use the following table:

Class Style Width Height

Button BS_CHECKBOX 12 BS_AUTOCHECKBOX 12 BS_3STATE 12 BS_AUTO3STATE 12 BS_PUSHBOX 14 BS_PUSHBUTTON 14 BS_DEFPUSHBUTTON 14 BS_RADIOBUTTON 12 BS_AUTORADIOBUTTON 12

ComboBox CBS_SIMPLE 12 CBS_DROPDOWN 12 CBS_DROPDOWNLIST 12 CBS_OWNERDRAWFIXED 17 CBS_OWNERDRAWVARIABLE 17

Edit all 12

ScrollBar SBS_HORZ 9 SBS_VERT 9

Static SS_LEFT 8 SS_CENTER 8 SS_RIGHT 8 SS_SIMPLE 8 SS_LEFTNOWORDWRAP 8

These values can be used directly in the Dialog Editor. If you need to convert these logical values to pixels on a particular display at run time, use the following code:

lDlgBaseUnits = GetDialogBaseUnits();

AveHeight = HIWORD(lDlgBaseUnits);

AveWidth = LOWORD(lDlgBaseUnits);

You can then use the AveWidth and AveHeight to calculate the actual pixel values for a standard window/control size by multiplying them by the logical values from the table and dividing by the logical units of the width and height.

aiButtonCheckboxDefH = AveHeight * 12 / 8;

aiButtonPushboxDefH = AveHeight * 14 / 8;

aiButtonPushbuttonDefH = AveHeight * 14 / 8;

aiButtonRadiobuttonDefH = AveHeight * 12 / 8;

aiComboboxNormalDefH = AveHeight * 12 / 8;

aiComboboxOwnerdrawDefH = AveHeight * 17 / 8;

aiEditDefH = AveHeight * 12 / 8;

aiScrollbarHorzDefH = AveHeight * 9 / 8;

aiScrollbarVertDefW = AveWidth * 9 / 4;

aiStaticTextDefH = AveHeight * 8 / 8;