Mar 12 2006

.NET Compact Framework Development

Published by


Development Considerations With

The .NET Compact Framework

Kyle Cordes

Oasis Digital Solutions Inc.

www.kylecordes.com / www.oasisdigital.com

Last Major Revisopn Nov. 30, 2003

Minor Update in 2006

These are notes from a short talk at the St. Louis Wireless Special Interest Group; in that group, we generally have a short talk then hands-on coding (with notebooks computers that various members bring) to try out the ideas presented. I covered all of the ideas here quickly, then as a group we experimented with form management and HTTP data transmission with the Compact Framework.

The Wireless Special Interest Group is at http://www.stlwebdev.org/membership/sigs/wireless/

At our October meeting, I introduced the .NET Compact Framework, and we built our simple pizza ordering application with a single form and a Web Service to transmit the data to the PC. This month, I’ll go in to more detail on the issues I found and tips I learned while writing a real-world application using the .NET Compact Framework, earlier this year.

The Application

Most of this comes from a piece of software I wrote to automate data collection for the inspection of chemical labs. I wrote this application for a client of Oasis Digital, who makes software to manage the abundance of data needed for regulatory compliance, safety, etc. in research laboratories. Here are a few screen shots of the application:

These images were captured with a program called “Developer One Pocket ScreenSnap”, the most effective of several different Pocket PC screen capture programs I tried.

Form Management

Creating the various screens of your application as separate Forms is very problematic. I spent about 12 hours trying to get reasonable behavior as users move back and forth between applications built this way, and was unable to get good results. (In the workshop portion of our meeting, we were able to demonstrate some of the problems with a simple two-form application.)

There is a downloadable example “Form Stack” system which attempts to deal with the anomalies:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetcomp/html/netcfuiframework.asp

But I didn’t get good results with that approach either. I found that a much better approach is to run the whole application (or rather, all of the non-modal screens of your application) as one Form. To do this, use either overlapped frames (controlling visibility in code), or a page-control. This appears to be how the built-in applications (The Calendar, Contacts, etc.) are constructed.

Form Design

My default position on screen layout mechanisms is that graphical form editors are highly valuable and worthwhile. With considerable reluctance, though, I ended up switching to manual coding of my form layouts. Why?

  • VS.NET can’t create code that works with both CF and desktop .NET; I was able to do that easily with a few #ifdefs and factory methods. To keep the code short and readable, and hide the differences between the CF API and desktop API in one place, I use a series of factory methods for control creation. An example of control creation:

yesRadio = AddRadioButton(x, y, “Y”, 32);

  • VS.NET has no way of building just a frame/panel, it can only build a whole Form. (Some of the Java IDEs with Swing layout capabilities can handle “Frames”, as can Delphi, so I expect this innovation to arrive in a future version of VS.NET.)
  • Because I needed to rearrange the controls to accommodate the “soft input panel”, I needed a modicum of layout management capabilities, which CF does not yet include.

Splash Screens

CF applications take some time to initialize. Part of this time is the framework / CLR itself, part of it is your own classes starting up. It is wise to display a “splash” form so the user knows that the software is working. The code to show and hide/destroy a form looks like this:

SplashForm splash;

public void ShowSplashScreen() {

splash = new SplashForm(this);

splash.Show();

splash.Update();

}

public void RemoveSplashScreen() {

if(splash != null) {

splash.Close();

splash.Dispose();

splash = null;

}

}

Between these, create all the objects your application needs, show your form, load data from storage, etc.

It is good to Dispose() and clear the reference to a form that will never be used again, so that the CF base classes can free any underlying Win32 API resources.

Main Method

I tend to write the Main method of my applications like so:

//[STAThread] only work on the desktop .NET

public static void Main(string[] args)

{

Gui instance = new Gui();

instance.Startup();

Application.Run(instance.mainForm);

}

… all of my application goes in classes, so Main only need to create an instance and start it. Above, I named the main controller class “Gui” – a lousy name which I will change later. (Without refactoring tools in VS.NET, I tend to delay many worth changes.)

The [STAThread] attribute means “single threaded apartment”, and only applies to desktop/server .NET applications which call COM components.

