Descriptors seem to be the bane of all newcomers to Symbian programming. I thought I had them down, but just came across two problem that took me a while to solve, and might send newbies running around for days.
I am using a TCP connection with sockets, which means a constantly connected stream. Therefore data comes in over the stream constantly and I don't know when it stops. In order to facilitate this, the server sends a 4-byte length of the next data packet. RSocket's Read() method takes in a descriptor, and reads until it fills up the descriptor's maximum length, and then calls the active object's RunL(). So, in the active object, the first thing to do is get the length:
// Member variables
TBuf8<4> iLengthBuffer;
TUint32 iRequiredLength;
// ------------------------------------------
// Class
// This call will not complete until iLengthBuffer is filled (4 bytes)
iState = KReadingLength;
iSocket->Read(iLengthBuffer, iStatus);
SetActive();
// ------------------------------------------
// And then to RunL()...
switch(iStatus)
case KReadingLength:
{
if (iStatus == KErrNone)
{
// Retrieve length from buffer
DataInputStream inputStream(iLengthBuffer.Ptr(), 4);
iRequiredLength = inputStream.readUInt32();
OnReceivedRequiredLength();
}
void NetConnectionReader::OnReceivedRequiredLength()
{
iState = KReading;
delete iBuffer;
iBuffer = HBufC8::NewL(iRequiredLength);
iSocket->Read(iBuffer->Des(), iStatus);
SetActive();
}
Problem 1: Look at the Read() call in OnReceivedRequiredLength(). The program crashes while copying a buffer somewhere in the SDK. The prototype for RSocket's Read is IMPORT_C void Read(TDes8& aDesc,TRequestStatus& aStatus);. Note that it is a reference. Also, HBufC's Des() prototype is IMPORT_C TPtr8 Des();. This returns a new TPtr8 object; the Read() call takes a reference to that. However, since this is a new TPtr8 object and not a pointer/reference, the TPtr8 object is destroyed after leaving OnReceivedRequiredLength()!
The only way around this, that I know of, is to create another pointer reference as a class member variable, which therefore doesn't get destroyed as the scope leaves in OnReceievedRequiredLength():
Okay, so that seemingly unnecessary workaround solved Problem 1. However, here's a new problem which I haven't found a workaround for:
Problem 2: Remember how above we received the length of the next incoming data, which told us how much to read for our next "data packet"? Well, you would think everything okay. We allocate the buffer for that data, and use RSocket's Read() method to fill it up to that size:
But it turns out the stream was returning with 1 more byte of data than iRequiredLength (sometimes). I looked into this problem. How could Read() be filling up the descriptor's buffer with more than it's maximum length? Well, looking at the SDK document's for HBufC::NewL()'s maximum length argument: The requested maximum length of the descriptor. Note that the resulting heap cell size and, therefore, the resulting maximum length of the descriptor may be larger than requested. This also means that the resulting maximum length of the descriptor may be greater than its length. .
And there we go. You have to re-explicitly set the maximum length, because the SDK just ignored you the first time without telling you! (To find the true maximum length, use iBuffer->Des().MaxLength()). For anyone that may be looking at this problem, if you don't require backwards compatibility then you may want to look into the class RBuf, which acts as a descriptor with a modifiable length, but beware that it was only introduced in Symbian v8.1.
Both your problems would be solved if you switched to using an RBuf - its max length doesn't get rounded up to the size of the heap cell like a HBufC and as RBuf derives from TDes you can pass it into a function taking that as a parameter.
Yep, you're absolutely correct... but unfortunately, like I mentioned in the post, it wasn't introduced in earlier version of the SDK, and this program has to be compatible with Series 80 devices.
Another "gotcha" I failed to mention on the above post:
The problem with this is that the RSocket will adjust the TPtr8's iBufPtr's new length, but not the HBufC8 iBuffer's descriptor (although the iBuffer correctly has the data). Guess this would be Problem 3 .
I ended up giving up on the idea of using the HBufC8... it just wasn't worth it. Now I have an array and only use one descriptor, the TPtr8. The HBufC8 was actually just extra unnecessary overhead to a byte buffer. As it turns out, I know the maximum size of any data coming from the server, so I simply have:
// member variable
unsigned char iBuffer[8192];
// In the class method...
iBufPtr.Set(iBuffer, 0, iRequiredLength);
iSocket.Read(iBufPtr, iStatus);
This way is a little easier to read, always guarantees I won't run out of memory for the buffer, and doesn't require the overhead of an HBufC8. (By the way, note that you could also have iBuffer as an unsigned char* pointer, and allocate the memory, and still be able to use the exact same code)
Forum posts: 364
Both your problems would be solved if you switched to using an RBuf - its max length doesn't get rounded up to the size of the heap cell like a HBufC and as RBuf derives from TDes you can pass it into a function taking that as a parameter.
Forum posts: 148
Yep, you're absolutely correct... but unfortunately, like I mentioned in the post, it wasn't introduced in earlier version of the SDK, and this program has to be compatible with Series 80 devices.
Another "gotcha" I failed to mention on the above post:
The problem with this is that the RSocket will adjust the TPtr8's iBufPtr's new length, but not the HBufC8 iBuffer's descriptor (although the iBuffer correctly has the data). Guess this would be Problem 3
.
I ended up giving up on the idea of using the HBufC8... it just wasn't worth it. Now I have an array and only use one descriptor, the TPtr8. The HBufC8 was actually just extra unnecessary overhead to a byte buffer. As it turns out, I know the maximum size of any data coming from the server, so I simply have:
This way is a little easier to read, always guarantees I won't run out of memory for the buffer, and doesn't require the overhead of an HBufC8. (By the way, note that you could also have iBuffer as an unsigned char* pointer, and allocate the memory, and still be able to use the exact same code)
-euroq