Delphi выделить память под pchar

Обновлено: 07.07.2024

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

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

2. Организация виртуальной памяти в Windows

Как известно, Windows 32 - тридцатидвуразрядная операционная система (число 32 как раз это и обозначает). Прежде всего, из этого следует, что запущенная программа может адресовать линейное адресное пространство размером 2^32 байт = 4 ГБ, при этом адресация производится при помощи тридцатидвуразрядных регистров-указателей. Каждый запущенный в системе процесс обладает своим собственным адресным пространством, каждое из которых не пересекается с адресными пространствами других процессов. Распределение системных областей в адресном пространстве систем Windows 95/98 и Windows NT различно.

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

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

Вторая фаза выделения памяти в Windows - это выделение реальной, физической памяти в зарезервированный участок виртуального адресного пространства. На этом этапе системой выделяется реальная память, со всеми вытекающими из этого последствиями. Выделение реальной памяти также гранулярно. Минимальный блок реальной памяти, которым оперирует система, и который можно выделить, называеся страницей памяти. Размер страницы зависит от типа операционной системы и составляет для Windows 95/98 - 4 кБ, а для Windows NT - 8 кБ. Гранулярность при резервировании участка памяти и при выделении реальной памяти призвана облегчить нагрузку на ядро системы.

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

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

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

На основании вышеперечисленного можно сделать следующие выводы:

каждый процесс со всеми своими потоками имеет отдельное и независимое линейное адресное пространство размером 4 Гб;

выделение памяти состоит из двух фаз: резервирования адресного пространства и выделение в нем реальной памяти;

при резервировании участка адресного пространства существует гранулярность размером 64 кБ;

выделение реальной памяти производится постранично, размер страницы записит от типа операционной системы;

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

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

3. Кучи и менеджеры куч

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

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

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

из-за страничной организации памяти (гранулярности) выделение памяти происходит с большими издержками; запрос на выделение 100-байтного участка приводит к выделению одной страницы памяти с размером 4 или 8 кБ.

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

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

Инженеры компании Borland по видимому не доверяют инженерам компании Microsoft, поэтому каждая программа на Delphi имеет свою собственную реализацию менеджера кучи, которая определена в системных модулях. Такое решение аргументируется инженерами Borland тем, что стандартный менеджер кучи Windows не обеспечивает достаточно эффективную работу с памятью. Конечно, как и любая Windows-программа, программа на Delphi имеет стандартную кучу по умолчанию, которую создает для нее система, однако функции New, Release, GetMem, FreeMem и некоторые другие оперируют с собственной реализацией менеджера куч. Менеджер кучи Delphi резервирует блоки адресного пространства размером 1 Мб, а выделяет блоки реальной памяти размером 16 Кб. Также вы можете написать и установить свою реализацию менеджера куч, если не доверяете ни инженерам Borland, ни инженерам Microsoft - для этого имеются все необходимые функции.

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

4. Группы функций работы с памятью

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

Delphi-функции

Фунции работают с менеджером кучи Delphi. Обеспечивают типизированное выделение и освобождение памяти. Используются для динамической работы со структурами.

Фунции работают с менеджером кучи Delphi. Обеспечивают нетипизированное выделение и освобождение памяти. Используются для динамической работы с небольшими бинарными блоками памяти (буфера, блоки).

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

LocalAlloc(), LocalFree(), . , GlobalAlloc(), GlobalFree(), .

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

"Основополагающие" функции выделения памяти в Windows. Используются как для резервирования адресного пространства, так и для выделения страниц реальной памяти в заранее зарезервированный участок адресного пространства. Позволяют выполнить обе фазы за один вызов функции. Используются для резервирования и выделения больших участков памяти.

5. Потоковые хранилища

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

В таких случаях используют динамические хранилища. В Delphi такими хранилищами являются динамические массивы, объект TMemoryStream, динамическое перераспределение памяти. Все эти хранилища работают на одном и том же принципе: под хранение данных выделяется блок памяти и поступающие данные последовательно записываются в этот блок, когда этот блок заполняется полностью, он перераспределяется с некоторым запасом (размер его увеличивается, а старые данные остаются). После того как каждый новый блок заполняется полностью он снова перераспределяется по мере поступления новых данных.

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

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

Для решения проблемы накопления данных автором были разработаны два объекта накопления данных, основанные на двух разных принципах и имеющих разные характеристики.

TLinearStorage

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

TSectionStorage

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

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

Дополнительно, каждое хранилище имеет функции записи и чтения в стандартные потоки Delphi с упаковкой. Упаковка производится по стандартным алгоритмам библиотеки ZLIB.

6. Библиотека потоковых хранилищ

TBaseStorage - базовый класс

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