Communications / Synchronization

Many Windows CE applications, including almost all business applications, require exchanging data between the CE device and a server. There are three main approaches to this:

  1. Store your data in files that will be synced by ActiveSync
  2. Write an ActiveSync plug-in to synchronize your data as part of the ActiveSync process
  3. Write a separate data transfer mechanism, not part of ActiveSync

Option 1 is effective for document-based, as opposed to data-based application.

Option 2 is nice because the process happens automatically, whenever the CE device is attached to the PC. However, it is several important downsides:

  • It only works when the CE device is attached to its “home” PC with a “standard partnership”; it doesn’t support syncing via whatever machines happen to be available via a “guest partnership”.
  • It can work via wireless, but only if configured to use ActiveSync via wireless to the “home” PC which is left on
  • It is complex to develop, using COM interfaces on the PC side – there is no .NET support for this yet, though it may be possible via .NET COM interop (on the desktop side).
  • It requires installing the ActiveSync plug-in, an additional installation step

For these reasons, option 3 is popular for enterprise CE applications. Our example program last week used SOAP Web Services for this communication, for example. With option 3, support for wireless data access is automatic – anytime the CE device has a network connection (via wired connection to a PC, wired Ethernet connection, WiFi wireless, etc.), it will work

Another variation on option 3 is to use a client-server protocol other than SOAP; this is the approach I took in my application. The software on the PDA communicates to a “synchronization server” via a simple text protocol over HTTP.

Yet another variation is to install appropriate database drivers on the CE device, and have it communicate directly with the underlying database on the server system. The advantage of this is that no middle tier (sync server) is needed. I generally don’t recommend this approach, but it is available for several database servers (including MS SQL Server, MySQL, etc.) The term for these drivers is “Managed Provider”.

IDE

