×
Namespaces

Variants
Actions

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

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

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

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


Contents

Об авторе.

Мое имя - Jo Stichbury, я автор книги Symbian OS Explained: Effective C++ Programming for Smartphones, издательства Symbian Press. Об этой книге вы можете узнать больше на моем сайте.

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

Многое в этом документе ранее было опубликовано в FAQ моего бывшего коллеги из Sony Ericsson - John Blaiklock. Я очень признательна ему за разрешение воспользоваться этой информацией. Я также хочу выразить признательность Jarmo Petajaaho, за его корректуру и ценные замечания.

Я благодарю Bill Bonney за его предложения и модификацию шаблона блога.

Добро пожаловать.

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

Большинство изучающих Symbian OS программистов, уже имели опыт программирования в других средах - они считают, что уже знают о строках все. Действительно, в C строки очень просты в использовании, хотя и позволяют допустить ошибку множеством способов. Классы String и StringBuf в Java обладают почти идеальным соотношением производительности и простоты использования. А различные String и CString подобные классы в библиотеках множества разнообразных сред программирования на C++ обычно работают удивительно быстро.

Но затем они сталкиваются с дескрипторами в Symbian OS. Если и есть лучший способ спустить с небес на землю C++ программиста в первую же неделю разработки для Symbian OS - то это дескрипторы. “Господи, да их тут тьма тьмущая” - вот что обычно можно услышать от читателя, познакомившегося с главой о дескрипторах из книги по программированию на Symbian OS. Они смотрят на разнообразные диаграммы соответствия типов дескрипторов их эквивалентам из C. Они запускают примеры и уверены, что уже во всем разобрались. А затем, они пытаются использовать дескрипторы на практике - и понимают, что все работает совсем не так, как они предполагали.

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

Не делайте этого. Боритесь с ними. Честно говоря, у вас нет другого выбора, если вы решили стать разработчиком для Symbian OS. Есть множество причин для использования дескрипторов и вытекающих из этого преимуществ - это станет очевидно по мере вашего знакомства с этим документом.

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

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

Новый подход к объяснению дескрипторов.

В каждой книге по программированию для Symbian OS есть глава о дескрипторах, обычно расположенная одной из первых. Они все очень похожи, и содержат иерархию классов, пару диаграмм со стрелочками, и, чаще всего, таблицу соответствия строк из C каждому типу дескрипторов. Кроме того, вы обнаружите несколько примеров использования дескрипторов, которые, конечно же, будут отлично работать.

Вы постараетесь не заснуть, и дочитаете эту неинтересную главу. Затем вы рассмотрите примеры, и окончательно убедитесь, что теперь вы можете использовать дескрипторы. Хотя в вашей голове могут затаиться вопросы вроде “Почему Ptr() не возвращает TPtr?”, вы продолжите читать книгу дальше. Затем вы начнете создавать какое-либо приложение, а оно неизбежно потребует использование дескрипторов, и тут - бац! Все не так. Вы не можете решить, какой тип дескрипторов использовать, а когда решите - эта дрянь не станет компилироваться, или не станет работать без ошибок или вообще не запуститься. Но не волнуйтесь, это случается с большинством новичков в программировании для Symbian OS, а, возможно, и со всеми новичками. Мы здесь для того, чтобы помочь вам разобраться со всем этим с помощью нашего нового подхода к объяснению дескрипторов.

Почему FAQ?

Эта статья содержит часто задаваемые вопросы. Длинные, сухие и невзрачные главы о дескрипторах тяжело изучать. Совсем другое дело - FAQ. Их гораздо интереснее читать, и они подают информацию в хорошо структурированном виде.

Хорошие примеры и плохие примеры

Книги о Symbian OS всегда содержат примеры, которые работают (или должны были бы, если бы не опечатки во время публикации). Обычно, с неправильным использованием дескрипторов мы знакомимся на досуге. В тот момент, когда мы разбираемся, почему оно не работает. Это не самый приятный досуг. Поэтому, в этой статье вы также найдете множество примеров неправильного использования дескрипторов. Мы публикуем наши собственные ошибки, для того чтобы вы их не повторяли.

Повторение

Мы будем повторять одно и то же разными способами. Повторять собственные слова. Мы делаем это специально. Без понимания некоторых вещей, невозможно успешное использование дескрипторов. Если повторение поможет вашему пониманию, то оно того стоит.

Я уже говорила, что буду повторяться?

Основы.

Что такое дескрипторы?

Дескрипторы – это строки в Symbian OS.

Так почему они называются дескрипторами, а не строками?

Их назвали дескрипторами (от англ. describe – описывать, также известны как описатели, прим. перев.), т.к. они сами себя описывают. Каждый объект дескриптор содержит не только строку данных, но и ее длину, а также ее “тип”, определяющий метод хранения дескриптора в памяти и его структуру.

Так они могут быть использованы для хранения текста?

Да, могут. Также они могут быть использованы для хранения бинарных данных.

Хранят ли они текстовые данные в ASCII или Unicode?

И так, и так. Есть два типа: 8-ми и 16-ти битные дескрипторы. 8-ми битный вариант - используется для хранения ASCII символов и бинарных данных. В этом случае, наименьшим элементом является 8-ми битный (1 байт) символ. 16-ти битный тип используется для хранения Unicode символов. В этом случае, наименьшим элементом является 16-ти битный (2 байта) символ.

Как я узнаю, какой из них я использую?

Каждый тип оканчивается на 8 или 16. Например, TDesC8 или TDesC16.

А что за тип дескриптора без 8 или 16 на конце? Например, TDesC?

Они известны как дескрипторы нейтрального размера (“neutral” width), т. к. размер их элементов определяется при компиляции и зависит от платформы. Давным-давно, в Symbian OS (тогда она была известна еще и как EPOC32) использовался 8-ми битный ASCII текст, и по-умолчанию размер элемента нейтрально дескриптора был 8-бит. Но после выхода Symbian OS версии 5U (также известной как ER5U), Symbian OS использует Unicode строки.

Поэтому, до тех пор, пока вы не работаете с SDK для платформ, вышедших до версии 5U - все нейтральные дескрипторы (TBufC, TPtr, HBufC и т.п.) будут 16-ти битными, и в действительности являются TBufC16, TPtr16, HBufC16 и т.д.

Как мне узнать размер символа для моей платформы?

Как было сказано выше – если вы не пишете программу для OS вышедшей до Symbian OS версии 5U (использовалась в телефоне Ericsson R380 в 2000 г.), то вы можете быть уверены, что по-умолчанию будут использованы 16-ти битные символы.

Так какой размер символа мне использовать?

Если вам нужны 8-ми битные данные, к примеру, в вызовах определенных методов API (таких как функции Read() или Write() класса RFile), для электронной почты, или для манипулирования ASCII данными, то вам следует использовать 8-ми битные дескрипторы. Они также пригодятся для хранения цепочки бинарных данных.

Если вам необходимо работать с 16-ти битным текстом, например, Java строками, и вы хотите явно на это указать, используйте 16-ти битные дескрипторы.

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

Дескрипторы также являются NULL-завершенными, как и строки в C?

Нет.

Почему?

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

Их сложнее использовать?

