×
Namespaces

Variants
Actions

Использование дескрипторов: советы

From Nokia Developer Wiki
Jump to: navigation, search
Article Metadata

Статья
Автор: truf (05 Feb 2008)
Последнее редактирование: hamishwillee (10 Nov 2011)

Разрешение на публикацию перевода этого документа любезно предоставлены Jo Stichbury. С оригинальной версией вы можете ознакомиться здесь.

Contents

Никогда не создавайте экземпляры TDes или TDesC.

В классах TDes16 и TDesC16 конструкторы по умолчанию объявлены приватными. Поэтому, компилятор не позволит вам создать их экземпляр явно. Но в этих классах не объявлены копирующие конструкторы, и при попытке создать экземпляр из копии другого дескриптора не возникнет ошибка компиляции. (Фактически, будет вызван неявный копирующий конструктор).

_LIT(KExample, "Fred");
TPtrC original(KExample); // обычный дескриптор, порожденный от TDesC
// Будет выполнено копирование полей типа и длины в объект базового класса
TDesC copy(original); // Использование неявного копирующего конструктора

Если вы грамотно воспользуетесь подобным кодом, то не вызовете ошибку в программе. Но навряд ли существует разумная причина для его использования. Все равно этот код приведет к сбою при обращении к данным, т. к. TDes и TDesC являются абстрактными классами и не могут хранить данные.

Перед записью данных, убедитесь что в дескрипторе достаточно места.

Попытка записать данные за границами выделенной дескриптору памяти вызовет панику в приложении и для debug и для release сборок. Функции дескрипторов используют выражение __ASSERT_ALWAYS для проверки возможности доступа к памяти. Паника приведет к немедленному аварийному завершению работы приложения или сервера. Поэтому, вам нужно быть абсолютно уверенными в том, что в вашем дескрипторе достаточно места. Проверить это вы сможете с помощью методов Length() и MaxLength().

_LIT8(KImageGif, “image/gif”); // 9 символов
TBuf8<8> mimeType8; // Места хватит только на 8 символов
mimeType8.Copy(KImageGif); // Паника!

Нужен небольшой дескриптор известного размера? Используйте TBuf или TBufC.

TBuf или TBufC подойдут вам, если требуемый размер дескриптора известен во время компиляции. Рекомендуемая длина для таких дескрипторов - не более 256 байт (что составляет 128 символов, т.е. TBuf<128>, так как каждый символ занимает 2 байта).

Не рекомендуется создавать большие буферы в стеке. Для работы с дескрипторами, хранящими более 256 символов, воспользуйтесь создаваемым в куче классом HBufC. Еще одним способом экономии памяти стека является объявление дескрипторов членами C класса. C классы всегда создаются в куче. Необходимость бережного отношения к выделению памяти в стеке продиктована его небольшими размерами. Обычно, в Symbian OS размер стека составляет 8 - 12 Кб. Поэтому, размещая в стеке слишком большие дескрипторы вы рискуете переполнить его.

Эмулятор Windows увеличивает размер стека по мере его заполнения, поэтому вы можете не обнаружить эту проблему до тех пор, пока не запустите свою программу на настоящем устройстве. Как определить, что вы столкнулись с переполнением стека? В случае переполнения, попытка использовать стек вызовет обращение к адресному пространству, не принадлежащему процессу. Это приведет к ошибке доступа, которая сгенерирует исключение и панику KERN-EXEC 3. После этого программа аварийно завершится.

Нужен большой дескриптор неизвестного размера? Используйте HBufC.

HBufC - дескриптор создаваемый в куче. Память для него выделяется во время работы программы. Он может быть использован и как локальная переменная, и как член класса. Как и при работе с прочими создаваемыми в куче объектами, вы должны остерегаться утечек памяти. Используйте стек очистки для локальных переменных этого типа, и не забывайте освобождать занимаемую дескриптором память в деструкторе класса, если HBufC является его членом.

Более подробную информацию об использовании HBufC вы можете получить в FAQ о дескрипторах.

Хотите изменить содержимое HBufC? Вызывайте Des().

