Monday, May 09, 2005

9. How do I use descriptors as parameters?

The descriptor base classes are described in 11. What is the layout of non-modifiable descriptors? and 12. What is the layout of non-modifiable descriptors?

The base classes provide and implement the constant and modifiable descriptor operations regardless of the actual type of the derived descriptor. For consistency, they should be used as arguments to functions, allowing descriptors to be passed without restricting the caller of the function to using a specific type.

For example, the caller can call the following function with anything derived from TDesC and TDes for the first and second arguments respectively. For example an HBufC and a TBuf<8>, or a TPtr and a TBuf<3>:

void SomeFunction(const TDesC& aReadOnlyDescriptor, TDes& aReadWriteDescriptor);

How do I make a descriptor parameter read-only?


Pass it as a constant reference to a non-modifiable descriptor (const TDesC&). For example:

void SomeFunction(const TDesC& aReadOnlyDescriptor);

This function can still be called with a modifiable descriptor like a TPtr as the argument, because these derive from TDesC (a TPtr is a specialised type of TDes). Inside the function, only the TDesC operations may be performed on the parameter. Its contents will not be changed, even if you have passed in a modifiable, TDes-derived descriptor.

How do I make a descriptor parameter read/write?

Pass it as a non-constant reference to a modifiable descriptor (TDes&). For example:

void SomeFunction(TDes& aReadWriteDescriptor);

You cannot call this function with a non-modifiable descriptor, which makes sense because you don’t want a read only descriptor to be modified.

Inside the function you can both read from and write to the parameter. Remember that the descriptor must already have sufficient space in it for the data to expand into, if necessary. If there isn’t, you’ll get a panic. (Some functions will be coded to check the length of the descriptor before attempting something that may cause a panic and return an overflow error instead. But others will not and the results will be messy).

[ This is one reason why you should always test your code with "boundary conditions" - that is, passing in parameters to functions at the boundary of their acceptable values, and beyond, to check that resulting errors are handled gracefully.]

Can I use other descriptor types as function parameters?

Yes you can, but don’t. For example you could specify your function requiring a parameter passed a TBuf<10>&. But what if the caller has got a TPtrC, or an HBufC, or even a TBuf<11>? This means that they won’t be able to call your function without some extra hassle to fit in with exactly what you have specified. TDes and TDesC are the most general types you can use for your function parameters.

My function uses TDes or TDesC parameters like you say, but my descriptor passing doesn’t work. Why not?

Oh how much pain has this one caused? Missing out that little & symbol makes all the difference. Your parameter types must be references, not values, ie const TDesC& or TDes&.

The base classes TDesC and TDes contain no string data. If you pass them by value rather than by reference, you are using static binding, which means that polymorphism won’t work, and you'll end up with a data-free base class object. It will all compile OK, but nothing works.

Never attempt to instantiate or work directly with objects of the base classes TDesC or TDes, as Tip 1 advises. They are effectively abstract classes. There is rarely, if ever, a valid reason for instantiating them rather than an object of their deriving classes (TBufC, TBuf, TPtrC, TPtr, HBufC or RBuf).

Comments:
Note that the the TDesC and TDesC base classes are referred to as 'abstract' by Symbian OS documentation.

But they're not abstract in typical C++ sense. Descriptors do not have pure virtual functions. In fact, descriptors have no virtual functions at all, despite the use of C++ inheritance. With no virtual functions, there is no need for a vtable pointer (4-byte overhead) for each object instantiated. This keeps memory usage to a minimum.
 
Hi,

TDesC is non modifiable version of descriptor,if that is the case, then why do we use const while passing parameter in function.

For eg
void FOO(const TDesC&);
 
Good question. There are two issues going on here.

(1) Firstly we have the meaning of C in descriptors such as TBufC, HBufC and TPtrC. It means that the descriptor is non-modifiable in the sense that it derives directly from TDesC, and has no modifiable functionality. It's a guarantee of constness from the point of view of that Symbian OS class.

However, as I've shown for HBufC (http://descriptors.blogspot.com/2005/05/how-do-i-use-heap-based-buffer.html) you can still modify a non-modifiable descriptor by creating a modifiable pointer (TPtr) over the non-modifiable descriptor's data.

For example (sorry about the formatting):

TBufC<12> myNonModifiableBuf(_L("Hello World"));

TPtr myModifiablePtr(myNonModifiableBuf.Des());

Then you can call modifiable methods on myModifiablePtr, and effectively change the contents of myNonModifiableBuf.

(2) Secondly we have the concept of C++ constness. This is a guarantee that the C++ object is non-modifiable (rather than just the guarantee from a Symbian OS)

This will now not compile:

const TBufC<12> myNonModifiableBuf(_L("Hello World"));

TPtr myModifiablePtr(myNonModifiableBuf.Des());

So passing const TDesC& as a parameter means that not only is the parameter conceptually constant (as in the Symbian OS class used) but it is enforced as constant by C++.
 
How to use a jstring value to initialize a TDesc. The jstring holds a filename which is to be passed to a Symbian class as a TDesc parameter. Hardcoding works but if this is obtained via a java pgm how to go abt it.
 
Sorry, this is a bit late comment. Anyway...

What if there was a function that would eventually delete and set to NULL the HBufC* I'm passing to it? You can't (shouldn't) set a reference to NULL. What would be the correct way to do this? I found this kind of function from the code I'm working with and as a noob I'm not so comfortable with my solution after reading this FAQ.
 
I'm not sure I understand the comment in the context of this FAQ.

If you're passing an HBufC* as a reference, given this text, I assume you're de-referencing it to pass as a TDesC& paramter - which can't be deleted and set to NULL. Well, it can, but only if the function knows that you're passing a de-referenced HBufC* - and it shouldn't.

It's not generally good practice to write functions that take parameters as HBufC*& - I have seen this kind of code, but far better that the function returns HBufC* and transfers ownership at that point.

Perhaps you could clarify and post a code snippet to help me understand?
 
The function is like this:
void foo(HBufC* a) {
/*...*/
delete a;
a = NULL;
}

And it is used like this:
HBufC* bar;
/*...*/
foo(bar);

So, eventually the foo function deletes the contents of bar and sets it to NULL.

Is there a "more correct" solution available?

Thank you!
 
Is it necessary to have foo delete bar? Does it have to know that it's dealing with an HBufC at all? If the answer to both is "No" then I'd rework foo to take TDesC& and allow the function that created bar to also destroy it.

If the answer is "Yes", can you re-design?
 
Yes, it was necessary to destroy it but I thought it's better to destroy in the same context it was created. That caused me some extra work but now it works as intended and is more elegant.
 
What if i write a function that takes Foo(TDesC &aParam)

How can i check within Foo() that aParam is allocated?
 
Post a Comment

<< Home

This page is powered by Blogger. Isn't yours?

Google
WWW Descriptors FAQ