Porting 16-Bit Windows[TM] Applications to Win32[TM]

Randy Kath
Microsoft Developer Network Technology Group

Created: July 15, 1992

Abstract

Porting an application from one operating system to another is a chore that many software engineers find they must repeatedly perform to keep up with the rapidly changing software industry. Fortunately for developers of applications for the MicrosoftÒ WindowsÔ operating system, the Win32Ô Application Programming Interface (API) specification was designed to eliminate this frustration. In fact, the Win32 API so resembles the Windows version 3.1 Software Development Kit (SDK) that many applications need only minor adjustments and a recompile for a complete port. Yet other applications, those that take advantage of operating system or hardware-specific features, require more time to rework incompatibilities.

Several measures, however, can significantly reduce the amount of time required to complete this process. This article focuses on measures that reduce the porting effort and introduces a porting tool that further assists in porting 16-bit Windows-based applications to the Win32 environment.

Win32 Architectural Porting Issues

Perhaps the greatest difficulty in porting to a new environment is learning each of the porting issues. Fortunately, not much of the API changed from MicrosoftÒ WindowsÔ version 3.x to Win32Ô and, consequently, porting from 16-bit Windows to Win32 is relatively straightforward. The main issues relate to architectural changes in the system and syntactical changes in the specification.

Asynchronous Input Model

The message model for Win32 remains compatible with previous versions of Windows. Yet, it is also integrated into a multithreaded operating environment. The result is that an application can now have more than one message queue by simply starting another message loop within another thread. This makes porting to Win32 easier because you don't have to make any change to the overall structure of your application input model, except to take advantage of new features.

One thing should be pointed out, however. Because the model is now multithreaded, more than one application can be processing a message at the same time. Similarly, because the input model is asynchronous, more than one application could be getting messages from the system queue at a time. This is obviously advantageous for multithreaded performance, but it has ramifications that make the input model more difficult to manage within an application. Take simple mouse messages, for example. If more than one application captures mouse messages (via SetCapture) at a time, which application deserves capture over the other? Neither. They both should be able to capture the mouse cursor to operate effectively in Windows version 3.x. In short, a Win32 application cannot rely on capture to encapsulate all system mouse messages, as can be done in Windows version 3.x. When porting an application to Win32, take some time to examine the input structure, making sure that it makes no assumptions about synchronous input.

Instance vs. Module

The ability to have multiple instances of a single application running concurrently is a feature of Windows that is achieved mostly by the ability of Windows to share module information among derived tasks. In Win32, it is also possible to run more than one copy of an application at a time, but the semantics have changed. In Win32, each application is its own module and cannot directly share information among other, similar modules.

Code to manage multiple instances of an application is easily written in Windows version 3.x by using the hPrevInstance command-line parameter passed to the WinMain function of the application. Some applications choose to limit a user to a single instance of their application at a time. In doing so, they relied on hPrevInstance as the basis for their logic. In Win32, this parameter is always NULL. An application in Win32 must use other means for determining if another copy of the program is already running. One way is to call FindWindow; still another is to use GetWindow.

One other subtle difference between Windows version 3.x and Win32 is the use of hInstance. The instance of an application basically refers to the data segment for an application in Windows version 3.1 or earlier. In Win32 there is no concept parallel to an application's data segment. Instead, each application exists within its own process. All code, data, and resource information is maintained within this single process. To retrieve any module information, such as code or resources, in Win32, you pass hModule in the function rather than hInstance as before. When an application first starts in Win32, its module handle is passed via the old hInstance command-line variable to WinMain. Win32 applications can either save this value to a global variable or call the GetModuleHandle function, passing a NULL string for the module filename. This handle is then used whenever an application needs to access module information (for example, resources supplied as part of the executable file). The following example demonstrates how hModule can be retrieved and used as hInstance.

16-bit code:

hIcon = LoadIcon (GetWindowWord (hWnd, GWW_HINSTANCE),

MAKEINTRESOURCE (IDR_HICON));

32-bit code

