Pocket PC (General) Technical Articles
Migrating Symbian OS Applications to Windows Mobile-based Smartphones
 

Andy Wigley
CM Group

September 2005

Applies to:
   Series 60 SDK 2.1 for Symbian OS, which supports Microsoft Visual C++ .NET
   Microsoft Visual Studio .NET 2003
   Microsoft .NET Compact Framework version 1.0 Service Pack 2 or later
   Microsoft Visual C++ version 4.0 Service Pack 3 or later
   Microsoft Smartphone 2003 SDK
   Microsoft Visual C#

Summary: Examine the techniques for migrating applications written for Symbian OS platforms, such as Series 60 and UIQ, to Windows Mobile–based Smartphones. (26 printed pages)


Download Migrating Symbian OS Apps to Windows Mobile 2003 for Smartphone.msi from the Microsoft Download Center.

Contents

Introduction
Porting or Reimplementation?
Porting Symbian C++ to eMbedded Visual C++
Exporting Functions
Implementing the GUI by Using the .NET Compact Framework
Creating Menus
Creating Dialog Boxes
Linking the GUI to the Engine
Conclusion

Introduction

This article is for C++ developers who use Symbian OS and who have developed, or are developing, C++ applications for Symbian OS platforms, such as Series 60 or UIQ, and developers who want to implement their applications on the Windows Mobile operating system for Smartphones.

When you first compare the Symbian OS application architecture with the architecture of a Windows Mobile application, they seem to have very little in common. On the Symbian OS, you build C++ graphical user interface (GUI) applications within an application framework by extending base classes that are provided as part of the platform, whereas eMbedded Visual C++ applications that are based on the Windows Mobile operating system are not so closely coupled to the operating system. Windows Mobile has no direct equivalents for common Symbian OS constructs such as two-phase construction or the cleanup stack, and Windows Mobile does not use a Symbian-style client-server model for accessing system resources, such as the file system or socket server.

In most cases, you will have to re-implement your Symbian OS application for Windows Mobile. Re-implementing does not mean that you throw away your design concept or application logic, but you will have to take your existing application concept and produce a new implementation for Windows Mobile. Fortunately, this task is a little easier if you decide to build your application using the C# language and the .NET Compact Framework. Later in the article, you will look at common Symbian OS GUI constructs, such as views, dialog boxes, menus, and lists, and find out how to implement them in a Windows Forms application that is built by using the .NET Compact Framework.

Some code porting may be possible in one situation. If you have implemented your Symbian OS application so it is split into an engine DLL (dynamic-link library) and a GUI application, where the engine DLL implements the engine data or model that the application uses, and the GUI application implements the user interface (UI), it may be possible to reuse some of the C++ code from the engine DLL in a new engine DLL written for Windows Mobile by using eMbedded Visual C++. It is not practical to port the GUI application code, so you must write new GUI code. You could use eMbedded Visual C++ for the new GUI code too, but it is usually much quicker to create a .NET Compact Framework application written in Visual C# and then make calls to the eMbedded Visual C++ engine DLL.

This article shows how to take a simple Symbian program that is built as an engine DLL and a separate GUI application and create a Windows Mobile–based version by porting the engine DLL to a Windows DLL that is written in C++ and by reimplementing the GUI by using C# and the .NET Compact Framework.

Porting or Reimplementation?

Many differences exist between the application architecture of a Symbian OS C++ GUI application and the architecture of a similar application based on Windows Mobile platform. These differences are illustrated by a look at that old favorite, the "Hello World" application.

If you download any software development kits (SDK) for developing Symbian OS applications, you will find that it comes with a collection of sample applications; one of which is the "Hello World" application. For example, if you visit Forum Nokia, you can download the Series 60 Developer Platform SDK for Symbian OS, for C++ (requires registration). There are many different versions of the SDK that support different IDEs (integrated development environments) and different languages; choose the SDK version 2.1 that supports development by using Microsoft Visual Studio .NET 2003.

If you choose the default installation folder when you install the SDK, you can find the sample "Hello World" application at C:\Symbian\7.0s\Series60_<version>\Series60Ex\helloworldbasic. If you study this application, you will see that it implements the standard class structure required by Symbian OS GUI applications, as shown in Figure 1. Each of the application classes inherits from classes in the Series 60 platform, which overlays the underlying Symbian OS.

Click here for larger image

Figure 1. Class structure of a Symbian OS GUI application. Click the thumbnail for a larger image.

You can easily build a Hello World application by using eMbedded Visual C++ because the New Project AppWizard offers Hello World as one of the standard options. The download sample also includes an example. If you study this code, you can see that it consists of a single main source file that contains some functions but nothing that resembles a class! In fact, the functions in HelloWorld.cpp are functions that the Windows Mobile shell calls to make the application operate. Both Symbian OS and Windows Mobile platforms use an application framework, but there is little in common between the two frameworks.

The sample code also includes a Hello World example that was written by using the .NET Compact Framework. If you study the file Form1.cs in the dotNETCFHelloWorld sample, you will see that it consists of only a few lines of code. The .NET Compact Framework offers a very full-featured application framework and makes it very easy to create GUI applications.

