Wednesday, September 10, 2008

Active Objects of Symbian - in the lights of Client-Server Architecture


When I started understanding the Active Object framework in Symbian, it was difficult for me to grasp the idea of how the front-end UI is not frozen, in the event of the long-running task, even when we don't use multiple threads. But when I started understanding the active object framework in conjunction with the client-server architecture of Symbian, I got the idea. I would like to share it with you.

Before we start, i want to throw some lights on the definition of Asynchronous Service Provider and Asynchronous functions in Symbian Active Object and Client-Server architecture. Asynchronous Service Providers are the functions which are fired from a client and returns immediately in that client. However, this function causes another function in the server to run. This server function may be blocking in server side. So we get two sides of a function. We get a non-blocking function in the client-side which initiates another function in the server side, which may be blocking, in the server side. Once the function in the server side finishes, it notifies the client side (which actually initiates the whole chain) about this. These asynchronous functions have TRequestStatus as one of the parameters which is passed as a reference from the client to the server.

These are all theoretical aspects of the Active object and Client-Server framework. Let me give you one practical scenario where this can be used. In this context i would also like to share what actually happens in a normal multi threaded application.

Suppose, we have a communication port which is continuously receiving data from an external source. Suppose we need to develop an application which will read the data and at the same time will render that data in some short of graphical form. So how is it possible?

In symbian environment, we can realize this scenario by employing the Active object framework in conjunction with the client-server architecture. Let me explain it in more details. We may delegate the task of reading the data from the communication port to a server application which will run as a different process than the front end UI application. Of course there are such functionalities already developed in the Symbian and related frameworks. But just for the sake of explanation, we will have to create an asynchronous function in the server which will actually read the data from the communication port. So in the server this function will be blocking. However, we may create an UI application using the active object framework, which will initiate this call to the server and will immediately return in the UI application (non-blocking). The moment, the server finishes reading certain amount of data (say 1024 Kb) and copying it to the UI application's memory area, (remember the UI application and the server are across the process boundary), it will notify the front end UI about this and again start reading the data from the port in the background. On the other hand, the UI application will render the data in the graphical format.

Now let me tell you how this is possible in an Windows application. Once I had developed one such application using the Windows asynchronous I/O pattern and windows event mechanism. There the task of reading of the data  from the communication port was delegated to a background thread. The background thread used to read the data and when it finishes some specific amount it used to fire an event to the front end UI, which then rendered the data in the front end UI.

This is all about theories and scenarios. Now, let me give you a real life symbian code which has got two applications. One is an UI application which has got an engine. This engine is working as an active object for this UI application. Then we have a Symbian Client-Server application. This application has got a client side interface which is a DLL and a Server side implementation which is developed as an EXE. The server has an Asynchronous function which will eventually be called from the UI application.The UI application is linked with the dynamic link library (DLL) of the client-server application.

Let us start from the client-server application. The client has just three exported functions. One is an Asynchronous function and the other if we cancel that function. And the third one is to connect to the server. It looks like the following:


//client.h

class RClient : public RSessionBase
    {
public:
    IMPORT_C TInt Connect();
    IMPORT_C void GetNotifiedWhenEventCompleted(TInt aCount, TDes& aData, TRequestStatus& aStatus);
    IMPORT_C void CancelGetNotifiedWhenEventCompleted();
    };



The implementation of the client looks like the following:


// client.cpp

#include "client.h"
#include "client-server.h"
#include <e32math.h>

// Runs client-side and starts the separate server process
static TInt StartTheServer()
    {
    RProcess server;
    TInt r=server.Create(KServerBinaryName, KNullDesC);
    if (r!=KErrNone)
        return r;
   
    TRequestStatus stat;
    server.Rendezvous(stat);
    if (stat!=KRequestPending)
        server.Kill(0);        // abort startup
    else
        server.Resume();    // logon OK - start the server
   
    User::WaitForRequest(stat);        // wait for start or death
   
    // Check the exit type.
    // We can't use the 'exit reason' because if the server panicked this
    // is set to the panic 'reason' (which may be '0' and cannot thus be distinguished
    // from KErrNone)
    r = server.ExitType();
    if (EExitPanic==r)
        r = KErrGeneral;
    else
        r = stat.Int();
   
    server.Close(); // This is no longer needed
    return r;
    }