Не совсем. Просто их часто применяют неправильно. А ведь для правильного использования вам потребуется совсем немного - знание нескольких правил и приемов. Они просто не были хорошо объяснены - до сих пор...

Вы можете ознакомиться с описанием дескрипторов в Symbian Developer Library (библиотека разработчика Symbian), которая входит в состав любого Software Development Kit (набор инструментальных средств разработки программного обеспечения), например, для телефонов на базе UIQ или S60. Это описание также доступно в сети – ссылка помещена в раздел "внешние ссылки на другие ресурсы о дескрипторах".

Описание дескрипторов [1].

Зачем использовать дескрипторы?

Могу я использовать обычные массивы из C для хранения текста и бинарных данных, и стандартную библиотеку C для работы с ними, как и прежде?

Да.

Должен ли я использовать стандартные массивы из C и стандартную библиотеку для строк из C при программировании для Symbian OS?

Нет.

Почему нет?

Стандартные массивы C небезопасны. Почему? Потому что они не имеют информации о своем размере. NULL-завершенные строки опасны и неэффективны. Более того, ничто не помешает вам продолжить запись данных за пределами памяти, отведенной C строке. Это может привести к разнообразным негативным последствиям, например - к повреждению данных.

Я пользуюсь C строками, но мне нужно воспользоваться API, требующим работы с дескрипторами. Как мне конвертировать мои данные?

Совершенно верно. Почти везде в Symbian OS C++ API вы встретитесь с использованием дескрипторов для передачи куда-либо текста и бинарных данных. Поэтому, если вы используете C строки, то у вас возникнут проблемы с вызовом функций API. Вы должны будете конвертировать данные при обращении к каждой требующей передачи дескриптора функции, а это неэффективно. Так может быть вам продолжить чтение, и, наконец, научиться правильно использовать дескрипторы?

В чем отличие дескрипторов от прочих типов?

Так как выделение памяти для дескриптора и ее освобождение ложится на плечи программиста (подробно описано в разделе "Управление памятью и дескрипторы"), они не похожи на стандартные строки из C++ и Java или CString из MFC.

Также, благодаря встроенной защите от переполнения буфера, они не нуждаются в определении размера хранящихся данных с помощью NULL-завершения, и тем самым не похожи на строки из C. В разделе "Зачем использовать дескрипторы?" вы увидите преимущества их использования по сравнению с привычными, но опасными C строками.

Управление памятью и дескрипторы.

Изменяют ли дескрипторы свой размер автоматически, когда им требуется больше памяти?

Нет. Размер выделенный дескриптору памяти не меняется, и указывается при создании. Они не могут динамически менять ее размер, однако вы можете повторно выделить в куче больший объем памяти для дескриптора типа HBufC. Но, как мы увидим в разделе "Как использовать дескрипторы-буферы, создаваемые в куче?", это имеет свои подводные камни.

Почему? Было бы удобнее делать это автоматически.

Это сделало бы жизнь программиста куда проще, но и превратило бы код в гораздо менее быстродействующий. Для обработки всех возникающих исключений, код пришлось бы усложнить. Задумайтесь над этим:

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

В Symbian OS производительность является наивысшим приоритетом, поэтому программист должен взять на себя заботу о выделении памяти.

Что будет, если я выйду за пределы выделенной дескриптором памяти?

Вы получите панику как в debug, так и в release сборках. Паника будет из категории USER, информация о которой можно найти в документации [2]. Предполагается, что не произойдет никакого неразрешенного доступа к памяти, перемещения или повреждения данных. Однако, выполнение вашего кода будет аварийно завершено. В случае если код выполнялся в системном потоке, это может привести к перезагрузке телефона.

А это не слишком радикально?

Может быть, зато безопасно. Вы не перепишите данные в не предназначенной для этого области памяти, что исключает возникновение сложно объяснимых и обнаруживаемых ошибок, к которым может приводить использование C строк.

Выполняют ли дескрипторы сборку мусора?

Нет. По тем же причинам, по которым они не выполняют выделения памяти или изменения ее размера. Дескрипторы не выполняют сборку мусора из-за большого количества вызываемых в связи с этим сложностей и издержек.

Есть ли ограничения на размер дескриптора?

Нечто вроде этого.

Дескрипторы могут храниться в стеке и куче. На них накладываются естественные ограничения Symbian OS, как и на прочие переменные:

  • Дескриптор в стеке должен быть небольшим: размер в 256 байтов (128 двухбайтовых символов) наиболее оптимален. В разделе "Большие дескрипторы в стеке" представлена информация о том, как избежать напрасных трат памяти в стеке из-за использования переопределений (typedef) типов дескрипторов и создаваемых в стеке классов, инкапсулирующих дескрипторы большого размера.
  • Дескриптор в куче может быть намного больше, и его размер зависит от количества доступной в куче памяти. Но помните, что Symbian OS работает на устройствах с весьма ограниченным объемом памяти, так что выделение дескриптору большого объема памяти в любом случае неразумно.

Сама структура объекта дескриптора (будет описана позднее) ограничивает максимальный размер дескриптора 2^28 байтами (256 Мб). Т.к. каждый Unicode символ равен двум байтам, то максимальной длиной дескриптора можно считать 2^27 символов.

Большие дескрипторы в стеке.

Размер стека в Symbian OS очень ограничен (по умолчанию - 8 KB). Поэтому, вы должны по возможности избегать создания в стеке дескрипторов большого размера. Иногда Symbian OS подталкивает к нарушению этого правила, определяя некоторые классы и переопределяя типы дескрипторов, с помощью которых очень легко исчерпать всю память стека. Вы должны не забывать об этом, и использовать их только убедившись, что в стеке достаточно свободного места.

К примеру, TFileName переопределяет следующий тип дескриптора:

typedef TBuf<KMaxFileName> TFileName;

где

const TInt KMaxFileName=0x100; // = 256 в десятичной системе счисления

Конечно же, каждый символ в этом дескрипторе займет 2 байта, т. к. ваша Symbian OS, скорее всего, использует UNICODE, и при сборке программы размер символа по умолчанию будет равен двум байтам (как было рассказано в разделе основы). Поэтому, каждый объект TFileName созданный в стеке займет 512 байт (1/16-ю от стандартного размера стека), в не зависимости от того, сколько текста вы в него поместите!

Конечно, это очень привлекательно - т.к. вы сможете не волноваться о переполнении буфера. Но это влетит вам в копеечку.

Задумываясь о размере стека, вы всегда должны помнить об объеме памяти, поглощаемом следующими объектами:

  • TFileName 512 байт
  • TEntry 544 байт
  • TFullName 512 байт
  • TName 256 байт

Если же вам все же пришлось их использовать, то лучше всего создавать эти объекты в куче. Этого можно с легкостью добиться, объявив их членами создаваемого в куче класса (к примеру, C класса). После этого вам уже не нужно выделять для них память в куче самостоятельно – будучи членами C класса, они будут созданы там автоматически. Другой способ – выделение для них памяти в куче с помощью оператора new. Но при этом убедитесь, что ваш код не приведет к сбросу, а объект уничтожается, как только он больше не нужен.

Как создать небольшой дескриптор в стеке?

