Windows[TM] Q&A

by Fran Finnegan

Fran Finnegan is chairman of Finnegan O'Malley & Company Inc., a Windows consulting and contract programming firm that is developing E-Mail ManagerÔ, an electronic-mail application.

QI have an I/O peripheral board that occupies address range D000:0000H to D000:FFFFH. In MS-DOSÒ-based applications, I can directly read or write to the board in this address range. I’m developing a WindowsÔ 3.x-based application that uses this I/O board, and it must run in real, standard, or enhanced mode. How do I access these memory-mapped locations in all modes?

Joe Siu
Toronto, Canada

AIn real mode, continue to use segment:offset addresses. In protected mode (standard or enhanced mode), you need to get a selector that points to that physical address range by using the DOS Protected Mode Interface (DPMI). Windows operating system version 3.x includes a DPMI-compliant MS-DOS extender that allows Windows1-based applications to access protected memory in standard and enhanced modes. Despite what the IntelÒ DPMI documentation says, DPMI is available to Windows-based applications in standard mode. As such, simply testing for protected mode does determine whether DPMI is present.

You can determine whether you are running in real or protected mode with GetWinFlags. If you are in protected mode then you know that DPMI is present.

You can use a DPMI API function that returns a selector to replace a segment value when in protected mode. Real mode segment values are converted into protected mode selectors (with a global descriptor limit of 64K) using the DPMI INT 31H Function 2H. All you need to do is make one call to this function during the startup phase of your program to set up a segment/selector variable.

In real mode, set your segment variable to the actual real mode segment value (D000H in your case). In protected mode, set the segment variable to the value returned by DPMI Function 2H. The GetSegmentSelector function in Figure 1 does this. You then build a far pointer to the desired memory-mapped location using code similar to that in Figure 2, which is a code fragment using GetSegmentSelector.

Be aware that only 8K of protected-mode selectors are available (in the Local Descriptor Table), and that they are shared among all applications in the Windows DPMI implementation. Selectors allocated with this function cannot be freed or modified, so use this service sparingly. Fortunately, multiple selector requests made for the same real mode segment return the same selector. More flexible selectors may be allocated, modified, or deleted using other DPMI functions. The complete DPMI specification is available from Intel or Microsoft; to request a copy, submit a service request via MicrosoftÒ OnLine or call Intel at 800-548-4725. The most recent version is implemented in Windows 3.1.

Figure 1 Testing for Real or Protected Mode

// Use the DPMI Real Mode Segment to Descriptor service

// to get a selector to the requested segment.

// The selector will be mapped to a 64K segment beginning

// at the wRealSegment address. This function works in

// all Windows modes. In real mode, the function returns

// the actual address instead of a selector.

// bErrFlag is set TRUE on error, FALSE otherwise.

extern WORD GetSegmentSelector

(

WORD wRealSegment,

BOOL *bErrFlag

)

{

auto WORD wProtSelector;

// assume success

*bErrFlag = FALSE;

// check for real mode

if (!(GetWinFlags() & WF_PMODE))

return wRealSegment;

// use the DPMI Segment to Descriptor Service

_asm

{

mov ax, 2 ; DPMI function 2

mov bx, wRealSegment ; real mode address

int 0x31 ;

jc err ; carry flag set on error

mov wProtSelector, ax ; selector is returned in AX

}

return wProtSelector;

err:

// report error

*bErrFlag = TRUE;

return 0;

}

Figure 2 Using GetSegmentSelector

auto WORD wSeg;

auto BOOL bErrFlag;

auto BYTE far *lpData;

.

.

.

// get a selector for the device at segment D000h

wSeg = GetSegmentSelector(0xD000, &bErrFlag);

if (bErrFlag)

{

// complain here

return;

}

// Setup a segment and offset value

_segment seg = wSeg;

BYTE _based(void) *off = (BYTE _based(void) *)(0x0000);

// Build a based pointer out of the selector/offset.

// This is Microsoft C 6 specific, but Microsoft C 5 and

// Borland C already includes macros to build a far pointer from

// a segment and offset.

lpData = seg:>off;

// lpData is now a far pointer to the unsigned char at

// D000:0000

.

.

.

QI have developed an installation program for Windows-based applications that transfers files from multiple floppies to a hard disk. The install program is on the first disk of a set and is to be run via the Program Manager File Run command.

I found that when the install program prompts for a disk change, if the second disk has a different volume label than the first, I get a system error message, explaining that the system can’t read from the specified disk drive.