EXPORT_C TInt RClient::Connect()
    {
    TInt retry=2;
    for (;;)
        {// Uses system-pool message slots
        TInt r=CreateSession(KServerName,TVersion(1,0,0));
        if ( (KErrNotFound!=r) && (KErrServerTerminated!=r) )
            return (r);
        if (--retry==0)
            return (r);
        r=StartTheServer();
        if ( (KErrNone!=r) && (KErrAlreadyExists!=r) )
            return (r);
        }
    }

EXPORT_C void RClient::GetNotifiedWhenEventCompleted(TInt aCount, TDes& aData, TRequestStatus& aStatus)
    {
    TIpcArgs args(aCount,&aData);
    SendReceive(EGetNotifiedWhenEventCompleted, args, aStatus);
    }
EXPORT_C void RClient::CancelGetNotifiedWhenEventCompleted()
    {
    SendReceive(ECancelGetNotifiedWhenEventCompleted, TIpcArgs());
    }


These are pretty staright forward. I am not going in details about the client server architecture of a symbian application. I am more interested in explaining you about how an asynchrounous function is handled in the client-server and active object framework.

Now let me focus on the server side implementation of this application.

The server header file looks like the following:


// server.h
#ifndef __SERVER_H__
#define __SERVER_H__

#include <e32base.h>
#include "client-server.h"

enum TServerPanic
    {
    EPanicBadDescriptor,
    EPanicNotSupported
    };

void PanicClient(const RMessage2& aMessage,TServerPanic TMyPanic);

const TInt KShutdownDelay=200000; // approx 2 seconds
class CShutdown : public CTimer
    {
public:
    inline CShutdown();
    inline void ConstructL();
    inline void Start();
private:
    void RunL();
    };


inline CShutdown::CShutdown()
    :CTimer(-1)
    {CActiveScheduler::Add(this);}
inline void CShutdown::ConstructL()
    {CTimer::ConstructL();}
inline void CShutdown::Start()
    {After(KShutdownDelay);}


class CMyServer : public CServer2
    {
public:
    static CServer2* NewLC();
    void AddSession();
    void RemoveSession();
private:
    CMyServer();
    void ConstructL();
    // From CServer2
    virtual CSession2* NewSessionL(const TVersion& aVersion,const RMessage2& aMessage) const;
private:
    TInt iSessionCount;
    CShutdown iShutdown;
    };


inline CMyServer::CMyServer()
    :CServer2(CActive::EPriorityStandard)
    {}


class CAsyncHandler; // Active object class for asynchronous requests

class CMyServerSession : public CSession2
    {
public:
    CMyServerSession();
    void CreateL();
public:
    virtual void ServiceL(const RMessage2& aMessage); // From CSession2
    virtual void ServiceError(const RMessage2& aMessage, TInt aError); // From CSession2
private:
    void GetNotifiedWhenEventCompletedL(const RMessage2& aMessage);
    void CancelGetNotifiedWhenEventCompletedL(const RMessage2& aMessage);
private:
    ~CMyServerSession();
private:
    CAsyncHandler* iAsyncRequestHandler;
    HBufC8* iClientBuf;
    };

inline CMyServerSession::CMyServerSession()
    {}


// Skeleton active object, for asynchronous server requests
// Uses a very basic timer for asynchronicity

class CAsyncHandler : public CActive
    {
public:
    static CAsyncHandler* NewL();
    static CAsyncHandler* NewLC();
    ~CAsyncHandler();
public:
    void ServiceAsyncRequest(const RMessage2& aMessage);
protected:
    CAsyncHandler();
    void ConstructL();
private:
    void DoCancel();
    void RunL();
private:
    RTimer iTimer;
    RMessage2 iMessage;
    };


#endif //__SERVER_H__



And the server implementation file looks like the following:

// server.cpp

#include "server.h"
#include <e32base.h>