Если он должен быть изменяемым, воспользуйтесь TBuf. Если не изменяемым - используйте TBufC. Вам потребуется указать размер памяти выделяемой для вашего дескриптора в стеке.

Вот пример:

_LIT(KFred, "fred"); // Символьная строка, о них мы расскажем позднее
TBufC<4> constantFred(KFred);

На практике, вы, возможно, не станете использовать неизменяемый дескриптор, т.к. вы можете сразу использовать символьную строку KFred с оператором () (об этом мы расскажем отдельно в разделе о символьных дескрипторах). Вместо создания дескриптора Fred, вы можете вызывать просто KFred().

Однако, этот подход необходим при использовании изменяемых дескрипторов. Например, для ведения лога:

TInt CMyActiveObject::RunError(TInt aError)
{
_LIT8(KError, "CMyActiveObject::RunError: %d");
TBuf8<35> errorBuf;
errorBuf.Format(KError, aError); // Информацию о TDes::Format() вы можете почерпнуть
// пройдя по ссылке 3 в разделе ссылок.
 
iLogger.Log(errorBuf); // iLogger - объект, принадлежащий классу,
// обеспечивающий вывод на экран или в файл.
return (KErrNone);
}


Что если я хочу хранить ASCII текст в дескрипторе?

В некоторых случаях, к примеру, при работе с MIME типами или бинарными данными, вам не избежать операций с 8-ми битным текстом. В этом случае, просто воспользуйтесь 8-ми битными дескрипторами:

_LIT8(KImageGif, "image/gif");
TBufC8<15> mimeType(KImageGif);

Как создать дескриптор в куче? Как использовать такой дескриптор?

Создайте дескриптор типа HBufC (или HBufC8, если вы работаете с 8-ми битным текстом).

HBufC* heapbasedFred=HBufC::NewL(4);

Но как записать данные в созданный в куче дескриптор? HBufC порожден от неизменяемого класса дескрипторов?

Очень просто. Сначала вызовите HBufC::Des() чтобы получить TPtr, а он - изменяемый. Вот пример:

TPtr fredPtr(heapbasedFred->Des());
_LIT(KFred, "fred");
fredPtr.Copy(KFred);

А как читать данные из HBufC?

Вы можете просто переобъявить указатель HBufC следующим образом:

TBuf<4> stackbasedFred(*heapBasedFred);

Но я видел код, в котором вызывается функция Des() класса HBufC. Почему?

Это типичная ошибка, вносящая свою лепту в снижение производительности вашего кода. Функция Des() возвращает изменяемый дескриптор типа TDes, порожденный от TDesC, поэтому вы можете использовать его в любой функции, принимающей аргумент типа TDesC. Но это не обязательно.

Вот пример этой распространенной ошибки:

TBuf<4> stackbasedFred(heapBasedFred->Des()); // Не обязательно, просто используйте
TBuf<4> stackBasedFred(*heapBasedFred); // Более эффективный код

Как использовать дескрипторы в качестве параметров?

Базовые классы описаны в разделах о неизменяемых и изменяемых дескрипторах.

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

К примеру, вы можете вызывать следующую функцию, передав два аргумента любых типов, порожденных от TDesC и TDes соответственно. Например, HBufC и TBuf<8>, или TPtr и TBuf<3>:

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

Как сделать дескриптор-параметр доступным только для чтения?

Нужно объявить его как константу от неизменяемого дескриптора (const TDesC&). Например:

void SomeFunction(const TDesC& aReadOnlyDescriptor);

Эта функция все равно может вызываться с изменяемым дескриптором в качестве аргумента, таким как TPtr, потому что он порожден от TDesC (TPtr - это специальный тип TDes). В теле функции вы сможете выполнять над таким параметром только операции реализованные в TDesC. Содержимое параметра нельзя будет изменить, даже если вы передадите изменяемый, порожденный от TDes дескриптор.

Как сделать дескриптор-параметр доступным и для чтения и для записи?

Объявите его как изменяемый дескриптор (TDes&). Например:

void SomeFunction(TDes& aReadWriteDescriptor);

Вы не сможете вызвать эту функцию с неизменяемым дескриптором.

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

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

Можно ли использовать другие типы дескрипторов в качестве параметров функций?

Можно, но не нужно. К примеру, вы можете объявить аргумент функции типа TBuf<10>&. Но что, если в нее потребуется передать TPtrC, или HBufC, или даже TBuf<11>? Это означает, что вашу функцию не смогут вызвать без дополнительных манипуляций для того, чтобы уместить дескриптор в тот тип, который в ней указан. TDes и TDesC - наиболее общие типы, и именно они должны использоваться в качестве типов аргументов функций.

Моя функция использует параметры типа TDes или TDesC, как вы и сказали, но передача дескриптора в функцию не работает. Что не так?

О, сколько головной боли доставляет эта ошибка! В забыли один маленький символ &. Ваши типы параметров должны передаваться по ссылке, а не по значению, т. е. const TDesC& или TDes&.

Базовые классы TDesC и TDes не содержат методов для хранения данных. Если вы передаете их по значению, а не по ссылке - то вы используете статическое связывание, а значит, полиморфизм уже не работает - и вы получите на входе объект базового класса, не содержащий данных. При этом, все прекрасно скомпилируется, но работать не будет.

Никогда не пытайтесь создавать или работать напрямую с объектами классов TDesC или TDes. Это высокоэффективные абстрактные классы. Необходимость в создании объекта базового класса появляется очень редко, если вообще может появиться. Объекты всегда создаются от порожденных ими классов (TBufC, TBuf, TPtrC, TPtr, HBufC или RBuf).

Как использовать созданный в куче дескриптор в качестве результата функции?

Я хочу создать новый дескриптор в моей функции, как вернуть его из функции?

Вы должны возвращать HBufC*, как показано ниже:

HBufC* CreateSomeDescriptorL()
{
_LIT(KBert, "bert");
HBufC* newBert = KBert().AllocL();
return (newBert);
}

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

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

HBufC* CreateSomeDescriptorLC()
{
_LIT(KBert, "bert");
HBufC* newBert = KBert().AllocLC();
return (newBert);
}

Когда я могу вернуть из функции TPtr или TPtrC? А когда нет?

TPtr или TPtrC - это дескрипторы, не владеющие строкой данных. Они просто ссылаются на данные, содержащиеся в другом дескрипторе. Поэтому, вы можете вернуть их в качестве дескриптора, ссылающегося на часть данных другого дескриптора. Но вы должны быть уверены, что владеющий данными дескриптор будет существовать достаточное время. Пример:

TPtrC LeftChar(const TDesC& aInput)
{
if (aInput.Length()>0)
return aInput.Left(1); // Возвращает первый символ в начале строки
else
return KNullDesC;
}

А вот следующей пример неверен, т. к. созданный в стеке fred будет уничтожен сразу после завершения выполнения функции GetFred():

TPtrC GetFred()
{
_LIT(KFred, "Fred");
TBufC<4> fred(KFred());
TPtrC fredPtr(fred);
return (fredPtr);
}

Как хранятся в памяти неизменяемые дескрипторы?

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

