Страницы

среда, 4 августа 2010 г.

Symbian OS. Асинхронные вызовы: как не надо делать.

Столкнувшись с необходимостью чтения большого файла, я решил воспользоваться кодом, который приведен в примере парсинга XML на форуме нокиа. Ключевыми там являются методы CXmlHandler::StartParsingWithAoL() и, естественно, callback-функция CXmlHandler::RunL(). Именно в этих методах производятся вызовы асинхронного метода RFile::Read().

void CXmlHandler::StartParsingWithAoL( const TDesC& aFileName )
    {
     // Отмена всех невыполненных задач.
    if ( IsActive() )
        {
        Cancel();
        }
    User::LeaveIfError( iFile.Open( CCoeEnv::Static()->FsSession(), aFileName,
        EFileRead ) );
    // Создание буфера для хранения содержимого файла.
    delete iBuffer;
    iBuffer = 0;
    iBuffer = HBufC8::NewL( KFileBufferSize );
    // !!! Такая реализация может быть причиной возникновения
    //     ошибки времени выполнения !!!
    TPtr8 bufferPtr( iBuffer->Des() ); 
    iFile.Read( bufferPtr, KFileBufferSize, iStatus );
    SetActive();
    // Приведение парсера в состояние готовности.    
    iParser->ParseBeginL();
    }

void CXmlHandler::RunL()
    {
    if ( KErrNone == iStatus.Int() )
        {
        // Если длина содержимого буфера равна 0 - достигнут
        // конец файла
        if ( iBuffer->Length() == 0)
            {
            iParser->ParseEndL();
            iFile.Close();
            delete iBuffer;
            iBuffer = 0;
            }
        // Иначе продолжаем чтение следующего отрезка XML-файла.
        else
            {
            // Парсинг прочитанного содержимого.
            iParser->ParseL( *iBuffer );

            // Чтение следующего отрезка.
            // !!! Такая реализация может быть причиной
            //     возникновения ошибки !!!
            TPtr8 bufferPtr( iBuffer->Des() );
            iFile.Read( bufferPtr, KFileBufferSize, iStatus );
 
            // Индикация невыполненной задачи
            SetActive();
            }
        }
    else
        {
        // Обработка ошибки.
        }
    }

Однако, запустив программу на эмуляторе, обнаружил, что не все так гладко, как описал автор. После чтения нескольких фрагментов (причем не важно, какого размера), программа "вылетала" с кодом -38: A non-descriptor parameter was passed by a client interface, when a server expected a descriptor. Из описания ошибки, в общем-то, стало понятно, что проблемы с автоматической переменной bufferPtr. Действительно, может произойти следующее (а в моем случае именно это и произошло). Метод RunL() завершает свое выполнение раньше, чем завершится асинхронная операция чтения содержимого файла, и вместе с этим методом прекращает свое существование переменная bufferPtr. Чтобы исправить ситуацию, я добавил в класс (в данном примере это класс CXmlHandler) переменную-член iBufferPtr, используя ее в приведенных выше методах вместо bufferPtr.


class CXmlHandler : public CActive, MContentHandler
{
    ...
    private:

        TPtr8 iBufferPtr;
    ...
};

Нужно отметить, что эта ошибка проявилась не сразу, программа нормально работала, пока размер файла не превысил 10 кбайт.