Porting Issues from WIN16 to WIN32 in Discuss for Windows

By Arjun Sanyal (Sanyal@cris.com)

This document is a summary of porting issues that I experienced in porting the Discuss for Windows application to Windows NT on the DEC Alpha. Not all of WIN16 to WIN32 porting is covered, these are simply some things to remember when attempting the port.

Code Compatibility

The WIN32 API is designed to be highly portable over the various 16-bit Windows versions (through the WIN32s library), the Window NT platforms, and Windows '95. 16-bit Windows and Window 95 run only on the Intel X86 processors, while Window NT runs on many processors, namley Intel X86, DEC Alpha, MIPS R4x000, and even the Motorola PowerPC chip. But a problem arises when the code to be ported depends on Dynamic Link Libraries and Static Link libraries. This applies to many complex applications coded to 16-bit Windows. (Remember that much of Windows itself is implemented as DLL's. USER.EXE, GDI.EXE, etc.) While the porting calls to Windows itself is automatically handeled by MFC (see below). Your own DLLs and LIBs will usually have to be ported by hand. From the MSDL:

"You can easily port projects from the Intel x86 platform to the Alpha AXP platform, but some changes are required. You can use the same project (.MAK) and source files, but not intermediate files, which have to be rebuilt."

This multiplies the re-coding task. This was the case with Discuss which depened on a enchanced editor contol DLL (MAGMAED.DLL), a Kerberos verison 4 DLL (KRBV4WIN.DLL), and LIBDS.LIB and UPS.LIB. The Kerberos DLL in turn depended on the WSHELPER.DLL which had to be ported. These backend libraries containing low-level, non-MFC code were the most dificult to port.

The LIBDS.LIB contained the lowest level code in the project, the RPC interface to the Discuss servers. Mainly this code gave the most trouble for two reasons. One, much of the code is a mix of Discuss meeting functions, home-brew RPC, Windows sockets calls, and the Unified Stream Protocol. The complexity slowed down the process of quickly finding and fixing a porting error. Second, some illogical bugs popped up in this code: see memcpy.

A Note on Kerberos DLL Naming Conventions

The 16-bit version of the Kerberos version 4 DLL was is "krbv4win.dll" When this DLL was ported to WIN32we had to use a new name to differenciate between the WIN16 AND WIN32 DLLs. The WIN32 DLL is named "krbv4w32.dll" - note there is no convention for the different _hardware_ Windows NT platforms yet. Also note that code that may be built on both the WIN16 and WIN32 platforms must load _differently_ named Kerberos DLL's. (This is similar to WINSOCK.DLL and WSOCK32.DLL for WIN16 and WIN32 respectivily.)

WINDOWSX.H

As a convenience, the WINDOWSX.H header file re-defines many obsolete and out of use function calls (among other things). This header file speeds ports, but fixes no complex problems. For example in WIN32 the "far" and "pascal" keywords are obsolete due to WIN32's flat memory model and different function calling procedures. So for "far" versions of common functions WINDOWSX.H re-defines these as thier WIN32 counterparts:

       "WINDOWSX.H"
	...
	#define far
	#define pascal
	...
	#define _fmemcpy memcpy
	...

Use WINDOWSX.H to speed up a port, but it may be a better idea to re-write the code to the WIN32 API standard becuse of the phase out of the WIN16 API and for clarity.

WIN32_LEAN_AND_MEAN

If there is trouble with the more esoteric header files that the mandatory WIN32 WINDOWS.H file includes (some OLE stuff and other strange things), try the WIN32_LEAN_AND_MEAN preprocessor definition. This excludes these headers from the build and reduces compile time.


#ifndef WIN32_LEAN_AND_MEAN
#include <cderr.h>
#include <dde.h>
#include <ddeml.h>
#include <dlgs.h>
#include <lzexpand.h>
#include <mmsystem.h>
#include <nb30.h>
#include <rpc.h>
#include <shellapi.h>
#include <winperf.h>
#include <winsock.h>
...
#endif /* WIN32_LEAN_AND_MEAN */
This defnintion was nessary to port on both platforms.

Multiuser Concerns

Because of the inherently multiuser natures of Windows NT and Windows 95 (Yes, Windows 95 is can be configured single user or multiuser), it is possible to access user specific information through envrioment varibles or the Registry. With older applications user specific envrioment varibles may or may not have been present due to indiviual configurations. This is a boon for Windows client applications involving securtiy since the option to simply use the USERNAME envrioment varible is avalible. Previously in Discuss, the non-standard NAME envrioment varible was used to identify the user's name. In WIN32, the USERNAME envrioment varible is always defined to the login name of the current user. Of course this means that you are depending on the inherent security features of Windows, which may not be the most secure idea.


#ifdef WIN32
	m_pConferenceList = new CConferenceList(getenv("USERNAME"));   
#else 
	m_pConferenceList = new CConferenceList(getenv("NAME"));
#endif
Also remember that in Windows NT and Windows 95 much of the system and user information that was previously stored in the WIN.INI and SYSTEM.INI files is now in the Registry. WIN16 applications should be changed to access the Registry instead of the INI files.

ERRNO.H Compatibility

The ERRNO.H header file, which contains the definitions of the errno values, under Windows NT maintains compatibility with the header file of the same name under UNIX. But, not all of the definitions in ERRNO.H are used under Windows NT and are simply for compatibility, therfore, if the code references an unused definition under Windows NT, correct the offending code. Also from the MSDL:

