The .NET Compact Framework does not exist in a vacuum . It rests upon three layers of technology: the Common Language Runtime, the Just-In-Time compiler, and the Windows CE Operating System. Figure 2.1 is a block diagram that describes how an application that targets the .NET Compact Framework interacts with the .NET Compact Framework and the layers below. Figure 2.1. This block diagram depicts the .NET Compact Framework architecture.
Figure 2.1 gives us a launching pad for discussing the architecture of the .NET Compact Framework. The discussion starts at the deepest level and works upward. The first layer is the hardware itself, which is composed minimally of the CPU and main memory. Typical devices include a video adapter with touch screen, network connectivity, sound hardware, and so on. All of the hardware is controlled by the second layer, the Windows CE operating system. Windows CE provides memory management routines and a program loader. The program loader pushes an executable into memory and launches it. It also manages threads, exposes methods for drawing windows and reacting to GUI events, handles network connectivity, and manages a host of other responsibilities. The Common Language RuntimeThe next layer above the Windows CE operating system is the Common Language Runtime, or CLR. Applications written in traditional languages, such as C or C++, target Windows CE as a platform. They are compiled into code understandable by the CPU on the device, but they rely on Windows CE to load them and provide services, such as drawing windows and reacting to user input. The CLR is the platform that "managed" applications target. Standard "native" applications are compiled to machine code directly understandable by the device's CPU. In contrast, managed applications are compiled into Microsoft Intermediate Language bytecodes, or IL code, an intermediate format that loosely resembles machine code for a CPU architecture that does not physically exist. Code that is compiled to IL code is referred to as managed code . Similarly, a managed executable is an entire EXE that is composed of IL code, and a managed library is a DLL file composed of IL code. A very important fact about managed code is that it cannot be directly executed by an existing CPU. It must be translated into native code first. The CLR's job is to execute managed code. In order to do so, the managed code must be rendered into a native form understandable by the CPU. The CLR follows these three basic steps to transform managed code into native code and execute it:
Transforming Managed Code to Native Code with the Just-In-Time CompilerThe JIT, the Just-In-Time compiler, is responsible for translating managed code into native code so that a managed program can execute. There are two JITs available with the .NET Compact Framework, the "SJIT" and the "IJIT."
By default, the IJIT compiler is used only on platforms for which the SJIT compiler is unavailable. This makes sense because if your device is not under memory pressure, then the time spent JITing code with either compiler seems insignificant compared to the time executing code. Thus, you want the SJIT compiler whenever available. An important part of writing well-performing applications is avoiding excess JIT activity. Both JIT engines compile code only as it as needed on a method-by-method basis. That is, if a class contains 100 methods but only 10 of them are ever executed by an application, then only 10 of them are ever JITed by the CLR. Once a method is JITed, the CLR tries to retain the native code in memory for the life of the application. Thus, the cost of JITing a method is paid only once, regardless of how many times the method is executed. The CLR retains all JITed code in memory for as long as possible. It will kick out JITed code on a method-by-method basis if the device encounters memory pressure. This is called code pitching . If the JITed code for a method is pitched and then the method is called again, then the method's IL code must be JITed all over again. The CLR pitches code for methods based on how recently the methods were executed; the code that was least recently executed is pitched first.
In extreme situations, applications can end up in a scenario where a method is re-JITed each time it is called in a loop. For very complex applications running under memory pressure, this is a plausible scenario that will horribly impact performance. This problem is analogous to running too many applications on a computer with insufficient memory. Pages of memory are swapped to disk, and if the memory pressure is severe enough, then the computer spends most of its time swapping pages instead of executing program code. There are some simple steps you can take to avoid this situation. If you are writing a custom application to deploy to your company's workforce, equip the devices that will run the application with enough memory. Also, note that the default behavior when closing an application on the Pocket PC is to retain it in memory. In this state, applications still consume memory. To see what applications you have running on a Pocket PC, follow these steps:
There are other tricks and tools in addition to those just listed that can help developers determine whether code pitching is occuring and where in the code it happens. For example, performance counters are very effective tools for determining what causes an application to perform poorly. Chapter 15, "Measuring the Performance of a .NET Compact Framework Application," describes such tools in detail. Comparing the .NET Compact Framework CLR to the Desktop CLRThe architecture of the CLR for the .NET Compact Framework is markedly different from that of the desktop .NET Framework. For example, the CLR for the .NET Compact Framework is built upon a Platform Abstraction Layer (PAL) that abstracts away differences in hardware from the rest of the CLR. To port the CLR to a new platform, one needs only to change the PAL on which the CLR rests and create a JIT for the target CPU, if it is different from all existing supported CPUs. This approach gives the .NET Compact Framework flexibility and nimbleness in keeping up with evolving mobile hardware. However, there are no publicly available white papers or utilities to allow third parties to port the CLR to a new hardware platform. Another difference between the CLR for the .NET Compact Framework and the desktop is how JITed code is handled. On the desktop, JITed code is retained even after a managed program exits, speeding up its load the next time. It is only re-JITed if the managed application's IL changes. The CLR for the .NET Compact Framework stores JITed code only for the lifetime of the application. The next time the application is launched, JITing must occur again. The CLR that supports the .NET Compact Framework is similar to the one supporting the desktop .NET Framework in that it supports the notion of assemblies and application domains. An assembly is the atomic unit by which an application is identified. For example, a managed EXE file or a managed DLL file is each an assembly. Each assembly contains metadata inside the binary that describes its structure, and it can contain a signature to allow the CLR to detect if the assembly has been tampered with. The desktop CLR supports the notion of an assembly that is composed of more than one file. The CLR for the .NET Compact Framework does not support this feature. This can have ramifications for developers who are trying to port managed code from a desktop application to a device. For example, if an assembly for a desktop component is composed of three individual DLLs, then the code must be merged into a single DLL when it is ported to the .NET Compact Framework. Another concept related to a CLR is that of an application domain , which is a reasonably secure container in which each managed program runs. In a modern operating system with protected memory, each stand-alone process is completely insulated from all others. The language used to write the program running in the process may allow developers to use pointers to wreak havoc on the process memory space. However, because the process lives in a virtual address space, it cannot touch the virtual address space of other applications. Similarly, the CLR enforces insulation between the application domains running within it. This level of insulation is cheaper than having separate processes, because the CLR actually operates as one process. Thus, application domains do live in the same virtual address space. This means it is technically possible for code in one application domain to write into memory being used by another application domain. The CLR can prevent this behavior in virtually all cases because, with few exceptions, IL code can be guaranteed type safe. There is no possibility of pointer use going awry and accessing unexpected locations if there is no notion of a pointer in the language and if type references are guarded carefully by the CLR. The C# language allows the direct manipulation of pointers if you embed the pointer activity in an unsafe block. One of the reasons the name "unsafe" was chosen as the keyword to delineate such a block is because it makes it possible for the C# code to escape the CLR type checking mechanisms and access unexpected memory locations. There are times when using unsafe code is completely necessary. For example, unsafe code can help when calling into native code with custom marshalling routines (see Chapter 12). Unsafe managed code should be avoided and used only if absolutely necessary. The .NET Compact Framework Class LibrariesWe now have enough of an understanding of the underlying technologies involved with the .NET Compact Framework to very specifically define the .NET Compact Framework itself. The .NET Compact Framework is a set of DLL files containing classes capable of performing all of the useful things that the rest of this book is devoted to. The DLL files are written in mostly managed code, although a handful of them interact through native code directly with the Windows CE operating system. Out of the box, developers can use Smart Device Extensions in Visual Studio 7.1 to target the .NET Compact Framework by using the C# and Visual BASIC.NET programming languages. Third-party languages that can also target the .NET Compact Framework may become available in the future. The specific DLLs comprising the .NET Compact Framework and the roles they play are now outlined:
|