Although generally thought of as a DOS/Windows development tool, Microsoft's Visual C++ and the Microsoft Foundation Class library can be used for cross-platform development. Chane discusses how you can use them to write portable code, whether you're coding for Windows, UNIX, NT, Macintosh, or whatever.
March 01, 1994
URL:http://www.drdobbs.com/windows/cross-platform-development-with-visual-c/184409201
Chane works with Wind/U at Bristol Technology and can be reached at [email protected] or 203-438-6969.
The current crop of hardware architectures and operating environments, each with its own particular set of features, offers exciting possibilities for software developers. However, timely development of applications that take advantage of the unique capabilities of platforms ranging from DOS, Windows 3.1, the upcoming Windows 4.0, and NT, to OS/2, UNIX, and Macintosh can be a challenging undertaking. Toss new CPUs such as the Pentium and PowerPC into the ring, and you're faced with some serious development decisions.
The most common approach to tackling such challenges includes using cross-platform APIs (such as XVT, Neuron Data Open Interface, and Visix Galaxy) or cross-platform application frameworks (Inmark's zApp, C++/Views, and the Zinc Framework, for example). These tools can solve most of your portability problems, but programmers often end up wanting a familiar API that's available across a wide variety of operating environments and hardware architectures.
Of all the available programming interfaces, the Microsoft Windows API has become the most pervasive. Furthermore, one of the benefits of using the Windows API is that a large number of high-quality tools and class libraries are available, including those that enable you to maintain a single set of source code for different platforms. For example, with Wind/U from Bristol Technology (the company I work for), you recompile Visual C++ code so that it runs as an X/Motif app on UNIX. The Mirrors toolkit from Micrografx, on the other hand, lets you recompile Windows code generated by Microsoft C 6.x or Watcom C++ 16-bit for OS/2 applications. Likewise Microsoft's Wings, an announced--yet unreleased--toolkit based on the Win32 API will someday allow you to port Windows applications to the Macintosh. (Wings will likely include the Microsoft Foundation classes, associated libraries, code generator, and cross-compiler to the 680x0 architecture.)
Although generally regarded as a DOS/Windows development tool, Microsoft's Visual C++ and the Microsoft Foundation Class (MFC) library can be used as a cross-platform development tool. This article discusses how you can use Visual C++/MFC as the cornerstone of your cross-platform development efforts. If you write code applying the guidelines presented here, you can more easily cross architectural hurdles when using cross-platform APIs, cross-platform frameworks, or current and future portability toolkits.
There are several technical reasons for choosing the Windows API over Motif, particularly for UNIX applications. Even without considering portability, Windows offers much richer GUI components and paradigms. The typical Motif application is about as sophisticated as Windows 1.0 programs were. Most applications don't print (X/Motif has no built-in printing model), provide online help, use tool/status bars, do much in the way of graphical drawing, or cleanly support multiple documents (there's no MDI in Motif). In other words, portability is only one reason why UNIX developers should consider the Windows API as a development environment.
Simply choosing a cross-platform class library or API does not solve all the portability problems involved in writing an application. You must also consider compiler differences, API nuances, and hardware-architecture dependencies.
In general, UNIX compilers are based on the AT&T cfront implementation, and PC compilers are implemented to be cfront 3.0 compatible. Visual C++ is very compatible with the UNIX C++ 3.0 compilers supplied by HP, IBM, and Sun, but not identical. One sure way to minimize the differences is to compile with verbose warning messages on all architectures and update the source code to remove these warnings. The following figures identify some minor differences between compilers and easy workarounds to remove the problems.
Figure 1, for instance, shows how Visual C++ allows typecasting using function-call syntax. cfront compilers only support the C syntax for typecasting (more on typecasting in the following sections).
Visual C++ allows type int and user-defined type BOOL to be interchanged. With other platforms, user-defined types may be defined slightly differently. Consistent use of the user-defined types will avoid any problems; see Figure 2.
In Figure 3, Visual C++ allows variable declaration in switch statement cases without requiring a new scope. Likewise, Visual C++ allows extra semicolons in class definitions, as in Figure 4. HP C++ 3.05, on the other hand, does not correctly handle nested macro expansion; see Figure 5. Nor are the Visual C++ compiler #pragma warning(disable: 4xxx) directives--pragmas used in MFC to eliminate warning messages during compiles--available in UNIX.
Templates and exceptions normally lead to nonportable code. Although VC++ doesn't directly support templates or exceptions, Microsoft supplies a template generator and includes exception classes with MFC which are portable and can be used on all platforms.
Of the various Windows API flavors (Win16 for Windows 3.1 for 16-bit applications, Win32 for 32-bit NT apps, and Win32s for portable 32-bit applications), the Win32s API is the cross-platform Windows API. Win32s is available on Windows 3.1 with the Win32s DLLs and on Windows NT, Macintosh System 7 from Microsoft, and UNIX from Bristol Technology.
Additionally, MFC allows you to have a single set of source for Windows 3.1, Windows 3.1 with Win32s DLLs, Windows NT, UNIX, and Macintosh. The Win32s API builds on the Win16 API by adding features from Win32 and does not include nonportable functions from Win16.
Consequently, you shouldn't make calls to the Win16 functions in Table 1 since they're not included in Win32s. The Win32 functions in Table 2, however, have been included, as have the Win32 messages in Table 3. Finally, the Win16 functions in Table 4 have been changed in Win32s.
Independent of the cross-platform toolkit, you must pay attention to differences in byte ordering, word sizes, and structure packing.
In the Windows 3.1 environment, integers are normally 16 bits wide; in most other environments, they are 32 bits wide. Example 1 is nonportable (but working) 16-bit Windows code. Porting this code to NT or UNIX would cause problems if the value of nOne was ever greater than 65,535 because it would suddenly become too large to fit into wTwo (which is only 16-bits wide); the wTwo variable would wrap and start back at 0. Normally, C++'s strong type checking will not allow code like this to survive, so 16/32-bit problems are not common in C++ unless typecasting is used.
Another common 16/32-bit problem is structure packing. On 16-bit systems, compilers pack structures based on 16-bit boundaries by default. On 32-bit systems, the compilers use 32- or 64-bit boundaries (they waste a byte here and there to ensure that the elements of a structure are aligned properly). The end result is that the sizeof operator will return different results in 16- and 32-bit environments. Structure packing can cause problems if you read structures to and from binary files. MFC does not write structures to file, but does not prevent the programmer from doing so.
The other common portability problem between Windows and UNIX is byte swapping. Some UNIX workstations, such as the Sun SPARCstation, have Big-endian (vs. Intel's Little-endian) byte ordering. This means that you can't make assumptions about the order of bytes in structures. C++ does not protect the programmer from these problems. Example 2(a) shows the byte-swapping problem using classes from MFC. This code makes the fatal mistake of assuming that data in the DWORD dwPoint will be ordered exactly the same as the tagPoint structure. To fix the problem, the typecasting is replaced by the Windows LOWORD and HIWORD macros to deconstruct a DWORD properly. Example 2(b) is a portable version of the CPoint::CPoint(DWORD) constructor.
When it comes to cross-platform application development, the Windows API is more than a least-common denominator. This, coupled with C/C++ standards, make it an attractive environment for programmers who have to support more than one platform.
With the great strides that software development tools are making, a year from now a cross-platform solution may be as easy as selecting a radio button in your visual development environment's Build Options dialog box.
(a) file.C, line 100: error: syntax error (b) int Number = 26; unsigned char Letter; ... Letter = unsigned char (Number); (c) Letter = (unsigned char)Number;
(a) file.C, line 100: error: WinCalApp::ExitInstance() type mismatch: int WinCalApp::ExitInstance() and BOOL WinCalApp::ExitInstance() (b) BOOL WinCalApp::ExitInstance() (c) int WinCalApp::ExitInstance()
(c) portable statements (enclose the statements in a pair of braces to explicitly define the scope of the new variable).
(a) file.C, line 100: error: jump past initializer (did you forget a '{ }'?) (b) default: int Number = GetSomeNumber(); ... Doit(Number) (c) default: { int Number = GetSomeNumber(); ... Doit(Number) }
(a) file.C, line 100: error: syntax error (b) DECLARE_DYNAMIC(ClassName); (c) DECLARE_DYNAMIC(ClassName)
AccessResource AllocDiskSpace AllocDSToCSAlias AllocFileHandles AllocGDIMem AllocMem AllocResource AllocSelector AllocUserMem Catch ChangeSelector ClassFirst ClassNext CloseComm CloseDriver CloseSound CountVoiceNotes DefDriverProc DeviceCapabilities DeviceMode DirectedYield DlgDirSelect DlgDirSelectComboBox DOS3Call ExtDeviceMode FlushComm FreeAllGDIMem FreeAllMem FreeAllUserMem FreeSelector GetAspectRatioFilter GetBitmapDimension GetCodeHandle GetCodeInfo GetCommError GetCommEventMask GetCurrentPDB GetCurrentPosition GetDCOrg GetEnvironment GetInstanceData GetKBCodePage GetMetaFileBits GetModuleUsage GetSelectorBase GetSelectorLimit GetSystemDebugState GetTempDrive GetTextExtent GetTextExtentEx GetThresholdEvent GetThresholdStatus GetViewportExt GetViewportOrg GetWindowExt GetWindowOrg GetWinFlags GlobalDosAlloc GlobalDosFree GlobalEntryHandle GlobalEntryModule GlobalFirst GlobalInfo GlobalNext GlobalPageLock GlobalPageUnlock InterruptRegister InterruptUnRegister LocalFirst LocalInfo LocalNext LockInput MemManInfo MemoryRead MemoryWrite ModuleFindHandle ModuleFindName ModuleFirst ModuleNext MoveTo NetBIOSCall NotifyRegister NotifyUnRegister OffsetViewportOrg OffsetWindowOrg OpenComm OpenDriver OpenSound PrestoChangoSelector Prof* (8 functions) QuerySendMessage ReadComm ScaleViewportExt ScaleWindowExt SetBitmapDimension SetCommBreak SetCommEventMask SetCommState SetEnvironment SetMetaFileBits SetResourceHandler SetSelectorBase SetSelectorLimit SetSoundNoise SetViewportExt SetViewportOrg SetVoice* (6 functions) SetWinDebugInfo SetWindowExt SetWindowOrg StackTraceCSIPFirst StackTraceFirst StackTraceNext StartSound StopSound SwapRecording SwitchStackBack SwitchStackTo SyncAllVoices SystemHeapInfo TerminateApp Throw TransmitCommChar UnAllocDiskSpace UnAllocFileHandles UngetCommChar ValidateCodeSegments ValidateFreeSpaces WaitSoundState WriteComm Yield
(a) file.C: 100: Overflowed replacement buffer. (b) #define DEBUG_NEW new(__FILE__, __LINE__) #if DEBUG #define new DEBUG_NEW #endif CObject *obj = new CObject; (c) #define DEBUG_NEW new(__FILE__, __LINE__) #if DEBUG #define MYnew DEBUG_NEW #else #define MYnew new #endif CObject *obj = MYnew CObject;
AbnormalTermination AddFontModule AdjustTokenGroups Beep CallNextHookEx CloseHandle CompareFileTime ContinueDebugEvent CopyCursor CopyFile CopyIcon CreateDirectory CreateFile CreateFileMapping CreateProcess DeleteCriticalSection DeleteFile DosDateTimeToFileTime DrawEscape DuplicateHandle EnterCriticalSection EnumFontFamProc EnumResLangProc EnumResNameProc EnumResourceLanguages EnumResourceNames EnumResourceTypes EnumResTypeProc EnumThreadWindows ExitProcess ExitThread ExtEscape FileTimeToDosDateTime FileTimeToSystemTime FindClose FindFirstFile FindNextFile FlushFileBuffers FreeDDElParam GetCommandLine GetCurrentDirectory GetCurrentProcess GetCurrentProcessId GetCurrentThread GetCurrentThreadId GetDiskFreeSpace GetEnvironmentStrings GetEnvironmentVariable GetExpandedName GetFileAttributes GetFileSize GetFileTime GetFileType GetFullPathName GetLastError GetLogicalDrives GetProcessExitCode GetSaveFileName GetStartupInfo GetStdHandle GetSystemTime GetTempPath GetThreadContext GetVolumeInformation HeapAlloc HeapCreate HeapDestroy HeapFree HeapSize InitializeCriticalSection IsWindowUnicode LeaveCriticalSection LockFile MapViewOfFile MapViewOfFileEx MoveFile NetBios PackDDElParam PeekMessageEx PostThreadMessage PrintDlg RaiseException ReadFile ReadProcessMemory RegCloseKey RegOpenRegistry ReleaseMutex ReleaseSemaphore RemoveDirectory RemoveFontModule ReuseDDElParam SearchPath SetBrushOrgEx SetCurrentDirectory SetEndOfFile SetEnvironmentVariable SetFileAttributes SetFilePointer SetFileTime SetLastError SetLastErrorEx SetStdHandle SetSystemTime SetThreadContext Sleep SystemTimeToFileTime TlsFree TlsGetValue TlsSetValue UnhandledExceptionFilter UnlockFile UnmapViewOfFile UnpackDDElParam VirtualAlloc VirtualFree VirtualQuery WaitForDebugEvent WordBreakProc WriteFile
BM_GETIMAGE BM_SETIMAGE DM_GETDEFID DM_SETDEFID EM_GETTHUMB WM_CTLCOLOR_BTN WM_CTLCOLOR_DLG WM_CTLCOLOR_EDIT WM_CTLCOLOR_LISTBOX WM_CTLCOLOR_MSGBOX WM_CTLCOLOR_SCROLLBAR WM_CTLCOLOR_STATIC WM_GETHOTKEY WM_HOTKEY WM_MOUSEENTER WM_SETHOTKEY
AddFontResource GetClassWord GetWindowWord RemoveFontResource SetClassWord SetWindowWord
typedef unsigned short WORD; int function() { int nOne; WORDwTwo; ... ... wTwo = (WORD)nOne; }
(b) a portable version of the CPoint::CPoint(DWORD) constructor.
(a) struct tagPOINT { short x; short y; }; class CPoint : tagPOINT { ... CPoint::CPoint(DWORD); ... }; CPoint::CPoint(DWORD dwPoint); { *(DWORD *)this = dwPoint; } (b) CPoint::CPoint(DWORD dwPoint) { x=LOWORD(dwPoint); y=HIWORD(dwPoint); }
Copyright © 1994, Dr. Dobb's Journal