_LIT(KDesMsgToServer, "To Server;");
// Called by the CServer framework
void CMyServerSession::CreateL()
    {
    CMyServer* server = static_cast<CMyServer*>(const_cast<CServer2*>(Server()));
    ASSERT(server);
    server->AddSession();
    iAsyncRequestHandler = CAsyncHandler::NewL();
    }

CMyServerSession::~CMyServerSession()
    {
    CMyServer* server = static_cast<CMyServer*>(const_cast<CServer2*>(Server()));
    ASSERT(server);
    server->RemoveSession();
    delete iAsyncRequestHandler;
    delete iClientBuf;
    }

// A bad descriptor error implies a badly programmed client, so panic it
// Report other errors to the client by completing the outstanding request with the error

void CMyServerSession::ServiceError(const RMessage2& aMessage, TInt aError)
    {
    if (KErrBadDescriptor==aError)
        PanicClient(aMessage,EPanicBadDescriptor);
    else
        aMessage.Complete(aError);
    }

// Handle a client request
void CMyServerSession::ServiceL(const RMessage2& aMessage)
    {
    switch (aMessage.Function())
        {
        case EGetNotifiedWhenEventCompleted:
            GetNotifiedWhenEventCompletedL(aMessage);
            break;
           
        case ECancelGetNotifiedWhenEventCompleted:
            CancelGetNotifiedWhenEventCompletedL(aMessage);
            break;
        default:
            PanicClient(aMessage,EPanicNotSupported);
            break;
        }
    }

// Asynchronous method
// message slot 0 contains TInt
// message slot 1 contains TDes8&
void CMyServerSession::GetNotifiedWhenEventCompletedL(const RMessage2& aMessage)
    {
        TInt val0 = aMessage.Int0();
        if (val0!=10)
            aMessage.Complete(KErrGeneral);

        // Determine the length of the client descriptor passed to the server
        TInt clientDesMaxLen = aMessage.GetDesMaxLength(1);
        if (iClientBuf)
        {
            delete iClientBuf;
            iClientBuf = NULL;
        }
  
    //    Make an asynchronous request
    //    for the purpose of example here don't worry about passing the
    //    descriptor retrieved above or modifying it
        iAsyncRequestHandler->ServiceAsyncRequest(aMessage);
    //    iClientBuf is destroyed by later call to this method or destructor
    }

void CMyServerSession::CancelGetNotifiedWhenEventCompletedL(const RMessage2& aMessage)
    {
//    Calls Cancel() on the CAsyncHandler active object
//    which must complete the outstanding async activity and complete
//    the original client-server request
    iAsyncRequestHandler->Cancel();   
    aMessage.Complete(KErrNone);
    }

CServer2* CMyServer::NewLC()
    {
    CMyServer* self=new(ELeave) CMyServer;
    CleanupStack::PushL(self);
    self->ConstructL();
    return self;
    }

// Starts the server and constructs the shutdown object, starting the timer to ensure that
// the server will exit even if the starting client connection fails
void CMyServer::ConstructL()
    {
    StartL(KServerName);
    iShutdown.ConstructL();
    iShutdown.Start();
    }

// Example doesn't bother checking the version
CSession2* CMyServer::NewSessionL(const TVersion& /*aVersion*/,const RMessage2& /*aMessage*/) const
    {
    return new(ELeave) CMyServerSession();
    }

// Cancel the shutdown timer now, the new session is connected
void CMyServer::AddSession()
    {
    ++iSessionCount;
    iShutdown.Cancel();  // Cancel any outstanding shutdown timer
    }

// Decrement the session counter and start the shutdown timer if the last client has disconnected
void CMyServer::RemoveSession()
    {
    if (--iSessionCount==0)
        iShutdown.Start();
    }


// Initiates server exit when the timer expires
void CShutdown::RunL()
    {
    CActiveScheduler::Stop();
    }

void PanicClient(const RMessage2& aMessage,TServerPanic aPanic)
    {
    _LIT(KPanic,"MyServer");
    aMessage.Panic(KPanic,aPanic);
    }