Все (кроме символьных) дескрипторы порождены от базового типа TDesC (переопределены от TDesC16 в e32std.h и определены в e32des16.h). Суффикс "C" в конце имени класса указывает на то, что этот дескриптор является неизменяемым. Класс TDesC содержит реализацию методов для определения размера хранящихся в нем данных и доступа к ним (чтение, сравнение, поиск), но не содержит методов для изменения этих данных.

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

Хранение всех методов дескрипторов в одном классе также позволяет серьезно уменьшить объем памяти, занимаемой кодом, реализующим полный набор функций для работы со строками. Повторное использование кода позволяет уменьшить размер ROM, и демонстрирует лучшие традиции программирования на C++, т. к. такой код легче тестировать и им легче управлять. Производные классы реализуют лишь специфичные методы для создания данных и копирующего присваивания.

Первые 4 байта любого объекта дескриптора содержат одно и то же: размер хранимых данных. Вообще-то, только 28 из 32 бит используется для этого - первые 4 бита содержат тип дескриптора. Использование лишь 4 бит для задания типа ограничивает максимальное число различных типов 2^4 (=16), но во всех ныне существующих версиях Symbian OS используются только шесть (TBufC, TBuf, TPtrC, TPtr, HBufC и RBuf). Не похоже, что в обозримом будущем их число резко возрастет. Остальные 28 бит хранят размер данных, а это значит, что максимальный размер хранимых данных ограничен 2^28 байтами = 256 Мб.

Размещение в памяти прочих данных о дескрипторе зависит от его типа. Доступ к данным дескриптора каждого типа происходит с помощью (не виртуального) метода Ptr() базового класса TDesC. Этот метод использует выражение switch чтобы определить тип дескриптора (используя первые 4 бита его объекта) и вернуть правильный адрес начала его данных.

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

Как хранятся в памяти изменяемые дескрипторы?

Все изменяемые дескрипторы порождены от класса TDes, который в свою очередь произведен от TDesC. TDes имеет дополнительный член - переменную размером в 4-байта, которая хранит максимальную длину хранимых данных (объем выделенной для хранения данных памяти).

TDes реализует ряд методов для хранения данных. Как и для неизменяемых дескрипторов, все эти методы наследуются порожденными классами, и работают вне зависимости от их типа. Производные классы реализуют лишь методы создания и копирующего присваивания.

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

Дескрипторы используют выражение assert, чтобы убедиться, что объема выделенной памяти достаточно. Проверки выполняются и в debug и в release сборках, а при переполнении памяти в приложении возникает паника. Это убережет вас от небрежного обращения с памятью и переполнений буфера, свойственных строкам в C.

Управляют ли дескрипторы-указатели памятью, на которую они ссылаются?

Нет. Ни в коем случае.

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

Данные дескриптора-указателя не зависят от самого объекта дескриптора, а сам объект чаще всего создается в стеке. Хотя, иногда он создается в куче в качестве члена класса, наследуемого от CBase.

Как хранятся в памяти дескрипторы-указатели? Сколько памяти требуется для их хранения, помимо памяти занимаемой данными?

Т. к. все дескрипторы порождены от TDesC (или, в случае изменяемых дескрипторов, от TDes), первые 4 байта используются для хранения текущего размера данных дескриптора.

В константном дескрипторе-указателе (TPtrC) далее следует 4-х байтный указатель, поэтому, общий размер дескриптора равен двум машинным словам (8 байт). В изменяемом дескрипторе-указателе (TPtr), наследуемом от TDes, второе слово хранит максимальный размер данных, на которые он ссылается. А лишь затем следуют 4 байта с указателем на эту память. Таким образом, размер дескриптора равен трем машинным словам (12 байт).


Вы можете обратиться к диаграммам, иллюстрирующим хранение дескриптора-указателя в памяти [4].

Для чего служит TPtrC::Set()?

Метод Set() может быть вызван в константном или изменяемом дескрипторе-указателе, для того, чтобы он ссылался на данные другого дескриптора.

Пример:

_LIT(KFred, "Fred");
_LIT(KBert, "Bert");
TPtrC fred(KFred); // ссылается на 'Fred'
TPtrC bert(KBert); // ссылается на 'Bert'
bert.Set(fred); // bert теперь ссылается на 'Fred'

Для чего служит TPtr::operator=()?

Оператор присваивания, operator=(), определен только для изменяемых указателей. С его помощью данные копируются в память, на которую ссылается указатель. Если размер данных превышает размер памяти, то в приложении будет вызвана паника. TPtr также имеет метод Set(), применяемый для того, чтобы дескриптор указывал на другой участок памяти. Будьте осторожны - не перепутайте их.

Для повторения: Как было рассказано в соответствующем разделе, TPtr::Set() изменит указатель дескриптора для того чтобы он ссылался на другие данные. При этом также изменятся такие члены дескриптора, как длина данных и максимальная длина данных.

Оператор присваивания в TPtr скопирует данные в память на которую дескриптор ссылается в данный момент, если ее достаточно. Он может изменить текущую длину данных, но не изменит их максимальную длину.

Как объявить TPtr или TPtrC для последующего использования?

Я хочу лишь объявить TPtr, а инициализировать позже. Как мне быть?

TPtr8 myPtr(NULL, 0);

или, для членов класса, в списке инициализации конструктора:

CMyClass::CMyClass()
: iPtr(NULL, 0)
{}

Чтобы инициализировать его позднее, воспользуйтесь Set(), как описано в соответствующем разделе. Например:

myTPtr.Set(myHBufC->Des());
iPtr.Set(someExistingBuf);

Как использовать дескрипторы-буферы, создаваемые в стеке?

Классами создаваемых в стеке дескрипторов-буферов являются TBufC<n> и TBuf<n>. Они полезны для операций со строками фиксированной длины и относительно небольшими строками. (Не длиннее 256-символьного имени файла, т. е. для n <= 256). Как мы уже говорили в разделе о больших дескрипторах в стеке и в совете №3, это продиктовано ограниченным размером стека в Symbian OS. Если вам нужен буфер большего размера, вы должны создавать его в куче (или с помощью дескрипторов, создаваемых в куче, или с помощью объявления буферов TBuf и TBufC членами-переменными C класса). Будучи созданными в стеке, эти дескрипторы должны быть использованы только тогда, когда их период существования соответствует жизненному циклу их создателя.

Дескрипторы TBuf и TBufC похожи на char[] в C, но имеют внутреннюю проверку переполнения. Как вы уже наверно догадались, TBufC является неизменяемым, а TBuf - изменяемым дескриптором.

TBufC

Схема размещения этих буферов в памяти такова, что хранящиеся в них данные являются их неотъемлемой частью. Данные следуют после слова, содержащего их длину, в TBufC, и после слова, содержащего их максимальную длину, в TBuf. Вы можете посмотреть диаграмму их размещения в памяти [5].

TBufC - это тонкий класс-шаблон, использующий значение типа TInt для определения длины области данных. Неизменяемый буферный класс служит для хранения констант в виде строк или бинарных данных. Он наследован от TBufCBase (который в свою очередь порожден TDesC, и существует только в цепочки наследования. Он не должен использоваться напрямую). Вы можете узнать больше о тонких шаблонах из главы 19 моей книги Symbian OS Explained, ссылка на которую находится в соответствующем разделе.