When Is Porting an Option?

From this quick examination of the different implementations of the Hello World application, it should be clear that it is rarely an option to port Symbian OS C++ GUI code to eMbedded Visual C++ because of deep differences in implementation. Although this sounds disappointing, you should take some encouragement from the fact that Series 60 Smartphones and Windows Mobile–based Smartphones both use left and right soft keys as an input mechanism. Also, Windows Mobile offers application programming interfaces (APIs), such as sockets servers, file system APIs, and database servers, which offer at least the same functionality as equivalent APIs on Symbian OS. Because of this commonality in user input mechanisms and API functionality, you should at least be able to reuse the design in a Windows Mobile version, even if you must put in a lot of work on the reimplementation.

As mentioned in the introduction, there is one situation where some porting may be possible. If you have a Symbian C++ class library, such as one that implements the logic and data management of an application but does not implement any of the GUI, porting may be an option. Such an architecture is used fairly often when implementing Symbian applications, because Symbian developers often must target many different platforms, such as Series 60, Series 80, and UIQ. If you want to develop a Symbian application for multiple platforms, one approach is to develop a platform-independent engine that implements the application logic and then write a separate GUI for each platform to work with the engine.

The remainder of this article describes how to port an application with an architecture that has a GUI-engine split to Windows Mobile 2003 software for Smartphones.

Porting Symbian C++ to eMbedded Visual C++

The application you will migrate is a simple mileage recorder that enables a user to record a list of journeys as a description and a number of miles. This application is as simple as possible, so it serves as a good example of the required techniques, but it is not a fully functional application. The user can add records, but the user cannot delete or edit individual records, although the user can clear the complete list.

The Sample Application

The application is interesting because it is an example of a GUI–engine split architecture that consists of two separate components:

  • The MileageManager.dll library is the engine that is responsible for storing the list of data objects (the model) and enables callers to create new records, read back the list of records, and clear the list of records. It has no graphical components.
  • The MileageGui.app application is the user interface of the application that shows a list of mileage records in the main application view. The menu enables the user to add a new record (which is achieved through the use of a custom dialog class), to clear the list and to close the application. Figure 2 shows the main list view and the dialog used to create new records.

    Click here for larger image

    Figure 2. The sample application running on the Series 60 SDK emulator. Click the thumbnail for a larger image.

To build and run this application yourself, first download the code sample, and then install it on a computer that has Visual Studio .NET 2003 installed. You also need to install the Series 60 Developer Platform SDK for Symbian OS for C++ version 2.1, which you can download from Forum Nokia.

To build the application and run it

  1. Click Start, point to All Programs, point to Microsoft Visual Studio .NET 2003, point to Visual Studio .NET Tools, and then click Visual Studio .NET 2003 Command Prompt. A command window opens.
  2. Change the directory to the \SymbianMileage\Engine subfolder in the sample code folders.
  3. Type the following command:
    makmake mileagemanager.mmp vc7
  4. This command creates a Visual Studio .NET 2003 solution and project file.
  5. On the Command Prompt window, change the directory to ..\group.
  6. Type the following command:

    makmake mileagegui.mmp vc7
    This command creates a Visual Studio .NET 2003 solution for the GUI application.

  7. Open this solution in Visual Studio .NET 2003, and then click Build Solution on the Build menu to create the MileageGui.app application.
  8. On the Debug menu, click Start to run the application in the Series 60 emulator.
  9. When prompted to enter the Executable for Debugging Session, type drive where installed:\Symbian\7.0s\Series60_v21\Epoc32\release\wins\udeb\epoc.exe. Leave the URL where the project can be accessed box empty, and then click OK.
  10. The Series 60 emulator starts. Navigate to the mileageGui icon in the applications list, and then click OK on the emulator to start the application.

Creating the Engine by Using eMbedded Visual C++

Now you can build a version of this application that runs on the Windows Mobile platform. The Windows Mobile version reuses as much of the code from the engine DLL as possible in a new native DLL that you will build with eMbedded Visual C++. You will also create a new GUI in managed code by using Visual C# and the .NET Compact Framework (managed code is the term used to describe code that is written to run within the .NET Framework or .NET Compact Framework).

To build the engine DLL for Windows Mobile 2003 software for Smartphones, you must install the following products:

To create the project

  1. Open eMbedded Visual C++ 4.0. On the File menu, click New.
  2. In the New dialog box, select the Projects tab, and click WCE Dynamic-Link Library. Type a suitable name in the Project Name box, such as MileageEngine. In the Location box, enter the name of a folder where you want to create the project, such as C:\MSSmartphoneEngine. Click OK.
  3. In the WCE Dynamic-Link Library – Step 1 of 1 wizard page select A DLL that exports some symbols, and then click Finish. Click OK.
  4. The New Project wizard generates the code for a DLL that exports symbols.

Next, you can copy the files that implement the CMileageManager and TMileage classes from the Symbian version of the mileage recorder application to the new project and modify them to work in the Windows Mobile environment.