// Initialize and run the server
static void RunTheServerL()
    {// First create and install the active scheduler
    CActiveScheduler* scheduler = new (ELeave) CActiveScheduler;
    CleanupStack::PushL(scheduler);
    CActiveScheduler::Install(scheduler);

    // create the server
    CMyServer::NewLC();
   
    // Naming the server thread after the server helps to debug panics
    User::LeaveIfError(User::RenameThread(KServerName));
   
    RProcess::Rendezvous(KErrNone);

    // Enter the wait loop
    CActiveScheduler::Start();
   
    // Exited - cleanup the server and scheduler
    CleanupStack::PopAndDestroy(2, scheduler);
    }

// Server process entry-point
TInt E32Main()
    {
    __UHEAP_MARK; // Heap checking
   
    CTrapCleanup* cleanup=CTrapCleanup::New();
    TInt r=KErrNoMemory;
    if (cleanup)
        {
        TRAP(r,RunTheServerL());
        delete cleanup;
        }
    __UHEAP_MARKEND;
    return r;
    }



The class CAsyncHandler has been implemented as the following:


// asynchandler.cpp

// Skeleton active object, for asynchronous server requests

#include <e32base.h>
#include "server.h"

CAsyncHandler* CAsyncHandler::NewL()
    {
    CAsyncHandler* me = CAsyncHandler::NewLC();
    CleanupStack::Pop(me);
    return (me);
    }

CAsyncHandler* CAsyncHandler::NewLC()
    {
    CAsyncHandler* me = new (ELeave) CAsyncHandler();
    CleanupStack::PushL(me);
    me->ConstructL();
    return (me);
    }

CAsyncHandler::~CAsyncHandler()
    {
    Cancel();
    iTimer.Close();   
    }

CAsyncHandler::CAsyncHandler()
: CActive(EPriorityStandard)
    {
    CActiveScheduler::Add(this);
    }

void CAsyncHandler::ConstructL()
    {
    User::LeaveIfError(iTimer.CreateLocal());
    }

void CAsyncHandler::ServiceAsyncRequest(const RMessage2& aMessage)
    {// Only allow one request to be submitted at a time
    _LIT(KOutstandingRequestPanic, "InUse");
    __ASSERT_ALWAYS(!IsActive(), User::Panic(KOutstandingRequestPanic, KErrInUse));
    iMessage = aMessage;
    iTimer.After(iStatus, 2000000); // Set the RTimer to expire in 2 seconds
    SetActive(); // Mark this object active   
    }

void CAsyncHandler::DoCancel()
    {
    iTimer.Cancel();
    iMessage.Complete(KErrCancel);
    }

void CAsyncHandler::RunL()
    {
    iMessage.Complete(iStatus.Int());
    }



The client-server.h file looks like the following:

// client-server.h
#ifndef CLIENTSERVER_H__
#define CLIENTSERVER_H__

#include <e32std.h>
#include <s32std.h>

_LIT(KServerName,"TestServer");// The server's identity within the client-server framework
_LIT(KServerBinaryName,"server"); // The name of the server binary (dll or exe)

#ifdef __WINS__
const TInt KMinServerHeapSize=0x1000;
const TInt KMaxServerHeapSize=0x1000000;
#endif

enum TClientServerCommands
    {
    EGetNotifiedWhenEventCompleted,
    ECancelGetNotifiedWhenEventCompleted
    };

#endif // CLIENTSERVER_H__


So this is all about the client-server application of the example. This client-server application will provide the basis of the Asynchronous signal handling in the Active Object framework.

Please see the function void CMyServerSession::GetNotifiedWhenEventCompletedL(const RMessage2& aMessage).

Towards the end of this function we are calling iAsyncRequestHandler->ServiceAsyncRequest(aMessage). Please remember I am not interested what the server is doing with the data that the client passes to it. I am more interested in explaining to you how the Asynchronous function is being handled by the server and in turn how it signals the Front-end UI.

If we delve into the function