TBufC определяет конструктор, позволяющий создать объект этого типа с помощью копирования данных из любого другого дескриптора или из оканчивающейся нулевым байтом строки. Он также может быть создан пустым и заполнен позднее. Это может показаться странным для неизменяемого буфера, но фактически константой являются хранящиеся в нем данные, а содержимое буфера может быть заменено целиком с помощью оператора присваивания этого же класса. Размер данных при замене не должен превысить объем выделенной для него во время создания памяти, иначе, как в debug, так и в release сборках в приложении будет вызвана паника.

TBufC также имеет метод Des(), который возвращает изменяемый дескриптор-указатель на хранящиеся в буфере данные. Итак, несмотря на то, что данные неизменяемого дескриптора не могут быть модифицированы явно (например, с помощью функций Format() или LowerCase()), вы можете изменить их, создав для них указатель TPtr. В этом случае, изменится и информация о длине хранимых данных в дескрипторе-указателе и в неизменяемом буферном дескрипторе. Но вы должны помнить, что она не может превысить максимальное значение, т.к. дескрипторы не управляют размером выделенной памяти. Поэтому, не вызывайте Append() из полученного TPtr, пока не убедитесь что в буфере достаточно места!

_LIT(KFred, "Fred");
_LIT(KBert, "Bert");
TBufC<4> fredBuf(KFred); // Создание из символьного дескриптора
TPtr ptr(FredBuf.Des()); // максимальная длина = 4
ptr = KBert; // fredBuf содержит "Bert"
 
_LIT(KCyril, "Cyril");
ptr = KCyril; // Паника! KCyril превышает максимальную длину ptr (=4)

TBuf

Как и TBufC, соответствующий изменяемый класс TBuf<n> является тонким классом-шаблоном, где n - определяет максимальный размер хранимых данных. TBuf порожден от TBufBase, который в свою очередь является потомком TDes. В связи с этим, TBuf позволяет выполнять все операции TDes и TDesC.

Не смотря на то, что буфер является изменяемым - управление выделенной для TBuf памятью ложится на ваши плечи (как и для всех прочих дескрипторов). Хранимые в нем данные не могут быть увеличены до большего, чем заданного при создании, размера. Если вы хотите увеличить размер данных - убедитесь, что во время компиляции вы указали достаточно большой размер буфера (но не слишком большой - иначе, вы впустую потратите память стека). Вы также можете воспользоваться дескриптором, память которого может быть увеличена во время выполнения программы. Таким дескриптором является создаваемый в куче HBufC, описанный здесь.

Как использовать дескрипторы-буферы, создаваемые в куче?

Дескрипторы создаваемые в куче, HBufC<n>, могут быть использованы для динамического выделения памяти и хранения в ней данных, размер которых не может быть оценен до компиляции. Также, они должны использоваться для работы с данными большого размера, т.к. их опасно хранить в стеке. В действительности, HBufC должен быть использован там же, где в C использовалась функция malloc().

Хотя эти дескрипторы реализуются классом HBufC, на практике используются только указатели на них: HBufC*. HBufC не соответствует соглашению об именовании классов в Symbian OS. Его префикс ‘H’ указывает на то, что его содержимое хранится в куче (от англ. Heap – куча, прим. перев.).

Этот дескриптор может быть создан с помощью одной из статических функций NewL() класса HBufC. Эта функция вызовет сброс, если в куче недостаточно свободной памяти (безопасная версия в этом случае возвращает NULL). Дескриптор кучи также может быть создан с помощью функций Alloc() или AllocL() класса TDesC - в этом случае HBufC будет инициализирован копией данных породившего его дескриптора.

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

_LIT(KFred, "Fred");
// Создаем дескриптор с максимальной длиной = 4 в куче
HBufC* heapBuf = HBufC::NewL(4);
// Копируем содержимое KFred
*heapBuf = KFred; // heapBuf теперь содержит "Fred"
// Альтернативный способ сделать то же самое:
HBufC* heapBuf = KFred().AllocL();
 
_LIT(KBert, "Bert");
TPtr ptr(heapBuf->Des()); // Изменяемый TPtr для содержимого heapBuf
ptr = KBert(); // Копируем "Bert" в heapBuf
delete heapBuf; // удаляем heapBuf - ptr больше не работоспособен

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

_LIT(KFred, "Fred");
// Создаем дескриптор с максимальной длиной = 4 в куче
HBufC* heapBuf = KFred().AllocLC();
 
_LIT(KCyril, "Cyril");
TPtr ptr(heapBuf->Des()); // Изменяемый TPtr для содержимого heapBuf
ptr = KCyril(); // Эта строка вызовет панику, т.к. максимальный размер буфера (4) превышен
 
// Что бы этого не произошло, мы должны выделить буферу больше памяти
// Будем считать, что наш дескриптор находится в стеке очистки
// На случай, если ReAllocL() вызовет сброс
heapBuf = heapBuf->ReAllocL(5);
 
// Realloc завершена, но указатель heapBuf мог измениться
// Мы должны обновить указатель, хранящийся в стеке
CleanupStack::Pop(); // Выталкиваем его из стека
// И помещаем в него обратно
CleanupStack::PushL(heapBuf);
 
// Т.к. функция ReAllocL могла изменить местоположение буфера в памяти
// мы также обязаны обновить ptr
ptr.Set(heapBuf->Des());
ptr = KCyril(); // копируем "Cyril" в heapBuf
 
CleanupStack::PopAndDestroy(heapBuf); // удаляем heapBuf

Мораль.

Дескрипторы кучи могут создаваться динамически, но они не изменяют свой размер автоматически, когда содержимое может превысить их размер.

Почему не существует класса HBuf?

Он и есть, и нет. До Symbian OS версии 8.0, изменяемого дескриптора кучи HBuf не существовало. Таким образом, у дескрипторов кучи нет симметрии с парой буферов стека TBufC и TBuf. Этому есть множество причин:

1). Программисты могли бы предположить, что изменяемый дескриптор кучи должен автоматически изменять размер выделенной памяти. Действительно, изменяемый класс HBuf без этой способности посчитали бы странным и бессмысленным, ведь содержимое можно редактировать и косвенно - через Des(). Но реализация динамического изменения размера в HBuf очень сложна, и сказывается на производительности: при нехватке памяти в куче, большинство его методов вызывали бы сброс, который нужно было бы обрабатывать. А так как почти все операции изменяемых дескрипторов реализуются базовым классом TDes, то это коснулось бы всех изменяемых дескрипторов.

2). Т. к. выделение дополнительной памяти для дескриптора кучи может полностью изменить его местоположение, то все указатели на его данные в объектах TPtr могут потерять смысл. Более того, если он был помещен в стек очистки, то его указатель в стеке также должен быть обновлен. Все это делает невозможным реализацию автоматического изменения размера HBuf.

