Bruce is an independent systems developer and can be contacted at [email protected]
Sidebar: Windows CE 1.0 versus Windows CE 2.0
The typical Windows CE-based handheld computer has a minimum of four MB of ROM and two MB of RAM. The operating system and bundled applications are stored in ROM. The processors currently supported by CE include the NEC 4100 MIPS, Hitachi SH3, and Phillips 3900 MIPS. The CPUs and operating system have been designed to conserve energy wherever and whenever possible. Most models include RAM extension cards so that some handhelds can have as much as 12 MB of combined RAM/ROM.
Windows CE provides a multitasking preemptive environment that manages threads, processes, and resources. It provides synchronization objects such as critical sections, mutexes, and events (semaphores, however, are not supported). It schedules the execution of a thread based on a priority time-sliced algorithm. Threads with the same priority are selected on a round-robin basis. Priorities are fixed and are not changed by the scheduler (in other words, there is no aging algorithm). Priorities can change if the CE scheduler detects a priority inversion problem. The Windows CE kernel supports a maximum of 32 processes.
The greatest constraint for application programs is limited memory. With the absence of a fixed disk, all persistent data is stored in memory. The object store is the portion of memory that has been allocated to store all files. Users can set the size of the object store through a Control Panel applet.
The display screen is also a consideration in application design. It is a minimum size of 480×240 pixels and allows for four colors -- two bits per pixel (bpp) in Version 1.0; see the accompanying text box entitled "Windows CE 1.0 versus CE 2.0" for information on CE 2.0. There is a small keyboard but it lacks a numeric keypad and function keys. A pen stylus and touch-sensitive screen is used instead of a mouse.
The User and GDI components from Windows 95/NT are combined into the Graphic Windows Event Subsystem -- the GWES component. This includes support for RAPI, Winsock, Telephony, serial connections, and infrared communications. Windows CE 1.0 does not support ActiveX, DDE, ODBC, OLE, COM, or printing. It provides limited support of the multimedia API functions -- wave files only (no MIDI, CD audio, and so on).
The Windows CE API is a subset of Win32; anything you write for CE will run on Win32. However, Win32 experience does not prepare Windows programmers for the differences between CE and the desktop. As the story goes, if the compatibility is 90 percent, developers will probably spend 90 percent of their time determining the 10 percent of the API that is different. Unfortunately, it is not as simple as setting a switch in the IDE to "compile my program for Windows CE."
The optimal way to develop for Windows CE, and to leverage your Win32 API expertise in the process, is to quickly learn which portion of the Win32 API is not supported by CE.
To develop for CE, I used Visual C++ 5.0 with the CE-compiler add-on. The add-on contains the compilers for each of the chip sets (MIPS, SH3, Intel emulation). After installing the add-on, you then have several more target platforms (MIPS, SH3, Emulation) to choose from. The add-on also contains an emulation environment that you can run on NT. You can compile, build, download, and debug programs under 95 or NT; however, the emulation only links and runs under NT.
There are a number of functions from the Standard C Runtime libraries missing under CE. Microsoft has compressed the run-time libraries and thrown away functions that are duplicated in the Win32 API. This is done to minimize the run-time footprint on the handheld.
For example, an application I was writing made extensive use of the mktime function to calculate time to expiration for option derivatives. Although this function is missing from the CE run-time library, you can replace calls to mktime with calls to GetSystemTime (and adjust for the global time base).
All of the standard C run-time file operations do not exist on CE. You are expected to use the Win32 API equivalents (CreateFile() for _open(), and so forth). The sprintf function does not exist in the CE run-time library, but you can use wsprintf from the Win32 API instead. Unfortunately, however, wsprintf in the Win32 API does not support formatting floating-point numbers ("%f", for instance). If you wish to format a float value, you have to use _fcvt() to convert the float to string, then wsprintf the string.
The Microsoft Visual C++ CE compiler(s) does not support RTTI. It does not support the Microsoft implementation of STL either. This was a major disappointment since I was writing C++ code and expected my code to be portable between desktops and handhelds. I wanted to use the STL for its list and vector template classes. Consequently, I ended up using a version of the STL from Hewlett-Packard, then retrofitting the template library code for CE.
If you wish to use the string class in your code, you will have to write your own version for the CE platform to emulate the string class. The Microsoft implementation of the string class and STL code is so tangled up in the fstream class that it will not work on CE without major retooling. This is because the CE run time does not use the standard C functions for file I/O upon which the STL codes are built.
The loading mechanism of your program is different on CE. Since there is no such thing as a "path" environment variable on CE, all support DLLs must be in the \Windows subdirectory on the handheld. There is no concept of a current working directory. Consequently, any pathname to data that your program uses must be fully qualified or the function will fail.
Windows CE API versus Win32 API
The CE API is a subset of the Win32 API. The optimal way to write a portable program would be to write specifically for the CE API, almost guaranteeing that the program will compile on the desktop. If you write for the desktop and then port to CE, you will have lots of "undefined function call" errors in your compilation. The trick is to learn which API calls are not available on CE so you do not include them in your code.
Applications written for CE don't have a lot of pop-up windows opened as on desktop applications. Nor do they have a mechanism to resize the windows or normal menus. The screen real estate is at a premium and consequently there are significant specific API differences in the area of window management:
- MDI windows are not supported.
- A menu name or identifier cannot be passed in the CreateWindow function.
- Nonclient messages (WM_NCxxxx) are not sent to a window.
- Windows created with a parent handle are implicitly set to the WS_CHILD style.
- The MSG structure passed to a window contains the last point tapped on the screen rather than the last mouse location.
- Windows cannot be resized by users (there are no window handles).
- GetClientRect() returns a rectangle that includes the command bar (menu) area.
- The window styles WS_OVERLAPPEDWINDOW and WS_EX_TOPMOST are not supported (for a complete list of window styles not supported, see Table 1).
- The class styles CS_OWNDC and CS_CLASSDC are not supported (for a complete list of class styles not supported, see Table 2).
Command bars are specific to Windows CE and are similar to toolbars. They can contain menus, buttons, and combo boxes. They are the preferred method on CE for adding a menu to a window. However, since they more or less interact with your window and the OS like any other control, there are a number of cases where undesirable side effects occur.
For instance, the client rectangle retrieved by calling GetClientRect() under Windows 95/NT does not include the menu area. Consequently, the coordinate (0,0) begins directly below the menu. However, on CE with a window with a command bar, the coordinate (0,0) refers to the top left, uppermost point of the command bar. So calling GetClientRect on CE includes the command bar area.
This difference requires you to adjust the coordinates by the size of the command bar (assuming that you are using a command bar with a menu inserted in it). Since the command bar is included in the client area, you have to be sure that you do not overlay this area when positioning any child windows or controls.
A side effect of this difference is that you also cannot rely on CE to add vertical scrollbars to a window by adding the WS_VSCROLL window style to the window (usually during the call to CreateWindow). Since the command bar is part of the client area, when the CE windowing system adds the vertical scrollbar it covers up the command bar. If the scrollbars are added to the right portion of the screen (and they usually are), then the Close (X) button is covered up on the command bar. Users have no way to close the window. One alternative is to create and add the scrollbars yourself. This way you can manually maintain the z-order so that the command bar's Close button is never obstructed by the scrollbar.
The differences in API functions relating to drawing are (also see Table 3):
- Display fonts can only be raster fonts. (There are seven raster fonts built into ROM. Users can install additional fonts using the HP/Explorer but the fonts are converted to raster fonts in the process.)
- No application cursors. The only cursors are the standard arrow and the spinning hourglass.
- No color bitmaps (two-bpp bitmaps only).
- Metafiles are not supported.
- Does not support compressed bitmap formats, such as run-length encoded bitmaps.
- The concept of a current point is unsupported. The functions MoveTo() and LineTo() are not available.
- MM_TEXT is the only mapping mode used with fonts in Windows CE.
- GetSysColor() returns a palette index instead of an RGB value.
- Does not support wide-dashed pens, dotted pens, and inside frame pens.
- CE does not support custom palettes.
CE uses a two-bpp grayscale display. The colors available are white, black, light gray, and dark gray (the associated COLORREF's are 0x01000003, 0x01000000, 0x01000002, and 0x01000001, respectively). (The leftmost byte is a 0x01 denoting a custom palette.) You can then use the PALETTEINDEX macro to pass the COLORREF value to SetBkColor. For example, to set the background color to white:
SetBkColor( hdc, PALETTEINDEX(3))
I prefer to code with COLORREFs that are conditionally compiled according to the platform; see Example 1. Of course, if your desktop application makes extensive use of color, you do not want to leave it up to CE to select the nearest color. This is another instance where conditional directives are needed.
The resource and common controls available to your program are also different:
- Cascading menus are not allowed.
- Group boxes are not transparent. When in the resource editor make sure that the z-order of the controls is such that any group boxes are behind the controls they are grouping; otherwise, controls will "disappear" on the handheld.
- Only the common dialog boxes Open and "Save As" are available.
- The combo style CBS_SIMPLE is unavailable.
- SS_CENTERIMAGE creates garbage on the handheld screen.
- WS_VISIBLE style must be set in the resource in order for the control or window to be shown.
- CE resource compilers give syntax errors at compile type to some desktop extensions that are specific to 95/NT controls.
There are a set of CE-specific command bar functions to create, destroy, and manipulate the control. If you are using MFC to build your application, using the LoadFrame function within your derived CFrameWnd class will provide a menu or command bar to your program transparently depending on your platform. Otherwise, you will need to create and insert the command bar into your window.
Everything on the handheld is done in Unicode. If you want your code to be portable to Windows 95, you can use the tchar.h header macros and definitions to select whether to use a char or wchar_t for TCHAR at compile time. When making calls to standard string manipulation functions, you would then use the _tcsxxx equivalent. For example, you would use the _tcslen function in place of strlen. When compiling on CE, the _tcslen call is then replaced with wcslen. When compiling on Windows 95, the call is replaced with strlen. All string constants are then qualified with a _T() macro that prepends an L to the string on CE to designate a wide character string (for instance, _T("foo") becomes L"foo"); see Example 2.
I built one application using MFC and found some quirks in the MFC CE implementations. Some codes just broke on the CE platform though they worked on the desktop. If you dynamically subclass a window using the CWnd::SubclassWindow() method, for example, it is imperative that you reverse the operation (via CWnd::UnsubclassWindow()) at the parent window deletion. Failure to do so is fatal on the handheld. On the desktop, however, the code works fine (that is, without the call to CWnd::UnsubclassWindow).
MFC for Windows CE implements a CommandBar class, which is a data member of the CFrameWnd object. If you need to manipulate a menu item, you will want to do this through the CommandBar object. Unfortunately, some of the canned code, which Visual C++ gives to you when creating an application, is not CE aware and gives you compile errors. For example, adding an About menu item to the system menu is part of the standard code in CFrameWnd::OnInitDialog. However, the GetSystemMenu() API function call is implemented as a macro under CE and fails to compile. In fact, there isn't any system menu under CE, so you just enclose the code with conditional predefined constants (#ifdef _WIN32_WCE, for instance). For further discussion of the MFC implementation, see George Shepherd and Scot Wingo's "Undocumented Corner" column (DDJ, October 1997; available electronically, see "Resource Center," page 3).
Windows CE supports multiple threads for a single process. It supports all of the synchronization objects except semaphores. Events are supported via the CreateEvent() API call; named events are not. Like Windows 95, the CE API does not support the security attributes associated with Win32 object handles.
Debugging under CE is a matter of connecting the handheld to the development machine via a serial cable. The executable is then downloaded to the HPC. The debugging communications is handled via the TCP/IP protocol between the HPC and desktop. Control of the program is handled much the same way that you would debug any other program with single step, execution breakpoints, examining variables, and so on. It is, however, incredibly slow. In fact, it is so slow that probably the only time you should actually debug on the handheld is for handheld-related issues. Using the emulation environment is much faster and avoids the extra step of having to download the executable.
Obviously, with a smaller screen the visual design of your applications will be different. I found that having two templates for some dialogs is useful. The dialog template for CE had the controls positioned and sized for the smaller screen. When the program was compiled, the identifier for the dialog is defined according to which platform is being built.
Unfortunately, I did run into a few minor problems when using MSVC to set up the build options for a platform. Visual C++ forgets to put the directives /subsystem:windowsce and /machine:xxx in the link options when setting up the build. When you then try to run the executable, you get a run-time error from the CE loader saying the file is not an executable file.
A small percentage of applications may also experience problems related to going to a slower platform. Obviously, CE is slower than the desktop, which can result in deadlocks, race conditions, or starvation in multithreaded applications that may run just fine on the desktop.
Copyright © 1998, Dr. Dobb's Journal