How can I get around this problem, other than ensuring that all disks of a set have the same label?

Peter D’Agostino
Dublin, Ohio

APutting the same label on all of the installation disks is likely to cause problems if the system doesn’t detect the floppy disk change.

The system error you described occurs when additional code or resources need to be loaded for the installation program. The trick is to force Windows to load all of the required elements of your program before you need to change disks. All of the code and data segments need to be marked PRELOAD in the module definition (DEF) file, so that they are loaded when you start the installation program. The typical definition file sets the code segments discardable; you need to flag your code as nondiscardable.

When a dialog box template, string, icon, or other resource is needed, Windows normally searches the executable file on disk for it. By making the name of the resource an integer, the MAKEINTRESOURCE macro allows Windows to find the resource without going back to the disk to find the name. Resources must be flagged PRELOAD to force early loading and marked FIXED to keep them from being discarded later. Use a header file with #define statements to refer to your resources in both the resource script (RC) and in your source file. Dialog templates are a little more trouble at run time. Use FindResource, LoadResource, and LockResource early to lock down your dialog templates before the first disk is removed. Be sure to free the templates before your install program exits. Figure 3 outlines the areas you need to focus on.

Figure 3 Forcing Windows to Load an Installer

INSTALLR.H

#define INST_ICON 100

#define GET_DSK_DLG 300

#define ABOUT_DLG 310

.

.

.

INSTALLR.DLG

GET_DSK_DLG DIALOG PRELOAD FIXED 0, 0, 100, 100

.

. (rest of dialog template)

.

ABOUT_DLG DIALOG PRELOAD FIXED 0, 0, 100, 100

.

. (rest of dialog template)

.

INSTALLR.RC

#include <windows.h>

#include "installr.h"

#include "installr.dlg"

.

.

.

INST_ICON ICON PRELOAD FIXED

.

.

.

STRINGTABLE PRELOAD FIXED

BEGIN

TITLE_STR, "Install Program"

WAIT_STR, "Copying..."

.

. (other string statements)

.

END

INSTALLR.C

#include "INSTALLR.H"

.

.

.

// use this code to load an icon when needed

hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(INST_ICON));

.

.

.

// grab and lock down any dialog templates that might be

// needed after the first disk is removed

hDlgGetDsk = FindResource(hInstance,

MAKEINTRESOURCE(GET_DSK_DLG), RT_DIALOG);

hDlgGetDsk = LoadResource(hInstance, hDlgGetDsk);

LockResource(hDlgGetDsk);

hDlgAbout = FindResource(hInstance,

MAKEINTRESOURCE(ABOUT_DLG), RT_DIALOG);

hDlgAbout = LoadResource(hInstance, hDlgAbout);

LockResource(hDlgAbout);

.

.

.

// when finished with the dialog templates,

// you need to free them

UnlockResource(hDlgGetDsk);

FreeResource(hDlgGetDsk);

UnlockResource(hDlgAbout);

FreeResource(hDlgAbout);

.

.

.

QI’m confused about the way that POINT and RECT address pixels. If I set the pixel at a POINT (such as 10,10) to a particular color, it works. But if I try to do something with that pixel with a RECT (such as 0,0,10,10), the pixel at POINT 10,10 is not affected. Why does using RECT deny access to the right-most and bottom-most pixels?

Katy Kennedy
Oklahoma City, OK

AIn Windows, pixels do not really have addresses. POINT seems to be the address of a pixel, but it really isn’t. What you have to visualize is a set of imaginary horizontal and vertical lines between the pixels on your display (or any bitmap). Figure 4 shows addressing numbers for those imaginary lines for VGA.

For a POINT, the pixel that is affected is the one to the lower right of the imaginary-line address you specify. To use POINT to get to the bottom right of a VGA display, the address would be 639,479.

For a RECT, the left/top/right/bottom that you specify becomes a bounding rectangle around the pixels that you want to address. To use a RECT to address the pixel at POINT 639,479, you would specify it as 639,479,640,480. This RECT would surround, or bound, a single pixel.

By implementing RECTs this way in Windows, a number of nice features are present. You can always get the number of pixels affected by an operation by subtracting the "left" from the "right" (for the width), or the "top" from the "bottom" (for the height). Conversely, you can always create a RECT of a given width and height by simply adding the width and height to the "left" and "top" elements of the RECT structure. Once you are used to this elegant concept, addressing pixels becomes very easy.