Only Microsoft Visual Studio.NET 2003 has support the CF – other IDEs (SharpDevelop, C#Builder), don’t yet support the CF because Microsoft has not yet shipped a standalone CF SDK.

VS.NET: http://www.borland.com/csharpbuilder/

SharpDevelop: http://www.icsharpcode.net/OpenSource/SD/

C#Builder: http://www.borland.com/csharpbuilder/

Unfortunately, VS.NET has rather poor support for several aspects of CF development. I experienced several issues:

  • VS.NET does not cooperate with building the same software for both CF and desktop .NET
  • VS.NET does not support visual editing of reusable panels, only of top-level Forms. (as described above)
  • If you include both a desktop and CF project in the same “Solution”, and you build/run the desktop app, VS.NET will build and deploy (!) the CF project.

I expect VS.NET CF support to improve greatly in the upcoming “Whidbey” release.

Combined Desktop and Compact Framework Support

I found it extremely useful to build both a CF and desktop version of my application – I could develop and test about 90% of the functionality on my desktop machine, without using either a PDA or PDA emulator. This made the compile/run/edit cycle tremendously faster.

  • You can’t run the same EXE/DLL on both the CF and desktop .NET
  • … because you need to bind to the right core assemblies for each platform
  • You can’t run exactly the same source code on both
  • … because you CF code will need to make a few P/Invoke calls specific to the CF
  • … because a few lines of code you need for the desktop, won’t work on the CF
  • You can use many non-UI modules with unchanged source
  • You can use a few #ifdefs to get around the small number of source differences needed
  • … if you also restrict you API calls to the CF subset
  • You can use a build process to automate using the right libraries for each platform and the right #defines

The InputPanel

Windows CE / Pocket PC includes an on-screen keyboard / entry area called the SIP (Soft Input Panel), accessed via the InputPanel class in the CF. Note the use of #ifdef, since this doesn’t exist on the desktop:

#if COMPACT_FRAMEWORK

InputPanel pan = new InputPanel();

#endif

Fonts

There is a bug in the CF which makes 8-point text not work correctly. The workaround, for the moment, is to not use 8-point text, use another size (7, 7.5, 8.5, 9) instead. For my app, I defined a short list of fonts:

public class Fonts {

public static Font smallFont = new Font(“Microsoft Sans Serif”, 7F,FontStyle.Regular);

public static Font smallboldFont = new Font(“Microsoft Sans Serif”, 7F,FontStyle.Bold);

public static Font normalFont = new Font(“Microsoft Sans Serif”, 9F,FontStyle.Regular);

public static Font normalboldFont = new Font(“Microsoft Sans Serif”, 9F,FontStyle.Bold);

}

…then used these fonts everywhere, for consistency. Note that the GUI editor spreads font data all over the application, with no centralized way to adjust fonts.

P/Invoke

P/Invoke is Microsoft nicely designed mechanism for accessing native APIs from .NET. You will find, at least in the current CF versions, that it is necessary to use this regularly; many important CE features are not yet exposed as CF framework classes. An example P/Invoke usage:

[DllImport(“coredll.dll”, SetLastError=true)]

private static extern bool SHGetSpecialFolderPath(int hwndOwner,

string lpszPath, SpecialFolderType nFolder, bool fCreate);

Unfortunately, the CF does not currently include COM interop; so while you can easily call DLLs, calling COM components requires coding a DLL wedge layer (to “flatten” the API) in C++.

XML

The CF does not include support for XML, except as part of the Web Services support. Therefore, while you can send and receive XML in SOAP packets, you can’t read/write XML data to local storage, unless you add a third-party XML library (or write your own).

Apparently, more XML support has been added to the CF in Service Pack 2.

Serialization

The CF does not include serialization. To make my application work with the data handling approach I chose, I wrote “manual” code to serialize/deserialize data to a simple text format. There are third-parts solutions to add in the missing serialization support in the CF, and this is also expected to be present in-the-box in a future version.

Error Messages

Many CF API calls throw/return much less helpful error messages than the corresponding desktop .NET calls; a lot of descriptive error text was left out to keep the CF small.

To work around this, it is necessary to catch and interpret (translate for the user) some kinds of errors that could be shown as-is on desktop .NET applications. An example is internet / HTTP communications errors.

Build process

I always recommend having a standalone, one-step, repeatable, automated, IDE-free build process for substantial applications. This is possible, with some gymnastics, for CF applications. Such a build process can:

  • Start with source
  • Build the various CAB files, etc. needed for an installer
  • End with a shippable installer
  • Generate both a CF version of the application and a desktop version, for easier desktop testing and debugging of non-PDA-specific features

Here is the (very rough) batch file I’ve used to build my CF application. A cleaned up version of this will be included in the downloadable example code I am working on.

set LIB=C:\Program Files\Microsoft Visual Studio .NET\Vc7\lib\;C:\Program Files\Microsoft.NET\SDK\1.1\Lib\

csc /noconfig /d:DEVELOPMENT @buildcf.rsp /debug:full /out:Debug\SiteInspectorCE.exe *.cs

if errorlevel 1 goto err

REM recompile with release settings

csc /noconfig @buildcf.rsp /o /debug- /out:Release\SiteInspectorCE.exe *.cs

if errorlevel 1 goto err

REM Generating CAB file

c:\net\cabwiz\cabwiz.exe SiteInspectorCE.inf /err CabWiz.log /cpu ARMV4 ARM SH3 MIPS X86 WCE420X86

if errorlevel 1 goto err

REM Building installer

del Release\SiteInspectorCESetup.exe

“C:\Program Files\My Inno Setup Extensions 3\iscc.exe” SiteInspectorCE.iss

if errorlevel 1 goto err

REM cleaning up temp files

del *.cab

del Site*.dat

echo “******************** SUCCESS *********************”

goto :pause

:err

echo “Build Failed”

And the buildcf.rsp file:

/nostdlib

/warnaserror

/d:COMPACT_FRAMEWORK

/t:winexe

/r:c:\net\cfdll\mscorlib.dll

/r:c:\net\cfdll\system.dll

/r:c:\net\cfdll\system.drawing.dll

/r:c:\net\cfdll\system.windows.forms.dll

/r:c:\net\cfdll\system.web.services.dll

/r:c:\net\cfdll\microsoft.windowsce.forms.dll

/Nostllib tells CSC not to include reference to the the (desktop) core .NET assemblies by default. Then /r: lines add all the needed references to the core CF assemblies, which I had copied to c:\net\cfdll on my machine.

Performance

CF apps start up with a noticeably longer delay than native (C++) applications. Fortunately, Windows CE keeps software running even with the user dismisses it (the “Smart Minimize” feature), so this delay is experienced rarely and is not bothersome to end users.

Once running, CF code appears to run very well. Like the desktop .NET platform, the code gets Just-In-Time compiled on first use, so the execution even of complex algorithmic code is reasonably fast. CF applications “feel” just a bit more sluggish than native apps.

It is necessary to take more care with performance issues with CF apps than with desktop apps, simply because the platform has so much less CPU and memory available. Some issues to consider:

  • Caching
  • Excessive object creation
  • Use BeginUpdate / EndUpdate before updating a lot of data in a grid/list/etc.
  • Handle lengthy operations in the background, don’t block the GUI processing in the main thread
  • Do complex rendering off-screen
  • As always, measure before (and after) optimization, don’t guess.

I found CF performance to be much more acceptable than, for example, Java PDA performance on the Palm platform.

SQL Server CE

SQL Server CE is Microsoft’s lightweight database engine for Windows CE. It includes both a local DBMS, and a driver to talk to a remote (real) SQL Server, in the same “box”. The local DBMS is remarkably limited – it allows only one connection (total across all databases and apps!) at a time, the query engine is much less feature-rich than the real SQL Server, etc.

As described above, I don’t generally recommend having an application like this interact directly with the server database; I prefer to use a middle tier / service layer instead. Thus, I haven’t had a need for the database-driver aspect of SQL Server CE, so I don’t know how well it works.

SQL Server CE has a several-megabyte installation size, so it may not be a wise choice for small applications; the database install would dwarf the size of the application itself.

Likewise, SQL Server CE’s query capabilities are not particularly impressive, so for complex applications you may find that you have to read the data in from SQL Server and process it in code, in situations where that would not necessary in an application using a “grown-up” database.

The strongest point in favor of SQL Server CE is that it can participate in the built-in replication capabilities of SQL Server. Thus, the to niches where SQL Server CE makes sense to me are:

  • Medium-complexity applications, where SQL Server CE’s features are sufficient for the application, and worth the installation size.
  • Applications that want to use the built in replication.

Data Storage

The approach I took to data storage was extremely simple; write a couple of pages of code to serialize/deserialize my application’s object model to a trivial text format. This worked surprisingly well, but for a more complex application, some kind of queryable local data storage would be much better.

I expect that third party providers will step in to fill the void between SQL Server CE and Pocket Access; something like PointBase (which is a Java, tiny, embeddable database), but written in a .NET language, would be an excellent choice.

Making an Installer

Your CE application will need an installation program. There are two approaches to this:

Two Phase Installation

Microsoft ships a built-in installation mechanism with works in two phases:

  1. Users run an installation program to put some files on their desktop PC
  2. That program invokes the built in CE installer (part of ActiveSync) with then puts the needed files, settings, etc. on the CE device.

This mechanism is based on CAB files, which include compressed data files as well as a ‘manifest’ to tell the installer about registry settings, file locations, etc.

Thus, using this approach you don’t need to have an CE-aware installer; you only need to copy some files to the PC then invoke the CAB installer. The generic installation process can use the Microsoft Installer (MSI files) or any other. For my application, I used Inno Setup (an excellent and free installation-builder) for this purpose.

One Phase Installation

There are third-party installation systems for Windows CE which work, in their entirely on the CE device, simply by entering a URL in “Pocket Internet Explorer” on the device. No PC is needed at all. This kind of installation would be ideally suites for users who work entirely on the Pocket PC, for applications purchased and installed over the internet, etc.

Conclusion

The .NET Compact Framework is usable for many kinds of production applications, but it very much a “1.0” version, both in its features and in the tool support.

Update: I’ve been mostly away from CF development since writing this, but anything else I post about it can be found in dot-net category.

One response so far

One Response to “.NET Compact Framework Development”

  1. […] since I posted my experiences creating software for the .NET Compact Framework back in 2006, I’ve received a steady trickle of thank-you emails. Apparently there are bits […]