3). Дескрипторы кучи изначально предназначались для чтения строк-констант различной длины из ресурсов. Т.к. они неизменяемы, то дескрипторы кучи имеют дополнительно только 4 байта, хранящие размер данных (и тип объекта). Это позволяет им занимать минимально возможный объем памяти в куче. При необходимости, данные можно изменить через метод Des(). Если бы HBuf существовал, он должен был бы занимать в памяти на 4 байта больше, что при частом его использовании увеличивало бы напрасную трату памяти кучи.

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

В связи с этим, в Symbian OS 8.0 и 8.1 был предложен класс RBuf, как аналог изменяемых дескрипторов кучи. Документацию Symbian об этом классе вы можете найти здесь [6].

Более подробно мы обсудим RBuf далее.

Что такое символьные дескрипторы?

Символьные дескрипторы - это дескрипторы-константы, которые компилируются в ROM, так как их содержимое никогда не меняется. Они эквивалентны static char[] в C.

Есть два типа символьных дескрипторов, макросы _LIT (наиболее эффективен, ему отдается предпочтение в использовании) и _L (не рекомендуется использовать, исключение составляет использование в тестировании).

В примерах, которые вы уже видели, я всегда использовала макрос _LIT:

_LIT(KFred, "Fred");

Для современных версий Symbian OS, это эквивалентно следующему коду:

_LIT16(KFred, "Fred");

Если нужен 8-ми битный символьный дескриптор, воспользуйтесь макросом _LIT8:

_LIT8(KFred, "Fred");

KFred может быть использован как дескриптор-константа напрямую, или для инициализации TPtrC/TPtr или TBufC/TBuf.

Этот макрос определен в заголовочном файле e32def.h и создает объект типа TLitC16 или TLitC8 (определены в e32des.h). Эти классы не наследованы от TDesC8 или TDesC16, но имеют ту же схему размещения в памяти, что и TBufC8 или TBufC16. Это значит, что символьные объекты могут использоваться везде, где используется TDesC. Символьные классы имеют очень полезный оператор (), который выполняет приведение символьного типа к TDesC. Пример:

_LIT(KFred, "Fred");
HBufC8* theHeapBuffer = KFred().AllocL();

Чтобы избежать лишнего кодирования, в Symbian OS уже определены символьные дескрипторы для пустых строк:

Нейтральный:

_LIT(KNullDesC,"");

8-и битный для не-Unicode строк:

_LIT8(KNullDesC8,"");

16-ти битный для Unicode строк:

_LIT16(KNullDesC16,"");

Почему макрос _L не рекомендуется использовать для символьных дескрипторов?

Почему макрос _L не рекомендуется использовать нигде, кроме как для целей тестирования? У него есть преимущество – его не нужно объявлять отдельно, а можно сразу использовать в выражении:

User::Panic(_L("TEST SERVER"), KErrNotOVerflow);

Как вы видите, использование этого макроса экономит время на объявление и выдумывание имен для каждого символьного объекта. Это хорошо, в том случае, если вы не заботитесь о размере занимаемым кодом (например, во время тестирования), но вам следует избегать его использования в конечном варианте программы. Почему?

В этом примере, текст "TEST SERVER" будет встроен в ROM как обычная, NULL-завершенная C строка - без информации о ее длине. Схема ее расположения не соответствует типичному дескриптору, а это значит, что для получения этих данных система должна будет создать в RAM указывающий на них объект TPtrC. Создание этого временного дескриптора-указателя потребует 4 байта для хранения размера данных и типа, а также выполнения инструкций для задания их значений и создания указателя на данные в ROM. Макрос _LIT помещает строку в ROM с той же схемой расположения, что и у дескрипторов. Поэтому, такая строка может быть использована напрямую из ROM.

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

Чтобы избежать напрасных трат памяти, вам следует всегда использовать макрос _LIT вместо _L для задания символьных строк.

Для чего служат Length(), MaxLength(), SetLength() и SetMax()?

TDesC::Length() возвращает число символов, содержащихся в дескрипторе.

Будьте осторожны, когда получаете размер дескриптора! Не перепутайте Size() и Length(). Они очень похожи, но Length() возвращает число символов, содержащихся в дескрипторе, а Size() - размер занимаемой данными памяти в байтах. Для 8-ми битных дескрипторов, когда каждый символ занимает один байт, эти функции будут эквивалентны. Однако, для всех версий Symbian OS начиная с версии 5u, символ будет занимать уже 16 бит - то есть два байта. В том случае, если вы не работаете с очень старой SDK, то вы увидите, что Size() всегда возвращает значение в два раза превышающее результат Length().

TDes::MaxLength() возвращает максимальный размер данных, которые может хранить изменяемый дескриптор.

TDes::SetLength() может быть использован для изменения длины дескриптора. Принимает значения от нуля до TDes::MaxLength(). Вы также можете воспользоваться функцией TDes::Zero(), чтобы установить размер данных в дескрипторе равным нулю. Или TDes::SetMax(), чтобы увеличить его до максимального значения.

Название функции SetMax() может ввести в заблуждение. Она не позволяет изменить максимальный размер дескриптора, а лишь, увеличить длину хранящейся в нем строки.

Как конвертировать данные из 8-ми битных в 16-ти битный дескрипторы и обратно?

В классе TDes определен ряд методов Copy() для копирования данных из различных 8-ми и 16-ти битных дескрипторов. Кроме того, вы можете получить данные из указателя на блок памяти фиксированного размера или из NULL-завершенной строки. Также эти методы могут выполнять копирование со сворачиванием, сверкой, и изменением регистра.

В любом случае, данные копируются из источника, указанного в качестве параметра, в изменяемый дескриптор, объект которого вызвал эту функцию. Если длина копируемых данных превысит размер дескриптора-приемника, возникнет паника USER 11.

// Из определения TDes16 в e32des16.h
 
IMPORT_C void Copy(const TDesC8& aDes); // Копирование 8-ми битного дескриптора
IMPORT_C void Copy(const TDesC16& aDes); // Копирование 16-ти битного дескриптора
// Копирование aLength символов из указателя aBuf
IMPORT_C void Copy(const TUint16* aBuf, TInt aLength);
 
IMPORT_C void Copy(const TUint16* aString); // Копирование NULL-завершенной aString
IMPORT_C void CopyF(const TDesC16& aDes); // Копирование и сворачивание
IMPORT_C void CopyC(const TDesC16& aDes); // Копирование и сверка
IMPORT_C void CopyLC(const TDesC16& aDes);// Копирование и перевод текста в нижний регистр
IMPORT_C void CopyUC(const TDesC16& aDes);// Копирование и перевод текста в верхний регистр
// Копирование и перевод первых символов строки в верхний,
// остальных символов - в нижний регистр
IMPORT_C void CopyCP(const TDesC16& aDes);

Заметим, что так как метод Copy() перегружен версиями для 8-и и 16-ти битных дескрипторов, то возможно произвести копирование данных:

   * из 8-и битного дескриптора в 8-и битный дескриптор (TDes8 -> TDes8),
   * из 16-и битного дескриптора в 16-и битный дескриптор (TDes16 -> TDes16),
   * из 8-и битного дескриптора в 16-и битный дескриптор (TDes8 -> TDes16),
   * из 16-и битного дескриптора в 8-и битный дескриптор (TDes16 -> TDes8).


