span.sup { vertical-align:text-top; }
CLR Inside Out
Program Silverlight with the CoreCLR
Andrew Pardoe
Contents
Inside the CoreCLR Engine
The CoreCLR Security Model
The Base Class Library
Working Cross Platform
Silverlight™ 2 includes a number of changes in the Windows® Presentation Foundation (WPF) UI framework: new controls, rich networking APIs, and Digital Rights Management (DRM) support. One major change in Silverlight 2 is the ability to use Microsoft® .NET-compliant languages to program the Web client. Here I will focus on the development core of Silverlight: CoreCLR.
The past dozen or so years have given us many different Web programming technologies ranging from CSS to variants of ECMAScript. Most of them are specific to the task of Web programming—skills learned while programming CSS aren't applicable in other domains. In contrast, Silverlight 2 allows you to take the same .NET Framework skills that you use for desktop programming, such as the Base Class Libraries, XAML, and C#, and apply those skills directly to Web client applications. Moreover, we didn't have to create a separate CoreCLR development environment: you can simply use Visual Studio® to design, develop, debug and profile C# or Visual Basic® the same way you would a desktop app. We created Silverlight 2 CoreCLR precisely to make Web programming as rich as desktop programming.
While it's good for developers to have a rich programming environment, users don't want to download big browser plug-ins. In order for Silverlight to be successful with users, we had to make the installation fast. We were able to get the Beta 1 install down to 4.3MB—about 6 to 10 seconds to install over a broadband connection. This is an amazing accomplishment when you consider that the two major core pieces of the .NET Framework 2.0 CLR—mscorwks.dll and mscorlib.dll—are each about the same size as the Silverlight 2 coreclr.dll and mscorlib.dll together.
Inside the CoreCLR Engine
The design of CoreCLR began right after version 2.0 of the CLR shipped in October 2005. The two main design goals were size and compatibility: from a programmer's point of view, coding against the CLR should always be the same while from a user's point of view, the download needed to be very small. Because Silverlight is intended to target a different set of scenarios from the desktop CLR, we were able to make some changes that simplified CoreCLR and allowed us to reduce the size of the Silverlight installation. But consistency at the bottom of the stack is of key importance. Behavioral differences—even if they are correct—manifest themselves as bugs higher in the stack.
In order to ensure compatibility we used the same code for components at the bottom of the stack. The execution engine and virtual machine are the same. This includes the type system and metadata, the garbage collector (GC), the JIT compiler, and the thread pool, as well as other core parts of the runtime engine.
However, some changes were made to fit the Web application scenario. For example, because rich Internet applications are normally simple and short-running, the JIT compiler focuses on decreasing start-up time instead of performing more complex optimizations. Likewise, the server garbage-collection mode, which is tuned for multiple worker threads that use similar allocation patterns, doesn't make sense for Web-hosted applications. So Silverlight only includes the standard workstation GC, which is tuned for interactive applications. But the Microsoft intermediate language (MSIL) and metadata used in Silverlight applications is the exact same as used in managed applications for the desktop, and the behavior of your application will be consistent from the user's desktop to the browser.
The fact that Silverlight is not intended to replace the desktop CLR caused the biggest change in the core engine: CoreCLR will run side-by-side in process with the desktop CLR. In the past, we've never been able to run two versions of the CLR from within the same process. There are a few reasons this is a difficult problem. One is managing process-wide state: each instance of the CLR assumes that it's the only one in the process and thus it is the only one touching its static data. If a variable staticFoo exists in versions 1.1 and 2.0 of the CLR, and both versions of the CLR are loaded in the same process at the same time, neither version can write to the variable staticFoo without affecting the other CLR's state.
While process-wide state is the most obvious issue, other problems can result from running two CLRs side-by-side in a single process. For example, if you have two GCs running at once, how do you keep one GC from suspending the other GC's thread? Additionally, there is a problem with footprint: when you load multiple CLRs into a process they each have to load code that may be common, and they each have their own space for static variables and managed heaps.
There are some key scenarios that require the ability to host CoreCLR side-by-side with the desktop runtime. If CoreCLR and the desktop CLR couldn't run next to each other, it would be impossible to write a desktop Windows Forms or WPF app that hosts a Web Browser Control, which could navigate to a Web page that uses Silverlight. To get around this potential problem, we could have just had Silverlight depend on the CLR installed on your Windows machine: every install of Windows XP SP2 and Windows Vista® has a reasonably recent CLR installed with the OS. But having all Silverlight code run on CoreCLR guarantees absolute compatibility no matter which CLR you have installed on your machine (or in the case of Mac OS X, even if you don't have any CLR on your machine!) So we did the work to make CoreCLR run side-by-side in process with the desktop CLR, and we think that users' Silverlight experience will be much better for our efforts.
The CoreCLR Security Model
Another big change in the core engine has to do with the new security model. Note that .NET developers have traditionally used Code Access Security (CAS) to prevent untrusted code from performing privileged operations. CAS is very capable, but rather complicated. It allows the user or administrator to define various sandboxes for code using permission sets and then map individual assemblies to those sandboxes. For Silverlight applications, we just need one sandbox that's equivalent to the sandbox that Internet Explorer® uses for running script in a Web page. This simplified scenario allowed us to remove all of the CAS policy.
We also simplified the model of security enforcement. The new model is based on security transparency, a concept introduced in the version 2.0 of the CLR. At the core of the transparency model is a categorization of your code into one of three buckets: Transparent, SafeCritical, or Critical code. Transparent, the lowest trust level for code, cannot elevate privilege or access sensitive resources or information on the computer. In Silverlight 2, all application code is Transparent. Critical code, the most trusted level of code, can interact with the system through P/Invokes or even contain unverifiable code. For Silverlight 2, all Critical code must be part of the Silverlight platform. SafeCritical code, then, acts as the bridge that allows Transparent code to access system resources by calling Critical code. Think of Critical code as the kernel APIs of Windows, Transparent code as user-application code, and SafeCritical code as the API between user code and kernel code.
Transparent code can only call other Transparent or SafeCritical code. SafeCritical code can then call Critical code on behalf of the user code. It's the responsibility of SafeCritical code to canonicalize, or put into a standard format, the inputs and sanitize the outputs of the Critical code to protect the security of the system (see Figure 1).
Figure 1 Security Enforcement in the CoreCLR
The case for canonicalizing inputs to Critical code is a bit more self-evident than the case for sanitizing outputs. For example, if my Web application wants to write to a file on the local disk, it can do so using Isolated Storage. However, you don't want my application asking to write to a file named "..\..\..\..\bootmgr," so it's important to make sure the input is in a regular, canonical format. It's a little more unusual to think that the outputs of the Critical code are a security risk. The key security concept is that controlling disclosure of information is of great importance in reducing the overall surface area for an attacker. Say I try to access some bit of user information on your system and get the response "permission denied." When I repeat the same access operation, but for a different user, I get the response "user Bob does not exist." If I know that I get both responses I can repeatedly attempt invalid accesses to garner a list of user names on the system.
A simplified security policy is a clear win for developers working in .NET code, but it also helps developers working on .NET code. We've tried to mark as little code Critical and SafeCritical as necessary. Having most of our code be Transparent helps us to decrease the amount of code that needs in-depth security reviews. We still have to review our Transparent code for correctness and security, but at least we know it can't perform any privileged operations. Large pieces of Silverlight, including the Dynamic Language Runtime (DLR), are written entirely in Transparent code. Limiting the privileged parts of Silverlight allows us to ship a more secure product by focusing our attention on the areas that really need careful review.
The Base Class Library
The .NET Framework has evolved on the desktop to address both user and server scenarios. Therefore, there's a lot of functionality in the Base Class Library (BCL) that doesn't make sense in Web client scenarios. For example, because Silverlight doesn't support CAS, much of System.Security is not necessary. Many other classes, like System.Console, don't make sense on the Web. (Why, then, do we include a stripped-down System.Console class? It helps us test the product.)
We had the same goal with the libraries that we had with the core engine: to pare down to the smallest set of functionality that would enable .NET developers to be successful without having to learn an entirely new technology. We garnered some inspiration and guidance from the .NET Compact Framework, which had dealt with the same problem applied to a different scenario. While trimming the BCL for Silverlight we also maintained compatibility between the .NET Compact Framework and Silverlight. Sharing a single library between all the platforms in this way maximizes the reusability of .NET skills.
There are a number of places in the BCL where you can find duplicated functionality. Sometimes the functionality is duplicated within the BCL itself—such is the case with generic collections and non-generic collections. Sometimes the functionality already exists in the base OS, as in the case of globalization support. Not only is it unnecessary to support every alternative in the Silverlight BCL, but we also can provide wins in performance and consistency by eliding this duplicated behavior.
Since we introduced support for generic collections in version 2.0 of the .Net Framework, we've been advocating that people move to generics. In version 1.x of the runtime, general-purpose data structures had to be based on objects so that the same core data structure class could be used to create collections of different types. With generic-type parameters, the compiler is able to extend these general-purpose data structures to provide type safety, making code easier to write and maintain. Additionally, generic collections generally perform better on value types than non-generic collections because there's no need to box items. Overall, generics provide all the functionality that non-generic collections provide. And because the duplication is unnecessary, we don't include non-generic collections like ArrayList in the Silverlight BCL.
Everyone is familiar with at least some of the problems of globalization: many European cultures use a comma as a decimal point; in Chinese numbers are grouped into fours (1000,0000), and so forth. The .NET Framework implements globalization functionality internally so it can work properly in multiple cultures. To do this, it includes globalization data for all supported cultures, allowing .NET–targeted applications to behave consistently across all supported versions of Windows. There are drawbacks, however. The CLR must include large data tables, and the data often becomes stale over time. In addition, the data is Windows-centric, so the data for some .NET cultures is different for the same cultures in Mac OS X. For these reasons, the CoreCLR does not include its own globalization data. Instead, System.Globalization.CultureInfo uses the globalization functionality provided by the host OS. Thus, Silverlight applications will behave more like Mac applications on Mac OS X and like Windows applications on Windows.
Overall we've tried to maintain a similar API surface area among the CLR, the .NET Compact Framework, and Silverlight, but there are other small differences scattered throughout the BCL. For example, because Silverlight has a single UI thread, it has a single Dispatcher object that holds a queue of work items for the UI. Using the Dispatcher will allow you to update the UI from a non-UI thread. This code will allow you to update a UI element—MyListBox—with a collection created on another thread (say, from a background thread):
MyListBox.Dispatcher.BeginInvoke(() => MyListBox.ItemsSource = MyItems);
We recommend using System.ComponentModel.BackgroundWorker in Silverlight because it encapsulates the updating of the UI on completion, but for compatibility we still include low-level threading APIs such as System.Threading.ThreadPool.QueueUserWorkItem and System.Threading.Monitor.Enter.
Like the Security Transparency model, some functionality that is new in the Silverlight BCL actually appeared in previous .NET Framework releases. A good example of this is isolated storage, which provides a virtualized file system to sandboxed applications. It's been around since the .NET Framework 1.0, but it has always addressed limited scenarios. Silverlight focuses on sandboxed applications and thus can make full use of isolated storage:
using (IsolatedStorageFile isoStore =
IsolatedStorageFile.GetUserStoreForApplication())
{
using (StreamWriter writer = new StreamWriter(isoStore))
{
writer.Write("This is an isolated storage file.");
}
}
Like cookies in a Web browser, isolated storage allows Silverlight applications to maintain state across invocations. However, isolated storage offers a full virtualized file system that supports creation of directories and files. Although isolated storage is not intended for storage of high-value data such as passwords, the storage location is obfuscated and access is limited to the application that owns the store.
Quotas for isolated storage are defined by application groups, which are based on the domain name of the Silverlight application. For example, two Microsoft applications, located in directories under microsoft.com, would share an application group, meaning that both applications share the same quota. By default, an application group is given 1MB of storage.
However, if an application needs additional storage space it can request a larger quota by prompting the user with a dialog box that would specify, for example, that microsoft.com wants to increase its quota to 8MB. Users can enable or disable isolated storage as well as delete current uses of it in the Silverlight configuration dialog (it is called Application Storage in the dialog). Application groups also can have shared stores, which enables related applications to share data among them.
While isolated storage has been around for some time, its uses have never been as compelling as they are with Silverlight. A configurable, secure file system for interactive Web applications allows development of traditional office applications, such as word processors, or applications that maintain large quantities of data, such as stock-tracking systems.
Working Cross Platform
Silverlight will run on non-Windows platforms. We have a partnership with Novell to support Linux through the Mono project's Moonlight runtime. Microsoft also is working on a version of Silverlight for the industry-leading Symbian OS and Windows Mobile®. Moonlight runs on Mono, and the mobile version of Silverlight will run on the .NET Compact Framework (which has a much lower memory footprint than CoreCLR). But the Mac OS X version of Silverlight runs on the exact same CoreCLR as on Windows.
We accomplished this with the help of a Platform Adaptation Layer (PAL). The PAL is an API written to work against different platforms. It provides abstractions for error handling, file handling, networking services, threading semantics, and so forth. Functions in the PAL share the names of Win32® APIs but differ in implementation. Some of the APIs just pass through the PAL function's parameters to an OS X function, whereas others need to use custom logic to match up OS X functionality with Windows API signatures. Some Windows functionality used by CoreCLR doesn't exist on the Mac and thus had to be implemented entirely in the PAL (see Figure 2).
Figure 2 Platform Adaptation Layer
A lot of the Silverlight PAL benefits from lessons learned when we developed the Shared Source Common Language Infrastructure (SSCLI), also known as Rotor. The SSCLI ran on a number of UNIX-style platforms as well as Windows. The base OS functionality varies widely on UNIX-style platforms. The SSCLI PAL had to work on both microkernels (such as the Mach kernel in Mac OS X) and monolithic kernels and had to cope with different OS services such as threading, exception handling, and networking stacks. Because Silverlight only targets Windows and Intel Mac machines we were able to write Mac-specific implementations for many of the functions in the PAL, which helps with the size and performance of the PAL.
The PAL only supports the subset of Win32 necessary to enable Silverlight to run. There is no need to support the registry, GDI+, or COM. We didn't implement Windows on top of the OS X, nor did we implement enough of Windows to support the full capabilities of the desktop CLR. Limiting the PAL to support only Silverlight allows it to be small and fast.
Hiding the differences between operating systems is a tricky problem when you consider just how different OS X is from Windows. Much of OS X is written in Objective C and exposes an exception handling system which is incompatible with C++. The CLR creates I/O threads, which are separate from worker threads. These are based on I/O Completion Ports that were introduced in Windows NT® 3.5 and don't exist on OS X. Even something as simple as locating a file is different on Mac because of the backslash directory delimiter in Windows.
Throughout the design and development of the CoreCLR we've focused on providing an environment that allows developers to reuse existing skills and tools to develop rich content for a small, secure runtime. Most of our decisions were driven by the reduced scenarios of rich Internet applications, but some designs also benefited from looking at some of the work we have done in the past. Some of the decisions we made with CoreCLR will eventually make their way back into the desktop. For example, you can look forward to the next version of the desktop CLR running side-by-side in process with other versions of the CLR. Also, most of the changes made for the improved Security Transparency model will appear in our next CLR.
We carefully considered what made sense for Web-based scenarios and what didn't need to be in the runtime. We hope we've made the right choices and trust that you'll tell us how we can continue to improve. Have fun coding against Silverlight 2, and keep your eye on this space for deeper dives into CoreCLR in the future.
Send your questions and comments to clrinout@microsoft.com.
Andrew Pardoe is a program manager for CLR at Microsoft. He works on all aspects of the execution engine for both Silverlight and the desktop runtime. He can be reached at Andrew.Pardoe@microsoft.com.