To copy and modify the files

  1. Using Windows Explorer, copy the files mileagemanager.cpp and mileage.cpp from the Symbian version of the application (they are in the \SymbianMileage\engine folder), and then paste them into the C:\MSSmartphoneEngine\MileageEngine folder.
  2. From the \SymbianMileage\inc folder in the Symbian version of the application, copy the files mileagemanager.h and mileage.h to the C:\MSSmartphoneEngine\MileageEngine folder.
  3. In eMbedded Visual C++, in the Workspace window, click the FileView tab, and then right-click the Source Files folder to display a menu of options. On the shortcut menu, click Add Files To Folder.
  4. In the Insert Files into Project dialog box, select mileagemanager.cpp and mileage.cpp, then click OK.
  5. Add the files mileagemanager.h and mileage.h to the Header Files folder in the same way.

Before you start working on these files, it is important to configure the integrated development environment (IDE) for the correct target device. In the drop-down boxes in the toolbar, select Smartphone 2003 as the target platform, Win32 (WCE emulator) Debug as the Active Configuration, and Smartphone 2003 Emulator as the default device, as shown in Figure 3.

Click here for larger image

Figure 3. Setting the correct target in the toolbar. Click the thumbnail for a larger image.

If you try to build the DLL now, the compiler generates errors because it cannot find the Symbian-specific header files e32std.h and w32std.h. You must delete the references to these files in mileagemanager.h and mileage.h.

When you compile again, the new error message is "C1010: unexpected end of file while looking for precompiled header directive." Precompiled headers are an optimization feature that aims to reduce unnecessary recompilation during development. The easiest solution is to turn off precompiled headers.

To turn off precompiled headers

  1. On the Project menu, click Settings.
  2. Select the C/C++ tab, and in the Category drop-down list, select Precompiled headers.
  3. Select Not using precompiled headers, and then click OK.

If you compile again, many errors appear. The first few of these are due to the use of Symbian typedefs for basic types, such as TInt for an integer. You can fix these errors by creating a new header file called typedefs.h and by including it in mileage.h. For many simple types, you can do a one-to-one substitution for Symbian types to Windows types. For this application, only one redefinition is required in typedefs.h.

#define TInt int

Replacing Descriptors

The next problem comes with the use of TDes, TdesC, and TBuf<n> objects in the Symbian C++ code. These objects are descriptors that Symbian C++ uses to manage string and binary buffers in a safe manner, avoiding the risk of buffer overrun that is associated with using char[] buffers and the C runtime library string handling functions, such as strcpy. Descriptor objects also offer a great many functions that make it easy to manipulate string buffers.

Although eMbedded Visual C++ 4.0 for Windows Mobile 2003 does not have descriptors, it supports the C++ STL (Standard Template Library). In many places where Symbian C++ uses descriptors, the std::wstring object is an excellent substitute.

Using these substitutions, the following members that are declared in mileage.h change from the Symbian C++ version of the code (as shown in the following code example)

#include <e32std.h>
class TMileage
    {
    public:
        TMileage() : iMiles(0) {};

        void  SetMiles(const TInt aMiles);
        const TInt Miles() const;
        void  SetDescription(const TDesC& aDescription);
        const TDes& Description() const;

    protected:
        TInt iMiles;
        TBuf<KMaxDescriptionLength> iDescription;

to the following for Windows Mobile 2003 platform, as shown in the following code.

#include <string>
#include "typedefs.h"
class TMileage
    {
    public:
        TMileage() : iMiles(0) {};

        void  SetMiles(const TInt aMiles);
        const TInt Miles() const;
 
        void  SetDescription(const std::wstring aDescription);
        const std::wstring Description() const;

    protected:
        TInt iMiles;
        std::wstring iDescription;
        };
    }

The std::wstring object offers many of the same string manipulation facilities as descriptors, but it uses different naming conventions. As you update the function implementations, you need to change calls to TDes::length() to std::wstring::GetSize() and TDes::Copy() to ' = ' as shown in the following code example.

void TMileage::SetDescription(const std::wstring aDescription)
    {
    if (aDescription.size() > KMaxDescriptionLength)
        {
        return;
        }
    iDescription = aDescription;
    }

const std::wstring TMileage::Description() const
    {
    return iDescription;
    }

If you compile mileage.cpp after you make these changes, the compiler returns the warning: "C4530: C++ exception handler used, but unwind semantics are not enabled." The STL uses C++ exception handling, but you must change the project settings so that the compiler builds in support for exception handling.

To change the project settings

  1. On the Project menu, click Settings.
  2. Click the C/C++ tab.
  3. In the Project Options, navigate to the end of the list of options.
  4. Add /GX, as shown in the following figure.

    Click here for larger image

    Handling Templated Collections