void CAsyncHandler::ServiceAsyncRequest(const RMessage2& aMessage)
    {// Only allow one request to be submitted at a time
    _LIT(KOutstandingRequestPanic, "InUse");
    __ASSERT_ALWAYS(!IsActive(), User::Panic(KOutstandingRequestPanic, KErrInUse));
    iMessage = aMessage;
    iTimer.After(iStatus, 2000000); // Set the RTimer to expire in 2 seconds
    SetActive(); // Mark this object active   
    }


we will find that it initiates a delay (iTimer.After (iStatus, 2000000) and at the end it calls SetActive which initiates the Active Scheduler of the server to call the RunL().

This RunL() function is implemented as the following:
<code>
void CAsyncHandler::RunL()
    {
    iMessage.Complete(iStatus.Int());
    }


We find that it calls Complete on the iMessage. This function Complete actually initiates an inter process communication and signals the front end UI application about the completion of the server-side task.

Now let us concentrate on the front end UI application. This is a simple UI helloworld application created through the carbide c++ wizard. This application has got an engine which looks like the following:

//engine.h

#ifndef __ENGINE_h__
#define __ENGINE_h__

#include <e32base.h>

#include "../../clientserver/inc/client.h"

//forward declaration
class CActiveObjectTestAppView;


class MObserver
    {
public:
    virtual void CallbackFunction_BeginProcessing() = 0;
   
    virtual void CallbackFunction_EndProcessing() = 0;
    };


class CEngine : public CActive
    {
public:
    ~CEngine();
   
    static CEngine* NewL(MObserver& aObserver);
   
private:
    //construction
    CEngine(MObserver& aObserver);
    void ConstructL();
 
public:
   
    //from CActive
    virtual void RunL();
    virtual void DoCancel();
   
    //new function.. Asynchronous service provider
    void MakeAsyncCall(TRequestStatus& aStatus);
   
private:
    //not owned
    MObserver& iObserver;
    RClient session;
    };

#endif



As you can see, this is working as an active object for the Front end UI. There is another M class called MObserver which provides the necessary call back functionalities for this UI application. This MObserver class will be realized by the CActiveObjectTestAppUi class of the application.

The engine class implementation will look like the following:

//engine.cpp

//#include <e32base.h>

#include "engine.h"
#include "ActiveObjectTestAppView.h"
//#include "client.h"

_LIT(KDesMsgToServer, "To Server;");

CEngine::CEngine(MObserver& aObserver):CActive(CActive::EPriorityStandard),iObserver(aObserver)
    {
    }

CEngine::~CEngine()
    {
    Cancel();
    }

void CEngine::ConstructL()
    {
    CActiveScheduler::Add(this);
    TInt ret = session.Connect();
    User::LeaveIfError(ret);
    }

CEngine* CEngine::NewL(MObserver& aObserver)
    {
    CEngine* self = new(ELeave) CEngine(aObserver);
    CleanupStack::PushL(self);
    self->ConstructL();
    CleanupStack::Pop(self);
    return self;
    }

void CEngine::MakeAsyncCall(TRequestStatus& aStatus)
    {
    TBuf<20> myBuf(KDesMsgToServer);
    session.GetNotifiedWhenEventCompleted(10, myBuf, aStatus);
    iObserver.CallbackFunction_BeginProcessing();
    SetActive();
    }

void CEngine::RunL()
    {
    iObserver.CallbackFunction_EndProcessing();
    }

void CEngine::DoCancel()
    {
    Cancel();
    }


As this is clear the task of the engine is to establish a connection with the client-server application and issue an asynchronous call through its MakeAsyncCall(TRequestStatus& aStatus) function.

Now if you look into the MakeAsyncCall function, we will find that after issuing the exported function GetNotifiedWhenEventCompleted of the client interface of the client-server application, it is calling the Observer's CallbackFunction_BeginProcessing(). This function actually makes the UI look responding with some message. In the example application I have rendered the text in the view as "Beginning...".

So, now the processing in the server of the client-server application has started. As in the server side CAsyncHandler::ServiceAsyncRequest function it creates some delay and then signals active scheduler of the fron end UI application. This active scheduler will then call the RunL function. Here this RunL() function simply refreshes the screenwith the message "End...".

Hence you will find two messages in the UI application - "Beginning..." and after some delay "End...". Thus the UI looks responding when thw server does some background task.The application will look like the following:







Let me show you how the AppUi class has been created in the application. It looks like the following:


/*
 ============================================================================
 Name        : ActiveObjectTestAppUi.h
 Author      : som
 Copyright   : Your copyright notice
 Description : Declares UI class for application.
 ============================================================================
 */

#ifndef __ACTIVEOBJECTTESTAPPUI_h__
#define __ACTIVEOBJECTTESTAPPUI_h__

// INCLUDES
#include <aknappui.h>
#include "engine.h"

// FORWARD DECLARATIONS
class CActiveObjectTestAppView;
class CEngine;
// CLASS DECLARATION
/**
 * CActiveObjectTestAppUi application UI class.
 * Interacts with the user through the UI and request message processing
 * from the handler class
 */
class CActiveObjectTestAppUi : public CAknAppUi, public MObserver
    {
public:
    // Constructors and destructor

    /**
     * ConstructL.
     * 2nd phase constructor.
     */
    void ConstructL();

    /**
     * CActiveObjectTestAppUi.
     * C++ default constructor. This needs to be public due to
     * the way the framework constructs the AppUi
     */
    CActiveObjectTestAppUi();

    /**
     * ~CActiveObjectTestAppUi.
     * Virtual Destructor.
     */
    virtual ~CActiveObjectTestAppUi();

private:
    // Functions from base classes

    /**
     * From CEikAppUi, HandleCommandL.
     * Takes care of command handling.
     * @param aCommand Command to be handled.
     */
    void HandleCommandL(TInt aCommand);

    /**
     *  HandleStatusPaneSizeChange.
     *  Called by the framework when the application status pane
     *  size is changed.
     */
    void HandleStatusPaneSizeChange();

    /**
     *  From CCoeAppUi, HelpContextL.
     *  Provides help context for the application.
     *  size is changed.
     */
    CArrayFix<TCoeHelpContext>* HelpContextL() const;
   
    CEngine* GetEnginePtr();
   
    //from MObserver
    void CallbackFunction_BeginProcessing();
   
    void CallbackFunction_EndProcessing();
       

private:
    // Data

    /**
     * The application view
     * Owned by CActiveObjectTestAppUi
     */
    CActiveObjectTestAppView* iAppView;
   
    //The engine is owned by CActiveObjectTestAppUi
    CEngine* iEngine;

    };

#endif // __ACTIVEOBJECTTESTAPPUI_h__
// End of File



And the implementation looks like the following:

/*
 ============================================================================
 Name        : ActiveObjectTestAppUi.cpp
 Author      : som
 Copyright   : Your copyright notice
 Description : CActiveObjectTestAppUi implementation
 ============================================================================
 */

// INCLUDE FILES
#include <avkon.hrh>
#include <aknmessagequerydialog.h>
#include <aknnotewrappers.h>
#include <stringloader.h>
#include <f32file.h>
#include <s32file.h>
#include <hlplch.h>

#include <ActiveObjectTest_0xEB0B3448.rsg>

//#include "ActiveObjectTest_0xEB0B3448.hlp.hrh"
#include "ActiveObjectTest.hrh"
#include "ActiveObjectTest.pan"
#include "ActiveObjectTestApplication.h"
#include "ActiveObjectTestAppUi.h"
#include "ActiveObjectTestAppView.h"
//#include "engine.h"
_LIT(KFileName, "C:\private\EB0B3448\ActiveObjectTest.txt");
_LIT(KText, "Active Object Test");
_LIT(KBeginningProcessingText, "Beginning...");
_LIT(KEndProcessingText, "End...");
// ============================ MEMBER FUNCTIONS ===============================


// -----------------------------------------------------------------------------
// CActiveObjectTestAppUi::ConstructL()
// Symbian 2nd phase constructor can leave.
// -----------------------------------------------------------------------------
//
void CActiveObjectTestAppUi::ConstructL()
    {
    // Initialise app UI with standard value.
    BaseConstructL(CAknAppUi::EAknEnableSkin);

    // Create view object
    iAppView = CActiveObjectTestAppView::NewL(ClientRect() );

    // Create a file to write the text to
    TInt err = CCoeEnv::Static()->FsSession().MkDirAll(KFileName);
    if ( (KErrNone != err) && (KErrAlreadyExists != err))
        {
        return;
        }

    RFile file;
    err = file.Replace(CCoeEnv::Static()->FsSession(), KFileName, EFileWrite);
    CleanupClosePushL(file);
    if (KErrNone != err)
        {
        CleanupStack::PopAndDestroy(1); // file
        return;
        }

    RFileWriteStream outputFileStream(file);
    CleanupClosePushL(outputFileStream);
    outputFileStream << KText;

    CleanupStack::PopAndDestroy(2); // outputFileStream, file
   
    //instantiation of the engine
    iEngine = CEngine::NewL(*this);

    }
// -----------------------------------------------------------------------------
// CActiveObjectTestAppUi::CActiveObjectTestAppUi()
// C++ default constructor can NOT contain any code, that might leave.
// -----------------------------------------------------------------------------
//
CActiveObjectTestAppUi::CActiveObjectTestAppUi()
    {
    // No implementation required
    }

// -----------------------------------------------------------------------------
// CActiveObjectTestAppUi::~CActiveObjectTestAppUi()
// Destructor.
// -----------------------------------------------------------------------------
//
CActiveObjectTestAppUi::~CActiveObjectTestAppUi()
    {
    if (iAppView)
        {
        delete iAppView;
        iAppView = NULL;
        }
    }

// -----------------------------------------------------------------------------
// CActiveObjectTestAppUi::HandleCommandL()
// Takes care of command handling.
// -----------------------------------------------------------------------------
//
void CActiveObjectTestAppUi::HandleCommandL(TInt aCommand)
    {
    switch (aCommand)
        {
        case EEikCmdExit:
        case EAknSoftkeyExit:
            Exit();
            break;

        case ECommand1:
            {
            //Here we are calling the Asynchronous function
            iEngine->MakeAsyncCall(GetEnginePtr()->iStatus);
            }
            break;

        //The rest of the function has not been touched. its the default created by the wizard
        case ECommand2:
            {
           
            RFile rFile;

            //Open file where the stream text is
            User::LeaveIfError(rFile.Open(CCoeEnv::Static()->FsSession(), KFileName, EFileStreamText));//EFileShareReadersOnly));// EFileStreamText));
            CleanupClosePushL(rFile);

            // copy stream from file to RFileStream object
            RFileReadStream inputFileStream(rFile);
            CleanupClosePushL(inputFileStream);

            // HBufC descriptor is created from the RFileStream object.
            HBufC* fileData = HBufC::NewLC(inputFileStream, 32);

            CAknInformationNote* informationNote;

            informationNote = new ( ELeave ) CAknInformationNote;
            // Show the information Note
            informationNote->ExecuteLD( *fileData);

            // Pop loaded resources from the cleanup stack
            CleanupStack::PopAndDestroy(3); // filedata, inputFileStream, rFile
            }
            break;
        case EHelp:
            {
           
            CArrayFix<TCoeHelpContext>* buf = CCoeAppUi::AppHelpContextL();
            HlpLauncher::LaunchHelpApplicationL(iEikonEnv->WsSession(), buf);
            }
            /*
            TRequestStatus status;
            iAppView->iEngine->MakeAsyncCall(status);
            */
            break;
        case EAbout:
            {
           
            CAknMessageQueryDialog* dlg = new (ELeave)CAknMessageQueryDialog();
            dlg->PrepareLC(R_ABOUT_QUERY_DIALOG);
            HBufC* title = iEikonEnv->AllocReadResourceLC(R_ABOUT_DIALOG_TITLE);
            dlg->QueryHeading()->SetTextL(*title);
            CleanupStack::PopAndDestroy(); //title
            HBufC* msg = iEikonEnv->AllocReadResourceLC(R_ABOUT_DIALOG_TEXT);
            dlg->SetMessageTextL(*msg);
            CleanupStack::PopAndDestroy(); //msg
            dlg->RunLD();
            }
            /*
            TRequestStatus status;
            iAppView->iEngine->MakeAsyncCall(status);
            break;
            */
            break;
        default:
            Panic(EActiveObjectTestUi);
            break;
        }
    }
// -----------------------------------------------------------------------------
//  Called by the framework when the application status pane
//  size is changed.  Passes the new client rectangle to the
//  AppView
// -----------------------------------------------------------------------------
//
void CActiveObjectTestAppUi::HandleStatusPaneSizeChange()
    {
    iAppView->SetRect(ClientRect() );
    }

CArrayFix<TCoeHelpContext>* CActiveObjectTestAppUi::HelpContextL() const
    {
#warning "Please see comment about help and UID3..."
    // Note: Help will not work if the application uid3 is not in the
    // protected range.  The default uid3 range for projects created
    // from this template (0xE0000000 - 0xEFFFFFFF) are not in the protected range so that they
    // can be self signed and installed on the device during testing.
    // Once you get your official uid3 from Symbian Ltd. and find/replace
    // all occurrences of uid3 in your project, the context help will
    // work.
    CArrayFixFlat<TCoeHelpContext>* array = new(ELeave)CArrayFixFlat<TCoeHelpContext>(1);
    CleanupStack::PushL(array);
    //array->AppendL(TCoeHelpContext(KUidActiveObjectTestApp,
            //KGeneral_Information));
    CleanupStack::Pop(array);
    return array;
    }

//Gets a pointer to the engine object
CEngine* CActiveObjectTestAppUi::GetEnginePtr()
    {
    return iEngine;
    }
void CActiveObjectTestAppUi::CallbackFunction_BeginProcessing()
    {
    iAppView->UpdateScreenText(KBeginningProcessingText);
    }

void CActiveObjectTestAppUi::CallbackFunction_EndProcessing()
    {
    iAppView->UpdateScreenText(KEndProcessingText);
    }

// End of File



Please look at the void CActiveObjectTestAppUi::HandleCommandL(TInt aCommand) funnction.

Go to case ECommand1:

Here it becomes clear how we call the Asynchronous function through the menu commands.

I have changed the Draw function of the View class as following:


void CActiveObjectTestAppView::Draw(const TRect& /*aRect*/) const
    {
    // Get the standard graphics context
    CWindowGc& gc = SystemGc();

    // Gets the control's extent
    TRect drawRect(Rect());

    // Clears the screen
    gc.Clear(drawRect);
   
    const CFont* font;
    font = iEikonEnv->TitleFont();
   
    gc.UseFont(font);
   
    TInt baseLineOffset = drawRect.Height()/2;
   
    gc.DrawText(iScreenText,drawRect, baseLineOffset, CGraphicsContext::ECenter,0);
   
    }



There is another function added in the view class which is as follows:

void CActiveObjectTestAppView::UpdateScreenText(const TDesC16& msg)
    {
    iScreenText.Copy(msg);
    DrawNow();
    }


So let me recapitulate the basic functionalities of the Active object, Asynchronous function and Client-Server framework.
The steps are as follows;

  1. The front end UI calls some asynchronous function of the engine through its menu command
  2. The Observer's callback function is called to notify the UI about this state
  3. The engine delegates the task to a background server application
  4. The server finishes this task asynchronously and signals the Active Scheduler of the UI application through inter process communication
  5. The Active Scheduler of the UI application calls the RunL() function
  6. Inside this RunL() function we update the Screen again stating the status of the task

Actually we implement this RunL() function of the UI to implement a state pattern wherein we call different Asynchronous functions of the server in each state and update the UI accordingly.

There is another point that I want to touch about. See how the cyclic reference between two concrete classes of the front end UI (CEngine and the CActiveObjectTestAppUi) has been implemented through an M class (MObserver).

I hope I am able to throw some lights on the haziness of Active Object and Client-Server Architecture. If this helps people, specifically the newbies of Symbian, I will feel good.

The screen capture of the application will look like the following:





Reference: The book for Accredited Symbian Developer exam and the associated code


Note: Here goes the source code of the full application...