Click here to Skip to main content
15,867,686 members
Articles / Mobile Apps / Windows Mobile

Task Manager for Windows Mobile and Windows CE

Rate me:
Please Sign up or sign in to vote.
4.51/5 (23 votes)
30 Nov 2008CPOL10 min read 111.8K   7.3K   79   13
Source code for writing your own Task Manager for Windows Mobile or Windows CE based devices
CETaskManager_src

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...

C#
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:

C#
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.

C++
// CComEnum2<> extends ATL CComEnum<> that the Next() method initializes its T parameter
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.

C++
// CComEnum2<> template class specialization where T = VARIANT
// This will overcome a bug in CF3.5 that will check erroneously VARIANT vt type
// when the last item of an IEnumVariant is checked for validity. 
// CComEnum<> returns than S_FALSE
template <class Base, const IID* piid, class Copy, 
			class ThreadModel/* = CComObjectThreadModel*/>
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 strings BUG1 and BUG2 (see Form1.cs and ProcessList.cpp). When these strings are defined, the code runs with the bugs still enabled in the code. By undefining the strings, 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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader
Belgium Belgium
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionInstalled demo CAB. Has no End Process option Pin
maxima12021-Jun-12 8:39
maxima12021-Jun-12 8:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.