HBufC порожден от TDesC и наследует весь набор функций неизменяемых дескрипторов. Но, так как он не является потомком TDes, его содержимое нельзя изменять. Поэтому, для записи в HBufC вам потребуется создать изменяемый дескриптор, ссылающийся на тот же участок памяти. Доступ к функциям изменения данных, таким как Format(), Append() и Fill(), вы можете получить с помощью функции HBufC::Des(), возвращающей TPtr:

HBufC* robert = HBufC::NewL(4); // Read Only Bert - только для чтения
TPtr rwbert(robert->Des()); // Read Write Bert - для чтения и записи
_LIT(KBert, "Bert");
rwbert.Copy(KBert); // В содержимое дескриптора robert скопирована строка Bert

Помните, что для выполнения изменений над хранящимися в HBufC данными, дескриптор должен иметь достаточно свободного места. Дескрипторы не увеличивают размер выделенной им памяти автоматически. Если выделяемой дескриптору памяти явно не достаточно - код все равно скомпилируется, но вовремя выполнения в приложении будет вызвана паника. (Подробнее об этом говорилось в Совете 2.). Следующий пример вызовет ошибку:

HBufC* robert = HBufC::NewL(2); // Read Only Bert - только для чтения
TPtr rwbert(robert->Des()); // Read Write Bert - для чтения и записи
_LIT(KBert, "Bert");
rwbert.Copy(KBert); // Паника! Выделено недостаточно памяти для хранения "Bert"

Причина, по которой не существует класса HBuf обсуждается здесь.

Необходимо считать данные из HBufC? Не пользуйтесь Des().

Для чтения данных из дескриптора, ему достаточно быть наследованным от TDesC. Класс HBufC порожден от TDesC и имеет доступ ко всем функциям чтения данных, реализованным в TDesC. Все что вам нужно - получить объект по указателю.

_LIT(KBert, "Bert");
HBufC* bert = KBert.AllocL();
TPtrC halfOfBert = bert->Left(2);

Одна из наиболее распространенных ошибок при использовании дескрипторов - вызов Des() класса HBufC* в случае, когда требуется лишь неизменяемый указатель.

_LIT(KBert, "Bert");
HBufC* bert = KBert().AllocL();
TPtrC halfOfBert = bert->Des().Left(2); // Ненужный вызов Des()

Это не приведет к ошибкам в программе, но в целом снижает быстродействие и впустую тратит память.

При проектировании API, используйте базовые классы дескрипторов в качестве типов параметров и возвращаемых значений.

При проектировании API, используйте базовые классы дескрипторов в качестве типов параметров и возвращаемых значений. И запомните, что их всегда нужно передавать по ссылке, никогда не передавайте их по значению. Поэтому, параметры и тип результата функций должны быть объявлены как const TDesC& (в случае неизменяемых дескрипторов) или TDes& (в случае изменяемых дескрипторов).

Вызовы функций вашего API не должны ограничивать пользователя в использовании различных типов дескрипторов. Поэтому, не объявляйте типом параметра TBuf или другой наследованный от базового класс. А сами функции не должны зависеть от типа переданного в них дескриптора. Единственное, что должно быть для вас важно - это размер переданного в них изменяемого дескриптора.

Одна из причин такого подхода - это снижает вероятность необходимости изменения вызовов API, при изменении их реализации. Вам вряд ли понравится просить ваших клиентов вносить изменения в их код, потому что была нарушена совместимость.

К тому же, если ваша функция не отвечает за освобождение памяти дескрипторов-параметров, то вам не нужно знать хранятся ли они в куче или стеке. В этом случае, местонахождение дескриптора в памяти не должно играть роли. К примеру, если вы вызываете метод возвращающий дескриптор, то вам не нужно знать его тип. Исключение составляют возвращенные дескрипторы кучи - т. к. на вас ложится ответственность за освобождение занимаемой ими памяти.

Поэтому, при проектировании API, вы всегда должны использовать абстрактные базовые классы дескрипторов в качестве типов параметров и возвращаемых значений функций. К примеру, класс RFile определяет явные методы чтения и записи файла следующим образом:

IMPORT_C TInt Write(const TDesC8& aDes);
IMPORT_C TInt Read(TDes8& aDes) const;

Дескриптор, содержимое которого должно быть записано в файл, является неизменяемым и константой. Чтение из файла требует использования изменяемого дескриптора (его максимальный размер ограничивает число байт, которые могут быть в него считаны).