Item[] - получение указателя на указанный элемент по его индексу.
ItemSize - запрос размера хранимого элемента. Count - запрос и установка числа хранимых элементов.

Clear - очистка хранилища, установление его размера в нуль.
AddItems, GetItems, SetItems - добавление, запрос и установка блока элементов. SaveStream, LoadStream - запись и загрузка хранилища в/из потока. Параметр Compression в этих процедурах означает следующее 0 - компрессия не производится, и хранилище записывается в линейном натуральном виде; 1 - наименьшая степень компрессии; 9 - наивысшая степень компрессии. Число между 1..9 - произвольная степень компрессии.

Линейное хранилище

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

Capacity - запрос и установка максимальной емкости хранилища. При установке емкости хранилища, все ранее хранимые данные теряются.

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

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

Секционное хранилище

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

Block - список указателей на блоки, из которых состоит хранилище.
BlockSize - размер блоков, измеряемый в числе хранимых элементов.

Create - конструктор, в котором необходимо указать размер хранимого элемента в байтах и размер блока хранения.

7. Пример использования библиотеки потоковых хранилищ

Первый пример демонстрирует эффективность менеджера кучи Delphi перед стандатным менеджером кучи Windows. На компьютерах, которые были мне доступны, тест показывал более чем четырехкратное превосходство менеджера кучи Delphi над менеджером кучи Windows.

Следующий пример содержит исходные тексты библиотеки потоковых хранилищ и тест, сравнивающий два потоковых хранилища, а также объекты TMemoryStream и TFileStream. Тест содержит один параметр, который вы можете регулировать - число добавляемых объектов. Увеличивайте этот параметр вдвое при каждом запуске теста и наблюдайте за поведением всех четырех объектов, особенно объекта TMemoryStream. Пока массив данных помещается в оперативной памяти, результаты этого объекта будут прекрасными, однако после того как массив перестанет помещаться в ОЗУ, объект начинает резко сдавать свои позиции, а вскоре перестает работать совсем. Когда же он работает на пределе возможностей, он создает помехи при выделении памяти - именно из-за этого тест желательно перезапускать.

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

Поиск ошибок ни к чему ни привел, однако в конце концов пришлось обратить внимание на странности в поведении объекта TMemoryStream. Пришлось создать свой поток THeapStream путем формальной замены функций семейства Global. на функции GetMem, FreeMem, ReallocMem - то есть заменой стандартного менеджера кучи Windows на менеджер кучи Delphi. После этого все странности при работе программы исчезли.

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

function StrLIComp(Strl, Str2: PChar; MaxLen: Cardinal) : Integer; Работает как StrLComp, но без учета регистра символов.

function StrScantStr: PChar; Chr: Char) : PChar; Отыскивает первое вхождение символа Chr в строку Str и возвращает указатель на него или nil в случае отстутствия.

function StrRScanfStr: PChar; Chr: Char) : PChar; Работает как StrScan, но отыскивается последнее вхождение Chr.

function StrPos(Strl, Str2: PChar) : PChar; Отыскивает первое вхождение строки Str2 в строку Strl и возвращает указатель на нее или nil в случае отстутствия.

function StrUpperfStr: PChar) : PChar; Преобразует строку к верхнему регистру.

function StrLower(Str: PChar): PChar; Преобразует строку к нижнему регистру.

function StrPaslStr: PChar): String; Преобразует строку Str в строку типа string.

function StrAlloc(Size: Cardinal): PChar; Размещает в куче памяти новую строку размером Size и возвращает указатель на нее.

function StrBufSize(Str: PChar): Cardinal; Возвращает размер блока памяти, выделенного для строки при помощи функции StrAlloc.

function StrNewfStr: PChar): PChar ; Размещает в куче памяти копню строки Str и возвращает указатель на нее.

procedure StrDispose(Str: PChar); Уничтожает строку, размещенную при помощи StrAlloc или StrNew.

function StrLenfStr: PChar): Возвращает число символов в строке Str (без учета завершающего нулевого).

function StrEndfStr: PChar): PChar; Возвращает указатель на завершающий нулевой символ строки Str.

function StrMove(Dest, Source: PChar; Count: Cardinal): PChar; Копирует из строки Source в строку Dest ровно Count символов, причем строки могут перекрываться.

function StrCopy(Dest, Source: PChar): PChar; Копирует Source в Dest и возвращает указатель на Dest.

function StrECopy(Dest, Source: PChar): PChar; Копирует Source в Dest и возвращает указатель на завершающий символ Dest.

function StrLCopy(Dest, Source: PChar; MaxLen: Cardinal): PChar; Работает как StrCopy, но копирует не более MaxLen символов.

function StrPCopy(Dest: PChar; const Source: String): PChar; Копирует строку Source (типа string) в Dest и возвращает указатель на Dest.