The concept also applies to the MoveTo and LineTo functions. This explains why you never seem to draw the last pixel on a line, or always have to draw the line one pixel longer than you think should be necessary. To remedy this mind-set, think in terms of bounding rectangles. The length of the line horizontally and vertically will always be the difference between the MoveTo x,y and the LineTo x,y, just as if you were figuring the width and height using a RECT structure. In other words, MoveTo and LineTo are specifying the bounding rectangle of the line that is being drawn, and are not referring to a POINT at each end.

Figure 4 Addressing Pixels

QI’m modifying an existing piece of software. I want to run more than one instance of it at once. When I try to run the program a second time, I get the following message from Windows:

Application Execution Error

Cannot start more than one

copy of the specified program.

Looking at another application that allows me to run multiple copies, I can find no discernible differences in the DEF files or in how the compile and link are done. The original program was compiled in large model. It was suggested to me that the large model was causing my problem. I recompiled the application in small model, but I still have the problem. I inserted a MessageBox call at the top of WinMain, and still got the error even before the call to MessageBox could be reached.

Kai Middleton
Oakland, California

AThe error message you are getting indicates that you have more than one data segment in your application. The Microsoft C default for large model programs is multiple data segments, which is why it is not recommended for most Windows-based applications. Since you switched to small model and you’re not getting complaints about exceeding data segment sizes, I suspect that either a library you are linking in is creating an extra data segment or that you have some far static data allocated.

You can tell for sure by looking at your link map for FAR_BSS data segments or other suspicious data segments. There should only be one DGROUP segment. Look for any global or static data declared with the far keyword in your program or in any extra libraries being linked with your program.

Second instances of an application with multiple writable data segments aren’t allowed. The problem occurs because the Windows memory manager does not have a way to fix the references to more than one data segment for multiple instances if the extra segments are not read-only segments. Windows simply does not allow a second instance of applications in this case. Instead, it is forced to flag all of the extra data segments for the first instance of the application as fixed, and fixes up the code to reference these multiple data segments. If the extra data segments are read-only, you can indicate this in the SEGMENTS section of your DEF file. Windows will then let you run multiple instances.

QIs there a way to intercept a call to a function in a DLL, and then (after some processing) pass it on to the intended DLL function, in a way that is transparent to both the caller and the called function? I would like to get between my application and the DLLs to do some debugging. But I would also like to know if it is possible to intercept APIs between any application and DLLs. How much more effort would be involved?

Don Hackler
via CompuServe

AIt’s relatively painless to intercept DLL calls from an application that you are building, but far more complicated to intercept calls from other applications that you can’t get to at link time. Adobe Type ManagerÒ is an example of an application that intercepts calls by every running application to one or more of the KERNEL, GDI, and USER DLL modules. Describing how it does this would require an entire article.

For programs you are creating, you can build a DLL that intercepts calls to another DLL. The names of the functions in the intercept DLL can be aliased in the DLL’s DEF file to match the function names of the original DLL. You need to place the import library for the intercept DLL in the link line ahead of the import library for the DLL whose calls you are intercepting.

Say you wanted to check some of the GDI calls you are making. In the intercept DLL, you would code the intercept functions similarly to Figure 5, which shows a debugging version of CreatePen. This debugging version calls the real function.

Figure 5 Creating a Debug Version of a Function

.

.

.

HPEN DbgCreatePen

(

int nPenStyle,

int nWidth,

COLORREF crColor

)

{

auto HPEN hPen;

.

. (Sanity-check input arguments here,and complain or return as needed.)

.

hPen = CreatePen(nPenStyle, nWidth, crColor);

.

. (Check return value and maybe call FatalExit instead of returning.)

.

return hPen;

}

.

.

.

You would then alias the debugging function names in the intercept DLL DEF file to match the original GDI names, as shown in Figure 6. Then, linking your application, which thinks it’s making a call directly to GDI, with the intercept DLL would cause it instead to execute the debugging function in the intercept DLL, which in turn calls the real function in GDI.

Figure 6 Aliasing Debugging Function Names

.

.

.

EXPORTS

WEP @1 RESIDENTNAME

.

.

.

CreatePen = DbgCreatePen @25

DeleteObject = DbgDeleteObject @26

LineTo = DbgLineTo @27

.

.

.

1For ease of reading, "Windows" refers to the Microsoft Windows operating system. "Windows" is a trademark that refers only to this Microsoft product.