При создании функции, получающей изменяемый дескриптор в качестве параметра, вы не обязаны знать, достаточно ли в нем места для таких операций как Append(). Реализованные в базовых классах дескрипторов методы сами выполнят проверку его размера и вызовут панику в случае переполнения. Однако, вам может потребоваться предотвратить эту панику, так как пользователь может и не знать заранее требуемый размер буфера.

В этом случае, вы должны выполнять проверку размера дескриптора перед изменением его содержимого, и, если он окажется недостаточным - возвращать ошибку или выполнять сброс. В дополнение, вы можете записать минимальный требуемый размер дескриптора в переданный дескриптор (для этого, он должен быть не меньше 4 байт) - благодаря этому, пользователь сможет попытаться вызвать вашу функцию снова с дескриптором большего размера.

При передаче или возвращении из функции дескрипторов кучи HBufC по ссылке или указателю, вы можете столкнуться с некоторыми трудностями. О них мы поговорим отдельно.

Также вы можете ознакомится с некоторыми вопросами в "FAQ по дескрипторам":

Не путайте Size() и Length().

В базовом классе TDesC определены два метода: Size() и Length(). Их легко спутать.

  • Size() - возвращает число байт, занимаемых содержимым дескриптора.
  • Length() - возвращает число символов, содержащихся в дескрипторе.

В 8-ми битных дескрипторах каждый символ занимает ровно один байт, поэтому эти функции будут равнозначны. Однако, во всех версиях Symbian OS начиная с 5u, размер символа по умолчанию равен 16-ти битам; а это значит, что каждый символ занимает два байта.

Поэтому, (если только вы не работаете с очень старой SDK) Size() всегда возвращает вдвое большее значение, чем Length().

Опасайтесь вызовов MaxLength() объекта TPtr полученного из HBufC::Des().

При получении изменяемого дескриптора с помощью Des() класса HBufC наблюдается интересный побочный эффект.

Как вы помните, HBufC порожден от неизменяемого TDesC, и поэтому объект HBufC не содержит информации о свей максимальной длине - она ему просто не требуется. Но при вызове Des() вы получаете изменяемый дескриптор TPtr, ссылающийся на хранящиеся в куче данные. А раз он изменяемый - то он должен содержать данные о своей максимальной длине. Но откуда они берутся?

В качестве максимальной длины TPtr берется размер ячейки кучи, в которой выделена память для размещения данных HBufC. Не ошибитесь! Это значение может не совпадать с размером, указанным при создании дескриптора - есть две причины, приводящие к такому несоответствию:

  1. Если размер указанный при создании не кратен машинному слову (т.е. не делится нацело на 4 байта). Это наиболее распространенная причина.
  2. Если дескриптор не полностью заполняет выделенную ячейку памяти кучи. К примеру, минимальный размер ячейки равен 12 байтам, поэтому, если размер дескриптора при создании меньше 12 байт - в ней гарантированно останется неиспользованная память (расположенная в после данных дескриптора).

В любом случае, максимальная длина дескриптора TPtr возвращенного функцией HBufC::Des() может оказаться больше, чем размер, указанный при создании дескриптора HBufC. Пример:

const TInt KMaxBufLength = 9;
HBufC8* buf = HBufC8::NewL(KMaxBufLength);
TPtr8 ptr(buf->Des());
TInt maxLength = ptr.MaxLength(); // maxLength > 9, но может быть не равен 12

Не думайте, что максимальная длина просто округляется до целого машинного слова. В приведенном примере она может оказать и больше 12 байт, в зависимости от размера ячейки в которой выделена память для дескриптора.

Поэтому, раз вы не можете быть уверены в результате MaxLength(), то безопаснее всего пользоваться тем значением, которое использовалось при выделении памяти дескриптору HBufC. Если длина буфера при создании была установлена равной размеру выделенной памяти, то вы можете воспользоваться следующим кодом:

const TInt KMaxBufLength = 9;
HBufC8* buf = HBufC8::NewL(KMaxBufLength);
TPtr8 ptr(buf->Des());
TInt unpredictable = ptr.MaxLength(); // maxLength > 9, но может быть не равен 12
TInt bufLen = buf->Length();