The errno values in NT are a subset of the values for errno in XENIX systems. Thus, the errno value is not necessarily the same as the actual error code returned by a Windows NT system call. To access the actual operating system error code, use the _doserrno variable, which contains this value.

Structured Exception Handling

A very common hardware exception reported by Windows NT is the "EXCEPTION ACCESS VIOLATION" When structured exception handeling is not being used (try..except protected blocks) this exception, like all other unhandled user mode exceptions, invokes the kernel to display a dialog and terminate your application. This nasty fate was experienced many time in the porting of Discuss. The definition of "ACCESS VIOLATION" is:

EXCEPTION ACCESS VIOLATION Occurs if a thread attempts to load or store data to or from an address that is not accessible to the current process. This is the most common exception: reading or writing through a bad or NULL pointer will cause it. But with a 4GB address space, what address could be considered bad? Well, if you read Jeff Richter’s 'An Introduction to Win32 Heap and Virtual Memory Management Routines,' MSJ, March 1993, then you know that only 2GB is available to a process and that some virtual addresses within that 2GB can be marked as PAGE READONLY or PAGE NOACCESS.

When the code is stopped is the _real_ access violation. In the port another message

First-Chance Exception in WDforNT.exe: 0xC0000005: Access Violation.

was seen several times. Ignore this one. The MSDN explanation:

Under Windows NT, the default top level handler detects writes to resources and will make the resource writable. If you are running outside of a debugger and you have no exception handler, your resource writes will silently work. If you are running under the debugger, your resource write will look like an access violation:

First-Chance Exception in msin32.exe: 0xC0000005: Access Violation

This allows you to "fix" your resource writes. If you have the debugger pass on the exception to your application and you have no handler, the default handler will make your resource writable.

The disadvantage of setting the attribute of the resource section to read/write is that Windows NT will use a separate copy of the resource section for each process that uses this section, instead of one copy for all processes.

Not-So-Universal Thunk

Since Discuss for NT would be a WIN32 application in the Windows NT envrioment, but it depended on many 16-bit DLLs that would add to the porting task, we thought "What about a thunk?" A thunk, in this context, is a piece of code that allows one side of the 16 to 32 bit process boundry to use functions on the other side. The thunk that was the most promising was the "Universal Thunk" which lets WIN32 applications load and call 16-bit DLLs. A convenient solution, except that the "Universal thunk" is not universal at all and can only be used in the WIN32s envrionment, not Windows NT or Windows 95.

The "Generic Thunk", 16-bit code calling 32-bit code, is avalible in Windows NT but it is irreversible.

The Simple Portability Features of the MFC from WIN16 to WIN32

It would seem after all these problems that WIN16 to WIN32 porting is very difficult, but if you are using the Microsoft Foundation Classes (MFC) as an object-oriented application framework, much of the WIN16 to WIN32 portablilty issues are handled automatically.

Most notably, the widening of the WPARAM type from 16 to 32 bits usually necessiitites the repacking of the wParam and lParam message parameters. The library handles most of the changes internally. Also, "un-natural" structure member alignment is automatically corrected, with a signifigant performance penalty, especially on RISC processors. See the article "Port Your 16-bit Applications to Windows NT Without Ripping Your hair Out" for information on natural structure member alignment.

Intrinsic Functions - Alpha Specific

There were a few errors in Discuss involving intrinsic functions. Visual C++ asked for the asm source to strlen a couple of times! Background from the MSDN:

Intrinsic functions (also called "intrinsics") are function calls that the compiler resolves directly, by generating code, rather than by calling an external function. The Alpha AXP version of the compiler supports a number of new intrinsics. With all new intrinsics, you must explicitly enable the intrinsic before calling it, by using it with the /Oi compiler option or the intrinsic pragma. The general syntax for using this pragma is:

#pragma intrinsic(intrinsic-name)

For example, to enable the use of the _InterlockIncrement intrinsic, include a pragma as shown, along with the declaration of the function:

long _ _stdcall _InterlockedIncrement(long *); #pragma intrinsic(_InterlockedIncrement)

It's importaint to know what functions are intrinsic so that if there is an error in that call, you are aware of both compilation options. Of course, intrinsics can be activated or deactivated using the compiler optimization options.

Memcpy Bug - Alpha Specific

This was the strangest bug encountered during the port. In the module RPCALL.C of LIBDS.LIB the following code failed without reason:


	 memcpy((LPSTR) &address.sin_addr, hp->h_addr, hp->h_length);
	
	// Note: memcpy returns the value of dest (&address.sin_addr) 
Similar code in the Winsock test application ran without fail. This error is still being investigated.

For further information:

Discuss: An Electronic Conferencing System for a Distributed Computing Enviromnent. Raeburn et al.

Visual C++ 2.0 Development System for DEC Alpha AXP, Alpha AXP Programmer's Guide

Port Your 16-bit Applications to Windows NT Without Ripping Your hair Out. MSJ. Vol. 8. Aug. 1993.

Slay the Porting Beasties: Dave's Top Ten Tips for Migrating to Windows NT. MSJ Vol. 8. Sept. 1993.

Clearer, More Comprehensive Error Processing with Win32 Structured Exception Handling. MSJ Vol. 9. Jan. 1994

Programming With MFC and WIN32. Vol. 2 of 6. MIcrosoft Press. 1994.