hIcon = LoadIcon (GetModuleHandle (NULL),

MAKEINTRESOURCE (IDR_HICON));

Sharing Memory in Win32

The concept of sharing memory with the rest of the system has changed considerably from Windows version 3.x to Win32. Win32 is based on compatibility with the Windows NTÔ operating system and thus inherits design changes that reflect the security capabilities of Windows NT. Now, each application lives in its own process environment that is linear-addressed using 32-bit pointers for up to 232 bytes of virtual address space per application. While applications have their own environment, it is important to point out that all applications and the operating system still share the same available system memory. This means that the amount of physical memory and pageable memory accounts for all of the memory in the system. So an application wishing to allocate 100 megabytes (MB) of memory may not be able to, depending upon the resources available to the system.

Psychologists say that a consequence of living in your own world is that it is more difficult to relate to others. Similarly, it is difficult to share information between applications in Win32 because each application lives in its own process. Thus, memory allotted to one application cannot be read by another or, more specifically, memory allocated within the address space of one process cannot be addressed from another. Even the use of GMEM_DDESHARE or GMEM_SHARE flags to allocate memory is no longer a valid technique in Win32 for sharing memory between processes. The only way to share memory between applications now is to employ the use of memory-mapped files1, which have been specifically designed for this purpose, or use Dynamic Data Exchange Management Library (DDEML), which remains a viable alternative.

Finally, the concept of global and local memory has changed ever so slightly from Windows version 3.x to Win32. While the API supports both global and local memory calls, they are simply mapped into the single 32-bit address space of their application's process. (Some of the global memory functions have been removed due to obsolescence. See "Other Obsolete Functions in Win32" below.) Thus, the difference between local and global memory is the functions that are available to each type. This scheme supports the theme described above: All memory for one application is contained within a single process and cannot be shared with other processes.

No Segments Means Lots of Stray Pointer Space

All of the memory used by an application exists in that application's heap, including global, local, and heap dynamic memory; memory-mapped files; automatic and static data; and memory used by child windows, such as edit controls and list boxes. Because all of this memory resides in the same place, it is more difficult in Win32 than in Windows version 3.x to ensure that none of it overlaps unintentionally. This places an increasing burden on the developer to make sure that each pointer stays within its intended bounds.

In Windows version 3.x, each chunk of memory resides in its own segment with bounds-checking automatically enforced at the edges of each segment. Attempting to access memory outside of a known segment results in a "protection violation." Protection violations of this sort no longer occur in Win32 because segment boundaries do not exist. As a result, stray pointers are harder to detect, and each pointer requires careful examination during the porting process.

Win32 API Porting Issues

Much of the Windows version 3.x API remains unchanged for Win32. Yet the Win32 API has grown considerably with the addition of several hundred new functions. The main concern for porting, however, is which functions, messages, and types within the old Windows version 3.x API have changed or been removed from the Win32 API specification.

File I/O Functions for Win32

The removal of the DOS3Call function from the Win32 API is of great concern. DOS3Call was often used for calling MS-DOS Interrupt 21h (Int 21h) advanced file input/output (I/O) operations, not available in the Windows version 3.x API. To supplant its use, Win32 introduces a much more robust file I/O API subset that actually makes practical file I/O a reality within the bounds of the API. Table 1 lists the Win32 functions and the interrupts they replace.

Table 1. File I/O Functions in Win32 and the MS-DOS Int 21h Calls They Replace

Int 21h Win32 API

3EH CloseHandle
39H CreateDirectory
3CH, 5BH CreateFile
41H DeleteFile
4EH FindFirstFile
4FH FindNextFile
43H GetAttributesFile
19H, 47H GetCurrentDirectory
2AH, 2CH, 57H GetDateAndTimeFile
36H GetDiskFreeSpace
59H GetLastError
5AH GetTempFileName
56H MoveFile
3FH ReadFile
3AH RemoveDirectory
43H SetAttributesFile
0EH, 3BH SetCurrentDirectory
2BH, 2DH, 57H SetDateAndTimeFile
42H SetFilePointer
40H WriteFile