Mileage.cpp now compiles without any errors, but many problems still exist with mileagemanager.cpp. You can fix the majority of these errors quite easily, as described in the following list:

  • The IMPORT_C directive used in mileagemanager.h and the EXPORT_C directive used in mileagemanager.cpp were responsible for exporting the functions in the Symbian application. Remove these directives because you will not be exporting these functions directly, for reasons this article explains later in the section Exporting Functions.
  • Delete the E32DLL function in mileagemanager.cpp. This function is only required to support the application startup mechanisms on Symbian OS and serves no purpose on Windows Mobile.
  • The CMileageManager class inherits from CBase. Delete this inheritance because Windows Mobile has no equivalent base class for objects.
    Note   The CBase class implements an overloaded new operator that zero-initializes all member data. You must examine any C classes that you move to an eMbedded Visual C++ project and explicitly initialize member data to replace the functionality lost by eliminating the CBase class.

Most of the functionality of the CMileageManager class is concerned with the management of a list of pointers to NMileage::TMileage objects — the list of mileage records. In the Symbian C++ version of CMileageManager, the data member iMileage is declared like the following:

    private:
      // The list of mileage records (in Symbian version)
      RPointerArray<NMileage::TMileage> iMileage;

As you can see, iMileage is an RPointerArray<T> type, which is a Symbian templated collection class for storing lists of pointers.

In eMbedded Visual C++, you can use the vector object from the STL for the same functionality, so in mileagemanager.h you add #include <vector> and then declare iMileage like the following.

    private:
      // The list of mileage records (Windows Mobile version)
      std::vector<NMileage::TMileage*> iMileage;

The functions to manipulate a vector are different from those in RPointerArray<T>, so the implementation of the functions in CMileageManager changes, as is described in the following:

  • In CMileageManager::GetNextRecord(), change iMileage.Count() to iMileage.size().
  • In CMileageManager::~CMileageManager() and CMileageManager::Clear(), change iMileage.ResetAndDestroy() to the following code.
        for(int i=0; i<iMileage.size(); i++)
            {
            delete iMileage[i];
            }
        iMileage.clear();
    
  • The Symbian C++ implementation of the CMileageManager::AddMileageL(TMileage*) function is interesting, as shown in the following code.
    User::LeaveIfError(iMileage.Append(aMileage));
    

    The eMbedded Visual C++ implementation becomes the following.

    iMileage.Append->push_back();
    

    You can delete the call to the User::LeaveIfError() function. eMbedded Visual C++ does not use leaves, but instead the std::vector::push_back() function throws a C++ exception if it cannot extend the vector to add the new element. C++ exception handling provides equivalent functionality to Symbian leaves.

All Symbian developers understand that leaves are a vital component of the Symbian memory management system, so you must now look at how you can implement similarly robust memory management in an eMbedded Visual C++ application.

Replacing Leaves, the Cleanup Stack, and Two-Phase Construction

If you try to compile the application now, you will find that the only remaining errors are in the static CMileageManager::NewL() function. This function is part of the standard Symbian two-phase construction technique, as shown in the following code.

CMileageManager* CMileageManager::NewL()
    {
    CMileageManager*    self = new (ELeave) CMileageManager;
    CleanupStack::PushL(self);
    self->ConstructL();
    CleanupStack::Pop(self);
    return self;
    }

 CMileageManager::CMileageManager()
    {
    //    Default constructor must not alloc new objects
    …
    }

 void CMileageManager::ConstructL()
    {
    //    Can alloc objects in second phase constructor
    …
    }

The Symbian two-phase construction technique is a neat solution that avoids any risk of memory leaks when you construct new objects. C++ classes often contain member data that is a pointer to some other object, and on non-Symbian platforms, C++ developers often allocate the child objects in the constructor. But what happens if there is an out of memory condition half way through the constructor? Which child objects were allocated, and which ones were not? The call to new ParentObject() returns null, so now you cannot check if there are any child objects because logically you need to do the following.

ptrParent = new ParentObject();
if (ptrParent == null) // If you could not create object
{
    // Cannot do this because ptrParent is null!!
    if (ptrParent->ptrChild1 != null) 
        delete ptrParent->ptrChild1;
    …
}

The result is a memory leak.

The Symbian two-phase construction technique works in the following way: the first line of the NewL() static function creates a new CMileageManager object by using the new (ELeave) overloaded function. If there is not enough memory for the new instance of CMileageManager, it leaves. However, a leave during the initial construction of the object cannot cause a memory leak because in Symbian C++, you never allocate child objects in the default constructor; you can only initialize member variables.

The next three lines of the NewL() static function do the heavy construction work of creating this object, this time using the cleanup stack to cause the object to be cleaned up if the ConstructL() function (the second-phase constructor) leaves, as shown in the following code example.

    CleanupStack::PushL(self);
    self->ConstructL();
    CleanupStack::Pop(self);

First, push the pointer to the new CMileageManager onto the cleanup stack (CleanupStack::PushL()). Then, call the object's ConstructL() function to complete object construction. If that leaves, the Symbian memory management system discovers the object on the cleanup stack and calls its destructor, ensuring that it is cleaned up completely. If ConstructL() does not leave, remove the object from the cleanup stack again (CleanupStack::Pop()) and continue processing.

Safe Construction in eMbedded Visual C++

