First submission to Symbian Foundation staging server.
// SensibleServer.cpp// // Copyright (c) 2006 - 2010 Accenture. All rights reserved.// This component and the accompanying materials are made available// under the terms of the "Eclipse Public License v1.0"// which accompanies this distribution, and is available// at the URL "http://www.eclipse.org/legal/epl-v10.html".// // Initial Contributors:// Accenture - Initial contribution//#include "SensibleServer.h"#include "SensibleServer_server_specific.h"#define DebugPanic() User::Panic(KDebugPanic, __LINE__)#define ClientPanic(aMsg) PanicClient(aMsg, - __LINE__)inline CShutdown::CShutdown() :CTimer(-1) {CActiveScheduler::Add(this);}inline void CShutdown::ConstructL() {CTimer::ConstructL();}inline void CShutdown::Start(TInt aDelay) { if (aDelay) { After(aDelay); } }CSensibleServer::CSensibleServer() :CServerBase(0,ESharableSessions) {}CSensibleSession::CSensibleSession() : iCallbackQ(_FOFF(CCallbackContext, iLink)) {}inline CSensibleServer& CSensibleSession::Server() {return *static_cast<CSensibleServer*>(const_cast<CServerBase*>(CSessionBase::Server()));}inline CFilteringScheduler* CSensibleServer::Scheduler() {return iScheduler;}#ifdef RUN_SERVER_WITH_EIKONENV#include <eikenv.h>#include <eikappui.h>class CSensibleAppUi : public CEikAppUi {public: void ConstructL(); ~CSensibleAppUi();private: CSensibleServer* iServer; };void CSensibleAppUi::ConstructL() { BaseConstructL(ENoAppResourceFile /*|ENoScreenFurniture*/); // // create the server iServer = new(ELeave) CServer_Class_Name(); iServer->ConstructL(); }CSensibleAppUi::~CSensibleAppUi() { delete iServer; }// Have to derive from CONE scheduler otherwise ccoeenv complains mightily#define SCHEDULER_SUPER CCoeScheduler#define SCHEDULER_CONSTRUCTOR CCoeScheduler(CCoeEnv::Static())inline void ExitScheduler() { CBaActiveScheduler::Exit(); }#else#define SCHEDULER_SUPER CActiveScheduler#define SCHEDULER_CONSTRUCTOR CActiveScheduler()inline void ExitScheduler() { CActiveScheduler::Stop(); }#endifclass CFilteringScheduler : public SCHEDULER_SUPER {public: CFilteringScheduler(); void WaitForAnyRequest(); //void OnlyRunThisObject(CActive* aActive); // This doesn't work atm! void RunEverythingExcept(CActive* aActive, CActive* aActive2); void StopFiltering();private: struct SObj { CActive* iObject; TInt iCachedStatus; }; static const TInt KNumFilteredObjects = 2; SObj iFilteredObjects[KNumFilteredObjects]; };inline TServerStart::TServerStart() {}void TServerStart::SignalL()//// Signal the owning thread that the server has started successfully// This may itself fail// {#ifdef EKA2 RProcess::Rendezvous(KErrNone);#else RThread starter; User::LeaveIfError(starter.Open(iId)); starter.RequestComplete(iStatus,KErrNone); starter.Close();#endif }///////////////////////#ifndef __HIDE_IPC_V1__void CSensibleSession::CreateL(const CServer& aServer)//// 2nd phase construct for sessions - called by the CServer framework// { CSharableSession::CreateL(aServer); // does not leave CreateL(); }#endifvoid CSensibleSession::CreateL() { Server().AddSession(); }CSensibleSession::~CSensibleSession() { Server().DropSession(); }static void JustPanic(TAny*) { User::Panic(_L("Crash!"), 0); }extern void CleanupPanicPushL() { CleanupStack::PushL(TCleanupItem(&JustPanic, 0)); }void CSensibleSession::ServiceL(const RMessage& aMessage)//// Handle a client request.// Leaving is handled by CSensibleServer::RunError() which reports the error code// to the client// { switch (aMessage.Function()) { case ERegisterCallbackNotifier: __ASSERT_ALWAYS(!iCallbackPending, ClientPanic(aMessage)); // Can't call Register is there's something already registered iCallbackNotifier = aMessage; iCallbackPending = ETrue; CompleteNextCallback(); break; case EGetCallbackContext: { __ASSERT_ALWAYS(!iCallbackQ.IsEmpty(), ClientPanic(aMessage)); CCallbackContext* c = iCallbackQ.First(); __ASSERT_ALWAYS(c->Flag(EActive), ClientPanic(aMessage)); __ASSERT_ALWAYS(c->CallbackHasContext(), ClientPanic(aMessage)); aMessage.WriteL(SLOT(aMessage, 0), *c->Context()); aMessage.Complete(KErrNone); if (!c->CallbackRequiresResult()) { iCallbackQ.Remove(*c); delete c; } break; } case EWriteCallbackResultAndReregister: { __ASSERT_ALWAYS(!iCallbackPending, ClientPanic(aMessage)); // Can't call Register is there's something already registered __ASSERT_ALWAYS(!iCallbackQ.IsEmpty(), ClientPanic(aMessage)); CCallbackContext* c = iCallbackQ.First(); __ASSERT_ALWAYS(c->Flag(EActive), ClientPanic(aMessage)); __ASSERT_ALWAYS(c->CallbackRequiresResult(), ClientPanic(aMessage)); // Get the reregistering out of the way iCallbackNotifier = aMessage; iCallbackPending = ETrue; if (aMessage.Int2() < 0) { // Leave code c->SetFlags(EResultIsLeaveCode); c->Result().integer = aMessage.Int2(); } else if (c->Flag(EResultHBufC16)) { HBufC16* result = HBufC16::New(aMessage.Int2()); if (!result) { c->SetFlags(EResultIsLeaveCode); c->Result().integer = KErrNoMemory; } else { *c->Result().l = result; TPtr16 ptr(result->Des()); aMessage.ReadL(SLOT(aMessage, 1), ptr); } } else if (c->Flag(EResultHBufC8)) { HBufC8* result = HBufC8::New(aMessage.Int2()); if (!result) { c->SetFlags(EResultIsLeaveCode); c->Result().integer = KErrNoMemory; } else { *c->Result().s = result; TPtr8 ptr(result->Des()); aMessage.ReadL(SLOT(aMessage, 1), ptr); } } else { // It's a TPkg aMessage.ReadL(SLOT(aMessage, 1), *c->Result().pkg); } CActiveScheduler::Stop(); break; } case ECancelCallbackNotifier: { if (iCallbackPending) { iCallbackNotifier.Complete(KErrCancel); iCallbackPending = EFalse; } aMessage.Complete(KErrNone); break; } case EDummy: aMessage.Complete(KErrNone); break; default: if (!DoServiceL(aMessage)) { PanicClient(aMessage, EPanicIllegalFunction); } break; } }TBool CSensibleSession::DoServiceL(const RMessage& aMessage) { // Subclasses override this! aMessage.Complete(KErrNone); return ETrue; }void CShutdown::RunL()//// Initiate server exit when the timer expires// { ExitScheduler(); }void CSensibleServer::ConstructL()//// 2nd phase construction - ensure the timer and server objects are running// { StartL(KMyServerName); iShutdown.ConstructL(); // ensure that the server still exits even if the 1st client fails to connect iShutdown.Start(TransientServerShutdownTime()); // Now set up our special scheduler. This is tricky because of good old eikonenv doing stuff differently // Basically without eikonenv, RunServer owns the old scheduler so we can't delete it // However eikonenv will delete the new one as part of its shutdown! iScheduler = new(ELeave) CFilteringScheduler(); iOldScheduler = CActiveScheduler::Replace(iScheduler);#ifdef RUN_SERVER_WITH_EIKONENV DISOWN(iOldScheduler);#endif }CSensibleServer::~CSensibleServer() {#ifndef RUN_SERVER_WITH_EIKONENV DISOWN(iScheduler); // To mimic what CCoeEnv does#endif }#ifdef __HIDE_IPC_V1__CSessionBase* CSensibleServer::NewSessionL(const TVersion& /*aVersion*/, const RMessage& /*aMessage*/) const#elseCSessionBase* CSensibleServer::NewSessionL(const TVersion& /*aVersion*/) const#endif//// Cretae a new client session. This should really check the version number.// { return new(ELeave) CSensibleSession(); }void CSensibleServer::AddSession()//// A new session is being created// Cancel the shutdown timer if it was running// { ++iSessionCount; iShutdown.Cancel(); }void CSensibleServer::DropSession()//// A session is being destroyed// Start the shutdown timer if it is the last session.// { if (--iSessionCount==0 && CActiveScheduler::Current()) // Check we have a scheduler, because if the server is being shut down there won't be one (and there'll be no point starting a shutdown timer iShutdown.Start(TransientServerShutdownTime()); }TInt CSensibleServer::RunError(TInt aError)//// Handle an error from CSensibleSession::ServiceL()// A bad descriptor error implies a badly programmed client, so panic it;// otherwise report the error to the client// { if (aError==KErrBadDescriptor) PanicClient(Message(),EPanicBadDescriptor); else Message().Complete(aError); // // The leave will result in an early return from CServer::RunL(), skipping // the call to request another message. So do that now in order to keep the // server running. ReStart(); return KErrNone; // handled the error fully }/*void CSensibleServer::BlockAllAOsExceptServerRequests() { Scheduler()->OnlyRunThisObject(this); }*/void CSensibleServer::BlockRequestsFrom(CActive* aActive1, CActive* aActive2) { Scheduler()->RunEverythingExcept(aActive1, aActive2); }void CSensibleServer::StopBlocking() { Scheduler()->StopFiltering(); }TInt CSensibleServer::TransientServerShutdownTime() const { return 2000000; // Default to 2 seconds }void PanicClient(const RMessage& aMessage, TInt aPanic)//// RMessage::Panic() also completes the message. This is:// (a) important for efficient cleanup within the kernel// (b) a problem if the message is completed a second time// { __DEBUGGER(); aMessage.Panic(KDebugPanic, aPanic); }static void RunServerL(TServerStart& aStart)//// Perform all server initialisation, in particular creation of the// scheduler and server and then run the scheduler// {#ifndef RUN_SERVER_WITH_EIKONENV // create and install the active scheduler we need CActiveScheduler* s=new(ELeave) CActiveScheduler; CleanupStack::PushL(s); CActiveScheduler::Install(s);#endif // // naming the server thread after the server helps to debug panics#ifdef __SECURE_API__ User::LeaveIfError(User::RenameThread(KMyServerName));#else User::LeaveIfError(RThread().Rename(KMyServerName));#endif#ifdef RUN_SERVER_WITH_EIKONENV // In this case, the server creation/destruction is pushed into CSensibleAppUi // Give ourselves a eikonenv CEikonEnv* env = new CEikonEnv; CEikAppUi* appui = NULL; TInt err = KErrNone; if (env != NULL) { TRAP(err, env->ConstructL(EFalse); appui = new (ELeave)CSensibleAppUi(); appui->ConstructL(); env->SetAppUi(appui); ); } if (err == KErrNone) { // // Initialisation complete, now signal the client aStart.SignalL(); env->ExecuteD(); } else { if (env != NULL) { env->DestroyEnvironment(); } User::Leave(err); // This will tell the client that something's gone wrong }#else // // create the server CSensibleServer* server = new(ELeave) CServer_Class_Name(); CleanupStack::PushL(server); server->ConstructL(); // // Initialisation complete, now signal the client aStart.SignalL(); // // Ready to run CActiveScheduler::Start(); CleanupStack::PopAndDestroy(2, s); // server, scheduler#endif }static TInt RunServer(TServerStart& aStart)//// Main entry-point for the server thread// { __UHEAP_MARK; // CTrapCleanup* cleanup=CTrapCleanup::New(); TInt r=KErrNoMemory; if (cleanup) {//#ifdef _DEBUG// TRAP(r, CleanupPanicPushL(); RunServerL(aStart); CleanupStack::Pop());//#else TRAP(r,RunServerL(aStart));//#endif delete cleanup; } // __UHEAP_MARKEND; return r; }#ifndef EKA2// The server binary is an "EPOCEXE" target type// Thus the server parameter passing and startup code for WINS and EPOC are// significantly different.#ifdef __WINS__// In WINS, the EPOCEXE target is a DLL with an entry point called WinsMain,// taking no parameters and returning TInt. This is not really valid as a thread// function which takes a TAny* parameter which we need.//// So the DLL entry-point WinsMain() is used to return a TInt representing the// real thread function within the DLL. This is good as long as// sizeof(TInt)>=sizeof(TThreadFunction).//static TInt ThreadFunction(TAny* aParms)//// WINS thread entry-point function.// The TServerStart objects is passed as the thread parameter// { return RunServer(*static_cast<TServerStart*>(aParms)); }IMPORT_C TInt WinsMain();EXPORT_C TInt WinsMain()//// WINS DLL entry-point. Just return the real thread function // cast to TInt// { return reinterpret_cast<TInt>(&ThreadFunction); }TInt E32Dll(TDllReason) { return KErrNone; }#else//// In EPOC, the EPOCEXE target is a process, and the server startup// parameters are encoded in the command line//TInt TServerStart::GetCommand() { RProcess p; if (p.CommandLineLength()!=sizeof(TServerStart)/sizeof(TText)) return KErrGeneral; TPtr ptr(reinterpret_cast<TText*>(this),0,sizeof(TServerStart)/sizeof(TText)); p.CommandLine(ptr); return KErrNone; }TInt E32Main()//// Server process entry-point// Recover the startup parameters and run the server// { TServerStart start; TInt r=start.GetCommand(); if (r==KErrNone) r=RunServer(start); return r; }#endif#elseTInt E32Main()//// Server process entry-point// { TServerStart start; TInt r = RunServer(start); return r; }#endif//// CCallbackContext ////CCallbackContext::CCallbackContext(TCallbackCode aCode) : iCallback(aCode) { iCallback.iCode = aCode; }CCallbackContext::~CCallbackContext() { __ASSERT_DEBUG(!Flag(EActive), DebugPanic()); delete iContext; }void CCallbackContext::SetFlags(TInt aFlags) { iFlags |= aFlags; }TBool CCallbackContext::Flag(TInt aFlags) const { return iFlags & aFlags; }void CCallbackContext::ClearFlags(TInt aFlags) { iFlags = iFlags & ~aFlags; }TBool CCallbackContext::CallbackRequiresResult() const { //TODO return EFalse; }TBool CCallbackContext::CallbackHasContext() const { return (iContext != NULL); }void CCallbackContext::SetResult(TDes8& aPkg) { iResult.pkg = &aPkg; ClearFlags(EResultHBufC8 | EResultHBufC16); }void CCallbackContext::SetResult(HBufC8*& aResult) { iResult.s = &aResult; SetFlags(EResultHBufC8); ClearFlags(EResultHBufC16); }void CCallbackContext::SetResult(HBufC16*& aResult) { iResult.l = &aResult; SetFlags(EResultHBufC16); ClearFlags(EResultHBufC8); }HBufC8* CCallbackContext::Context() { return iContext; }CCallbackContext::TResult& CCallbackContext::Result() { return iResult; }TServerCallback& CCallbackContext::Callback() { return iCallback; }TCallbackWriter CCallbackContext::Writer() { TCallbackWriter res(iCallback, &iContext); return res; }//// CFilteringScheduler ////CFilteringScheduler::CFilteringScheduler(): SCHEDULER_CONSTRUCTOR {}/*void CFilteringScheduler::OnlyRunThisObject(CActive* aActive) { __ASSERT_ALWAYS(!iObject, DebugPanic()); iObject = &aActive->iStatus; iOnlyRunThisObject = ETrue; User::Panic(_L("OnlyRunThisObject doesn't work yet!"), 0); }*/void CFilteringScheduler::RunEverythingExcept(CActive* aActive, CActive* aActive2) { __ASSERT_ALWAYS(!iFilteredObjects[0].iObject, DebugPanic()); iFilteredObjects[0].iObject = aActive; iFilteredObjects[1].iObject = aActive2; iFilteredObjects[0].iCachedStatus = KRequestPending; iFilteredObjects[1].iCachedStatus = KRequestPending; }void CFilteringScheduler::StopFiltering() { for (TInt i = 0; i < KNumFilteredObjects; i++) { SObj& obj = iFilteredObjects[i]; if (obj.iObject && obj.iCachedStatus != KRequestPending) { TRequestStatus* stat = &obj.iObject->iStatus; User::RequestComplete(stat, obj.iCachedStatus); // Since we consumed the signal from the previous complete, we need to re-signal by calling RequestComplete rather than just updating the object status ourselves } obj.iObject = NULL; } }void CFilteringScheduler::WaitForAnyRequest() { if (!iFilteredObjects[0].iObject) { SCHEDULER_SUPER::WaitForAnyRequest(); return; } for (;;) { SCHEDULER_SUPER::WaitForAnyRequest(); TBool found = EFalse; for (TInt i = 0; i < KNumFilteredObjects; i++) { SObj& obj = iFilteredObjects[i]; TBool isReadyToRun = obj.iObject && obj.iObject->IsActive() && obj.iObject->iStatus != KRequestPending; if (isReadyToRun) { // Our target object has completed, so mark it back as pending, and consume the signal ASSERT(obj.iCachedStatus == KRequestPending); // If this is already set something has gone quite wrong with our logic obj.iCachedStatus = obj.iObject->iStatus.Int(); *((TInt*)&obj.iObject->iStatus) = KRequestPending; // Cast this to a TInt* to prevent TRequestStatus::operator= changing the flags found = ETrue; break; } } if (!found) break; // It wasn't one of our objects that completed so no need to go round the loop again } }//// TCallbackWriter ////TCallbackWriter::TCallbackWriter(TServerCallback& aCallback, HBufC8** aContext): iCallback(aCallback), iContext(aContext), iBuf((TUint8*)aCallback.iData.Ptr(), aCallback.iData.MaxLength()), iInContext(EFalse) { if (iContext) *iContext = NULL; }void TCallbackWriter::AddL(const TDesC8& aData, char* aType) { __ASSERT_DEBUG(aData.Length(), DebugPanic()); __ASSERT_DEBUG(aType, DebugPanic()); TInt bytesRemaining = iBuf.MaxSize() - iBuf.Size(); TInt typeSize = 1; if (*aType == 'D') { // TDesC16s need to be 2-byte aligned, so make sure the data after the type byte will be if (!(iBuf.Length() & 1)) typeSize = 2; } if (aData.Size() + typeSize > bytesRemaining) { // No room for arg and type if (!iContext) User::Leave(KErrNoMemory); if (!iInContext) { // so construct context if (*aType == 'D') typeSize = 2; *iContext = HBufC8::NewL(aData.Length() + typeSize); iInContext = ETrue; } else { // realloc HBufC8* newContext = (*iContext)->ReAlloc(Max(iBuf.MaxSize() * 2, iBuf.MaxSize() + aData.Size() + typeSize)); if (!newContext) { delete *iContext; *iContext = NULL; User::Leave(KErrNoMemory); } *iContext = newContext; } iBuf.Set((*iContext)->Des()); } iBuf.Append(*aType); if (typeSize == 2) iBuf.Append('-'); // Padding iBuf.Append(aData); if (iInContext) { iCallback.iContextLength = iBuf.Length(); } else { iCallback.iData.SetLength(iBuf.Length()); // Because a TPtr pointing to a buf doesn't behave like one pointing to an HBufC, sigh... } }#define ADD(T, arg, type) { TPckg<T> x(arg); AddL(x, #type); }void TCallbackWriter::AddL(TInt aInt) { ADD(TInt, aInt, i); }void TCallbackWriter::AddL(TUint aInt) { ADD(TUint, aInt, u); }void TCallbackWriter::AddL(TPoint aPoint) { ADD(TPoint, aPoint, P); }void TCallbackWriter::AddL(TSize aSize) { ADD(TSize, aSize, S); }void TCallbackWriter::AddL(TRgb aRgb) { ADD(TRgb, aRgb, G); }void TCallbackWriter::AddL(TRect aRect) { ADD(TRect, aRect, R); }void TCallbackWriter::AddL(const TDesC16& aDesc) { ADD(TInt, aDesc.Length(), i); TPtrC8 x((TUint8*)aDesc.Ptr(), aDesc.Size()); AddL(x, "D"); }void TCallbackWriter::AddL(const TDesC8& aDesc) { ADD(TInt, aDesc.Length(), i); AddL(aDesc, "8"); }//// CSensibleSession ////void CSensibleSession::QueueCallbackL(CCallbackContext* aContext) { iCallbackQ.AddLast(*aContext); if (aContext->CallbackRequiresResult()) { if (aContext->Flag(EBlockServer)) { //Server().Scheduler()->OnlyRunThisObject(&Server()); //TODO fix this at some point! } __ASSERT_ALWAYS(!iWaitingForCallbackResult, DebugPanic()); // This means someone queued a callback that required a result without specifying EBlockServer, and in the meantime someone else has queued another callback requiring a result. This isn't supported! If there is the remotest chance this could happen, then all must specify EBlockServer. iWaitingForCallbackResult = ETrue; CompleteNextCallback(); CActiveScheduler::Start(); // When we reach here, the client stuff has finished and aContext has our result in // We call CompleteNextCallback again here since to reach this point the server must have received a EWriteCallbackResultAndReregister, which means it's ready for another callback CompleteNextCallback(); if (aContext->Flag(EBlockServer)) { Server().Scheduler()->StopFiltering(); } iWaitingForCallbackResult = EFalse; if (aContext->Flag(EResultIsLeaveCode)) { iCallbackQ.Remove(*aContext); TInt err = aContext->Result().integer; delete aContext; User::Leave(err); } // Nothing else needed } else { CompleteNextCallback(); } }TBool CSensibleSession::DispatchCallback(TServerCallback& aCallback) { if (!iCallbackPending) { // Client not ready to be notified return EFalse; } TPckg<TServerCallback> pkg(aCallback); TRAPD(err, iCallbackNotifier.WriteL(SLOT(iCallbackNotifier, 0), pkg)); if (err) { PanicClient(iCallbackNotifier, EPanicBadDescriptor); iCallbackPending = EFalse; return EFalse; } iCallbackNotifier.Complete(KErrNone); iCallbackPending = EFalse; return ETrue; }void CSensibleSession::CompleteNextCallback() { if (!iCallbackPending) { // Client not ready to be notified return; } else if (iCallbackQ.IsEmpty()) { // Nothing to complete yet return; } CCallbackContext* c = iCallbackQ.First(); TPckg<TServerCallback> pkg(c->Callback()); #ifdef __HIDE_IPC_V1__ TRAPD(err, iCallbackNotifier.WriteL(0, pkg)); #else TRAPD(err, iCallbackNotifier.WriteL(iCallbackNotifier.Ptr0(), pkg)); #endif if (err) { PanicClient(iCallbackNotifier, EPanicBadDescriptor); iCallbackPending = EFalse; return; } iCallbackNotifier.Complete(KErrNone); iCallbackPending = EFalse; if (!c->CallbackRequiresResult() && !c->CallbackHasContext()) { iCallbackQ.Remove(*c); delete c; } else { c->SetFlags(EActive); } }