Communication Functions

Communication support has improved in the Win32 API. Communications devices no longer need to be dealt with in a nonstandard way. To communicate with a port in Win32, you must first get a handle to the communications port. The handle is obtained through a call to CreateFile, using COM1 as the name of the file to open. Later, this handle is released through a call to CloseHandle, the same as any typical file handle management call. Similarly, normal file I/O operations, like ReadFile and WriteFile, are used to access this device in a standard way. This change renders the OpenComm and CloseComm functions obsolete. Each of the communication functions has been replaced as noted in Table 2.

Table 2. Obsolete Communication Functions and Their Win32 Replacements

Obsolete function Win32 API

OpenComm CreateFile
CloseComm CloseHandle
FlushComm PurgeComm
GetCommError ClearCommError
ReadComm ReadFile
WriteComm WriteFile
UngetCommChar No replacement available

Sound Functions

Prior to Windows version 3.1, limited sound capability existed in Windows. With Windows version 3.1, multimedia was added and with it the PlaySound function. Win32 discards all of the sound functionality found in the Windows version 3.1 API with the exception of the call to the multimedia PlaySound function. Consequently, any code using the sound functions will have to be rewritten to make use of wave files or resources according to the PlaySound function specification. Any occurrence in your source code of the functions listed below needs to be removed before porting to Win32:

CloseSound

CountVoiceNotes

GetThresholdEvent

GetThresholdStatus

OpenSound

SetSoundNoise

SetVoiceAccent

SetVoiceEnvelope

SetVoiceNote

SetVoiceQueueSize

SetVoiceSound

SetVoiceThreshold

StartSound

StopSound

SyncAllVoices

WaitSoundState

Extended Functions

A few of the existing 16-bit Windows functions were extended to include a new parameter. In some cases, the additional parameter makes room for passing widened coordinate values. In 16-bit Windows, many graphics device interface (GDI) functions returned the location of the current position as the high and low words of a DWORD return value. In Win32, a coordinate is represented using a 32-bit signed integer, and 64 bits are required for a POINT. (A POINT structure is an ordered pair of (X,Y) coordinates). Thus, a parameter was added for passing a pointer to a POINT structure to these functions, allowing them to return full 32-bit values for each coordinate in the POINT. At the same time, each of these functions was changed to indicate their success or failure through the use of a Boolean return value. The following example contrasts how similar functions are used differently in Windows version 3.x and Win32.

16-bit code:

{

DWORD dwLoc;

// increment current position

dwLoc = GetCurrentPosition (hDC);

MoveTo (hDC, LOWORD (dwLoc)+dx, HIWORD (dwLoc)+dy);

}

32-bit code:

{

POINT pt;

// increment current position

if (GetCurrentPosition (hDC, &pt);

MoveToEx (hDC, pt.x+dx, pt.y+dy, &pt);

}

In other cases, the parameter is added to make the function more robust by passing additional information to the function for parameter checking. For example, the DlgDirSelect and DlgDirSelectComboBox functions were extended to include an additional parameter for you to specify the length of the string being passed to the function.

In every case, the suffix Ex was added to the function to indicate extended functions by name. Table 3 lists each of these functions, its new name, and the additional parameters.

Table 3. GDI Functions Extended to Include a POINT Structure Parameter

Obsolete GDI function Extended Win32 function Additional parameters

    Type Name
DlgDirSelect DlgDirSelectEx int cchString
DlgDirSelectComboBox DlgDirSelectComboBoxEx int cchString
GetBitmapDimension GetBitmapDimensionEx LPSIZE lpDimension
SetBitmapDimension SetBitmapDimensionEx LPSIZE lpDimension
GetBrushOrg GetBrushOrgEx LPPOINT lppt
GetAspectRatioFilter GetAspectRatioFilterEx LPSIZE lpAspectRatio
GetTextExtent GetTextExtentPoint LPSIZE lpSize
GetViewportExt GetViewportExtEx LPSIZE lpSize
GetViewportOrg GetViewportOrgEx LPPOINT lpPoint
GetWindowExt GetWindowExtEx LPSIZE lpSize
GetWindowOrg GetWindowOrgEx LPPOINT lpPoint
OffsetViewportOrg OffsetViewportOrgEx LPPOINT lpPoint
OffsetWindowOrg OffsetWindowOrgEx LPPOINT lpPoint
ScaleViewportExt ScaleViewportExtEx LPSIZE lpSize
ScaleWindowExt ScaleWindowExtEx LPSIZE lpSize
SetViewportExt SetViewportExtEx LPSIZE lpSize
SetViewportOrg SetViewportOrgEx LPPOINT lpPoint
SetWindowExt SetWindowExtEx LPSIZE lpSize
SetWindowOrg SetWindowOrgEx LPPOINT lpPoint
GetMetafileBits GetMetafileBitsEx UINT
LPVOID
nSize
lpvData
SetMetafileBits SetMetafileBitsEx UINT
LPVOID
nSize
lpvData
GetCurrentPosition GetCurrentPositionEx LPPOINT lppt
MoveTo MoveToEx LPPOINT lppt
DeviceCapabilities DeviceCapabilitiesEx LPSTR lpDriverName
DeviceMode DeviceModeEx LPSTR lpDriverName
ExtDeviceMode ExtDeviceModeEx LPSTR lpDriverName

Window and Class, WORDs and LONGs

As all the base data types for Windows move to 32 bits, so do Windows derived types. Because the handle types (for example, HICON, HINSTANCE, and HBRUSH) all become 32 bits, they are no longer accessible through 16-bit retrieval functions. Specifically, the Get/SetWindowWord and Get/SetClassWord functions no longer access window and class information. Instead, the Get/SetWindowLong and Get/SetClassLong functions are used. Use of the WORD versions of these functions is, however, still supported for storing and retrieving application-defined window and class information. To port these functions to Win32, simply convert the function being used to its LONG counterpart and change the flag accordingly, as in the following example.

16-bit code:

// Retrieve application instance handle and module handle.

hInstance = GetWindowWord (hWnd, GWW_HINSTANCE);

hModule = GetClassWord (hWnd, GCW_HMODULE);

32-bit code:

// Retrieve application instance handle and module handle.

hInstance = GetWindowLong (hWnd, GWL_HINSTANCE);

hModule = GetClassLong (hWnd, GCL_HMODULE);

Additionally, use of these functions for values that were already 32 bit is still supported in the same way it was before.

MS-DOS or Hardware-Specific System Calls

Win32 was designed to be the specification for Windows on the NT operating system. For that reason, the Win32 API must exist completely independent of operating system or hardware support. Because Windows NT is designed to be a portable operating system, there is no reliance on the hardware for special-purpose support. For that reason, all the functions in the Windows version 3.x API that provided access to MS-DOSÒ or x86-specific features were removed from the Win32 API. Table 4 lists these removed functions, a replacement if one exists, and the reason for the function's removal.

Table 4. Functions Removed Due To Operating System or Hardware Dependence

Removed function Replacement Reason for omission

Catch Structured exception handling x86 hardware-specific instructions
Throw Structured exception handling x86 hardware-specific instructions
NetBIOSCall NetBios Interrupt 5CH is MS-DOS-specific
FreeSelector N/A Selector tables are x86 hardware-specific
AllocSelector N/A Selector tables are x86 hardware-specific
ChangeSelector N/A Selector tables are x86 hardware-specific
GetCodeInfo N/A x86 hardware-specific information
GetCurrentPDB GetCommandLine and/or GetEnvironmentStrings PDB is an MS-DOS construct
GlobalDOSAlloc N/A MS-DOS requirement, not needed
GlobalDOSFree N/A MS-DOS requirement, not needed
SwitchStackBack N/A x86 hardware-specific instruction
SwitchStackTo N/A x86 hardware-specific instruction

Other Obsolete Functions in Win32

Several other functions have either been replaced or removed altogether. Remove the functions in Table 5 from your code when porting to Win32.

Table 5. Obsolete Functions in Win32

Removed function Replacement function Reason for omission

GetEnvironment File I/O functions Devices are handled using standard I/O functions.
SetEnvironment File I/O functions Devices are handled using standard I/O functions.
ValidateCodeSegments N/A This level of debugging support is not available in Win32.
ValidateFreeSpaces N/A This level of debugging support is not available in Win32.
GetInstanceData N/A Applications cannot share instance information in Win32.
GetKBCodePage N/A Replaced by built-in unicode support.
GetModuleUsage N/A Applications run in their own address space.
Yield WaitMessage and/or Sleep Serves no useful purpose.
AccessResource N/A Not necessary; resources are kept in image-mapped file.
AllocResource N/A Not necessary; resources are kept in image-mapped file.
SetResourceHandler N/A Not necessary; resources are kept in image-mapped file.
AllocDSToCSAlias N/A No longer necessary; all read-write memory allocated in Win32 is executable by default.
GetCodeHandle N/A Win32 does not associate handles with code addresses.
LockData N/A The system manages an application's local heap.
UnlockData N/A The system manages an application's local heap.
GlobalNotify N/A No mechanism to support one process notifying another in the operating system
GlobalPageLock VirtualLock Win32 memory manager behaves differently.

Functions That Exist for Compatibility Only

Another set of functions remains in the Win32 API for compatibility reasons only. You can deal with these compatibility functions in one of three ways. First, because these functions either have alternative functions available or are not necessary in Win32, you could remove them. Before you remove them, though, consider the second alternative of leaving them in your application in case you want to create a common code base that can be compiled for either 16-bit or 32-bit environments. In that case, it may be beneficial to build a construct that identifies the target for which you're building. For example:

#ifdef Win32

// Perform operations using Win32 API.

DialogBox (hInst, lpDialog, hWnd, DialogBoxProc);

#else

{

FARPROC lpDlgProc;

// Perform operations using Windows 3.x SDK API.

lpDlgProc = MakeProcInstance (DialogBoxProc, hInst);

DialogBox (hInst, lpDialog, hWnd, lpDlgProc);

FreeProcInstance (lpDlgProc);

}

#endif

For the most part, these functions are implemented as no-operation (NOP) instructions and are really dead weight in your source code. Yet in some cases they actually return a meaningful value. MakeProcInstance, for example, returns the function address supplied as the lpProc parameter. So MakeProcInstance performs no function, but it does return a value that allows it to be called in Win32 the same way it was called in Windows version 3.x. Thus, a third alternative is presented: You could leave the obsolete functions as NOPs in your source code for compatibility reasons. Employing this third alternative has very little impact on application performance, and the solution is much cleaner than in the second alternative. However, the problem in this case is cleaning up the obsolete code at some later date. While it may be clear now why some code is obsolete, it may not be clear in a year or two—especially if the code changes hands one or two times over that period.

Table 6. Obsolete Functions That Remain in the API for Compatibility

Functions as NOPs Reason for obsolescence

DefineHandleTable Real mode compatibility is not available for Win32.
MakeProcInstance All Win32 functions can be called directly; no instance thunk is required.
FreeProcInstance All Win32 functions can be called directly; no instance thunk is required.
GetFreeSpace There is no global heap in Win32; use GlobalMemoryStatus to determine available memory.
GlobalCompact This functionality is not needed in Win32.
GlobalFix This functionality is not needed in Win32.
GlobalUnfix This functionality is not needed in Win32.
GlobalWire This functionality is not needed in Win32.
GlobalUnwire This functionality is not needed in Win32.
LocalCompact This functionality is not needed in Win32.
LocalShrink This functionality is not needed in Win32.
LockSegment Each application has at most one address space.
UnlockSegment Each application has at most one address space.
SetSwapAreaSize Swapping is managed by the system in Win32.

Tool Helper and Stress-Testing Library Functions

Win32 drops the entire TOOLHELP.DLL and STRESS.DLL API subsets. These sets of Windows version 3.x functions assist in the development of tools and debuggers by providing access to internal information and structures. Win32 is an API designed to be compatible with Windows NT, which has a level of security built into the operating system. Consequently, accessing internal information in Windows NT would violate any attempt at a secure environment. Thus, these functions have been removed altogether with no alternative access to the information they provide.

New and Widened Functions

Win32 has several hundred new functions, most of which add new capability or enhance existing Windows features. Except for functionality that has been moved to new functions (for example, new file I/O functions replace Int 21h calls), you do not need to learn the new functions to port your applications to Win32. Once your application is ported to Win32, integrating new features is a much smoother transition.

The rest of the functions in the Win32 API (at least another 500) have all been "widened" to fit the new 32-bit parameters and return values. Porting to these functions rarely requires any changes to your code. If you use exact types for the parameters passed or the return values from these functions, you'll need to make few, if any, changes. However, occurrences of code where a native type is used in place of the exact type (for example, WORD instead of HBRUSH) must be fixed. An example of this type of problem and how to fix it is demonstrated below.

16-bit code:

{

WORD hBkBrush;

// Return the class background brush.

hBkBrush = (WORD)DefWindowProc (hWnd, WM_CTLCOLOR, 0, 0L);

return ((BOOL)hBkBrush);

}

32-bit code:

{

HBRUSH hBkBrush;

// Return the class background brush.

hBkBrush = (HBRUSH)DefWindowProc (hWnd, WM_CTLCOLORDLG, 0, 0);

return ((BOOL)hBkBrush);

}

In the example above, the brush handle is known to be a WORD type, or 16 bits in Windows version 3.1 or earlier. In Win32 this and all other handles grow to 32 bits, but the native WORD type remains a 16-bit type. Also, with the cast to a WORD type explicitly placed in the line, all chance of the compiler catching this conversion is lost. Barring type mismatches as described above, porting to the widened functions requires no effort at all.

Messages

The 32-bit environment's greatest impact is on the messages that are passed in window functions. The good news is that the wParam parameter becomes a 32-bit parameter, the same as the lParam parameter, making more room for information to be passed with each message. The bad news is that much of the significant information that messages communicate also grows to 32 bits but is not necessarily within the wParam parameter. Many messages packed two 16-bit values in the lParam parameter of a message, and some of these values have now grown to 32 bits. As a result, many of the messages have been rearranged to make room for their widened values.

WM_COMMAND message

Most significant of the affected messages is the WM_COMMAND message. Because this message is used for both child windows and messages, there are typically many places in your application that use this message. Fortunately, the repacking was ordered in a way that lessens the impact as much as possible. In Windows version 3.x, the wParam parameter of this message is used to hold the control ID of the child window or menu responsible for the message. For Win32, that control ID remains a 16-bit entity and is located in the low-order word of the wParam parameter. Adjacent to it, in the high-order word of the wParam parameter, is the notification code. In Windows version 3.x, the notification code is in the high-order word of the lParam parameter. In Win32, it was moved to make room for the child window's window handle. Because window handles are all 32 bits, the child window handle requires all of the 32-bit lParam parameter. To make this change when you port your code, follow the example below.

16-bit code:

case WM_COMMAND:

switch (wParam)

{

case IDC_EDIT:

if ((HIWORD (lParam) == EN_CHANGE) &&

SendMessage (LOWORD (lParam), WM_GETTEXTLENGTH, 0, 0L))

EnableWindow (GetDlgItem (hDlg, IDOK), TRUE);

break;

.

.

.

}

break;

32-bit code:

case WM_COMMAND:

switch (LOWORD (wParam))

{

case IDC_EDIT:

if ((HIWORD (wParam) == EN_CHANGE) &&

SendMessage ((HWND)lParam, WM_GETTEXTLENGTH, 0, 0L))

EnableWindow (GetDlgItem (hDlg, IDOK), TRUE);

break;

.

.

.

}

break;

WM_CTLCOLOR message

The WM_CTLCOLOR message has been replaced by several CTLCOLOR messages because the WM_CTLCOLOR message carried three 16-bit parameters with it, and two of them have grown to 32-bit parameters. The WM_CTLCOLOR message for Windows version 3.x passes a handle to a device context in the wParam parameter and to both the child window handle and the CTLCOLOR flag in the lParam parameter. Both the device context handle and the child window handle become 32-bit entities, leaving no room for the CTLCOLOR flag. Consequently, the message itself is split into separate messages, one for each of the various CTLCOLOR flag values. Table 7 lists the new messages for the CTLCOLOR flag values.

Table 7. The WM_CTLCOLOR Messages

CTLCOLOR flag New message

CTLCOLOR_BTN WM_CTLCOLORBTN
CTLCOLOR_DLG WM_CTLCOLORDLG
CTLCOLOR_EDIT WM_CTLCOLOREDIT
CTLCOLOR_LISTBOX WM_CTLCOLORLISTBOX
CTLCOLOR_MSGBOX WM_CTLCOLORMSGBOX
CTLCOLOR_SCROLLBAR WM_CTLCOLORSCROLLBAR
CTLCOLOR_STATIC WM_CTLCOLORSTATIC

Other repacked messages

A variety of other messages are also affected by parameter widening. Table 8 lists these messages along with their new parameter configurations.

Table 8. Windows Messages That Have Been Repacked for Win32

Message wParam lParam

WM_CHARTOITEM LOWORD = caret position
HIWORD = character code
List box window handle
WM_HSCROLL LOWORD = scrollbar value
[HIWORD = thumb position]2
[handle of parent control]
WM_VSCROLL LOWORD = scrollbar value
[HIWORD = thumb position]
[handle of parent control]
WM_MDIACTIVATE Handle of child window to be activated or deactivated 0 or handle of child window being activated
WM_MDISETMENU Handle of frame menu Handle of window pop-up menu
WM_MENUCHAR LOWORD = character code
HIWORD = menu flag, MF_
Handle of pop-up menu
WM_MENUSELECT LOWORD = 0 or menu item ID
HIWORD = menu flag, MF_
Handle of pop-up menu
WM_PARENTNOTIFY LOWORD = event notification
[HIWORD = child window ID]
Handle of child window
[or packed cursor position]
WM_VKEYTOITEM LOWORD = character code
HIWORD = caret location
Handle of list box
EM_GETSEL Returns selection start position Returns selection end position
EM_SETSEL Specifies beginning of selection Specifies end of selection
EM_LINESCROLL Characters to scroll horizontally Characters to scroll vertically

Mouse message handling

Mouse messages work the same way in Win32 as they do in previous versions of Windows. Yet this creates a potential problem in the 32-bit environment. Because every mouse message returns the coordinate of the mouse event in the lParam parameter, that coordinate pair must fit in 32 bits of space. Consequently, the coordinate space of the mouse is limited to 16-bit integers. Yet all GDI functions now work with coordinates that are each 32-bit integers and thus a type conversion must take place. A potential problem with this conversion is illustrated below.

Incorrect cast from 16-bit WORD to 32-bit integer:

case WM_LBUTTONDOWN:

{

POINT pt;

HDC hDC = GetDC (hWnd);

// Draw line from old position to current mouse position.

LineTo (hDC,

(int)LOWORD (lParam),

(int)HIWORD (lParam),

&pt);

ReleaseDC (hWnd, hDC);

}

break;

Correct cast from 16-bit WORD to 32-bit integer:

case WM_LBUTTONDOWN:

{

POINT pt;

HDC hDC = GetDC (hWnd);

// Draw line from old position to current mouse position.

LineTo (hDC,

(int)(short)LOWORD (lParam),

(int)(short)HIWORD (lParam),

&pt);

ReleaseDC (hWnd, hDC);

}

break;

In the incorrect cast example, the ability to represent negative cursor locations is lost. When casting from a 16-bit unsigned integer to a 32-bit signed integer directly, the compiler zero extends the upper word, losing the sign bit. To avoid this, first convert to a 16-bit signed integer and then to the 32-bit signed integer, allowing the compiler to promote the sign bit.

Types

A key to understanding each of the porting issues is understanding the underlying type sizes. Table 9 contrasts the various integral and derived types common to the Windows programming environment.

Table 9. Basic Types for Windows 16-Bit and 32-Bit Environments

16-bit Windows types 32-bit Windows types

Type Bits Range Type Bits Range
unsigned int 16 0 to 65,535 unsigned int 32 0 to 4,294,967,295
UINT 16 0 to 65,535 UINT 32 0 to 4,294,967,295
int 16 -32,768 to 32,767 int 32 -2,147,483,647 to 2,147,483,647
short 16 -32,768 to 32,767 short 16 -32,767 to 32,767
long 32 -2,147,483,648 to 2,147,483,647 long 32 -2,147,483,647 to 2,147,483,647
char 8 -128 to 127 char 8 -127 to 127
BOOL 16 0 to 65,535 BOOL 32 -2,147,483,647 to 2,147,483,647
WORD 16 0 to 65,535 WORD 16 0 to 65,535
BYTE 8 0 to 255 BYTE 8 0 to 255
HANDLE 16 0 to 65,535 HANDLE 32 0 to 4,294,967,295

Porting Source Code Using PortTool

All of the porting issues presented above combine to make a rather exhausting list of porting issues. To relinquish the burden of having to remember each of these issues or, alternatively, having to make several passes over your code on all of the issues, I developed a porting tool. PortTool interactively searches your source files for occurrences of any of the issues presented here. When it finds a porting issue in your code, it pauses at the location of the issue, identifies the specific problem, and allows you to make changes to your code. In this way, it finds the problems and lets you make the changes interactively. PortTool presents this functionality in a Windows-based application that closely resembles the Windows Notepad application.

PortTool can also be run as a background process on a series of files.3 In this operation, PortTool searches files for porting issues and, when it finds them, inserts comments into the file indicating what the potential problem is and how to fix the code. Additionally, while PortTool is running as a background process, it is represented by an icon in your Windows environment. You can activate this icon to view status information about the background process or to cancel the operation.

Customizing the Data File

The heart of the PortTool application is the data file that defines each of the porting issues. This data file is organized into an .INI file format, making it easy to read and modify. You can modify PortTool to search for any porting strings. Entering custom search strings may come in handy if you find that throughout your source code you have specific porting issues other than those described here. Also, PortTool provides an option for disabling the types of porting issues currently being searched. This allows for breaking the porting process into several passes and lets you concentrate on a subset of the porting issues.

Importing PortTool Functionality in Your Editor

One other nice feature in the PortTool application is that its primary function is importable in your own environment.4 PortTool includes an executable, a dynamic-link library (.DLL) file, an include file, and a .LIB file for the .DLL and the data file mentioned above. Within the include file is a function prototype and a structure definition that are necessary to call the PortTool DLL. This is designed to permit developers to write simple interfaces, called from within their editors, that invoke the PortTool functionality. The PortTool application is included on the disc.

Conclusion

Porting 16-bit Windows-based applications to Win32 is significantly less difficult than porting to an entirely new API. This article presented the main issues surrounding porting 16-bit Windows-based applications to the Win32 API. Yet there are undoubtedly other porting issues that will arise from one implementation to the next. Fortunately, PortTool provides the means for quickly porting applications and allows a degree of customization that improves its usefulness in specific implementations. PortTool's importability provides an even greater flexibility in that it is callable from most common editors on the market today. Happy Porting!

1Memory-mapped files are a new feature in Win32 and exist outside the scope of this article. An upcoming article entitled "Managing Memory with Win32" will provide detailed information about the types of memory available in Win32.

2Italics are used here to indicate conditional values on message parameters. For information on what circumstances influence these conditions, refer to the documentation for these messages.

3The ability to run PortTool as a background process is only present in version 2.1 or later. Please use the Help About command to check the version number.

4PortTool's import functionality is only present in version 2.1 or later. Please use the Help About command to check the version number.