Копирование между дескрипторами с символами одинаковой длины тривиально, но копирование между дескрипторами с символами различной длины требует пояснений:

Применение метода Copy() из TDes16 к 8-ми битному дескриптору параметру, добавит к каждому символу копируемой строки нулевой байт.

// Создаем 16-ти битный дескриптор
_LIT16(KFred, "Fred");
TBuf16<4> wideFred(KFred); // В памяти = F\0R\0E\0D\0
 
// Создаем 8-ми битный дескриптор
_LIT8(KBert, "Bert");
TBuf8<5> narrowBert(KBert); // В памяти = BERT
 
// Копируем содержимое 8-ми битного дескриптора в 16-ти битный дескриптор
wideFred.Copy(narrowBert); // В памяти = B\0E\0R\0T\0

Применение метода Copy() из TDes8 к 16-ми битному дескриптору параметру, отбросит второй байт каждого символа (обычно это нулевой байт). Это значит, что такое копирование будет работать только для символов, код которых не превышает 255 (в десятичной системе счисления).

// Создаем 16-ти битный дескриптор
_LIT16(KFred, "Fred");
TBuf16<4> wideFred(KFred); // В памяти = F\0R\0E\0D\0
 
// Создаем 8-ми битный дескриптор
_LIT8(KBert, "Bert");
TBuf8<5> narrowBert(KBert); // В памяти = BERT
 
// Копируем содержимое 16-ти битного дескриптора в 8-ми битный дескриптор
narrowBert.Copy(wideFred); // В памяти = FRED

Метод Copy() предоставляет простой способ копирования и конвертирования данных между 8-ми и 16-ти битными дескрипторами, прекрасно работающий в том случае, если каждый Unicode символ оканчивается нулевым байтом.

Если это условие не выполняется, то вы можете воспользоваться функциями из библиотеки конвертирования Symbian OS, реализованной в charconv.lib (она работает не только с ASCII и Unicode, но и с UTF-7 и UTF-8).

Как решить, какой тип дескриптора стоит использовать?

Следующая диаграмма должна вам помочь. Она взята из моей книги, Symbian OS Explained (стр. 76), с небольшими изменениями:

Descriptor flowchart.jpg

Где найти эквивалентные функции для дескрипторов из библиотеки C?

Почти вся функциональность библиотеки работы со строками из C была реализована в базовых классах дескрипторов. Поэтому, вы можете производить большинство операций над строками точно так же, как вы привыкли делать это в C.

Также существует очень полезная таблица соответствия функций работы со строками в С и эквивалентным им функциям дескрипторов. Эту информацию вы можете найти в секции 5 ссылки [7]

Как увеличить быстродействие при использовании HBufC?

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

HBufC может быть получен с помощью функций Alloc() или AllocL() класса TDesC. Поэтому, вы можете заменить следующий код:

void SomeFunctionL(const TDesC& aDes)
{
HBufC* heapBuffer = HBufC::NewL(aDes.Length());
TPtr ptr(heapBuffer->Des());
ptr.Copy(aDes);
...
}

Куда более эффективным:

void SomeFunctionL(const TDesC& aDes)
{
HBufC* heapBuffer = aDes.AllocL();
...
}

(2) Не обращайтесь к HBufC::Des(), если вам нужен неизменяемый дескриптор для чтения данных.

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

void Log(const TDesC& aLogBuf); // Прототипы функций
void Convert(TDes& aBuf);
 
void CMyClass::SomeFunction()
{// iHeapBuf член класса CMyClass
// Получим доступ на чтение (TDesC&) данных из iHeapBuf
Log(*iHeapBuf); // Вызов метода, принимающего const TDesC&
 
// Получим доступ на чтение и запись (TDes&) данных из iHeapBuf
Convert(iHeapBuf->Des()); // Вызов метода, принимающего TDes&
}


Между прочим, интересный побочный эффект создания изменяемых дескрипторов из неизменяемых дескрипторов в куче освещен в совете №9.

Как конвертировать 8-ми битный дескриптор в Java строку?

Взгляните на ответ FAQ-0298 на сайте Symbian's Developer FAQ & Tech Tips:

"How do I convert an 8-bit descriptor to a Java string?"

В настоящее время, нет необходимости использовать макросы приведения Symbian (например, REINTERPRET_CAST), т. к. определены макросы, эквивалентные стандартным макросам из C++ (например, reinterpret_cast). Они остались со времен, когда GCC не поддерживал приведения.

Как конвертировать данные между строками из Java и дескрипторами?

Взгляните на ответ FAQ-0277 на сайте Symbian's Developer FAQ & Tech Tips:

"How do I convert between Java strings and Unicode descriptors?"

Как использовать RBuf? Что это?

Объект RBuf схож с HBufC тем, что он может создаваться динамически с указанием объема выделяемой ему памяти. Кроме того, он является изменяемым и наследуются от TDes16. Это значит, что вам больше не нужно создавать TPtr, чтобы изменить данные в куче. RBuf рекомендуется использовать вместо HBufC, если вам необходимо не только создавать дескриптор динамически, но и изменять его в последствии.

Вам не потребуется знать его внутреннее устройство, и как он инкапсулирует TPtr или HBufC. Вам достаточно уметь создавать и удалять RBuf, все остальное вам уже знакомо - это стандартный набор методов базовых классов TDes16 и TDes16C.

Также запомните, что RBuf управляет выделенной ему памятью, автоматически освобождая ее во время вызова метода Close(). Но он не контролирует ее размер, и не увеличит его, если этого потребуют некоторые операции.

Поэтому, если вы вызовете Append() для исчерпавшего выделенную ему память объекта RBuf - в программе будет вызвана паника. RBuf не увеличивает буфер автоматически. Это напрямую следует из того факта, что методы базовых классов не способны вызвать сброс.

Забота о размере памяти, необходимой для работы с данными дескриптора, все еще лежит на ваших плечах.

Класс RBuf впервые был реализован в Symbian OS версии 8.0, а документация о нем появилась в SDK версии 8.1. Он часто применяется в программировании для устройств на базе операционной системы 9-й версии. Класс RBuf идеально подходит для управления HBufC через создаваемый в стеке объект R класса, и его часто предпочитают тем конструкциям, которые необходимы для изменения HBufC.

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

Благодарю за помощь и советы при написании этого раздела JP.

Вы можете привести пример использования RBuf?

Да. Сначала мне не хотелось делать этого - потому что до сих пор не было никаких опубликованных примеров. Но я, все таки, решилась опубликовать этот пример, демонстрирующий использование RBuf16. Я оставляю использование RBuf8, и безопасной версии Create() в качестве домашнего задания для читателя.