You can construct a similar safe construction method for eMbedded Visual C++ by using C++ exception handling and the auto_ptr templated class, as shown in the following code example.

CMileageManager* CMileageManager::NewL()
    {
    CMileageManager*    self = 0;
    self = new CMileageManager;
    if (self == 0)
        throw ("Memory allocation error creating CMileageManager");

    //Pass ownership to an auto_ptr
    std::auto_ptr<CMileageManager> safeself( self);
    safeself->ConstructL();

    // Release self - if you did not, self is deleted when the 
    // auto_ptr goes out of scope
    self = safeself.release();

        return self;
    }

The auto_ptr object stores a pointer to an allocated object and ensures that the object to which it points is destroyed automatically when control leaves a block. Therefore, it operates like the Symbian cleanup stack. If an exception is thrown inside the ConstructL() function, the stack unwinds and control returns to the caller (the NewL() function). The NewL() function does not have a try…catch block in it, so control returns to the caller of NewL(). At this point, the auto_ptr automatic variable goes out of scope, so it automatically deletes the CMileageManager object.

For the correct operation of this two-phase construction for Windows Mobile, you must throw an exception inside your ConstructL() function if there is insufficient memory to allocate child objects. By now, the observant reader will have noticed that in this example application, the CMileageManager object does not allocate any child objects, and, in fact, both the default constructor and second-phase constructor are empty! However, the implementation in this example application nicely illustrates how to write a facsimile of the Symbian two-phase construction method.

Note   If you compile this project now, you will get a linker error: an unresolved external symbol "const type_info::'vftable'. This error occurs because the Run-time Type Information library was inadvertently excluded from both the Pocket PC 2003 and the Smartphone 2003 SDK, and it is needed to support C++ exception handling. You can download the required library from Microsoft Help and Support. Even though this knowledge base article only describes support for the Pocket PC 2003 SDK; it will work fine on Windows Mobile-based Smartphones. When you have downloaded and installed the libraries, be sure to add the Ccrtrtti.lib library to the list of link libraries.

To add the Ccrtrtti.lib library

  1. In the Project Settings dialog box, click the Link tab.
  2. In the Object/Library modules box, type Ccrtrtti.lib.
  3. Click OK.

Exporting Functions

If you compile this project now, it should compile without any errors or warnings. However, this article has not yet discussed how you call into this DLL from the .NET Compact Framework GUI.

It is not easy to cross the divide between managed and unmanaged code. Although you can use a technique in the .NET Compact Framework called platform invoke that enables managed code to call functions in unmanaged code, this technique works best if the unmanaged API is a flat "C" style API, and the types that cross the boundary are simple types such as int and word, or C-style strings represented by a WCHAR*.

If this article explained the full intricacies about how to use platform invoke, this article would rapidly approach the size of a small book. So this article will give you the implementation of the exported functions you need to use to call this DLL from the managed code. If you want to discover more about platform invoke, you can read the technical articles in the MSDN Mobile and Embedded Application Developer Center.

The eMbedded Visual C++ New Project wizard helpfully created some example code that is close to what you need. After adapting the code in MileageEngine.h, you have the following.

#ifdef MILEAGEENGINE_EXPORTS
#define MILEAGEENGINE_API __declspec(dllexport)
#else
#define MILEAGEENGINE_API __declspec(dllimport)
#endif

class CMileageManager;

EXTERN_C MILEAGEENGINE_API CMileageManager* InitMileageManager(void);
EXTERN_C MILEAGEENGINE_API void DeleteMileageManager(CMileageManager* aMileageManager);

EXTERN_C MILEAGEENGINE_API void AddMileage(CMileageManager* aMileageManager, WCHAR* aDescription, int aMiles);
EXTERN_C MILEAGEENGINE_API bool GetNextRecord(CMileageManager* aMileageManager, WCHAR* aDescription, int& aMiles);
EXTERN_C MILEAGEENGINE_API void ClearMileage(CMileageManager* aMileageManager);

This code is essentially a wrapper for the CMileageManager class, containing C style functions to create a CMileageManager object (InitMileageManager()); a function to delete the CMilageManager object when you are finished with it (DeleteMileageManager()); and functions to call the CMileageManager AddMileage(), GetNextRecord(), and ClearMileage() methods.

You implement these functions in MileageEngine.cpp as follows.

// MileageEngine.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
#include "MileageEngine.h"
#include "mileagemanager.h"


BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
         )
{
    switch (ul_reason_for_call)
      {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
          break;
    }
    return TRUE;
}


// Exported function to create a CMileageManager.
EXTERN_C MILEAGEENGINE_API CMileageManager* InitMileageManager(void)
{
    return CMileageManager::NewL();
}

// Exported function to delete a CMileageManager instance.
EXTERN_C MILEAGEENGINE_API void DeleteMileageManager(CMileageManager* aMileageManager)
{
    delete aMileageManager;
}

