Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Cross-Platform Development with Visual C++


MAR94: Cross-Platform Development with Visual C++

A familiar API for UNIX, Windows, and more

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.

Portability Pitfalls

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.

API Differences

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.

Word Sizes, Structure Packing, and Byte-Ordering Issues

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.

Conclusion

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.


Figure 1: (a) Sample error message; (b) nonportable statements; (c) portable statements (change to normal C-style typecasting).

(a)
     file.C, line 100: error: syntax error

(b)
     int  Number = 26;
     unsigned char Letter;
     ...
     Letter = unsigned char (Number);

(c)
     Letter = (unsigned char)Number;

Figure 2: (a) Sample error message; (b) nonportable statement; (c) portable statement (change the return value to match the base-class return-value type).

(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()

Figure 3: (a) Sample error message; (b) nonportable statements;

(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)
       }

Figure 4: (a) Sample error message; (b) nonportable statement; (c) portable statement (the tailing semicolon after the macro must be deleted).


(a)
     file.C, line 100: error: syntax error

(b)
     DECLARE_DYNAMIC(ClassName);

(c)
     DECLARE_DYNAMIC(ClassName)

Table 1: WIN16 functions not included in Win32s.

<I>AccessResource</I>
<I>AllocDiskSpace</I>
<I>AllocDSToCSAlias</I>
<I>AllocFileHandles</I>
<I>AllocGDIMem</I>
<I>AllocMem</I>
<I>AllocResource</I>
<I>AllocSelector</I>
<I>AllocUserMem</I>
<I>Catch</I>
<I>ChangeSelector</I>
<I>ClassFirst</I>
<I>ClassNext</I>
<I>CloseComm</I>
<I>CloseDriver</I>
<I>CloseSound</I>
<I>CountVoiceNotes</I>
<I>DefDriverProc</I>
<I>DeviceCapabilities</I>
<I>DeviceMode</I>
<I>DirectedYield</I>
<I>DlgDirSelect</I>
<I>DlgDirSelectComboBox</I>
<I>DOS3Call</I>
<I>ExtDeviceMode</I>
<I>FlushComm</I>
<I>FreeAllGDIMem</I>
<I>FreeAllMem</I>
<I>FreeAllUserMem</I>
<I>FreeSelector</I>
<I>GetAspectRatioFilter</I>
<I>GetBitmapDimension</I>
<I>GetCodeHandle</I>
<I>GetCodeInfo</I>
<I>GetCommError</I>
<I>GetCommEventMask</I>
<I>GetCurrentPDB</I>
<I>GetCurrentPosition</I>
<I>GetDCOrg</I>
<I>GetEnvironment</I>
<I>GetInstanceData</I>
<I>GetKBCodePage</I>
<I>GetMetaFileBits</I>
<I>GetModuleUsage</I>
<I>GetSelectorBase</I>
<I>GetSelectorLimit</I>
<I>GetSystemDebugState</I>
<I>GetTempDrive</I>
<I>GetTextExtent</I>
<I>GetTextExtentEx</I>
<I>GetThresholdEvent</I>
<I>GetThresholdStatus</I>
<I>GetViewportExt</I>
<I>GetViewportOrg</I>
<I>GetWindowExt</I>
<I>GetWindowOrg</I>
<I>GetWinFlags</I>
<I>GlobalDosAlloc</I>
<I>GlobalDosFree</I>
<I>GlobalEntryHandle</I>
<I>GlobalEntryModule</I>
<I>GlobalFirst</I>
<I>GlobalInfo</I>
<I>GlobalNext</I>
<I>GlobalPageLock</I>
<I>GlobalPageUnlock</I>
<I>InterruptRegister</I>
<I>InterruptUnRegister</I>
<I>LocalFirst</I>
<I>LocalInfo</I>
<I>LocalNext</I>
<I>LockInput</I>
<I>MemManInfo</I>
<I>MemoryRead</I>
<I>MemoryWrite</I>
<I>ModuleFindHandle</I>
<I>ModuleFindName</I>
<I>ModuleFirst</I>
<I>ModuleNext</I>
<I>MoveTo</I>
<I>NetBIOSCall</I>
<I>NotifyRegister</I>
<I>NotifyUnRegister</I>
<I>OffsetViewportOrg</I>
<I>OffsetWindowOrg</I>
<I>OpenComm</I>
<I>OpenDriver</I>
<I>OpenSound</I>
<I>PrestoChangoSelector</I>
<I>Prof* (8 functions)</I>
<I>QuerySendMessage</I>
<I>ReadComm</I>
<I>ScaleViewportExt</I>
<I>ScaleWindowExt</I>
<I>SetBitmapDimension</I>
<I>SetCommBreak</I>
<I>SetCommEventMask</I>
<I>SetCommState</I>
<I>SetEnvironment</I>
<I>SetMetaFileBits</I>
<I>SetResourceHandler</I>
<I>SetSelectorBase</I>
<I>SetSelectorLimit</I>
<I>SetSoundNoise</I>
<I>SetViewportExt</I>
<I>SetViewportOrg</I>
<I>SetVoice* (6 functions)</I>
<I>SetWinDebugInfo</I>
<I>SetWindowExt</I>
<I>SetWindowOrg</I>
<I>StackTraceCSIPFirst</I>
<I>StackTraceFirst</I>
<I>StackTraceNext</I>
<I>StartSound</I>
<I>StopSound</I>
<I>SwapRecording</I>
<I>SwitchStackBack</I>
<I>SwitchStackTo</I>
<I>SyncAllVoices</I>
<I>SystemHeapInfo</I>
<I>TerminateApp</I>
<I>Throw</I>
<I>TransmitCommChar</I>
<I>UnAllocDiskSpace</I>
<I>UnAllocFileHandles</I>
<I>UngetCommChar</I>
<I>ValidateCodeSegments</I>
<I>ValidateFreeSpaces</I>
<I>WaitSoundState</I>
<I>WriteComm</I>
<I>Yield</I>

