Secrets of the Windows Mobile Home Screen API

homescreen

One of my recent projects at Enterprise Mobile was creating a custom home screen (see above) for the new MotoQ9h and Blackjack II devices. The top row is the standard Windows Mobile "Icon Bar". The second row has the clock, messaging center (SMS, Mail, Voicemail), and current profile. Below that is the status text and the application bar. (Everything below the icon bar is a single full screen plug-in.)

At first glance, I assumed this would be a breeze. I have a powerful layout, key handling, and paint framework that I use in every project I work on, and thought that creating a home screen leveraging that would make it trivial. Sadly I was sorely mistaken.

Those of you familiar with WM Home Screen development know that there are actually two APIs: the Home Screen API (Smartphone) and the Today Screen API (Pocket PC). I was only interested in the prior. Smartphone Home Screen development is considerably more difficult: you don't have nearly as much control. The developer must implement IHomePlugin which only receives paint events (the plug-in is supplied only an HDC) and some key events (left and right only). On Pocket PC, you get an HWND and all the good things that go along with it.

That didn't phase me too much. My plan of action was to implement this IHomePlugin interface in managed code and then let the magic happen. Easy enough. I implement a dummy plug-in in C#, and try to register the COM object... and it doesn't work. It refused to register, the error claiming it was an invalid DLL. I poked at it for a while before finally giving up and checking to see if Google would know why I could not register a C# COM object on Windows Mobile.

I searched for a while, and found plenty of examples of how to invoke unmanaged code from managed code. Then I noticed, not a single site showed how to invoke managed from unmanaged. I began searching explicitly for that, and found my answer:

The .Net Compact Framework does not support hosting the runtime from native code. This means that you will not be able to call CoCreateInstace to instantiate a managed object, or register managed objects as COM objects on the system. You will also not be able to call CorBindToRuntime or ClrCreateManagedInstance. In order to call managed functions from native code, you must first use the runtime to marshal a managed interface or a delegate down to native code. This means you must always start out in managed code (with a .net executable) in order to expose .net components to native code.

This was news to me. And pretty devastating; the thought of creating a fairly complex home screen from scratch in unmanaged code was daunting. If it is not obvious, I'm primarily a .NET developer. Don't get me wrong, I have grass roots in C, but, the facts of life are that the lower level your programming language, the higher the development time and bug count.

So I quickly thought of a workaround though: create a managed service executable that hosts the UI framework. And using a combination of MessageWindows and sharing memory between processes, I could construct a bridge from unmanaged to managed code:

Implement IHomePlugin in unmanaged code and add that plug-in to the home screen. The unmanaged plug-in will do the following:

  1. Create a pool of shared memory.
  2. Create a back buffer HDC. This back buffer is what painted whenever the home screen requests a repaint. The managed code will write to this back buffer across process boundaries.
  3. Whenever the PE_DATACHANGE event is received, invalidate the plug-in.
  4. In the shared memory, write the HDC value, system colors, and plug-in dimensions.
  5. Start my managed executable.

Once started, the managed executable does the following:

  1. Gets the back buffer HDC and other relevant values from the shared memory pool.
  2. Paint to the back buffer HDC.
  3. Notifies the Home Screen API that a plug-in has had a data change event using SHOnPluginDataChange. This in turn causes the plug-in to invalidate and repaint from the back buffer.

That solves the painting issue, but the plug-in still needed to get key events from the Home Screen API and deliver them to managed code. Easy enough: the managed executable can create a MessageWindow and the unmanaged can deliver the key events provided by the Home Screen API. The managed code can then process the key event, repaint the back buffer, and invalidate the plug-in to refresh the screen.

This works great, but the catch with the API is that it delivers only left and right key presses. It won't send up and down key events (because then the user can't navigate to another plug-in on the home screen). However, since my plug-in was full screen, I did not matter. I needed a way to navigate vertically between elements laid out in my single full screen plug-in.

The solution to capturing all key events is a neat trick that native Win32 API developers are familiar with. Have the unmanaged plug-in subclass the home screen window (DesktopExplorerWindow as found in the VS Remote Spy++ tool) proc to steal the key presses and send them off to the managed code.

And there you go, a fully functional "managed" Home Screen Plug-In. My initial worry was that performance would suffer terribly from the cross process communication, but the home screen was actually very responsive. Users reported that the UI was quite "snappy".

Unfortunately I don't have any sample code to provide (yet), since this is all company IP. But maybe it will be released in the future!

Note: I should mention that Home Screen development actually gets easier with this interprocess communication. Developing a purely unmanaged plug-in means that the entire home.exe process needs to be stopped and started with every little change made to the plug-in. This can grow to be quite tedious. With the two-process model, starting and stopping the second managed process leaves the home.exe undisturbed.

3 comments:

Anonymous said...

Thats looking great, to bad you cant share the code.

Great work

Andersson

Anonymous said...

Very nice, thank you!

Joshua Smith said...

Thanks for useful information. For industry solutions it is the right way to get software development services.