// Exported function to create and add a new TMileage record.
EXTERN_C MILEAGEENGINE_API void AddMileage(CMileageManager* aMileageManager, WCHAR* aDescription, int aMiles)
{
    NMileage::TMileage* mileage = new NMileage::TMileage();
    mileage->SetMiles(aMiles);
    std::wstring desc(aDescription);
    mileage->SetDescription(desc);

    aMileageManager->AddMileageL(mileage);
}

// Exported function to return next TMileage record in the collection.
EXTERN_C MILEAGEENGINE_API bool GetNextRecord(CMileageManager* aMileageManager, WCHAR* aDescription, int& aMiles)
{
    bool ret = false;

    NMileage::TMileage* record = aMileageManager->GetNextRecord();
    if (record)
    {
        aMiles = record->Miles();

        size_t len = record->Description().size();
        size_t k;
        for (k = 0; k < len; ++k)
        {
            aDescription[k] = record->Description()[k];
        }
        aDescription[len] = '\0';

        ret = true;
    }
    return ret;
}

// Exported function to clear the TMileage record collection.
EXTERN_C MILEAGEENGINE_API void ClearMileage(CMileageManager* aMileageManager)
{
    aMileageManager->Clear();
}

This code example completes the work to create the unmanaged part of your hybrid application for the Windows Mobile–based Smartphone.

Implementing the GUI by Using the .NET Compact Framework

If you are working through this example, you will be pleased to know that from here on, things become easier. The Rapid Application Development (RAD) tools in Visual Studio .NET 2003 and the .NET Compact Framework class libraries make it easy to create a new GUI for your application.

To create a new Smartphone project in Visual Studio .NET 2003

  1. On the File menu, point to New, and then click Project.
  2. In the New Project dialog box, select Visual C# Projects under Project Types, and then select Smart Device Application under Templates. Type a suitable name, such as MileageGUI, and location, such as C:\MSSmartphoneGUI, as shown in the following figure. Click OK.

    Click here for larger image

    In the Smart Device Application wizard, select the Smartphone platform and the Windows Application project type. Click OK.

Visual Studio .NET 2003 creates the new project and displays the Windows Form Designer.

Creating the Application View

The Designer shows a representation of a Windows Form, which is already the correct size for display on a Windows Mobile–based Smartphone. A Windows Form is a .NET Compact Framework class that inherits from System.Windows.Forms.Form; the New Project wizard created such a class in the file Form1.cs. Think of this class as similar to the container or AppView class in a Symbian OS application that inherits, directly or indirectly, from CCoeControl.

You use the Properties window to set many of the properties of .NET Compact Framework controls. In the Properties window, set the Text property of Form1 to Mileage. If you cannot see the Properties window, click Properties Window on the View menu.

Both Symbian OS and Windows Forms applications are event-driven. You write code in event handlers to perform actions in response to events, such as the form displaying, the user entering text, or the user clicking a button. The Forms Designer helps you to create event handlers for the most common events. Double-click the background of Form1, and Visual Studio .NET 2003 opens a code window named Form1.cs — with the Form1_Load event handler created for you automatically. You will add code to this event handler later.

You add controls to the form by dragging them from the Toolbox. By default, the Toolbox appears whenever you have the Forms Designer window open; if you can't see it, click Toolbox on the View menu. Click the Form1.cs [Design] tab at the top of the Editor/Designer window to return to the Designer and then set up a ListView control.

To set up ListView controls

  1. Drag a ListView control from the Toolbox onto the form.
  2. Position the ListView control at the top-left corner of the form, so it fills the form completely.
  3. ListView is a versatile list control that can display simple lists, small and large icon lists, or data columns. You want to display two columns: one for the journey description from the mileage records, and one for the miles. To select this display mode, go to the Properties window and change the View property to Details.
  4. To define the columns, click the Columns property, and then click the ellipsis button that appears. This action causes the ColumnHeader Collection Editor dialog box to appear.
  5. Click Add to add the first column. In the Text property box for columnHeader1, type From/To, and then type 130 for the Width.
  6. Click Add again to add the second column. In the Text property box, type Miles, and then type 44 for the Width.

    Click here for larger image

    Click OK to close the ColumnHeader Collection Editor dialog box. The Designer view updates to show the ListView control with the columns defined.

The font used is a little larger than it should be, but there is no way of setting the font through the Properties window. Instead, you can set the property in code. To switch back to the code view of Form1, click the Form1.cs tab at the top of the Designer window. In the Form1_Load event handler method, type the following code.

    this.listView1.Font = new Font("Nina", 9, FontStyle.Regular);

The Designer window does not reflect this change, but the ListView control will use the smaller font at runtime.

Creating Menus

In Symbian OS GUI applications, you define the menu options assigned to the phone soft keys in a resource file, the command identifiers (IDs) that are associated with each menu option in the application .hrh file, and you code the event handlers in the AppUi file. In the .NET Compact Framework, you code all menu definitions in the form class.

One difference between Symbian GUI applications and Windows Mobile applications is that the UI guidelines for Windows Mobile–based Smartphones state that you can assign only a single option to the left soft key, but you can assign a list of options to the right soft key. (For more information, see the topic Smartphone User Interface Guidelines on MSDN). Therefore, in this application, you will create the New command on the left soft key, and on the right soft key you will have an Options menu, containing Clear and Exit commands.

