Common mis-use and abuse of Symbian OS
I'm going to start my own thread and post responses to it over time of common mis-uses / mis-understandings / bad advice that I see on here regarding the use of Symbian OS.
Number1:
-------------
TRequestStatus status;
status = KRequestPending;
AsynchronousFunction(status);
You do not and should not be setting the value of status to KRequestPending, that is not your responsibility, it is the responsibility of AsynchronousFunction().
However in practice its not very serious and 99% of the time shouldn't cause any problems - but if you didn't write AsynchronousFunction() and don't have the code how do you know its not got the following as its first line for example?
__ASSERT_ALWAYS(aStatus != KRequestPending, Panic(shouldn't be already set to KRequestPending))
Number 2:
------------
People putting calls to User::WaitForRequest(iStatus) in an active object.
DONT.
Number 3
-------------
People declaring an additional TRequestStatus in an active object then using that instead of iRequestStatus in combination** with SetActive().
Don't declare an additional TRequestStatus in an active object, CActive already has one, you should use that.
[** Actually it is possible to do this sort of thing in an active object:
TRequestStatus status
AsynchronousCall(status);
User::WaitForRequest(status);
ADifferentAsynchronousCall(iStatus);
SetActive()
Assuming AsynchronousCall() gets the chance to execute that is, however if you are doing this the chance is you don't really know what you are doing and are just trying stuff at random to try and get your program to work!
Number 4
-------------
How to return descriptors from functions.
First off, a mis-use: some of the descriptor classes are abstract, stop trying to declare a TDesC as a variable.
Now, how to use them in combination with functions:
If you are returning data from a function as a descriptor then:
- if its an entire descriptor and which will still exist while the caller uses it you should return const TDesC& or TDes& (if the caller is allowed to modify it).
- if you are returning data which is not an entire descriptor (i.e. it lives in a buffer or is part of another descriptor), which will still exist while the caller uses it, return TPtrC or a TPtr (if the caller is allowed to modify it).
- if the data has to be constructed, but has a known (not too big) maximum length, return a TBufC<> or TBuf<>
-if the data has to be constructed, and could have variable or long length, there are two basic options:
a) allocate an HBufC for the data and return HBufC*, the caller is then responsible for deletion of the object. Note: this requires allocation and free, which have a run time overhead.
b) have the caller pass a TDes& as a parameter and write back to that descriptor, ensuring that what happens if this is not "large enough" is well defined. This is expecially good for when the caller should specify how much data it wants returned--the descriptor's MaxLength() provides this.
Number 5
-------------
Stop using a granularity of 1 for arrays and buffers.
CBufBase* buffer = CBufFlat::NewL(1);
buffer->InsertL(something big lots of times);
Everybody seems to do this but its a horrendous thing to do.
Here's what the SDK says about it:
"The granularity of buffer expansion. Additional space, when required, is always allocated in multiples of this number. Note: although a value of zero is permitted by this interface, it has no meaning, and risks raising panics later during execution. We suggest that you pass a positive value".
Did you see the bit about "Additional space, when required, is always allocated in multiples of this number"?
Number 6
-------------
Back to active objects again, specifically cancelling. Here's some examples of what not to do that is seen frequently.
a) Calling DoCancel() directly - don't try to do this. Just call Cancel(), DoCancel() gets called automatically via Cancel()
b) Not calling Cancel() - if you've got an outstanding asynchronous request* and your active object is being deleted for example then you must call Cancel().
c) if (IsActive())
   Cancel()