bufLen будет равен 0, т.к. размер буфера не был увеличен до максимального. Для этого воспользуйтесь NewMaxL():

HBufC8* buf = HBufC8::NewMaxL(KMaxBufLength);
TInt bufLen = buf->Length(); // Теперь bufLen равен 9

Что нам это дает?

Вы можете отловить ошибку. Вот пример, который я использовала в своей книге. Он демонстрирует применение указателей для изменения содержимого дескриптора и проверку на доступ к памяти за границами дескриптора:

_LIT(KPanic, "TestPointer");
const TInt KBufferLength = 10;
void TestPointer()
{// Создадим буфер с размером KBufferLength = 10 байт
HBufC8* myBuffer = HBufC8::NewMaxL(KBufferLength);
TPtr8 myPtr(myBuffer->Des());
myPtr.Fill('?'); // Заполняем символами '?'
 
// Указатель на дескриптор в памяти
TUint8* ptr = (TUint8*)myPtr.Ptr();
TInt maxLength = myPtr.MaxLength();
 
for (TInt index = 0; index < maxLength; index++)
{// Следующий код вызовет панику в конце буфера (index = 10)
// потому что myPtr.MaxLength() > KBufferLength
__ASSERT_DEBUG(index<KBufferLength, User::Panic(KPanic, KErrOverflow));
(*ptr) = '!'; //Заменяем содержимое символом '!'
++ptr;
}
 
}

Используйте операторы >> и << для сохранения и чтения дескриптора из потока. Но помните, что они могут вызвать сброс!

При сохранении данных в поток или считывании данных из потока, предпочтительнее пользоваться стандартными операторами. Следующий код, демонстрирует чтение/запись дескриптора из потока. Сначала сохраняется размер дескриптора, а затем его данные. Этот пример неудобный и неэффективный.

// Сохраняем содержимое aDes в aStream
void CMyClass::ExternalizeL(RWriteStream& aStream, const TDesC& aDes) const
{// Запишем длину aDes
aStream.WriteUint32L(aDes.Length());
// Запишем содержимое aDes
aStream.WriteL(aDes, aDes.Length());
}
 
// Считываем данные из aStream и создаем HBufC
HBufC* CMyClass::InternalizeL(RReadStream& aStream)
{// Считываем длину aDes
TInt size=aStream.ReadUint32L();
// Выделяем буфер
HBufC* heapBuf = HBufC::NewL(size);
// Создаем изменяемый дескриптор над heapBuffer
TPtr ptr(heapBuffer->Des());
// Считываем данные в дескриптор
aStream.ReadL(ptr,size);
return (heapBuf);
}

Если вы подключите библиотеку estor.lib, вы сможете воспользоваться операторами-шаблонами Symbian OS для более эффективного записи и чтения из потока. При этом информация о размере дескриптора сжимается, что делает хранение дескриптора максимально компактным.

Вы можете использовать оператор >> для чтения строковых данных, записанных с помощью оператора >>, либо передать поток в HBufC::NewL() и воссоздать дескриптор. Любой из этих способов намного более эффективен ранее приведенного:

// Запишем содержимое aDes в aStream
void CMyClass::ExternalizeL(RWriteStream& aStream, const TDesC& aDes) const
{
aStream << aDes;
}
 
// Считаем содержимое aStream и создадим HBufC
HBufC* CMyClass::InternalizeL(RReadStream& aStream)
{// KMaxLength нужно где-то определить.
// Это максимальное количество данных, которое может быть считано из буфера
HBufC* heapBuf = HBufC::NewL(aStream, KMaxLength);
}

Как вы уже наверное заметили, и ExternalizeL() и InternalizeL() - функции, способные вызвать сброс. Все дело в том, что сброс способны вызвать операторы-шаблоны >> и <<. Они не могут быть объявлены в соответствии с соглашением об именовании в Symbian OS и иметь суффикс L. Кроме того, утилита LeaveScan не замечает их возможности вызвать сброс.

Поэтому, при их использовании, всегда помните о необходимости обработки сбросов.

Внутренние ссылки

FAQ по дескрипторам

This page was last modified on 10 November 2011, at 06:13.
241 page views in the last 30 days.
×