Introduction
This article describes how to write a Task Manager for the Windows Embedded CE OS. The sample code runs on Windows Mobile 6.x or any other Windows Embedded CE based OS image. In order to develop applications for Windows Mobile 6.x from within Visual Studio 2005 or 2008, you need to download a separate SDK from Microsoft. The SDK also ships with a full featured emulator, a powerful and indispensable tool to test your applications.
Experienced developers will know that Windows Mobile 6.x is based on Windows Embedded CE 5.0. However, the code was also tested for Windows Embedded CE 6.0, which uses a different memory model. As there is no Windows Mobile version yet available based on this version of Windows Embedded CE, you need to develop your own OS image. How you do that is out of the scope of this article.
This sample code uses COM and Compact .NET Framework 3.5.
Background
For work I write software for controlling machines in real time, i.e. timing in which software executes code that should be deterministic and predictable. A minimum requirement for the job is that you have a Real Time Operating System (RTOS). Since a few years, we use Windows Embedded CE (5.0 and 6.0) for that purpose.
To monitor the real time behavior of our applications, we use a lot of monitor and trace tools, among them a few self-written tools. One tool to judge the overall behavior of our current system (CPU load, memory allocation, thread count,...) is a Task Manager, also known from desktop Windows. Unfortunately such a tool does not exist for Windows Embedded CE out-of-the-box. At first we searched the Internet, but we couldn't find a convenient tool that included all our requirements. So we decided to write our own one, moreover we can now add new features when we need them.
To write your own Task Manager (be it desktop Windows or Windows Embedded CE), you can use Microsoft’s ToolHelp
API libraries (available for both Windows Embedded CE and desktop Windows) that help you with querying memory usage, system resources... In order to write programs that require a nice User Interface, I prefer to use the Compact .NET Framework (C#).
The Compact .NET Framework has no libraries that give direct access to the WIN32 ToolHelp
API. So I started writing code that called those functions through P/Invoke. At first that seemed to work fine, but then I discovered a nasty problem. If you query properties of a running process that was stopped/killed during the query, it turned out that the ToolHelp
API crashed your .NET application. Surrounding the P/Invoke call with a C# try catch
block didn't help. Eventually it turned out that the MSDN documentation warns you about possible flaws in the code as it states that all ToolHelp
calls should be protected with a WIN32 __try __except
block. Baah, you can't use this Win32 (C++) mechanism in C#.
Note: If you examine the code carefully, you will notice that instead of __try __except
, I use (prefer) the standard C++ try catch
statement. To direct all SEH exceptions to standard C++ try catch
, you need to specify the /EHa compiler option. This will guarantee that e.g. an access violation or any other SEH exception will be caught with try { } catch (...)
.
To overcome this problem, I decided to split up the application in a UI part and a COM DLL in C++ that does the real (nasty) work. C++ allows me to use the WIN32 __try __except
mechanism and hides this complexity from .NET. Moreover it always looks more impressive and more professional if you split up code in separate layers, no?
Thanks to COM interop – supported from Compact .NET Framework 2.0 onwards – this shouldn't be a big problem. But who said that writing software was easy? During development, I discovered two major bugs in the COM interop library from Microsoft. These issues were reported to Microsoft (and confirmed as bugs) and will be fixed (hopefully) in a next release of the Compact .NET Framework. Nevertheless I will present you now a workaround for these bugs, one of the reasons I decided to write this article.
The Code
The COM DLL is called Toolbox.dll and implements the following COM coclasses:
CpuLoad
: Gives an event every second that tells you the overall CPU load of the system
ProcessList
: Enumerates all ‘Process
’es currently running on your system
Process
: Tells you all details about a process: used system resources (virtual memory, heap, DLL count, thread count...)
System
: Gives you some system wide information (total virtual memory, total heap,...) and some general features like killing or starting a process
CpuLoad
How can you determine what the current CPU load of your system is?
The answer is basically simple.
If you run a low priority thread and measure how much time it consumes in a certain time span, you know how much time the system didn't spend doing something else (serving interrupts, running other threads...). Luckily Windows Embedded CE supports the GetThreadTimes()
WIN32 API that will tell you everything you need to know.
Our application will create a thread with the lowest possible priority and call GetThreadTimes()
on it every second. We need another normal priority thread that will run briefly once every second, calculate the CPU load and fire a COM event.
Bug 1
From our C# code, we subscribe onto the COM event. But it turns out that when the Compact .NET Framework Garbage Collector runs, it mistakenly unsubscribes our event.
You can see this if you run the sample code for some time and place a breakpoint in the CProxy_ICpuLoadEvents::Unadvise()
method. After a while – typically when you switch to another application and back to our sample application – the GC will kick in and unsubscribe our event totally out of our control. This is of course unwanted.
The solution turns out to manually call the CProxy_ICpuLoadEvents::Advise()
method from C# as follows...
System.Runtime.InteropServices.ComTypes.IConnectionPointContainer iCPC =
(System.Runtime.InteropServices.ComTypes.IConnectionPointContainer)m_cpuLoad;
System.Runtime.InteropServices.ComTypes.IConnectionPoint iCP;
Guid IID_ICpuLoadEvents = new Guid("9C6705E3-E8F9-4692-8FC8-FE15B6226022");
iCPC.FindConnectionPoint(ref IID_ICpuLoadEvents, out iCP);
iCP.Advise(this, out m_cookie);
... instead of using the standard C# way of subscribing to an event:
m_cpuLoad.Measurement +=
new ToolBoxLib._ICpuLoadEvents_MeasurementEventHandler(cpuLoad_Measurement);
ProcessList
ProcessList
implements the reserved COM __NewEnum()
method that translates in C# to the IEnumerable<T>
interface. This interface allows you to use the C# foreach() { }
statement to iterate over all running processes.
Bug 2
Periodically we want to update our UI with the current list of running processes and display their properties. In our application, we use the CpuLoad
event for that, although you can use another timer event for that as well. Now again, if the code runs for some time, the COM interop library throws an unexpected “Bad variable type” (0x80020008) while iterating with foreach
over the list of Processes.
It turns out to be a bug in the COM interop library that by mistake misinterprets the HRESULT
value returned by the CComEnum<T>::Next()
method. The CComEnum<T>
helper class is typically used for implementing the EnumVariant
object that is returned by the get__NewEnum(IUnknown** retval)
method.
According to MSDN, when S_FALSE
is returned, the last element of the EnumVariant
has been reached and does not have to contain valid data. But the COM interop library does not seem to do this, instead it checks whether the VARIANT
has a valid VARTYPE
. But now, as the data in the VARIANT
is not valid (CComEnum<T>
does not initialize it when S_FALSE
is returned), the embedded VARTYPE
can be anything. If an unknown VARTYPE
is encountered, the “Bad variable type” error is issued.
To overcome this problem, we simply create a new CComEnum2<T>
template class that inherits most functionality from CComEnum<T>
except for the CComEnum<T>::Next()
method that we will reimplement.
template <class Base, const IID* piid, class T, class Copy,
class ThreadModel = CComObjectThreadModel>
class CComEnum2 :
public CComEnum<Base, piid, T, Copy, ThreadModel>
{
public:
STDMETHOD(Next)(ULONG celt, T* rgelt, ULONG* pceltFetched)
{
*rgelt = T();
return CComEnum::Next(celt, rgelt, pceltFetched);
}
};
We first make sure that we call the default constructor for the template type T
to initialize it properly before we call the base CComEnum<T>::Next()
. This works for most types, but isn't enough when VARIANT
is used. Therefore we create a template specialization when the template type is of type VARIANT
.
template <class Base, const IID* piid, class Copy,
class ThreadModel>
class CComEnum2<Base, piid, VARIANT, Copy, ThreadModel> :
public CComEnum<Base, piid, VARIANT, Copy, ThreadModel>
{
public:
STDMETHOD(Next)(ULONG celt, VARIANT* rgelt, ULONG* pceltFetched)
{
VariantInit(rgelt);
return CComEnum::Next(celt, rgelt, pceltFetched);
}
};
This specialized version of CComEnum2<T>::Next()
calls VariantInit()
on the VARIANT
to be sure the VARTYPE
is properly initialized to VT_EMPTY
. This seems to avoid the bug. In our sample code CProcessList::get__NewEnum(IUnknown** retval)
uses the new CComEnum2<T>
template class instead of the normal CComEnum<T>
template class.
Process
Process
implements an interface that will give all information about the Process
. It will use the ToolHelp
API for that.
System
System
implements an interface for querying system wide memory usage and an interface for ending (killing) a process.
Testing and Debugging
The sample package includes a Visual Studio 2008 solution that contains 2 projects:
- CETaskManager.vcproj C# project that builds the CETaskManager.exe executable
- ToolBox.vcproj C++ project that builds the
Toolbox
COM DLL
By default, you can deploy the components onto the Windows Mobile 6.x emulator or if you wish, a real Smart Device.
Hint: As this is a 'Mixed Platforms' solution, you might need to deploy each project individually. If you choose 'Deploy Solution', Visual Studio might only deploy the 'Startup Project'. Also make sure CEChart.dll is present in the deployment directory.
Interesting to note is that the emulator is so good that it suffers from the same bugs as a real device. Microsoft did a good job in creating a bug emulator too.
The code (both C# and COM C++ project) defines 2 string
s BUG1
and BUG2
(see Form1.cs and ProcessList.cpp). When these string
s are defined, the code runs with the bugs still enabled in the code. By undefining the string
s, you compile in the fixes for these bugs and the program will work fine.
For the interested reader, I have also included a solution that will compile both ToolBox.dll and TaskManager.exe for desktop Windows. This is especially useful in debugging and comparing behaviors. It turns out that the desktop variant has none of the 2 mentioned bugs. I must mention however that the performance of the desktop version of the ToolHelp
API is not good compared to the CE version. Nevertheless, it is interesting to compare both versions.
Installation
- Deploy Compact Framework 3.5 runtime on your device
- Copy TaskManager.exe on a folder of your device, typically “%CSIDL_PROGRAM_FILES%\CETaskManager”
- Copy ToolBox.dll and Interop.ToolBoxLib.dll to the same folder and register ToolBox.dll (required for all COM DLLs)
- Copy CEchart.dll to the same folder. This component is required for drawing the CPU load history graph.
Of course, you can also use Visual Studio for deploying the components onto your device while debugging.
A CAB file is also included for Windows Mobile 6.x based devices (ARMV4I).
Conclusion
If you are a Windows Mobile developer and you use COM interop regularly, you might run into these problems sooner or later. I have presented you a solution for each of these problems. I hope this will encourage you to keep on writing software for Windows Smart Devices. If you encounter problems and if you are convinced they are bugs, you can always contact Microsoft and try to solve them. I have done it several times and it works.
As a bonus, this article presents you sample code to write your own full featured TaskManager
, a vital missing tool for any developer writing software for Windows Smart Devices. Good luck.
References
Acknowledgement
Many thanks to my fellow 'late-hour code writer' and friend Kurt Mampaey who provided me with the CEChart.dll component.
History
- 10th November, 2008: Initial version
- 11th November, 2008: Version 2 - updated demo and source files
- 29th November, 2008: Corrected a few syntactical sentences and rephrased some of my explanation