void ExampleRBufCodeL()
{
__UHEAP_MARK;
// RBuf::CreateL
RBuf modifiableBuf;
modifiableBuf.CreateL(12);
ASSERT(modifiableBuf.Length()==0);
ASSERT(modifiableBuf.MaxLength()==12);
... // Делаем что-нибудь. Для начала поместите modifiableBuf в стек очистки на случай сброса
modifiableBuf.Close();
 
// RBuf::CreateMaxL
modifiableBuf.CreateMaxL(12);
ASSERT(modifiableBuf.Length()==12);
ASSERT(modifiableBuf.MaxLength()==12);
... // Делаем что-нибудь. Для начала поместите modifiableBuf в стек очистки на случай сброса
modifiableBuf.Close();
 
// RBuf::CreateL с передачей дескриптора
_LIT(KHelloWorld, "Hello World");
modifiableBuf.CreateL(KHelloWorld());
ASSERT(modifiableBuf.Length()==11);
ASSERT(modifiableBuf.MaxLength()==11);
... // Делаем что-нибудь. Для начала поместите modifiableBuf в стек очистки на случай сброса
modifiableBuf.Close();
 
// RBuf::CreateL с передачей дескриптора и максимальной длины
modifiableBuf.CreateL(KHelloWorld(), 15);
ASSERT(modifiableBuf.Length()==11);
ASSERT(modifiableBuf.MaxLength()==15);
... // Делаем что-нибудь. Для начала поместите modifiableBuf в стек очистки на случай сброса
modifiableBuf.Close()
 
// RBuf::CreateL, ReAllocL и методы изменяемых дескрипторов
_LIT(KHello, "Hello");
_LIT(KWorld, " World");
modifiableBuf.CreateL(5);
modifiableBuf.Copy(KHello());
modifiableBuf.CleanupClosePushL(); // Заносим в стек очистки на случай сброса
modifiableBuf.ReAllocL(11);
modifiableBuf.Append(KWorld);
CleanupStack::PopAndDestroy(); // Вызов modifiableBuf.Close()
 
// RBuf::Assign
HBufC* hBuf = KHello().AllocL();
modifiableBuf.Assign(hBuf);
ASSERT(modifiableBuf.Length()==5);
... // Делаем что-нибудь. Для начала поместите modifiableBuf в стек очистки на случай сброса
modifiableBuf.Close();
 
// RBuf::Assign, ReAllocL и использование TDes::Append
TUint16* ptr = static_cast(User::AllocL(5*sizeof(TText)));
modifiableBuf.Assign(ptr,5);
ASSERT(modifiableBuf.Length()==0);
modifiableBuf.Copy(KHello()); // Если скопируете более 5 символов - получите панику
modifiableBuf.CleanupClosePushL(); // Заносим в стек очистки на случай сброса
modifiableBuf.ReAllocL(12);
modifiableBuf.Append(KWorld);
CleanupStack::PopAndDestroy(); // Вызов modifiableBuf.Close()
 
__UHEAP_MARKEND;
}

Можно ли поместить бинарные данные в _LIT?

Взгляните на ответ FAQ-0814 на сайте Symbian's Developer FAQ & Tech Tips:

"Is there a way to get binary data in a _LIT?"

Как использовать TLex?

Для начала, что такое TLex?

Это класс для лексического анализа и его использование демонстрирует пример, поставляемый вместе с Symbian OS SDK. Вы можете найти краткое описание этого примера в документации из Symbian Developer Library, находящейся в каждом SDK; она также доступна на сайте Symbian (см. раздел 'внешних ссылок).

И, в том случае, если у вас все еще остались сомнения о том, что же делает этот пример - здесь [8] находится очень хороший ресурс об обратной польской нотации.

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

Главный класс TLex, как и дескрипторы, имеет 8-ми и 16-ти битный варианты - TLex8 и TLex16, а также нейтральную версию TLex (т. к. все современные версии Symbian OS используют Unicode, то он эквивалентен TLex16). Вы можете найти описание TLex16 в документации Symbian API [9].

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

При создании экземпляра TLex вы можете передать ему данные для последующего лексического анализа, или создать его пустым и передать данные для анализа позже. Функции создания и присваивания данных могут принимать в качестве параметров другой экземпляр TLex, 16-ти битный неизменяемый дескриптор или указатель на строку TUint16*.

В самом простом случае, когда строка содержит только цифры, содержимое дескриптора может быть конвертировано в число с помощью функции Val() класса TLex. Например:

_LIT(KTestString1, "54321");
 
TLex lex(KTestString1());
TInt value = 0;
User::LeaveIfError(lex.Val(value));
ASSERT(value==54321);

Функция Val() перегружена для различных знаковых целочисленных типов: TInt, TInt8, TInt16, TInt32, TInt64 - с или без проверки предельного значения. Точно также, существуют перегруженные методы Val() для беззнаковых целочисленных типов, записанных в различных системах счисления (десятичной, шестнадцатиричной, двоичной или восьмеричной). Кроме того, существуют перегруженные версии Val() для TReal.

Однако, это далеко не все, что вы можете делать с помощью TLex. Больше информации о лексическом анализе с помощью TLex вы можете почерпнуть из Symbian Developer Library [10].

К примеру, вы можете двигаться по строке с помощью функции Inc(), или просто проверять каждый символ с помощью Peek(). Вызов функции Get() одновременно возвратит текущий символ в строке и сместит позицию - его действие может быть отменено с помощью UnGet().

Вы можете пропускать пробелы или символы с помощью SkipSpace() и SkipCharacters() соответственно. Достижение конца строки можно проверить с помощью EoS().

TLex позволяет помечать позицию в строке с помощью функции Mark(). Метки полезны, если вы хотите к ним вернуться (UnGetToMark() или пропустить с помощью SkipAndMark() или SkipSpaceAndMark()).

Строка, содержащая пробелы, может быть представлена в виде множества элементов (tokens). Для работы с ними в TLex реализован целый ряд методов, таких как TokenLength(), MarkedToken() - извлекающих элемент, и NextToken(). TLex также реализует методы для перемещения по строке с помощью смещений (offset) и расстояний (remainders).

Как использовать RBuf для чтения из файла?

Изначально помещено Simo в качестве комментария здесь:

RBuf - лучший способ считать строки из файла. Использование RBuf вместо HBufC требует меньше кода, и упрощает обработку ошибок.

Вот пример его использования при чтении из RReadStream:

в .h:

RBuf iSomeSetting;

в .cpp:

iSomeSetting.CreateL( stream, KMaxTInt );

При использовании HBufC пришлось бы делать следующее:

в .h:

HBufC* iSomeSetting;

в .cpp:

iSomeSetting = HBufC::NewL( stream, KMaxTInt );
TPtr tmp( iSomeSetting->Des() );
stream >> tmp;

Обработка ошибок также намного проще, т.к. нет необходимости проверять указатели на NULL значения.

Ресурсы о Symbian

Developer Library на сайте Symbian:

Сообщества разработчиков:

Моя книга Symbian OS Explained: Effective C++ Programming for Smartphones и Мой сайт.

Внешние ссылки на другие ресурсы о дескрипторах.

[1] Описание дескрипторов в Developer Library

[2] Категория паники USER в Developer Library

[3] Синтаксис форматирования строк в Developer Library

[4] Руководство по дескрипторам-указателям в Developer Library

[5] Руководство по дескрипторам-буферам в Developer Library

[6] Fundamentals of Symbian C++/Descriptors

[7] Nokia Developer: "Symbian OS: Descriptors For Text And Binary Data (With Example) v1.0"

[8] Обратная польская нотация

[9] TLex в Symbian Developer Library

[10] Лексический анализ и TLex в Developer Library:

Лексический анализ. обзор

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

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

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