To define these options, simply click the mainMenu1 control in the Designer (it is at the bottom of the screen), and then type the menu options into the placeholders on the form as shown in Figure 7.

Click here for larger image

Figure 7. Defining menus in the Menu Editor. Click the thumbnail for a larger image.

Then, to create the event handler associated with a particular menu option, double-click the option. For now, just set up the Exit menu by double-clicking the Exit menu option in the Designer, and entering the following code in the event handler.

    private void menuItem4_Click(object sender, System.EventArgs e)
        {
            this.Close();
        }

Creating Dialog Boxes

The Symbian OS version of the mileage application allows the user to use a dialog box to enter new mileage records. The dialog box is defined in the standard way for Symbian OS applications. The controls are declared in the resource file, and code implements the loading and unloading of data in the CMileageGuiForm class.

In Windows Forms applications, a dialog box is treated the same as a standard Windows Form because the System.Windows.Forms.Form class supports the ShowDialog() method that causes the form to act as a modal dialog box.

To define the dialog box

  1. On the Project menu, click Add Windows Form.
  2. In the Add New Item dialog box, type the name NewMileageForm.cs, and then click Open.
  3. In the Designer, set the Text property of the new form to New Mileage. Now drag two Label controls and two Textbox controls from the Toolbox onto the Form, and then position them as shown in the following figure.

  4. Set the MaxLength property of the top textbox to 30, and the MaxLength of the lower textbox to 5.
  5. This dialog box needs two soft keys: OK on the left soft key and Cancel on the right soft key. You can implement these commands with a menu control. Drag a MainMenu control from the Toolbox onto the form.
  6. Define the OK and Cancel commands in the menu in the same way as you did for the main form.
  7. In the event handler for the OK menu command, type the following code.
        private void menuItem1_Click(object sender, System.EventArgs e)
        {
            this.DialogResult = DialogResult.OK;
        }
    
  8. In the event handler for the Cancel menu command, type the following code.
        private void menuItem2_Click(object sender, System.EventArgs e)
        {
            this.DialogResult = DialogResult.Cancel;    
        }
    
  9. You need to ensure that any values the user types in the two TextBox controls are accessible outside the NewMileageForm class. The easiest way to ensure they are accessible is to search near the top of the code module for where the two controls are declared, and change them from private to public.
    public class NewMileageForm : System.Windows.Forms.Form
    {
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.Label label2;
        public System.Windows.Forms.TextBox textBox1; //now public
        public System.Windows.Forms.TextBox textBox2; //now public
        
    

The final touch for this dialog is to ensure that when the lower textbox has the input focus, the phone keypad keys switch to numeric mode. To make this change, you must create an event handler for the GotFocus event of the textbox.

To create an event handler for the GotFocus event

  1. In the Forms Designer, click the lower textbox to select it.
  2. Click the yellow lightning flash at the top of the Properties window. Clicking this icon reveals all of the events associated with the control.
  3. Double-click the area next to the GotFocus event, and Visual Studio .NET 2003 fills in a suitable event handler method for you, creates the code stub, and switches you back to the code editor.

There are no properties of the System.Windows.Forms.TextBox control to switch input mode. Instead, you must call out of the .NET Compact Framework to the native APIs in Windows Mobile 2003 software for Smartphones. You use platform invoke to make the call (the same technique you will be using to call functions in the native engine DLL that you created earlier), which requires you to add the following using statement to the top of the class module.

using System.Runtime.InteropServices;

Next, add the following function declarations inside the class.

    public class NewMileageForm : System.Windows.Forms.Form
    {
        public const uint EM_SETINPUTMODE = 0xDE;
        public const uint EIM_SPELL = 0;
        public const uint EIM_AMBIG = 1;
        public const uint EIM_NUMBERS = 2;

        [DllImport("coredll.dll")]
        public static extern IntPtr GetFocus();

        [DllImport("coredll.dll")]
        public static extern int SendMessage(IntPtr hWnd,
            uint Message, uint wParam, uint lParam);
        

These are static function declarations for the native functions GetFocus() and SendMessage(), which are in the Windows Mobile operating system DLL, coredll.dll. Inside the textBox2_GotFocus() event handler, add the following code, which calls the native methods to set the input mode for the phone keypad buttons.

    private void textBox2_GotFocus(object sender, System.EventArgs e)
    {
        IntPtr hWnd;
        hWnd = GetFocus();
        SendMessage(hWnd, EM_SETINPUTMODE, 0, EIM_NUMBERS);
        textBox2.SelectionStart = textBox2.Text.Length;
    }

Finally, you must add the code to the main form to call the dialog. Create an event handler for the New menu option on Form1 that contains the following code.

    private void menuItem1_Click(object sender, System.EventArgs e)
    {
        NewMileageForm frm = new NewMileageForm();
        if (frm.ShowDialog() == DialogResult.OK)
        {
            // Save the new mileage record
            AddMileage(mileageManager, frm.textBox1.Text,
                             Int32.Parse(frm.textBox2.Text));

            // Reload records from Model
            UpdateListFromModel();
        }
    }

This code creates a new instance of the NewMileageForm and displays it by using the ShowDialog() method. When the user presses a soft key on the NewMileageForm, the event handler code for the menu commands on that form sets the return value of the form to DialogResult.OK or DialogResult.Cancel, depending on which button the user pressed, and then closes the NewMileageForm instance.

If the result is DialogResult.OK, the previous code calls the AddMileage() function; AddMileage() is a function in your eMbedded Visual C++ DLL that you created earlier. You can call it by using platform invoke.

Linking the GUI to the Engine

On the Project menu, click Add Existing Item. Browse to the directories where you created the eMbedded Visual C++ DLL, and then locate MileageEngine.dll in the \emulatorDbg subdirectory. Click Open. Now the native DLL that you created earlier deploys with the .NET Compact Framework application when you run this application.

Add the following two using statements to the top of Form1.cs.

using System.Text;
using System.Runtime.InteropServices; 

Now add the following function declarations inside the class Form1.

    [DllImport("MileageEngine.dll")]
    public static extern IntPtr InitMileageManager();
    [DllImport("MileageEngine.dll")]
    public static extern void DeleteMileageManager(IntPtr aManager);
    [DllImport("MileageEngine.dll")]
    public static extern void AddMileage(IntPtr aManager, 
        string aDescription, int aMiles);
    [DllImport("MileageEngine.dll")]
    public static extern bool GetNextRecord(IntPtr aManager, 
        StringBuilder aDescription,ref int aMiles);
    [DllImport("MileageEngine.dll")]
    public static extern void ClearMileage(IntPtr aManager);

You must store the address of the native MileageManager object when you create it. The .NET object used to store a reference to a native object that you create from managed code is an IntPtr, so add the following private data member to the Form1 class.

    private IntPtr mileageManager;

Add the following statement, which creates the MileageManager object in the native DLL, to the Form1_Load() event handler.

    mileageManager = InitMileageManager();

Add the following statement before the this.Close() statement in menuItem4_Click(), which is the event handler for the Close menu command.

    DeleteMileageManager(mileageManager);

Next, in the Forms Designer, double-click the Clear menu command to create the event handler. Add the following code.

        private void menuItem3_Click(object sender, System.EventArgs e)
        {
            ClearMileage(mileageManager);
            // Reload records from Model
            UpdateListFromModel();
        }

The final step is to create the UpdateListFromModel() method, which calls out to the native DLL to retrieve details of records that the model is storing.

        private void UpdateListFromModel()
        {
            // Update the listview from the model
            this.listView1.Items.Clear();
            int miles = 0;
            StringBuilder desc = new StringBuilder(30);
            while (GetNextRecord(mileageManager, desc, ref miles))
            {
                string[] items = new string[] 
                    {desc.ToString(), miles.ToString()};
                this.listView1.Items.Add(new ListViewItem(items));
            }
        }

On the Debug menu, click Start, and if all is well, the application compiles, and you are prompted for the destination device.

Note   It is likely that the Smartphone 2003 emulator is already running from your eMbedded Visual C++ development session. If it is, close it before continuing because the eMbedded Visual C++ emulator and the Visual Studio .NET 2003 emulators are different — only one can run at a time.

In the Deploy MileageGUI dialog box, select Smartphone 2003 (Virtual radio) and click Deploy. You are now able to use the Windows Mobile 2003 platform for the Smartphone version of the Mileage application on the Smartphone 2003 emulator, as shown in Figure 9.

Click here for larger image

Figure 9. The completed application running on Windows Mobile 2003 for Smartphone. Click the thumbnail for a larger image.

Conclusion

This article has demonstrated that, in some circumstances, it is possible to retain the class design of an engine component of a Symbian OS application and to modify the classes to work in a native DLL that you build by using eMbedded Visual C++. You can build a new GUI by using the .NET Compact Framework that calls the native DLL.

The application used in this exercise was very simple, and, in this particular case, it would have been quicker to re-implement the whole application as a .NET Compact Framework application written in Visual C#. It is likely that this technique for migrating application code from Symbian OS to Windows Mobile 2003 for Smartphones is only of use to a very small number of more complex commercial applications.

However, a major goal of this article was to inform Symbian OS developers how to develop applications for Windows Mobile 2003 software for Smartphones. If you are a Symbian OS developer who is considering developing class libraries for Windows Mobile by using eMbedded Visual C++, you should have learned much from the comparison between Symbian OS C++ code and its eMbedded Visual C++ equivalent. If you are considering developing applications by using the .NET Compact Framework, you should have learned how to take advantage of the RAD tools in Visual Studio .NET 2003 to easily implement the common components of a GUI application by using Windows Forms. The benefits of GUI application development by using the .NET Compact Framework require much less effort and much less code than is needed to implement the same functionality in a Symbian OS C++ application.

Page view tracker