Application Development Landscape for Windows CE .NET
Paul Yao, Windows Embedded MVP
The Paul Yao Company
Microsoft® Windows® CE .NET application development
Microsoft .NET Compact Framework
The rich set of development choices for Microsoft® Windows® CE .NET can be daunting. Developers who want to build traditional GUI applications can choose between the Microsoft Win32® Application Programming Interface (API), the object-oriented approach of the Microsoft Foundation Class (MFC) library, or the rich programming model and great tool support for the .NET Compact Framework. This white paper outlines the basic features of these choices, and provides some background to help in selecting an API.
The purpose of this white paper is to compare and contrast the technical merits of three programming interfaces on Microsoft Windows CE, including Pocket PC and Windows CE .NET. Having a lot of choices can be a good thing, but it can also be daunting and lead more to analysis paralysis than to getting work done. You should put a lot of thought into selecting the API to use, because the code you write today will be around for a long time—not just during your development project, but also afterwards while the code is being maintained.
Each of the APIs discussed in this white paper was first implemented on the desktop versions of Microsoft Windows. What is implemented on Windows CE .NET is a subset of the desktop APIs. If you have been working with any of these APIs on the desktop, you will find that most core elements are supported. Being familiar with the desktop equivalent, in fact, might be enough of a factor for you to pick that particular API for your Windows CE .NET work. But if you have no experience with any of these APIs, you must first decide which one to learn. Table 1 summarizes the strengths and weaknesses of the three APIs. The rest of this white paper provides details for the features mentioned in the table.
Table 1. Strengths and Weaknesses of three Windows CE .NET Programming Interfaces
(C / C++)
| || |
| || |
|.NET Compact Framework
(C# and Microsoft Visual Basic® .NET)
| || |
Win32 is the short name for the "Windows 32-bit API". It is the oldest of the three APIs, tracing its roots back to Microsoft Windows NT®, which started shipping in 1992. Its roots go back further than that, however. Win32 was based on the 16-bit Windows API, also called Win16, which first shipped in November of 1985 with Windows 1.01.
All of the other APIs ultimately rely on the Win32 API. As someone once said, "Win32 is the assembly language of Windows." Even if you do not use Win32 as your primary API, any other tool that you use will end up calling Win32 functions to make the real work happen. This is especially important when you want to do something not supported in MFC or the .NET Compact Framework. At such times, you will need to drill through to the underlying Win32 functions. Both MFC and the .NET Compact Framework allow this.
Windows CE .NET supports a smaller set of Win32 functions compared to those available on desktop versions of Windows. Although some people use the term "subset" to refer to the Win32 functions included in Windows CE .NET, you can also look at it as Windows CE .NET having the "Greatest Hits of the Win32 API." The architects of Windows CE .NET took great care in selecting which functions to include and which to exclude. The most important design goal that they followed was that Windows CE .NET was built to be small. Redundant functions are eliminated. For example, the desktop has both TextOut and ExtTextOut. Windows CE .NET only supports ExtTextOut. Functions with the primary role of backward compatibility were also eliminated. The Win16 registry functions are not supported; only the most up-to-date Win32 versions are available. The Win32 on Windows CE .NET is lean, capable, and focused on real work that developers need to do.
Strengths of the Win32 API
The Win32 API is the way to go for building the smallest software. Unlike MFC and the .NET Compact Framework, Win32 does not require a separate runtime to operate. Instead, the operating system itself is the runtime.
The Win32 API is also the way to build the fastest software, which makes it the ideal candidate for real-time threads. That is, when you have any kind of time critical activity, you should use Win32. Because it brings the overhead of C++, MFC is slightly slower.
The other choice in this paper, .NET Compact Framework, has the disadvantage of delays caused by the MSIL-to-native instruction set conversion. This occurs only the first time code is loaded into memory; the in-memory native code is reused. In addition, the Garbage Collector might run at the wrong time, causing unexpected delays for time-critical code.
The Win32 API is also the most capable API. If something can be done in Windows CE .NET, it can be done by using the Win32 API. Even dedicated MFC fans know that they can rely on Win32 to help them out when a system feature is not supported in MFC. In such cases, the global scope operator, double-colon "::", is an MFC programmer's best friend. Although the mechanism is different—there is no global scope operator in .NET—a .NET Compact Framework programmer can call through to Win32 whenever the need arises. An important difference is that the call between the managed world of .NET Compact Framework and the unmanaged world of Win32 comes at a price. There is a slight amount of overhead in the Platform Invoke (P/Invoke), so you should consider carefully how you access Win32 from a .NET Compact Framework application.
Because it does not require a runtime, Win32 is also the most widely supported API. If you want to write code that can run on any Windows CE .NET platform, it is the way to go. If you are writing applications for a wide range of platforms, such as a developer tool, Win32 is a must for you. It is also highly recommended if you are building anything that extends the operating system, such as a device driver.
Operating System Extensions
Win32 is the best choice for certain components, particularly if you are supporting a wide range of platforms and do not know whether a particular runtime will be present or not. These components are listed in Table 2. Each is essentially an extension to the operating system.
Table 2. Win32 API is recommended for the components in this table.
|Device driver||Although other members of the Windows family of operating systems define a device driver programming interface separate from the API, in Windows CE .NET, Win32 is the official programming interface for device drivers.|
|Custom Software Input Panel (SIP)||The SIP is the on-screen keyboard on Pocket PC and other Windows CE .NET-based devices.|
|Control Panel Applet||Behind each icon in the Control Panel is a set of DLLs that provide the dialog boxes, property pages, and property sheets for customizing installable components.|
|User Interface Skin||Windows CE .NET introduces user interface skins, which you can use to customize the look of the user interface. You can, for example, change the look of the system controls to have a different appearance from the standard Windows CE .NET layout. This feature is not available for the Pocket PC or the Pocket PC 2002.|
|Real-Time Threads||Win32 will provide the best performance over the other two APIs, and therefore should be your choice when writing time-critical code.|
The Challenges of Programming Win32
Win32 was created as an upgrade to the Windows 16 Bit API (Win16). Win16 was developed at the same time that Microsoft was building its first graphical user interface (GUI), Windows 1.01. In those days, the focus was on making a usable user interface. At the time, most computers were running with a character-based user interface. Just to put things into context, Windows 1.01 shipped in November 1985. The Apple Macintosh had started shipping shortly before then, in January of 1984.
Although the focus was on giving Windows 1.01 a usable user interface, according to Marlin Eller, who was a member of the team that built the graphics engine, GDI, for Windows 1.01, there was little or no emphasis on making the programming interface usable. The result was a quirky and inconsistent API. In many cases, parameters were left empty—reserved for future use—and many functions require you to enter a NULL or zero with little or no explanation.
The clearest example of inconsistencies involves two functions called by almost every Win32 program: RegisterClass and CreateWindow. You call the first one by filling in a data structure and passing an address of that structure to the function. You call the second one by passing it a long list of parameters. This type of inconsistency appears over and over again throughout the Win32 API, and can make Win32 hard to learn, hard to understand and hard to debug.
Win32: A Procedural Programming Interface
The Win16 and Win32 APIs are fundamentally C programming interfaces, because they were created long before the first C++ compilers shipped. For this reason, there is no benefit from features that many programmers take for granted such as function overloading. In other words, Win16 and Win32 are procedural-oriented programming interfaces. Built in C and assembly language, neither API supports any concept of classes or inheritance.
The nicest thing you can say about these APIs, in fact, is that they are object-based, but definitely not object-oriented. The term "object-based" applies because each API makes extensive use of the idea of encapsulating data in objects, and there are many functions that create system objects and return a handle. For example, you create a window by calling the CreateWindow function, and the return value is a window handle (HWND). You access the window by passing the handle to various functions that allow you to move, resize, hide, show and destroy the window. In fact, the need for you to handle this last action, destroying a window, is part of another core characteristic of the Win32 API, which is that it is easy to write code that has memory leaks.
Win32 Memory Management
If you have ever been a manager, you know how important it is to delegate. Whether you have ever delegated to others, someone has certainly delegated tasks to you. That describes the Win32 approach to managing system objects, because that task is delegated to you, the programmer.
To access the features of the Win32 API, an application program must create system objects®windows, menus, dialog boxes, controls, pens, brushes, fonts and so on. Each time this happens, memory is allocated. To prevent memory leaks, a program must destroy each system object that it creates. Failure to destroy objects creates the worst possible situation for software: a memory leak. If there are too many memory leaks, the operating system will fail, whether it is desktop Windows, Windows CE .NET or another operating system.
The operating system is able to automatically clean up objects that have been created, but there is only one time when it can safely do so for a Win32 program: when an application shuts down. Otherwise, if a program is going to continue running for an extended period of time, that program has to be thoroughly tested to remove any possibility that any memory will leak.
There is some relief, however. A third-party tool named CODESNITCH from Entrek, available at www.entrek.com, will track down memory leaks and report them to you. It tells you both about the specific type of object found and the specific line of code that caused the leak. CODESNITCH takes the guesswork out of tracking pesky memory leak bugs.
After Win32 was released, there were several efforts to create a more usable programming interface. The result of one such effort was the Microsoft Foundation Class (MFC) Library. MFC started shipping in 1992.
MFC was built to be a thin layer on top of the Win32 API. It was also built to be easy to use by Win32 programmers, who were the programmers expected to adopt it as a better alternative to Win32. For these reasons, many of the naming conventions used by MFC come from Win32. Among drawing functions, for example, the Win32 functions named ExtTextOut, Rectangle, and BitBlt, are supported by like-named functions in MFC. In most cases, the MFC equivalent is implemented as an inline function that contains little more than a call to its Win32 equivalent. MFC provides a more object-oriented Win32. MFC also smoothes over some of the bumps in the road caused by different versions of the Win16 and Win32 APIs. For example, Win16 had a function named MoveTo that was renamed in Win32 to MoveToEx, which was subsequently excluded from Windows CE .NET. An MFC program in each of the environments would simply call MoveTo, and MFC would take care of the details. What MFC provides, then, is a level of source code portability between the various platforms on which it runs.
The Benefits of an Object-Oriented Approach
The key benefit that MFC provides over Win32 is an object-oriented programming approach. What this traditionally means is support for inheritance, encapsulation and polymorphism.
Inheritance is used in MFC, and the inheritance tree for MFC classes is quite extensive. As you might expect from working in other OOP environments, you can inherit from base MFC classes to create your own, more specialized classes. For example, CWnd is the base class for every type of window. CView is a window for the document / view architecture, and CScrollView is a scrollable version of CView.
MFC also uses encapsulation in its basic design. The most basic MFC classes wrap a Win32 object: CWnd wraps the Win32 window, CDC wraps the Win32 DC, CListBox wraps the Win32 list box and so on. MFC is based on Win32, so all class names have Win32-like names, a convention that was adopted to simplify the task of porting a Win32 programmer into the world of MFC.
Polymorphism, the ability for a single method to be implemented in different forms, is supported in C++. It is also used extensively in the desktop version of MFC when there are, for example, two versions of CDC::TextOut, with each one taking different parameters as input. This feature is not used on the Windows CE .NET version of MFC, however, because multiple versions of the same function occupy more room. In addition, the MFC version that runs on Windows CE .NET follows the basic dictum of Windows CE .NET, which is to make things as small as possible to conserve memory.
A fundamental model that is often associated with MFC is the document/view architecture. Although you do not strictly need to follow this model, you will find that you need to strip things out if you want to do things differently. The basic idea behind document/view is that there is some data—the document—that the user will want to create, edit, write to disk and read back into memory. That data can be displayed to the user in several different formats—the views. You see this when you use the New Project Wizard in Embedded Visual C++ to create an MFC project. You have a choice between a dialog-based MFC application, or a Document/View MFC application. The easiest way to get around the Document/View approach is to select the dialog-based option.
Another element that is traditionally provided for in class libraries is container classes. MFC has these, including arrays, lists and object maps. You can take any of the CObject-derived classes and group them together by using the container classes. If you want the same support in Win32 you must write the support yourself. The .NET Compact Framework, on the other hand, does provide a nice set of container classes that are even better than what MFC provides.
MFC has historically been the first choice among C++ programmers who build applications for desktop versions of Windows. It provides lots of helpers for user interface programming, container classes and support for core features of the host operating system. For example, MFC has classes for the Windows CE .NET-specific databases CCeDBDatabase and CCeDBProp. MFC only supports features that are part of the underlying operating system, so features that are not present, such as ODBC support on Windows CE .NET, are also not supported in MFC.
MFC Memory Management
Given that MFC is a thin layer on top of Win32, and that memory leaks can be a problem for Win32 application and device driver programmers, just how well does MFC address this issue? The presence of constructors and destructors helps address some of the object cleanup issues. With GDI objects, such as bitmaps, brushes, fonts, pens, palettes and regions, when you destroy the MFC object, the underlying GDI object is also cleaned up.
The process is not perfect, so you need to be on the lookout, just as you must with the Win32 API, to make sure you clean up every object you use. Desktop developers who have used MFC might be aware of the AfxDumpMemoryLeaks function that, as the name implies, displays objects that have been allocated but not freed. The only problem is that this particular function is not available on the Windows CE .NET version of MFC. Fortunately, Entrek's CODESNITCH can help track MFC memory leaks in addition to helping with Win32 memory leaks.
Note As of this writing, the .NET Compact Framework was still in beta and had not yet been released. Because it is expected to ship soon after this writing, it is worth discussing. Some of what is discussed here may have changed in the final product.
The.NET Compact Framework provides a subset of the desktop Framework, the .NET Framework, in the same way that MFC on Windows CE .NET and Win32 on Windows CE .NET are a subset of their desktop equivalents. The .NET Framework represents the next step in the continuing evolution and refinement of a programming interface. The .NET Framework, and by extension the Windows CE .NET equivalent, the .NET Compact Framework, solves many of the problems that Win32 and MFC programmers have been grappling with over the years. When the final version ships, the .NET Compact Framework will support the Pocket PC, Pocket PC 2002 and all appropriately configured Windows CE .NET-based platforms.
.NET Compact Framework: A Well Designed API
.NET Compact Framework is a well-designed API. Like MFC, it is an object-oriented API, and so all the pieces that you need are included in classes. Where it goes beyond what MFC provides is through the use of namespaces. Although C++ supports namespaces, MFC does not use them. The use of namespaces in .NET Compact Framework helps to organize the elements of the API to make it easy to find the different features of the API. To borrow a word from the world of user interface design, the good design is reflected in the fact that the elements are discoverable. All of the drawing support, for example, is built into the System.Drawing namespace. One member of this class is the Graphics class, which is the equivalent of the CDC class in MFC. What you also find is that other members of the System.Drawing namespace will help create graphic output.
Another example of good design is shown in the use of containers. This refers not to the fact that .NET Compact Framework has container classes, but that containers are used everywhere, and the use of containers is highly consistent in the various contexts in which you find them. For example, each Form has a ControlCollection, a ListBox has its ObjectCollection, and a DataSet has its DataTableCollection. This is just a small number of the many examples. In each of these collections, you have Add, Remove and GetEnumerator methods. The high degree of consistency means that after you get the general idea of using one of these collections, much less effort is required for you to use other collections.
.NET Compact Framework Memory Management
Both Win32 and MFC have their problems with memory management, mostly that they rely on the programmer handling some, if not all of the work. When you write .NET Compact Framework code, you write code that runs in a memory-managed environment. This fact is so important that the term Managed Code is often used synonymously with .NET Framework code, which in turn leads to the unfortunate use of the term Unmanaged Code for code built outside the .NET Framework.
Managed code means that when your code allocates an object, such as a byte, a string, a window object, or a drawing object, the runtime keeps track of the object. When you are done using the object, the object is automatically cleaned up for you. This is done by the system tracking the objects so that it knows when you are done using the object. It knows about the stack, and it knows about objects that are referenced on the stack. When memory runs low, the runtime, called the common language runtime (CLR) walks the stack to figure out which objects you are using. Such objects are preserved and all other objects are destroyed as the Garbage Collector packs the known used objects together to create free memory. The operation of the garbage collector means that .NET Compact Framework is less than ideal for time-critical code because there is no way to predict when it will need to be run.
The presence of the garbage collector means, in practical terms, that the C# language has a new operator for allocating objects, but no delete operator for deallocating objects. By spending less effort worrying about memory issues, you can concentrate on the problem at hand.
Using Visual Basic .NET, the other language you can use for building .NET Compact Framework programs, you get the same benefit of the garbage collector as in C#. Visual Basic 6 programmers will be happy to hear that, as with Visual Basic 6, Visual Basic .NET also has a New operator to create objects. Visual Basic 6 programmers will be overjoyed to learn that they no longer have a second allocator, the CreateObject function, because in Visual Basic .NET there only one way to create objects, not two. When such objects are no longer needed, they just vanish without a trace.
The .NET Compact Framework has a number of collection classes to aid you in your application development efforts, just like the MFC library. As mentioned earlier, .NET Compact Framework itself uses some of these collection classes to collect together controls in a form, elements in a List box, and database elements in ADO .NET classes. They are, in other words, well-used classes that have been tuned to work well. If you have worked with the desktop version of the .NET Framework, you will find that most but not all collection classes have made it into the Compact Framework. Omitted classes include support for Queues and SortedList. Some of the features that are included are arrays, array lists, hashtables and stacks.
The .NET Compact Framework has fairly sophisticated database support that is basically a subset of the ADO .NET classes found on the desktop. ADO .NET will create an in-memory table, which is a DataTable in ADO .NET terminology. A table can be bound to user interface controls, such that changes in the table get reflected in the control and vice versa. You can also connect to various external databases, such as a Data Provider, SQL Server or a native Windows CE .NET database. Any of the data can be spun into XML, which makes it a good choice for interacting with any XML-based protocols such as SOAP.
The .NET Compact Framework makes it easy to interact with SOAP-based Web service running on a Web server. Web services are an integral part of .NET, and allow you to make a function call over the HTTP protocol to a Web service provider. Although function calls over a network are nothing new, the mechanism used by the .NET Web Services is. It replaces the low-low-level socket calls, the specialized low-level RPC code, and DCOM remote interfaces. What you see when you call a .NET Web Service looks like a regular function call made to a locally created object.
Here is an example of calling a Web service in C#:
paulyao.HelloServer serv = new paulyao.HelloServer(); MessageBox.Show(serv.HelloWorld(), "Say");
In this example, the Web service is located on a server named paulyao, and the name of the class is HelloServer. After you create a local instance of this object, you can call the member function HelloWorld, which returns the string "Hello World." Although it is a very simple example, it shows how easy it is to make a .NET Web Services function call. More importantly, the building of Web Service clients is supported by the .NET Compact Framework.
.NET Compact Framework Portability
Another benefit of using the .NET Compact Framework is that its executable files are portable. This is very different from Win32 and MFC, both of which build computer-specific executable files. Both APIs are quite portable, but only at the source code level. After you build a binary executable for Win32 or MFC, that file contains the machine instructions for an x86, Advanced RISC Machine (ARM), MIPS, SH3, SH4, or any other target CPU.
By contrast, the .NET Compact Framework executable file contains a truly portable machine instruction set. The executable file format is the PE file format that is the standard for all Win32-based systems, including all versions of Windows. Rather than containing x86 machine instructions or ARM instructions, these executable files are written by using the Microsoft Intermediate Language (MSIL). When it was submitted to the European Computer Manufacturers Association (ECMA) standards group, it was called Common Instruction Language (CIL). A single executable file written with MSIL/CIL and targeted for the .NET Compact Framework will run on any Windows CE .NET-based system that has the runtime installed.
.NET Compact Framework Weaknesses
Like MFC, the .NET Compact Framework requires a runtime library, actually a set of runtime libraries, to run. The footprint for those libraries is expected to occupy less than 2 MB of storage space, which might be too expensive for some devices with a very low memory budget.
Another potential problem with the .NET Compact Framework has to do with performance concerns. In particular, the conversion from MSIL/CIL to native instructions will take some time. The delay might be a problem for applications with real time requirements. In most cases, however, time-critical work can be delegated to a thread running Win32 code that has been set aside in its own DLL. Real time work, after all, is mostly the domain of device drivers and other low-level code. The .NET Compact Framework is especially useful in the area of building the user interface and graphical drawing code of an application and in connecting to databases and Web services. If you are building an application that has time-critical requirements, you can have the best of both worlds by building a user interface with the .NET Compact Framework and putting a Win32 native DLL to handle the real time threads.
During the development of .NET, the Component Object Model (COM) started to become a legacy technology. COM is still widely supported both on the desktop and in some cases in Windows CE .NET. However, .NET, with its support for managed code, increased security and portable MSIL/CIL instruction set, has displaced COM as the core direction for Microsoft's technology. Ten years ago, COM and Win32 were the centerpieces of Microsoft software technology. With the coming of .NET, COM is no longer core technology. The desktop .NET Framework still supports COM through a basket of services known as Interoperating with Unmanaged Code. In an effort to save space on devices with sparse memory, .NET Compact Framework does not support direct interoperability with COM components. There is still interoperability with Win32 libraries, though, and COM components can be accessed through a C-callable Win32 DLL.
The .NET Compact Framework is heavily GUI-oriented, and as such relies on the GWES (Graphical, Windowing, & Event Subsystem) for its operation. What this means is that .NET Compact Framework requires an IABASE (display-screen based) platform to run on; it is not supported for the HLBASE (headless) platforms. For Platform Builder developers, this means it is not supported for the "Media Appliance", a "Residential Gateway" or the "Tiny Kernel" configuration. The biggest impact this will have is that headless devices cannot use the .NET Compact Framework to create Web service clients; they must instead use the SOAP 2.0 toolkit.
In conclusion, to make the decision about which API to use, consider the following comparisons between the different types of APIs.
The Win32 API is the smallest and most portable API of them all. If you use Platform Builder to create a headless platform built on HLBASE, then Win32 makes the most sense for you. If you plan to extend the operating system through a device driver, user interface extension, Software Input Panel (SIP), or Control Panel applet, you should use Win32. If you are building a system that has a real time requirement, for example, a piece of hardware that requires a consistent response time in the tens of milliseconds, you should use Win32. Most importantly, even if you select one of the other APIs, you can still use Win32 and a programmer adept with Win32, because there will always be cases when an MFC or .NET Compact Framework application has to drill down into the underlying Win32 layer to access some low-level feature.
Using MFC is a good way to build a data editing and/or data viewing application that takes advantage of the built-in document/view architecture sometimes called the model / view pattern. It is also good if you want a set of classes that tame the underlying Win32 API in an object-oriented way. If you make extensive use of existing Microsoft ActiveX® and COM components, MFC is the only object-oriented framework that has built-in integration with the COM component architecture. The presence of the MFC source code means that when you encounter debugging problems, you can trace into the actual Win32 code underlying all of MFC—an invaluable aid that will help you learn Win32 while working with MFC.
There also is the .NET Compact Framework, which provides an even better object-oriented framework than MFC with more container classes and a bulletproof garbage collector. .NET Compact Framework builds executable files that are more portable than either Win32 or MFC executable files, owing to the support for the MSIL/CIL portable instruction set. As a full player in the world of .NET, the .NET Compact Framework provides great support for .NET Web Services, support for manipulating XML and great support for accessing databases with ADO.NET. It also provides a choice of two programming languages, C# and Visual Basic .NET, which can build executable files that can seamlessly integrate with each other.