There's no need to have this check that the object is active before calling Cancel(). It is safe to call Cancel() even if the active object isn't active, the active object base class code makes this check so there's no need for you to also.
d) Finally, you'd think this would be obvious but apparently not seeing as quite a few people do it, or rather don't do it:
Calling Cancel() but not adding any code to the body of the overridden DoCancel() to actually cancel anything.
If your DoCancel() function is empty how can any outstanding asynchronous request get canceled?
* However don't forget, some active objects are self scheduling.
Number 7
------------
Not a mis-use of Symbian for number 7. Instead a history lesson for the people that are always complaining that Symbian OS doesn't support some standard C++ features etc.
Q:When do you think Symbian OS was written?
A: It started in 1994.
Q: Have you ever used the compilers from 1994?
A: Probably not. See if you can find MSCVC++ 2.0 or an early Boreland compiler for example, now try and write a program using exceptions or templates or a few other features that you take for granted and expect your compiler to be capable of dealing with and prepare to be shocked (the Gnu compiler also didn't support some C++ features, however one thing I never understood was why Symbian didn't request that they be added, after all they were requesting other features and indeed paying for their development I believe, mind you if the PC compilers didn't support those features in the first place then as thats where all the development gets done it didn't matter if Gnu did or didn't).
Two main reasons why Symbian OS doesn't support some standard features:
1) There was no choice for certain features, the compilers simply didn't support those features or did but incorrectly.
2) Efficiency and size. ER1 had to fit into 4Mg I think it was. That's tiny. You try and write an entire operating system, plus a whole suite of programs (agenda, spreadsheet, contacts, drawing, word processor etc. .....) along with a UI and get it to fit into 4Mg.
And here's a nice soundbite timeline I saw somebody post somewhere regarding Symbian OS and C++ standardization:
1) Writing of Epoc32 ER1 begins: September 1994
2) Psion Series 5 PDA ships containing the first release of the Symbian OS in a product: June 1997
3) ISO/IEC publication of the C++ standard: September 1998
Number 8
-------------
"My program hangs when I call CActiveScheduler::Start() and never reaches the next line "
A lot of people who have never developed for a real-time environment have trouble initially getting their heads round the concepts involved. This is especially the case if they've previously only written simple programs where there is a sequence of steps and the code starts at the first step, goes through them all, gets to the last one then the program finishes. So they've previously only written a simple program, try to program on SYmbian and find out they have to use active objects and then it starts to fall apart (it not just Symbian, they'd have similar problems using threads or similar on any OS if they've never used them before).
If you're using an active object then you have to have an active scheduler installed. If you're writing an application i.e. a user interface then there will already be one installed which has been started and you shouldn't be messing around with them. If you're not writing a UI application (i.e. you're writing an exe for example) then you have to install and start an active scheduler yourself. This invaraibly starts to cause problems with newbies, consider this abbreviated pseudo code snippet and simpilfied explanation to consider why:
main()
{
CActiveScheduler* scheduler = new CActiveScheduler;
CActiveScheduler::Install(scheduler);
CActiveScheduler::Start();
CMyActive* active = CMyActive::NewL();
active->DoStuff();
}
They code this and then step through it in the debugger but after they get to CActiveScheduler::Start() nothing happens, it doesn't reach the next line and they don't understand why their program has hung.
The reason is because the call to CActiveScheduler::Start() effectivey shifts the paradigm of your program, you've now stopped working with baby programs where there is a simple sequence of steps of execution and its easy to follow, you've now jumped into the realm of real-time programming, where your code flow is now dictated by events external to your program so you have to change your way of thinking and make sure you properly understand how it works.
The above code won't work because once a call to CActiveScheduler::Start() is made then imagine that an infinite loop is being executed inside that function (actually it effectively is). Suppose this function were implemented as this:
CActiveScheduler::Start()
{
  while (true)
  {
  }
}
Now can you see why the main() code will hang and the next line never executed after the call to CActiveScheudler::Start()?
Of course its not as simple as that, but now that you can see why it would hang then we'll move onto showing a bit more of the code.
The code for the active schuduler does something like this (note this isn't actually exactly what happens but for the purposes of explanation assume that it is)
CActiveScheduler::Start()
{
  while (true)
  {
  if (something has happened that an active object is interested in == TRUE)
    YourActiveObject->RunL();
  }
}
The "something has happened that an active object is interested in" is set by another piece of code 'signalling' a TRequestStatus that you passed to it in your active object code.
So lets consider if in your active object you wanted to open a socket and the socket API is SomeSocketClass::Open(TRequestStatus& aStatus). Then
when the socket code has opened the socket this will result in the TRequestStatus being signalled which results in "something has happened that an active object is interested in"
becomeing set to TRUE and thus the active object's RunL() gets called.
Now lets assume the call to open the socket is in the following function:
CMyActive::DoStuff()
{
SetActive();
iSocket->Open(iStatus);
}
With the code as it is in main() you can see that "something has happened that an active object is interested in" will never be able to be set to TRUE because the call to open the socket
doesn't happen until after the call to CActiveScheuler::Start(),and thats stuck in an infinite loop. So you have to rearange your code as:
main()
{
CActiveScheduler* scheduler = new CActiveScheduler;
CActiveScheduler::Install(scheduler);
CMyActive* active = CMyActive::NewL();
active->DoStuff();
CActiveScheduler::Start();
TInt ImANumpty = 5;
}
Now the call to open the socket is made before we enter CActiveScheduler::Start()'s infinite loop.
Now the important thing to bear in mind is that when we are executing through the loop the socket code is also executing effectively in parallel in another part
of the operating system, and when it has opened the socket the "something has happened that an active object is interested in" turns
from being FALSE to being TRUE and as a consequence your active object's RunL() gets called.
People still have problems with the above code however, wondering why after they call CActiveScheduler::Start(), and even though their active object's
RunL() get's called they wonder why ImANumpty never gets called. Well its because CMyActive::RunL() is called but as we're still inside the infinite loop then calling
RunL() results in "something has happened that an active object is interested in" being set back to FALSE. So after RunL() is called we're back inside the infinite
loop forever unless (a) "something has happened that an active object is interested in" gets set back to TRUE again somehow or (b) we can break out of the loop somehow.
For (a) to happen you have to send another TRequestStatus to something, so in the case of the socket example it might be to send some data over the socket, so you're
code might be:
CMyActive::RunL()
{
SetActive();
iSocket->SendData(_L("the data to send"), iStatus);
}
When the data has been sent "something has happened that an active object is interested in" gets set to TRUE so RunL() gets called again.
Suppose now we're finished and don't to do anything else what then? We have to break out of the infinite loop, and this can be done with
CActiveScheduler::Stop(), so our RunL() code might look like:
CMyActive::RunL()
{
if (iState == ENotSentAnyDataYet)
  {
  SetActive();
  iSocket->SendData(_L("the data to send"), iStatus);
  iState = ESentData;Â
  }
  else
   if (iState == ESentData)
     CActiveSchuduler::Stop();
}
The first time RunL() is called iSocket->SendData() gets called, the second time the RunL() is called CActiveScheduler::Stop() is called
which causes the infinite loop to stop and thus execution of CActiveScheduler::Start() finishes and thus the line TInt ImANumpty = 5; gets executed and the
program finishes.
important point: in the example code it shows execution returning to the main part of the program after the active scheduler has stopped. You would usually write this sort of code in an exe which is test code for example and which only runs for a short time and has a predetermined life span. Programs (servers, applications) on a mobile device aren't like that however, who knows when they will end. You have to stop thinking in terms of a program running and stopping once all its lines have been executed and start thinking in terms of a program running potentially for ever and it reacting to various events whenever they might occur.
There are not many times when you need to use an active scheduler, writing test code is one as mentioned, the other main one is if you write a server. For most users it is sufficient for you to not know they even exist.
8b
----
When to use the active scheduler: if you are writing an application i.e. a UI then the UI framework creates and starts an active scheulder for you, so you should have no need to create one of your own*.
If you are writing a DLL that uses active objects and your DLL is intended to be used by another component then again you shouldn't add an active scheulder to your DLL.
AN example when you'd use one are if you are writing an exe which makes use of active objects or uses components which use them.
*This, and indeed this whole posting, is a simplified overview of the active scheduler, you might be in situations where you need to add your own to have nested active schedulers. But thats a more advanced area than the simplified overview given here
Number 9 - Misunderstanding about the stack and the heap in relation to descriptors
A lot of people seem to get confused between the stack and the heap when it comes to descriptors. A main reason for the confusion is actually from documentation (including from Symbian themselves) that isn't clear or is misleading.
For example documentation that says TBuf descriptors go on the stack is incorrect.
Also documentation that says the data pointed to by a HBufC* goes on the heap but the HBufC pointer goes on the stack is also incorrect.
The above two statements are correct IF the TBuf and HBufC pointer are local variables i.e.
void Function()
{
TBuf<20> buf;
TPtr ptr;
HBufC* hBuf = HBufC::NewL(...)
}
buf, ptr and hBuf are all on the stack, while the data pointed to by hBuf is on the heap, however in this code:
class CClass : public CBase
{
...
TBuf<20> ibuf;
TPtr iptr;
HBufC* ihBuf;
}
CClass *class = CClass::NewLC();
Then ibuf, iptr, ihBuf and the data pointed to by hBuf are all on the heap.
Avoid putting large descriptors on the stack:
void Function()
{
TBuf<1000000> buf;
}
This will go on the stack and is too large so will cause stack overflow or the infamous __chkstk compiler warning.
So instead if you do this:
void Function()
{
TBuf<1000000>* buf = new(ELeave) TBuf<10000000>;
}
Now it will go on the heap but you should probably use RBuf or HBuf instead, (or for data this large look at CBufFlat or preferably CBugSeg).
In this example, the data is also on the heap:
class CClass : public CBase
{
...
TBuf<10000000) iBuf;
}
CClass* class = CClass::NewLC();
2007年11月11日星期日
订阅:
博文评论 (Atom)
没有评论:
发表评论