David Pellerin
September 1997
Microsoft® Windows® CE is a compact, highly efficient and scalable operating system for use in a wide variety of embedded products, ranging from handheld PCs to specialized industrial controllers and consumer electronic devices. Windows CE has proven itself capable of handling the most demanding 32-bit embedded applications. Equally important, Windows CE brings the full power of the Microsoft 32-bit Windows–based development tools to the embedded systems designer.
One of the primary reasons to choose Windows CE for embedded applications is the widely used Microsoft Win32® application programming interface (API). The Win32 API is at the core of nearly every 32-bit application being written for Windows today, from high-end server products running on the Microsoft Windows NT® operating system to the smallest desktop and embedded applications.
The goal of this paper is to introduce Win32 event-driven programming to embedded system developers who are creating new embedded systems based on the Windows CE operating system. This article will:
This paper is not intended as a complete tutorial on Win32 programming, but serves instead as an introduction to Win32 as a development tool for embedded applications.
The Win32 programming model has become familiar to hundreds of thousands of application developers worldwide. Win32 is a powerful target for desktop and enterprise-wide applications and, with the advent of Windows CE, is an ideal programming interface for embedded systems design as well. Win32 provides a consistent, well-documented, and robust programming interface to the Windows CE operating system.
Programming for the Windows CE platform using Win32 is similar to programming for other Win32 platforms; this means that developers are able to make use of the vast quantity of Win32 programming resources, third-party tools, and outside expertise available when developing new applications for the Windows CE operating system. Programmers experienced with Win32 find that creating new applications (or porting existing applications) for Windows CE devices such as handheld PCs (H/PCs) is far easier than creating similar applications for platform-specific or other less widely used embedded operating systems.
Embedded system designers have been quick to acknowledge the power of Windows CE and are incorporating the operating system into new products at an astonishing rate. Many embedded software developers, however, are unfamiliar with the general techniques of event-driven programming in Windows, and have little or no experience writing Win32-based programs. These software developers can benefit by learning a few fundamentals of Win32 before beginning their first embedded Windows CE–based project.
The term Win32 is used to describe an API that is common to all of the Microsoft 32-bit Windows platforms. These platforms currently include:
In this paper we are most concerned with the Windows CE operating system, but it is important to understand that most of the Win32 API is common to all three platforms. The benefits of a common API are many: easier porting of applications, less required learning when moving from one platform to another, and a vast library of existing programming knowledge, examples, and third-party resources.
The Win32 API defines the interfaces to the Windows platforms that are available to you as a programmer. The goal of the Win32 API is to provide a common set of interfaces, but in reality, there are some differences due to the differing feature sets—and hardware constraints—of the various platforms. Some members of the Win32 platform family support the entire Win32 API, while others support only a subset of the API. Windows CE, designed for compact, embedded applications and small-footprint devices, has the most constrained Win32 API. The Windows CE API is robust enough, however, to handle a virtually unlimited number of advanced embedded applications.
Figure 1: The Win32 API provides a common interface to all 32-bit Windows platforms, while MFC provides higher-level encapsulations of commonly used Win32 features.
Programming with the Win32 interface is not the only way to create 32-bit Windows–based applications. Another important programming interface available to Win32 and Microsoft Visual C++® developers is the Microsoft Foundation Class library (MFC). MFC provides higher-level encapsulations for much (but not all) of the Win32 API (see Figure 1). In general, MFC supplies C++ classes that represent important Windows user-interface objects such as windows, dialog boxes, brushes, pens, and fonts. MFC also provides classes that are appropriate for embedded applications that have little or no user-interface requirements. MFC class member functions make calls to the Win32 API functions, and can dramatically simplify the design of a complex application.
As a programmer using Win32, you are free to choose between C or C++ and the Win32 API, or C++ and MFC. The Visual C++ development system supports both methods of design (or a combination) for all Win32 target operating systems, including Windows CE.
This article will focus on using the Win32 API directly. Detailed information about MFC for Windows CE applications is available in the Windows CE Software Development Kit (SDK) documentation.
Win32 is an API that is common and consistent (although not entirely equivalent) across all 32-bit Windows platforms. To have a solid understanding of the Win32 API and make effective use of its features, it is important to understand some fundamentals of the underlying operating systems. This section summarizes the most important concepts of 32-bit Windows operating systems and the Win32 API, and will provide you with a starting point for further learning. For more detailed information about the 32-bit Windows architecture, the Win32 API, and other advanced programming topics, you are encouraged to read one of the many published books on the subject. Microsoft Press offers a number of such titles.
Those unfamiliar with Windows CE may have a mistaken notion that it is simply a reduced feature-set version of an existing operating system (such as Windows 95). The truth is that Windows CE was developed from the ground up as a small-footprint, highly customizable operating system for embedded applications. The Windows CE kernel borrows much of what is best from other Microsoft 32-bit operating systems, while eliminating (or replacing) those operating-system features that are not needed for typical Windows CE–based applications. For example, as on Windows NT, all applications running on Windows CE run in a fully preemptive multitasking environment, in fully protected memory spaces. Also, like Windows NT, Windows CE supports native Unicode strings, making it more applicable to internationalized applications. But unlike other 32-bit Windows platforms, Windows CE is compact—and customizable—enough to be contained in less than 200K of read-only memory (ROM).
The Win32 API for Windows CE is smaller than the Win32 API for the other 32-bit Windows operating systems; it includes approximately half the interface methods of the Windows NT version of the API. But the Win32 API for Windows CE also includes features found in no other Microsoft operating system. The notification API, for example, makes it possible to handle user or application notification events (such as timer events) at the operating-system level, rather than in a running application. The touch screen API and the built-in support for the Windows CE database are added examples of features not found in other Windows operating systems. The touch screen API makes it easy to manage screen calibration and user interactions for touch-sensitive displays, while the database API provides quick and easy access to a fast, general-purpose data storage facility.
Another little-understood aspect of Windows CE is its high degree of modularity; embedded system developers are able (using the Microsoft Windows CE Embedded Toolkit for Visual C++) to create a version of Windows CE that is customized and optimized for their unique hardware platforms and applications.
The Windows CE operating system truly represents a clean slate for design. The developers of Windows CE had little or no requirement to support older (legacy) applications or devices, so the operating system could be designed using the most up-to-date understanding of operating systems and applications, and using the most advanced embedded 32-bit microprocessor families as target hardware platforms.
What does this mean to a user of the Win32 API who is targeting the Windows CE platform? It means a simpler API and a target operating system better optimized for modern, 32-bit embedded systems. In the following sections, we'll briefly examine some of the important aspects of the Win32 API and the Windows CE operating system.
The first step in understanding the Win32 API (and the underlying Windows CE operating system) is to understand how multitasking and multithreaded applications are handled. In Win32 terminology, a process is defined as one instance of a running application. Windows CE is, like other 32-bit Windows platforms, a multitasking operating system that supports multiple threads of execution within a running process.
The multithread capabilities of Windows CE are important to its power for embedded applications. This makes an understanding of Win32 thread creation and synchronization a high priority for developers of Win32 embedded software.
Thread handling in Win32 is somewhat different from thread handling in other commonly used embedded operating systems. Unlike UNIX and its derivatives, the 32-bit Windows platforms were designed from the start to support multithread applications. Thread management (scheduling, synchronization, and resource management) is handled by the kernel itself, and application developers make use of functions built into the kernel (and accessible via the Win32 API) to set up and manage threads in their applications.
Figure 2: An application that must manage asynchronous events and shared resources requires a reliable, easily synchronized system of thread management.
For example, consider an embedded application that must monitor multiple input devices and react appropriately when asynchronous events are detected on one or more of those devices. Further, consider such an application that also needs to update some shared resource (such as a global data structure, a disk file, or another device) in response to those device-related events. An application such as this requires a reliable, easily synchronized system of thread management (see Figure 2). This is exactly the access that the Win32 API provides: multiple threads can be quickly and easily set up using the Win32 CreateThread interface; and thread synchronization (such as would be needed when multiple threads are accessing the same data) can be achieved using a variety of methods, including critical sections, named and unnamed events, and mutex objects.
Windows CE has been designed to implement these synchronization methods with a minimum of processor resources. This latter point is important for developers of low-power applications; because the kernel handles thread management, there is no need to consume additional processor execution cycles polling for process or thread completion or performing other wasteful, application-level thread management. The kernel has built-in knowledge of how to manage multiple threads and processes most efficiently.
For applications involving multiple processes, Win32 provides access to a rich set of methods for thread and process management and synchronization. These thread management features are well-suited to embedded software applications, and are readily available to application developers targeting Windows CE.
Processes that run on 32-bit Windows platforms—more specifically, the threads within those processes—rely on messages to initiate processing, control system resources, and communicate with the operating system and user. Windows messages originate from a variety of sources, including the operating system, user activity such as keyboard, mouse or touch screen actions, and other running processes or threads.
Figure 3: Each thread in a Win32 application has its own message queue, and is responsible for handling messages in that queue.
When a message is sent to a thread, that message is placed in a message queue for that thread for eventual handling (see Figure 3). Each thread owns a message queue that is completely independent of message queues owned by other threads. Threads typically have a message loop that runs continuously, retrieving and handling messages. When there are no messages in the queue and the thread is not engaged in any other activity, the system can suspend the thread, thereby saving CPU resources.
Messages can be used for control purposes, to initiate various types of processing in your application, and they can also be used to pass data using message parameters. For example, a thread might receive a message indicating that a touch screen has been activated, and the message parameter might indicate the x and y coordinates of the user's action. In another type of message, the parameter might include a pointer—or handle—to a data structure, window, or other object.
As an embedded software developer, you will probably be most concerned about how the message processing rules of Windows CE impact the timing of your external system interfaces. Windows CE has been carefully designed—and clearly measured—to ensure that its interrupt timing and other related characteristics are appropriate for embedded system design.
Embedded applications often have time-critical device interface requirements, with a need to detect and respond to device and system events within a minimum specified time. To support such applications, Windows CE includes a highly optimized interrupt delivery, prioritizing, and servicing system.
In the Windows CE kernel, interrupt handling is split into two distinct parts: the interrupt service routing (ISR) and the interrupt service thread (IST). The goal of this system is to keep the ISR as small and fast as possible. At the hardware level, each interrupt request (IRQ) line is associated with one specific software ISR. When triggered, a given ISR does little more than direct the kernel to the location of the IST. Once the IST has been initiated (though not necessarily completed), the system is ready to accept and handle the next interrupt.
Interrupts each have a priority associated with them. Windows CE uses a priority-based time-slice algorithm for thread scheduling. The ISTs associated with each ISR are normal threads, so it is up to the application software developer to set priorities for ISTs that meet the timing requirements of the application.
The end result of this splitting of interrupt processing between ISR and IST is that typical interrupt latencies are minimized, and the likelihood of unacceptable delays in interrupt processing is minimized. In addition, features in the Embedded Toolkit and the Windows CE kernel make it possible to custom-tune the interrupt timing and priorities for the requirements of a specific application.
The subject of Windows CE and its use in time-critical, real-time applications is covered in more detail in a companion article, "Real-time Systems with Microsoft Windows CE."
The Win32 API provides developers with a rich and consistent set of interfaces for memory management. For the most part, software developers do not need to consider a specific memory architecture when developing applications. For many embedded applications, however, particularly those with severe memory resource constraints or critical timing requirements, a good understanding of the way in which memory is managed is important.
The general architecture of Windows memory differs from one 32-bit Windows platform to another, and specific details of the architecture can also differ from one processor type to another within the same 32-bit Windows operating system. (For example, the memory architecture that Windows NT uses on a DEC Alpha platform is quite different from the memory architecture used by Windows NT on an x86 platform.) For the discussions in this section, we will focus exclusively on the Windows CE operating system.
The Windows CE operating system, like the other 32-bit Windows platforms, includes virtual memory features. Memory is always allocated to applications one page at a time, with a page size determined by the system designer (and specified when the operating system is built for the target hardware platform). On an H/PC, for example, the memory page size is typically either 1K or 4K.
During initialization (startup), Windows CE creates a single 4-gigabyte (4-GB) virtual address space that is shared by all processes. When a process references a virtual address, it is mapped onto physical memory by the kernel. This reduces (or eliminates) the need for an application software developer to consider the physical layout of the target system's memory.
Although all processes share a single address space, applications are still prevented from corrupting each other. Instead of assigning each process a different address space, Windows CE protects process memory by altering page protections.
As an application developer, you will probably not be too concerned about the physical architecture of the memory on the target system. The memory may be entirely RAM, or it may include flash memory cards or hard disk drives. The Windows CE operating system manages the memory resources for you, and the Win32 API provides you with the necessary interfaces to allocate, use, and free blocks of memory.
As an embedded system designer, however, you will need to carefully consider the memory requirements of the applications that will be executed on your new hardware platform, and balance the conflicting goals of cost, speed, and reliability. If you are developing a new hardware platform for use with Windows CE, the Windows CE Embedded Toolkit for Visual C++ includes resources to help you make these decisions and configure the operating system accordingly.
Whatever the configuration of your system's memory, ROM will play an important role. Unlike other 32-bit Windows operating systems, Windows CE operating-system code resides in ROM, and executes in place in that ROM. Depending on your product requirements, you may also choose to place application code in ROM. For example, Microsoft Pocket Word, Pocket Excel and other application programs included with an H/PC are provided in ROM.
Programs stored in ROM execute in place in Windows CE, so embedded system designers can get by with only a modest amount of RAM for such purposes as stack and heap storage. Alternatively, your embedded application may make use of RAM for program memory as well as for temporary storage.
To further increase the performance of application software, Windows CE features on-demand paging; the operating system only needs to uncompress and load a small portion of a RAM-based program for execution. The flexibility and speed of ROM and RAM-based programs mean that devices based on Windows CE can be constructed with a wide variety of memory configurations.
The memory architecture of the typical Windows CE hardware platform is quite different from the memory architecture of a desktop Windows–based system. To understand how memory is normally handled in Windows CE, it is useful to examine the most common category of Windows CE–based devices, the H/PCs.
In an H/PC, RAM is partitioned into two primary blocks: storage memory and program memory. The relative amount of RAM devoted to the two blocks can be modified (within limits) by the H/PC user. This RAM partitioning is diagrammed in Figure 4.
Figure 4: H/PC devices provide one example of how RAM storage can be implemented in an embedded application.
Storage memory in an H/PC is similar to a RAM disk on a desktop PC. It is used to store data and nonsystem applications. Each of its three sections is accessed by a different set of Win32 API functions:
Program Memory is used for stack and heap storage for both system and nonsystem programs. Nonsystem applications are taken from storage memory (or perhaps a PC Card), uncompressed and loaded into program memory for execution.
Structured exception handling is a powerful programming technique—and a corresponding set of Win32 API functions—that makes it easy to detect and recover from unexpected error conditions. Structured exception handling allows critical sections of code—code that has a possibility of failing due to hardware resource problems, device conflicts, or subtle coding errors—to be guarded from the rest of the application. This protects the application from premature termination or more subtle system-level problems.
Structured exception handing involves defining a series of statements as guarded, and defining another set of statements as the exception handler for the first set of statements. An exception handler defines one or more statements that are guaranteed to execute, regardless of the exit status of the guarded statements.
Programmers using the Win32 API on most 32-bit Windows platforms currently have two choices when implementing exception handlers: write the application using C or C++ and use the structured exception-handling macros provided with Win32, or write the application using C++ and make use of the exception-handling features defined by the C++ language.
As of this writing, developers for Windows CE do not have access to C++ exception handling (Visual C++ for Windows CE does not yet support C++ exception handling), and so must use the exception handling features built into the Win32 API.
To use the Win32 exception handling features, you can make use of a set of macros defined in the Win32 API. The following code fragment demonstrates the basic concept:
__try {
// The statements in here have a possibility of failure
// and so are guarded.
}
__finally {
// This is the exception handler. This code will execute
// after the guarded statements, no matter what happened
// in the guarded block of code above.
}
// This code will execute normally if the program flow allows
// it (no goto, exit, etc.)
The __try and __finally macros generate the low-level code necessary to implement the exception handlers.
Exception handling is particularly useful for multithreaded programs such as those common in embedded applications. The Win32 structured exception-handling features are an easy and powerful way to protect applications from unexpected failure.
There are countless hardware devices (peripherals) that are compatible with desktop Windows platforms (Windows NT and Windows 95), and more appear on the market each year. Windows CE platforms, on the other hand, typically do not support the wide variety of peripheral hardware devices supported by desktop computers. Creating reliable device interfaces for an embedded system can be one of the more challenging parts of the embedded design process. This is in part because the timing and other operational requirements of typical embedded system interfaces are far more critical than in desktop computing systems and applications. Fortunately, the Win32 API provides a rich set of interface methods that make device interfaces easier to write and more adaptable to the specific requirements of an embedded system.
How does the Win32 API help? The Win32 API provides a consistent interface to all stream-based devices on your hardware platform. To use a device, you first open it using a function appropriate for the type of device. For most devices, the function you use is the CreateFile function, as shown in the following example:
HANDLE hPort = CreateFile("COM1"); // Open the serial port
The CreateFile function opens the specified device (in this case the serial port) and returns a handle that is used for subsequent operations (such as read and write operations) on that device. A variety of functions (including ReadFile, WriteFile, LockFile, and others) accept this handle as a parameter, and allow you to (for example) read and write data, check device status, and specify if the device or file is to be locked from access by other processes.
File input/output (I/O) operations are handled using the same API functions as for other device types, and include functions for randomly accessing locations within a file. Devices and files that must be accessed by multiple processes or threads simultaneously can be locked on a region-by-region basis using the LockFile function.
After your application has finished with a device or file, it calls the CloseFile function to close the device and perform any necessary device cleanup.
Embedded systems often have critical device-related timing requirements. For this reason, the software interface to the underlying operating system must enable the software-level management of simultaneous (or nearly simultaneous) events from different types of devices in the system. The Win32 API supports both synchronous and asynchronous methods of device access, and is designed with complex device interfaces in mind.
Synchronous interfaces are those in which the software requests an action from a device and then waits for the result. The most commonly used synchronous device interfaces are the ReadFile and WriteFile functions mentioned previously. These functions, when used to perform synchronous I/O, are common and consistent whether you are interfacing to a disk file, a serial or parallel port, a pipe, or other type of device.
Asynchronous interfaces are those in which a device requests services from the application. A good example of an asynchronous device is a keyboard. Proper and timely handling of asynchronous events is crucial to many embedded applications. Fortunately, device drivers written for Windows CE can support multiple threads that access the driver simultaneously. This greatly simplifies the handling of asynchronous input devices.
The method you use to access a given device will depend on the characteristics of that device and the requirements of the specific application you are developing. If you are creating an entirely new device (and device driver) along with your Windows CE hardware platform, you have many options to choose from both at the level of your device hardware and driver, and at the application level.
Although they may support a smaller total number of devices, embedded systems can pose unique and challenging device interface problems. When developing a new hardware platform and its supporting I/O devices, you will be making decisions and tradeoffs at different levels in the design. For example, unless you are exclusively using common off-the-shelf hardware, you will almost certainly need to write a custom device driver to support your new peripheral device. You may also need to configure Windows CE to include the necessary device handling components. At the application level, you will need to write interface code that meets the requirements of the new device. With so many variables, how do you maintain some degree of consistency among your devices? One answer lies in the Win32 API. By writing your device driver with the Win32 API as your target, you can be more confident that the developers who write applications that may interface to your new device will be able to create a reliable, testable, and maintainable code base. The Windows CE Driver Development Kit, or DDK, provides information and examples on how to create Win32-capable device drivers.
Windows CE supports two fundamental categories of device drivers: built-in drivers and installable drivers. As the name implies, built-in device drivers are intended for devices that are built into a given Windows CE hardware platform. A Windows CE embedded system designer is responsible for providing the built-in drivers to operate any devices to be included with the system. For example, many Windows CE platforms have a touch-sensitive LCD screen built into them. The manufacturers of these platforms are responsible for providing device-driver software for their devices in order to allow the Windows CE operating system to use the touch-screen device. In the complete system, these built-in drivers reside in ROM along with the customized Windows CE kernel.
Installable device drivers are intended for any peripheral device that can be temporarily connected to a Windows CE hardware platform. This category covers devices such as modems, printers, digital cameras, PC Cards, and any number of other external devices. An installable device driver may reside in ROM, but is more typically loaded along with the application software that interfaces to the temporary device.
This article has presented the Win32 API for Windows CE in broad terms, with the intent to highlight the general features—and benefits—of this important and widely used API. There are many additional details that you will need to learn before launching into your first Windows CE embedded product; fortunately, there are also many sources of information about the Win32 API. These sources include the Microsoft Developers Network (MSDN), articles and papers (such as this one) and a wealth of published books on the subject.
The worldwide use of the Win32 API (whose users today extend into the hundreds of thousands, if not millions) virtually guarantees that you will not be building obsolescence into your embedded system (at least not at the software level), and that you will have access to a enormous store of existing knowledge.
The Win32 API for Windows CE is a powerful enabling technology for embedded system design. It allows full access to all the advanced features of the Windows CE operating system by providing a rich, consistent, and well-documented set of interfaces. The result is faster development, more flexible hardware and software options, and more robust end products.
The information contained in this document represents the current view of Microsoft Corporation on the issues discussed as of the date of publication. Because Microsoft must respond to changing market conditions, it should not be interpreted to be a commitment on the part of Microsoft, and Microsoft cannot guarantee the accuracy of any information presented after the date publication. This document is for informational purposes only.
MICROSOFT MAKES NO WARRANTIES, EXPRESS OR IMPLIED, IN THIS SUMMARY.