function StrPLCopy(Dest: PChar; const Source: string; MaxLen: Cardinal): PChar; Работает как StrPCopy, но копирует не более MaxLen символов.

function StrCat(Dest, Source: PChar): PChar; Дописывает Source к концу Dest и возвращает указатель на Dest.

function StrLCatfDest, Source: PChar; MaxLen: Cardinal) : PChar; Работает как StrCat, но копирует не более MaxLen-StrLen(Dest) символов.

function StrCoirip(Strl, Str2: PChar): Integer; Сравнивает две строки (посимвольно). Возвращает значение: <0 — при Strl <Str2, 0 — при Strl =Str2, >0 — при Strl >Str2.

function StrIComp(Strl, Str2: PChar): Integer; Работает как StrComp, но без учета регистра символов.

function StrLComp(Strl, Str2: PChar; MaxLen: Cardinal): Integer; Работает как StrComp, но сравнение происходит на протяжении не более чем MaxLen символов.

Давайте сначала рассмотрим комментарий от "Борланд", создаваемый мастером динамических библиотек DLL.

Важное примечание об управлении памятью в DLL: модуль ShareMem должен быть указан первым в разделе USES вашей библиотеки и в этом же разделе вашего проекта (пункт меню Project -> View Source), если ваша DLL экспортирует любые процедуры или функции, которые передают строки как результат или используют как параметры. Это относится ко всем строкам, передаваемым или получаемым вашей библиотекой — даже тем, которые используются в записях (records) и классах (classes). ShareMem — это интерфейс к библиотеке BORLNDMM.DLL, менеджеру совместного использования памяти, который должен быть развернут наряду с вашим проектом. Чтобы избегать использовать BORLNDMM.DLL, передавайте строки как PChar или ShortString.

Почему эти предосторожности необходимы?
Причина скрывается в способе Delphi выделять и использовать память. В то время как Windows предоставляет родные функции распределения памяти (VirtualAlloc, HeapAlloc, GlobalAlloc, LocalAlloc и т.д.), "Дельфи" осуществляет его собственную политику распределения, или, более точно, существует свой менеджер памяти, который осуществляет ее подраспределение. В языке "Паскаль" ("Дельфи") это называют кучей (Heap); C/C++-программисты более знакомы с термином free store. Задача подраспределителя состоит в том, чтобы разместить всю динамическую память: от всей памяти, явно размещенной программистом, до неявно размещенной компилятором при создании строк, динамических массивов и объектов.
Немногие из разработчиков понимают, что они неявно выделяют память в утверждениях типа:

var s: string
.
s: = s + "abc";

И снова причина ошибки — сложившееся восприятие о типе PChar, которое предполагает, что "программный интерфейс приложения (то есть попросту API) Windows делает это таким образом, и значит, это правильно". Но программный интерфейс приложения Windows очень редко размещает PChar для того, чтобы передать его к приложению. От вызывающего приложения требуется выделить для PChar буфер и передать параметр, определяющий его длину, а API тогда сам пишет в этот буфер. Фактически есть очень небольшое, но все-таки весомое преимущество для использования PChar в "Дельфи", так как такой способ передачи строки намного более безопасен и более эффективен. Только очень продвинутые пользователи, имеющие точное и кристально ясное понимание причин так делать, должны их использовать.
При передаче объектов наблюдаем схожую картину:

В DLL:
function GetPChar: PChar;
begin
Result := StrAlloc( 13 );
StrCopy( Result, 'Привет, Мир!' );
end;

procedure FreePChar( p: PChar );
begin
StrDispose( p );
end;

В EXE:
var p: PChar;
.
p:= GetPChar;
// что-то делаем
FreePChar( p ); // безвредное для кучи освобождение ресурсов.

В зависимости от того, какое действие совершила исполняемая программа по отношению к объекту, это может причинить ущерб не одной, а сразу обеим кучам. Обратите внимание, что в "Дельфи 6" модуль, освободивший память из кучи другого модуля, фактически может и не испортить структуру кучи. Менеджер кучи держит свободные блоки памяти в связном списке и в моменты удаления блоков пытается сливать два смежных свободных блока. "Invalid pointer operation" происходит тогда, когда модуль пытается освободить последний выделенный блок списка освобождения памяти другого модуля и, будучи не в состоянии распознать некий ошибочный его элемент, пытается слить свободный блок с тем, что, как ему кажется, является маркером (а может быть, и просто мусором), что и приводит к ошибке. Хотя ошибка не выходит за рамки текущего экземпляра, дальнейшая работа менеджера кучи может быть нарушена уже в любом другом месте из-за искажений в структуре кучи.
Вышеупомянутый пример использования PChar может быть "исправлен" следующим образом:

В DLL:
function GetPChar: PChar;
begin
result: = StrAlloc (13);
StrCopy (result, 'Привет Мир!');
end;

procedure FreePChar (p: PChar);
begin
StrDispose (p);
end;

В EXE:
var p: PChar;
.
p: = GetPChar;
// сделать кое-что
FreePChar (p); // безвредное для кучи освобождение ресурсов

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

Так что же является надлежащим решением?

Существует несколько вариантов. Первый — использовать модули Sharemem.pas и Borlndmm.dll. Это, кажется, самое безопасное и простое решение, которое не требует никаких специальных предосторожностей. Есть, однако, одно замечание: все запросы выделения памяти через модуль Borlndmm.dll могут работать в 2-8 раз медленнее, чем "нормальное" выделение памяти. Описанный способ также требует распространения модуля управления памятью вместе с вашим приложением.
Второй — никогда не передавайте динамические данные между модулями. Это довольно трудно для понимания и требует глубоких знаний системы типов и классов в "Дельфи". Когда в разработку вовлечено большое количество разработчиков, этого особенно трудно избежать.
Разрабатывайте все библиотеки, как будто они были написаны на других языках программирования и/или с использованием API. Такой подход разумен в том случае, если проект пишется более, чем в одном языке. Тогда простой отказ от любых Delphi-ориентированных конструкций во всех DLL не сможет добавить еще больше сложности в написании. Неудобство — то, что много конструкций "Дельфи" будут недоступны.
Четвертое. Использование объектов COM в DLL: это экстремальное решение, особенно если COM будет использоваться для обеспечения межбиблиотечных связей. Это также замедляет работу приложения и выливается в значительные усилия при разработке.
Есть и пятый выбор. Модуль FastShareMem — это попытка построения альтернативного решения. Он очень прост в использовании — не сложнее, чем включение еще одного модуля в проект, — и не вызывает никакого замедления работы приложения. Более полно об этом модуле я расскажу в следующей статье.

Компьютерная газета. Статья была опубликована в номере 37 за 2003 год в рубрике программирование :: delphi

Здравствуйте, дельфисты! Сегодня вам поведую, что это за тип PCHAR. И как его корректно использовать. Этот тип упоминается во всех API функциях, которые принимают в качестве параметра какое-либо строковое значение.

Сначала я расскажу вам про тип string. Тип string является главным преимуществом языка Pascal над языком С. Именно из-за этого типа программы, написанные на Pascal, весят больше, чем программы, написанные на С. Все знают, что тип string является массивом, котором каждый элемент является типом CHAR (следовательно, юникодовский тип WideString - массив из WideChar). Только размер этого массива неизвестен заранее и при каждом присваивании его длина изменяется. Но так же можно и при объявлении ограничить размер строки. Но размер строки ограничивается только формально, потому что нельзя обратиться к элементу массива больше чем размер строки указанной в разделе var. Конечно, размер каждой строки должен быть не более 2ГБ, примерно 2 миллиарда символов (для widestring 1 миллиард символов, так как один символ задаётся 2 байтами). Так как строка это массив, следовательно, через квадратные скобки можно обращаться к каждому элементу массива.

Обращаться к 55 символу переменной str2 уже нельзя, но физически в памяти он существует. При получении указателя на строку возвращается адрес первого символа в строке, значит, такие выражения одинаковы:

Теперь тип PCHAR. Фактически тип PCHAR это указатель на тип CHAR. Это понятно и потому как он назван. По "программеским" правилам при объявлении нового типа типизированных указателей берётся тип, на который указывает указатель и спереди ставится буква P. Вот его объявление:

Всё нормально. Тот же результат при использовании указателей.

Это потому что массивы обычно заполняются нулями.

Пример 1: Совсем другая история:

А массив выводится нормально, потому что Delphi автоматически правит указатель на массив.

Пример 2: Не знаю почему, но вместо двоек выводится не пойми что. Мистика.

Отсюда понятно что изначально все переменные заполняются нулями. И переменная STR тому не исключение.

Пример 4: Интересная ситуация:

Как видите в заголовке есть символы 'hs' потом квадратик потом двойки. Всему есть разумное объяснение. Строка STR_ARR не кончается нулём, следовательно, функция не нашла нуль только в конце строки STR_ARR и пошла дальше к переменной STR, поэтому строка STR_ARR получилась такой длинной. Квадратик в после строки 'hs' понятен из первого примера.

Вот такие пироги. Изначально казалось, что тип string проще и лучше, а получилось как всегда! Тип string принёс нам массу неприятностей. Поэтому C++ намного популярнее Delphi. Вот такими словами кончается моя очередная статья.

Читайте также: