.NET Fusion
Home About Workshops Articles Writing Talks Books Contact

11. Fusion and the Compact Framework

The Compact Framework is the name of the version of the .NET framework that is available on what Microsoft calls 'smart devices' or 'mobile devices'. This includes PocketPC PDAs and mobile phones. These devices have two important criteria in the context of this workshop: firstly they do not use hard discs and so they use flash memory for storage, and secondly, they tend to have limited amounts of memory (both for run time usage and for storage). For these reasons, these devices run a cut down version of Windows that has been ported to one of several non-Intel platforms and is stored in flash ROM.

At this point, the talk of developing for multiple platforms may make you think 'write once, run anywhere', but before you get carried away remember that 1) that is a catchphrase of Java (and is inaccurate anyway, it should be 'write once, test and rewrite everywhere') and 2) just because a piece of code runs on the desktop version of the framework it does not mean that it will run under the Compact Framework. The reason why I say this, is because the Compact Framework has a limited number of classes and the classes it has often do not have the full range of methods available on the desktop. The Compact Framework classes have a subset of the methods available on the desktop and usually they do not have the full range of overloaded methods. Further, since the version of the Windows API is limited (for example there is no concept of security or cryptography) then the number of classes in the CF are also limited.

Fusion is very different in the CF than it is on the desktop. The .NET Compact Framework (in version 1.0) does not use configuration files, so there is no concept of binding redirects in Fusion. This lack of config files also affects probing - the process of locating assemblies - and probing is further hampered by the fact that Windows CE does not have a concept of 'current directory'. Finally, the GAC itself is implemented in a totally different way to the desktop version and this means that there is a different way to add assemblies to the GAC. You'll learn about all these issues later on this page.

11.1 CF Development

Before you start this part of the workshop there is an important issue to consider. Throughout the workshop I have given examples using applications compiled for the console. The reason why I do this is because I want to show you that there is no magic involved. I think that wizards and RAD hide you away from technology and make it more difficult for you to fully understand the technology. Using the console means that I can show you all of the command line parameters and give you a clear understanding how each tool works. I also want you to be able to develop applications using the .NET Framework SDK which is a free download, after all, you should only need to pay Microsoft money when you are actually earning some.

However, there are some other issues. The first issue is that 'smart devices' do not have a command line and so this means that the example must be compiled as GUI application. The issue here is that a typically there will be more GUI code than the actual code that I am trying to demonstrate. There really isn't any way around this, and in the text below I have tried to keep the UI as simple as possible. Another issue is the limited memory and storage on a smart device. This means that it is not practical to have a development platform  on the devices, and so instead the development is carried out on the desktop and the application is then deployed to the device.

Thus any development of Compact Framework applications means that you have to have the deployment and debugging tools that will access the device. This means that there is a compelling argument to use Visual Studio because it has all of these tools and makes their use seamless. Visual Studio has a project type for PocketPC and Smart Devices and this code will compile against the Compact Framework. Visual Studio also allows you to deploy to either a device attached with ActiveSync or to an emulator. Deployment to an attached device uses RAPI, the remote API. Microsoft provides other tools as part of their Power Toys for mobile devices that also use RAPI, as I'll explain in a moment.

However, it is possible to create applications without using Visual Studio. The C# compiler that you use for the desktop is also used for Compact Framework applications, the only difference is the assemblies that the compiler will reference when it creates an assembly. If you have Visual Studio installed you will find that the Compact Framework SDK is installed under the Visual Studio folder. This SDK contains the metadata assemblies for the framework, so compiling a Compact Framework application merely involves using /nostdlib, so you don't get the desktop libraries and then use a response file or the /r switch to point to the CF assemblies that you use. The details are given here.

Microsoft does provide the Compact Framework redistributable for free, so with some tweaking it is possible to use these to install the Compact Framework on the desktop. Details of what you need to do are given here, for an older version of the SDK.

Once you have created an application you need to distribute it. Microsoft provides ActiveSync as a free download. This software provides the tools to allow you to connect to a device in its docking station. It also provides the Remote API (RAPI) which you can use to write applications to access your device. Microsoft provides the Windows Mobile Developer Power Toys which contains tools that use RAPI. I use the following tools on this page:

Tool Description
ActiveSync Remote Display This Power Toy will display the screen of the docked device on the desktop machine. This is great for taking screen shots and makes it easier to see what is being displayed on your device.
CeCopy Uses RAPI to copy files from the desktop to the device
RAPIStart Runs code on the device. This allows you to start a program from your desktop, and it allows you to pass command line parameters to that device.

If you don't have a connected device then you can explore the Compact Framework GAC using the emulator device. This is available as part of the PocketPC SDK. To use the Compact Framework on the emulator you must first install the framework, and the details are given here. Note that if you use Visual Studio you are given the option of deploying to the device or the emulator and the first time that you deploy a CF application (either to the emulator or to a device) Visual Studio will install the Compact Framework runtime. 

All of the tools I have mentioned provide you with a way to develop Compact Framework applications for free. While I applaud the ability to learn about the Compact Framework without having to pay Microsoft any money, there are too many downloads, and the fact that there is no official support means that I cannot recommend that you use them for my workshop so I will wimp out and provide a Visual Studio project. (My point of view - for those interested - is that if you are developing a product for sale then you should expect to pay for the tools that you use. If you are learning how to develop a product then clearly you have no revenue.)

On this page I will use just one example. To create this, use Visual Studio New Project wizard to create a new Smart Device Application:

This will start the project wizard that gives you the option of the target device and the type of project to create. For these examples I used Pocket PC for the device and Windows Application for the project. One of the reasons that I have not used Visual Studio for the rest of the examples in this workshop is because the project wizards bloat the code with unnecessary code and comments, another reason is the dumb names it uses. To tidy up the code follow these steps:

  1. Use the solution explorer to rename the main code file (Form1.cs) to MainForm.cs
  2. View the code for MainForm.cs and remove all comments. Then rename the class to MainForm, change the name of the constructor and change Main so that it creates an instance of this class.
  3. Remove System.Data and System.Xml assemblies from the References folder and any using statements for the associated namespaces. We won't use these assemblies, so there's no point in referencing them.
  4. Remove AssemblyInfo.cs from the project, I want to keep the project compact and use just one source file.

As with the other examples, this example will use a library. So use the project wizard to add a new Smart Device Application called lib and select Pocket PC for the target and Class Library for the project type. Again, clean up the project:

  1. Rename the class to LibraryCode and rename the file to lib.cs.
  2. Remove the constructor and comments.
  3. Remove System.Data and System.Xml assemblies from the References folder. Also remove the using statement for System.Data.
  4. Remove AssemblyInfo.cs from the project and add a using statement for System.Reflection to lib.cs. The assembly attributes will be placed in this file.
  5. Finally, add a reference to the lib project to the References folder of the application project.

The start up files for this project can be found here.

11.2 Private Assemblies

In this example you will investigate the locations of assemblies. To do this add a method called GetVersion:

public string GetVersion()
{
   Assembly a = Assembly.GetExecutingAssembly();
   Module m = a.GetModules()[0];
   return String.Format("version: {0}\r\nfile: {1}",
      a.GetName().Version.ToString(),
      m.FullyQualifiedName);
}

Note that the desktop has a property called Assembly.Codebase that returns the address of the assembly, however, this property is not available in the Compact Framework. The workaround is to get access to the first module in the assembly (which will be the assembly file that contains the manifest) and access the FullQualifiedName property.

Next add a button and a text box to the form of the application. Change the properties of the text box so that it is multiline and has a vertical scrollbar. Double click on the button so that the designer adds a click handler. These changes are shown here (note that I have also changed the button's name too):

private void InitializeComponent()
{
   this.mainMenu = new System.Windows.Forms.MainMenu();
   this.btnLoad = new System.Windows.Forms.Button();
   this.txtResults = new System.Windows.Forms.TextBox();

   this.btnLoad.Location = new System.Drawing.Point(80, 8);
   this.btnLoad.Size = new System.Drawing.Size(80, 20);
   this.btnLoad.Text = "Load Library";
   this.btnLoad.Click += new System.EventHandler(this.btnLoad_Click);

   this.txtResults.Location = new System.Drawing.Point(8, 40);
   this.txtResults.Multiline = true;
   this.txtResults.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
   this.txtResults.Size = new System.Drawing.Size(224, 224);
   this.txtResults.Text = "";
 

   this.Controls.Add(this.txtResults);
   this.Controls.Add(this.btnLoad);
   this.Menu = this.mainMenu;
   this.Text = "Fusion Test";
}
private void btnLoad_Click(object sender, System.EventArgs e)
{
}

Add the following code to the click handler:

try
{
   lib.LibraryCode lib = new lib.LibraryCode();
   this.txtResults.Text += String.Concat(lib.GetVersion(), "\r\n");
}
catch(Exception ex)
{
   this.txtResults.Text += String.Concat("Exception:\r\n", ex.ToString(), "\r\n");
}

.NET will delay load library assemblies. So it is only when the button is clicked that .NET will attempt to find the library assembly. So the first time you click on this button .NET will attempt to locate the assembly and if the assembly cannot be found an exception will be thrown. As you can see, this code will either give the assembly version, or the exception text, to the textbox.

Build the example, then right click on the solution in the Solution Explorer and select deploy. Next, you'll be asked where you want to deploy the application to (a connected device, of the emulator), so select whichever is appropriate and then click on the Deploy button. Next, move to the device (or use asrdisp.exe from the desktop) and use the Pocket PC File Explorer from the Programs folder to navigate to \Program Files\Fusion (where Fusion is the name of the application project) to locate the application and start it. On my machine I get the following:

The library does not have a strong name and so versioning is not used. Notice that the library is loaded from the same folder as the application.

11.3 Locating Libraries

Next, we will change the library so that it has a version. Before we do that we'll learn another difference between the desktop and CE edition of Windows.

Click on the close button in the top right hand corner. Now view the applications running on your machine. To do this open the Settings folder, then select the System tab, select the Memory applet and then the Running Programs tab. You'll find that the example program is still running:

This might confuse you because you appear to have already closed the application. In fact, the close button on mobile applications does not close the application, it merely hides the application. To close the application you have to use the Memory application . To do this, select Fusion Test and click on Stop. You must stop the application like this because if you try to replace the application or a library it uses while the application is running you'll get a sharing violation. However, it gets irritating to have to use the Memory screen to close the application, so to get round this issue add another button to the form, double click on it to add a handler and add the following:

private void btnExit_Click(object sender, System.EventArgs e)
{
   Application.Exit();
}

Now you can shut down the application simply by clicking on the new button.

The desktop version of .NET has various probing rules, so let's see if they apply to the Compact Framework. Switch to ActiveSync and click on the Explore button. Next, navigate to \Program Files\Fusion by first double clicking on My Pocket PC. Use explorer to rename lib.dll to lib.exe. Now run the application again and click on the Load Library button. You'll get a TypeLoadException. The desktop version will probe for an assembly using the short name and either dll or exe for the extension, however, the Compact Framework will only probe for libraries with the extension of dll. Use Windows Explorer to rename the file back to lib.dll.

Notice that on the desktop you can use fuslogvw to get information about why the attempt to load an assembly failed. The Compact Framework does not store information about a binding or probing failure, so this tool is not available,and neither is there a FusionLog property on the FileNotFoundexception.

Now use Windows Explorer to create a folder called lib under the application folder and move the library to that folder. Repeat the previous test. Again, you'll find that a TypeLoadException will be thrown. The desktop version of the framework allows you to put library assemblies in a subfolder that has the same name as the assembly, but the Compact Framework does not allow this. Use Windows Explorer to move the library to the root of the device. The easiest way to do this is to navigate to the lib folder, select lib.dll and then select Cut from the context menu. Now use Explorer's up button until you get to the root (you'll see the Windows and Program Files folders there) and paste the file. Repeat the previous test. You'll see something like this:

This time the application worked, which means that the file was loaded from the root. This is quite concerning because it means that multiple applications can share an assembly without a strong name simply by placing the assembly in the root folder. All the advantages of .NET versioning have disappeared.

Be warned about this. .NET versioning has been designed to prevent the problems of DLL Hell. If you install library assemblies in the root you will return back to the bad old days of DLL Hell. 

Close down the application and move the library from the root to the \Windows folder and repeat the test. Again, you'll get a TypeLoadException, indicating that this folder is not used to share assemblies. The rules for the Compact Framework are that Fusion will look for private assemblies in the application folder or in the root.

Move the library back to the application folder and delete the subfolder that you created earlier.

11.4 Versioning

The next action is to change the library so that it has a version. To do this the library must be given a strong name. Visual Studio isn't too good as far as strong naming goes. I find this a very serious deficiency, and proof that Microsoft cannot use their own development tools to develop their own .NET code. I would have expected a menu item that would generate a key-pair for you and a project setting to indicate that the project uses a key-pair (in a key container or file). However, Visual Studio 2003 does not have such features, so you have to do much of the work by hand. The first action is to obtain a key file and to do this you use the sn utility or simply copy the key file that you used earlier in this workshop. The trick is to copy the key file to a location where the compiler will find it and such a location is under the library's folder. Use Windows Explorer to navigate to the library's project folder then paste the key file into obj\Debug or obj\Release depending on the build configuration you are using. Once you have done this you can add the following to the library file:

using System;
using System.Reflection;

[assembly: AssemblyKeyFile("key.snk")]
[assembly: AssemblyVersion("1.0.0.0")]

Recompile the solution and deploy it to the device, then run the application to confirm that it now reports the correct version.

Use the desktop Explorer to access the device and create a temporary folder under the application folder. Move the library to that location. You are keeping this version of the file for a later test. Now change the version of the library to 2.0.0.0 and rebuild and re-deploy the application. Run the application to confirm that it is loading the new version and then close the application. Now use Explorer to make a copy of the old version of the library that you copied to the temporary folder a few moments ago and use it to replace the new version of the library. Run the application again. You should get a TypeLoadException. The reason is that even though the library is a private assembly versioning is used because the assembly has a strong name. The runtime looks for version 2.0.0.0 but the only version that can be found is version 1.0.0.0, and since the version is different to the required version the exception is thrown.

At this point you should have a copy of version 1.0.0.0 in the temporary folder, so now change the source code so that the library is version 1.0.0.1, that is, only the final (revision) figure has changed. Compile and re-deply the code and repeat the test: run the application to confirm that it picks up version 1.0.0.1, close the application and then replace the new version with the 1.0.0.0 version of the library before running the application again.

This time you'll find that the old version of the library will be loaded. This conflicts with the desktop version of the runtime. On the desktop, if a library has a strong name then applications that use the library will have the version stored in them and Fusion will attempt to load the exact version specified, or not at all. With the Compact Framework the runtime will try to load the version stored in the assembly that uses the library, but it will ignore the revision setting. If there are two libraries with the same major, minor and build values then the runtime will load the library that has the highest revision.

Change the source code back to version 1.0.0.0, build and deploy it and then use the desktop Windows Explorer to delete the temporary version of the library and the temporary folder that you created.

11.5 The GAC

There are two ways to add assemblies to the GAC and I'll demonstrate both here. The first way is to use the cgacutil.exe utility. This takes the name of the library file as the parameter, and since Pocket PCs do not allow you to launch an application through the UI with a parameter, we will use the RapiStart utility to access the Pocket PC device remotely. This Power Toy is run on the desktop in a console and it uses RAPI to launch the process, and any command line parameters to the device.

Open a console and type the following:

rapistart cgacutil /i \Program Files\Fusion\lib.dll

Now run the application and load the library. You will see something like this:

Notice that the library has been picked up from the \Windows folder and that the name of the file is:

GAC_lib_v1_0_0_0_cneutral_1.dll

Clearly the Compact Framework uses a different strategy than the desktop. The desktop version uses nested folders to store different versions of an assembly in the GAC, whereas the CF renames the files using the version and culture. Use Explorer on the desktop to navigate to the \Windows folder. You should see something like this:

As you can see, all of the framework assemblies have been renamed. The general format is: 

GAC_<short name>_<version string>_<culture string>_<ref>.dll

where <version string> is in the format v<major>_<minor>_<build>_<revision>, <culture string> is in the format c<culture> with neutral for a file that does not have a culture and <ref> is used to differentiate between multiple versions of the same file and is typically 1. Note that the public key token of the assembly is not used in the naming scheme, but it is used by cgacutil; if you try to add the same assembly to the GAC twice, you'll find that the request will be ignored the second time. If you add two assemblies which only differ by their public key token, then you'll find they'll be added but with different <ref> values. I think this is confusing. It would not have taken much to extract the public key token and use it in the name of the GAC file so that it has an obviously different name to an assembly with the same short name from a different publisher.

Another solution is to make sure that you give your assemblies a unique name, especially if you intend to put them in the GAC. The best way to do this is to code your company's name into the assembly name, for example, acme.lib.dll.

To remove the file from the GAC you should use cgacutil with the /uf switch and the complete name of the assembly including the public key token. Also note that you must use /uf for the switch and not -uf. You can use sn -Tp on the desktop on the file created by Visual Studio (ie the file in the bin\Debug folder under the library's project folder) to get the public key token.

Stop the application on the device and remove the library from the GAC. For my machine I type the following at the command line:

rapistart cgacutil /uf lib,Version=1.0.0.0,Culture=neutral,PublicKeyToken=3bf941bb1f722efe

Now re-run the application and load the library. You should find that the library will be loaded from the application's folder.

There is another way to install items into the GAC and this diverges completely from the desktop.

On the desktop machine create a file called Fusion.gac (copy con Fusion.gac then the press Ctrl-Z.) Edit this file with notepad (type notepad Fusion.gac) and add the following text and then save the file:

\Program Files\Fusion\lib.dll

What you have done here is created a text file with the short name of the application and the extension gac; then you have added the full path of a library assembly used by the application that you want to add to the GAC.

Now copy this file to the \Windows folder on the device. There are two ways to do this. The first way is to copy the file using the desktop Windows Explorer: select the file you created and copy it, then navigate to the My Device item in Windows Explorer (it is under My Computer), move to the \Windows folder and paste the file. The other way is to use cecopy Power Toy:

cecopy fusion.gac dev:\Windows

The dev: prefix indicates that the folder is on the device.

Use Explorer to check that lib.dll is not in the GAC. If it is, then remove it with cgacutil. Now run the application, but do not click the Load Library button. Use the desktop Explorer to browse \Windows on the device, you should find that lib.dll has been added to the GAC, and you can confirm this by clicking on the Load Library button. What has happened here is that when an application starts the Compact Framework looks for an appropriate .gac file, and if this file exists it reads the file and adds the specified assemblies to the GAC.

Now close down the application. Use the desktop Explorer to locate Fusion.gac in \Windows and delete it. Run the application again and load the library. The documentation for the Compact Framework says that if you delete a .gac file, or if you remove the entry for a library in such a file, then the assembly will be removed from the GAC. On my Pocket PC 2003 machine (CF version 1.0.2268.00) this does not work, and I still have to use cgacutil to remove a file from the GAC. It may work on your machine. Incidentally, if you run cgacutil on the device without a command line it will give a dialog with the current version of the framework.

At this point, close down the application and use rapistart to run cgacutil to remove the library from the GAC, if it has not already been removed.

If you browse \Windows on the device you'll find that there are two other .gac files:

Microsoft .NET Compact Framework 1.0.gac
System.SR.ENU.gac

The first contains most of the framework assemblies like mscorlib and system, the second contains the name of a resource assembly called System.SR.dll. It is interesting that although this second .gac file appears to be localised to the English locale, the actual assembly is not localised (its name is GAC_System.SR_v1_0_5000_0_cneutral_1.dll, that is, the culture is neutral).

11.6 Programmatically Adding and Removing Assemblies From the GAC

If you would like to add assemblies to, or remove them from, the GAC using code you should do so by launching cgacutil from your application. I mention this here because it is not as trivial as it first appears. Yet again the problem is that the Compact Framework is a cut down version of the framework. On the desktop you would launch an application by calling System.Diagnostics.Process, but this class is not available in the Compact Framework. Instead, you have to use platform invoke to call an API to launch the utility.

The following code requires a using statement for System.Runtime.InteropServices:

[StructLayout(LayoutKind.Sequential)]
struct PROCESS_INFORMATION
{
   public uint hProcess;
   public uint hThread;
   public uint dwProcessId;
   public uint dwThreadId;
}

[DllImport("coredll")]
private static extern int CreateProcess(
   string lpszImageName, string lpszCmdLine, IntPtr lpsaProcess,
   IntPtr lpsaThread, int fInheritHandles, uint fdwCreate,
   IntPtr lpvEnvironment, IntPtr lpszCurDir, IntPtr lpsiStartInfo,
   ref PROCESS_INFORMATION lppiProcInfo);

private static void StartProcess(string proc, string cmdLine)
{
   PROCESS_INFORMATION procInfo = new PROCESS_INFORMATION();
   CreateProcess(proc, cmdLine, IntPtr.Zero, IntPtr.Zero,
      0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, ref procInfo);
}
 
The key to this code is the CreateProcess which is part of Windows CE API. You can now call StartProcess passing the name of cgacutil and the appropriate command line to install or remove an assembly from the GAC.

11.7 Strong Name Verification

On the desktop version of the .NET framework strong names are verified only at install time when they are added to the GAC. The strong name of an assembly is not checked when the assembly is loaded for performance reasons, and anyway, the check is unnecessary because the GAC on the desktop is secure so the file can not be changed by someone other than an administrator or power user since it was installed.

The PocketPC file system lacks many of the features of NTFS, and principally in this discussion, there are no permissions set on folders so the GAC cannot be viewed as a secure location. For this reason the compact framework always checks the strong name signature of a shared assembly every time the assembly is used. Although this does affect performance, it ensures that assemblies are not tampered.

11.8 Concluding Remarks

The Compact Framework is a cut down version of the desktop framework, the Windows CE API is a cut down version of the Windows API. Both of these facts explain the differences between the way that Fusion works on mobile devices and how it works on the desktop. However, I have to admit that I am a little disappointed in some of its 'features'. In many cases it would take very little to change these problems.

I am disappointed that the root is used when Fusion probes for private assemblies. This is a bad mistake for the CF team to make because it means that lazy developers (and if we are honest most of us would admit to being lazy) will use the root as a mechanism to share assemblies just as Win32 developers use System32 to share DLLs. If one developer uses the root this way, you can be sure that almost all developers will, and so install programs will end up writing over other assemblies and we have returned to DLL Hell. It would be simple for the CF team to remove this option: private assemblies reside in the application folder; shared assemblies reside in the GAC. There should not be an intermediate limbo.

I am disappointed that the files in the GAC are not named using the public key token. This would make the names of files much more unique. .NET developers are told that giving a file a strong name distinguishes it from an assembly created by another publisher, and this should be apparent in the CF GAC too. It is true that if an assembly with the same short name, the same version and the same culture exists in the GAC as the one you are installing, then the new assembly will be added with a different ref parameter (the number at the end of the renamed file). However, I think this is rather lame and it would make more sense simply to append the public key token to the renamed file's name.

I hope that you enjoy this tutorial and value the knowledge that you will gain from it. I am always pleased to hear from people who use this tutorial (contact me). If you find this tutorial useful then please also email your comments to mvpga@microsoft.com.

Errata

If you see an error on this page, please contact me and I will fix the problem.

Page Twelve

This page is (c) 2007 Richard Grimes, all rights reserved