Figure 5: (a) Sample error message; (b) nonportable statements; (c) port-able statements.


(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;

Table 2: Win32 functions included in Win32s (some functions are no-ops).

<I>AbnormalTermination</I>
<I>AddFontModule</I>
<I>AdjustTokenGroups</I>
<I>Beep</I>
<I>CallNextHookEx</I>
<I>CloseHandle</I>
<I>CompareFileTime</I>
<I>ContinueDebugEvent</I>
<I>CopyCursor</I>
<I>CopyFile</I>
<I>CopyIcon</I>
<I>CreateDirectory</I>
<I>CreateFile</I>
<I>CreateFileMapping</I>
<I>CreateProcess</I>
<I>DeleteCriticalSection</I>
<I>DeleteFile</I>
<I>DosDateTimeToFileTime</I>
<I>DrawEscape</I>
<I>DuplicateHandle</I>
<I>EnterCriticalSection</I>
<I>EnumFontFamProc</I>
<I>EnumResLangProc</I>
<I>EnumResNameProc</I>
<I>EnumResourceLanguages</I>
<I>EnumResourceNames</I>
<I>EnumResourceTypes</I>
<I>EnumResTypeProc</I>
<I>EnumThreadWindows</I>
<I>ExitProcess</I>
<I>ExitThread</I>
<I>ExtEscape</I>
<I>FileTimeToDosDateTime</I>
<I>FileTimeToSystemTime</I>
<I>FindClose</I>
<I>FindFirstFile</I>
<I>FindNextFile</I>
<I>FlushFileBuffers</I>
<I>FreeDDElParam</I>
<I>GetCommandLine</I>
<I>GetCurrentDirectory</I>
<I>GetCurrentProcess</I>
<I>GetCurrentProcessId</I>
<I>GetCurrentThread</I>
<I>GetCurrentThreadId</I>
<I>GetDiskFreeSpace</I>
<I>GetEnvironmentStrings</I>
<I>GetEnvironmentVariable</I>
<I>GetExpandedName</I>
<I>GetFileAttributes</I>
<I>GetFileSize</I>
<I>GetFileTime</I>
<I>GetFileType</I>
<I>GetFullPathName</I>
<I>GetLastError</I>
<I>GetLogicalDrives</I>
<I>GetProcessExitCode</I>
<I>GetSaveFileName</I>
<I>GetStartupInfo</I>
<I>GetStdHandle</I>
<I>GetSystemTime</I>
<I>GetTempPath</I>
<I>GetThreadContext</I>
<I>GetVolumeInformation</I>
<I>HeapAlloc</I>
<I>HeapCreate</I>
<I>HeapDestroy</I>
<I>HeapFree</I>
<I>HeapSize</I>
<I>InitializeCriticalSection</I>
<I>IsWindowUnicode</I>
<I>LeaveCriticalSection</I>
<I>LockFile</I>
<I>MapViewOfFile</I>
<I>MapViewOfFileEx</I>
<I>MoveFile</I>
<I>NetBios</I>
<I>PackDDElParam</I>
<I>PeekMessageEx</I>
<I>PostThreadMessage</I>
<I>PrintDlg</I>
<I>RaiseException</I>
<I>ReadFile</I>
<I>ReadProcessMemory</I>
<I>RegCloseKey</I>
<I>RegOpenRegistry</I>
<I>ReleaseMutex</I>
<I>ReleaseSemaphore</I>
<I>RemoveDirectory</I>
<I>RemoveFontModule</I>
<I>ReuseDDElParam</I>
<I>SearchPath</I>
<I>SetBrushOrgEx</I>
<I>SetCurrentDirectory</I>
<I>SetEndOfFile</I>
<I>SetEnvironmentVariable</I>
<I>SetFileAttributes</I>
<I>SetFilePointer</I>
<I>SetFileTime</I>
<I>SetLastError</I>
<I>SetLastErrorEx</I>
<I>SetStdHandle</I>
<I>SetSystemTime</I>
<I>SetThreadContext</I>
<I>Sleep</I>
<I>SystemTimeToFileTime</I>
<I>TlsFree</I>
<I>TlsGetValue</I>
<I>TlsSetValue</I>
<I>UnhandledExceptionFilter</I>
<I>UnlockFile</I>
<I>UnmapViewOfFile</I>
<I>UnpackDDElParam</I>
<I>VirtualAlloc</I>
<I>VirtualFree</I>
<I>VirtualQuery</I>
<I>WaitForDebugEvent</I>
<I>WordBreakProc</I>
<I>WriteFile</I>


Table 3: Win32 messages included in Win32s.

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

Table 4: Win16 functions in Win32s.

<I>AddFontResource</I>
<I>GetClassWord</I>
<I>GetWindowWord</I>
<I>RemoveFontResource</I>
<I>SetClassWord</I>
<I>SetWindowWord</I>

Example 1: Nonportable, 16-bit Windows code.

typedef unsigned short WORD;
int function()
{
     int nOne;
     WORDwTwo;
     ...
     ...
     wTwo = (WORD)nOne;
}

Example 2: (a) Byte-swapping problem using MFC classes;

(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


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.