Delphi 7 выделение памяти

Обновлено: 02.07.2024

Отслеживать ответы на этот вопрос по RSS

Функция VirtualAlloc очевидно самай быстрая, но использовать ее по
моему ст
ит если Вы распределяете
память большими кусками. (она выделяет память участками по 4К)

Для работы с большим количеством маленьких кусочков в WinAPI есть
функции ра
оты с кучей -
HeapCreate, HeapAlloc и иже с нею - они работают чуть медленнее.

GetMem - "родная" для Дельфи функция и хотя она, возможно, чуть
медленне чем
VirtualAlloc
но она надежнее - тот же VirtualAlloc вызывается в ней при
необходимости внутр
CriticalSection,
вызывается Exception в случае ошибок и т.п. Кстати есть еще функции
SetMemoryManager
и GetMemoryManager
предназначенные, на сколько я понимаю, для переопределения работы
GetMem, New
и т.д. - однако вряд ли
это когда-нибудь понадобится.
Функция AllocMem просто вызывает GetMem и заполняет полученный блок
памяти
улями.

New и Dispose удобно вызывать когда вы выделяете не память вообще
для каког
-то буфера, а память для какой-то переменной
конкретного типа (record как правило). Можно например обьявить
массив указателей
(а в 4-м Дельфи даже динамический массив)
а потом каждому элементу массива выделить блок:

var
massiv : array[1..SkolkoNujno] of PZ;

for i:=0 to SkolkoNujno do
massiv[i]:=New(Zapis); - по моему удобно не правда ли? :-)
///////////////////


Резюме - "родные" функции Дельфи удобнее и надежнее^ хотя и немного
медленнее

(вряд ли эта мизерная задержка в скорости актуальна по нынешним
временам).

VirtualAlloc и HeapAlloc - быстрее, гибче но сложнее в
использованиии и не так
надежны.


___________
^ - Под надежностью я имею в виду следующее - GetMem сперва
проверяет переме
ную
IsMultiThread, генерирует EXCEPTION при ошибке и вообще. :-)

P.S. Приведенный в предыдущем ответе пример на ассемблере на самом
деле выз
вает текущую функцию GetMem из MemoryManager - те исходники про
которые гов
рю я находятся в файле Getmem.inc
C:\Program Files\Borland\Delphi4\Source\Rtl\Sys\getmem.inc

Вот исходники New, GetMem (модуль System) и AllocMem (модуль
SysUtils).

procedure _GetMem;
asm
TEST EAX,EAX
JE @@1
CALL MemoryManager.GetMem
OR EAX,EAX
JE @@2
@@1: RET
@@2: MOV AL,reOutOfMemory
JMP Error
end;

function AllocMem(Size: Cardinal): Pointer;
begin
GetMem(Result, Size);
FillChar(Result^, Size, 0);
end;

Видим, что и New, и AllocMem работают через GetMem. При этом New
исполнена на
Ассемблере, а AllocMem - на Паскале, но этот факт не должен сильно
влиять на
скорость, т.к. AllocMem очень проста для эффективной компиляции.
Далее, New
вызывает Initialize (т.к. требуется определить размер выделяемой
памяти), а
AllocMem - FillChar (т.к. требуется эту память обнулить). Судя,
опять же, по
исходникам (не привожу, поверьте на слово или проверьте сами),
FillChar,
содержащая машинные команды REP должна работать побыстрее, чем
Initialize,
содержащая явные ассемблерные циклы (хотя скорость сильно зависит
от размера
выделяемой памяти). Таким образом, по скорости должно быть примерно
так: самая
быстрая - GetMem, затем AllocMem, затем New (вероятно, две
последние могут
меняться местами, если выделяется малое количество памяти).

Теперь попробуем найти место для VirtualAlloc. GetMem выделяет
память через
MemoryManager.GetMem, т.е. дает запрос в систему, а память
предоставляется уже
системой (ясно, что иначе и быть не может). Система же, скорее
всего, выделяет
память через те же функции API - GlobalAlloc, VirtualAlloc и т.п.
Получается, что
прямое обращение к функции API должно (как всегда!) сработать
быстрее, чем
опосредованное, через Паскалевскую. При этом GlobalAlloc и
LocalAlloc, которые
выделяют память сразу из хипа процесса (в Win32 все хипы -
локальные) должны быть
пошустрее, чем VirtualAlloc и VirtualAllocEx, которым надо не
только выделить
страничную память, но и отобразить ее в адресное пространство
процесса, да еще и
обнулить.


Это по быстродействию. Теперь вторая часть вопроса - какие действия
совершаются
при выделении памяти. Думаю, что все детали этого таинства знает
только тот, кто
писал часть ядра, управляющую распределением памяти. Программист же
Delphi даже не
в состоянии определить, какую система дает ему память - физическую
или страничную.
Для него важно только знать, как ее лучше получить. Наверное,
GetMem - самая
универсальная вещь (проще, чем API-функции и быстрее, чем прочие
функции Delphi).
Но, если, скажем, требуется вставить выделение памяти в критичный
по скорости
цикл, то лучше, например, так :


MemPtr := LocalLock(LocalAlloc(LMEM_FIXED, MemSize));


Но еще быстрее, конечно, будет получать память не кусочками, а
разом всю.

Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
Функция может не работать в некоторых версиях броузеров.

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

Естественно перед вызовом конструктора ему нужно передать dl регистр как 0.

asm
mov eax, <- здесь адрес объекта твоего менеджера.
xor dl,dl;
call Tobject.create;
end;
.

Не совсем понятно о чем вы говорите.

Если экземпляры класса едят много памяти, и их много, то почему бы не распределить эту память сбоку, а в классах оставить ссылки на начало "своего" блока и длину?

Ну идея ясна. Ну в обощем-то в первоначальном варианте все рабочее вполне. Только я так понял можно сразу Destroy вызывать ну и перекрыть FreeInstance (как у тебя).

Хотя я не могу понять вот чего - ты с рекордами умеешь выполнять initialize/finalize (т.е. есть входа точка, где ты работаешь с память для записей), почему не можешь вызывать Create/Free для объектов?

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

Кстати, у меня только что вопрос возник. Пусть есть var Obj: TObject. После выполнения Obj := TObject.Create будут ли какие-либо данные *про этот объект* по отрицательному смещению относительно Obj?

Про этот объект не будет. А служебные поля heap manager никто не запрещает. Но тебя это не должно волновать.

Разъяснение ниже привожу здесь

Update: In comments, Allen Bauer explains the reasons for this implementation:

The reason for always placing the monitor reference field at the end of the object instance is mainly for backward compatibility. I didn’t want to alter the layout of objects in case there was some code out there that depended upon it. Also, by hiding the implementation the chance of inadvertent mucking with the monitor data was reduced and therefore it increased the overall safety of using it.

Это всё проще делать на уровне менеджера памяти.

Та же запись, только с методами, да еще и с перекрываемыми. SizeOf(TMyObject) = размеру под хранение твоих данных.

Каждой программе выделяется своё адресное пространство. В нём хранятся коды команд, ячейки для хранения значений переменных, область стека. Область для хранения значений переменных выделяется при компиляции программы и не меняется в процессе работы программы. Такая память называется статической.

В 32-х разрядных приложениях адресное пространство ограничено значением 2 32 -1

4 Гб (4 294 967 255 байт).

Предположим, что половина памяти отведена под переменные. Длина машинных команд может иметь максимум 15 байт. Тогда в оставшейся половине может разместиться примерно 143 000 000 машинных команд.

Будем считать (очень грубо), что на выполнение одного операнда (сложение, умножение и так далее) требуется 10 команд. Тогда оказывается доступно 14 300 00 операндов.

Если в одной строке (операторе) присутствует в среднем 10 операндов, то в программе может быть примерно 1 340 000 строк (операторов).

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

Тогда в запасе у программиста есть ещё 4 Гб памяти, называемой динамической. В отличие от статической, она распределяется в процессе работы программы.

Тип char.

Мы уже неоднократно использовали различные типы данных. Например, тип char. В переменной такого типа может храниться один символ. Всего под хранение символа отведен 1 байт, то есть можно закодировать 2 8 -1=255 символа. В первой половине таблицы (127 символов) хранятся специальные символы языка, буквы латинского алфавита и цифры. Во второй половине — буквы национального алфавита.

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

Для примера, латинской букве A соответствует код $41 в шестнадцатеричном исчислении, 65 в десятичном или 01 00 00 01 в двоичном.

Тип Integer.

Для хранения целых чисел мы использовали тип Integer. По него отводится 4 байта, то есть 32 бита. Так как старший бит кодирует знак (0 — число положительное, 1 — отрицательное), то под само число остаётся 31 бит, в которых может храниться число, меньшее 2 31 -1 =2 147 483 647.

Тип double и extended.

Для работы с вещественными (десятичными) числами чаще всего применяют тип double, под который отводится 8 байт.

Но в хранении вещественных чисел есть одна особенность. Пусть у нас есть число (999 999 999 999. 999). В памяти компьютера оно будет представлено в виде (0.999 999 999 999 999 * 10 12 ). то есть вещественное число хранится в двух частях. Первая часть 6 байт хранит мантису (наши девятки), а вторая — порядок (степень числа 10).

Для типа double мантиса размещается в 52 битах, то максимальное число, которое можно хранить 2 51 -1 =4 503 599 627 370 495. Если вывести это число в Edit (преобразовав его в строку функцией floatToStr(х) ), то мы увидим
4, 503 599 627 370 5*10 15 , то есть вывелась десятичная дробь, имеющая 14 знаков. Это произошло потому, что был отброшен 16-ый знак. Но так как это 5, то следующий знак увеличен на 1 и получилась единица переноса, поэтому вместо 4 мы увидели 5.

Если вывести число 888 888 888 888 888, занимающее 15 позиций, то оно выведется именно как 888 888 888 888 888. Если его умножить на 2, то позиций в числе станет 16, и число выведется в экспоненциальном виде
1,77 777 777 777 778E15, хотя на самом деле это число
1,77 777 777 777 7776E15 .Таким образом все разряды, большие пятнадцати, отбрасываются, а последний разряд округляется.

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

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

Подобные ситуации трудно диагностировать. Поэтому при работе с большими числами надо быть очень внимательным!

Есть ещё один тип для работы с десятичными числами, для хранения которых отводится 10 байт — это тип Extended. У него 19 значащих цифр, в отличие от 15 у double.

Тип Currency.

Под этот тип тоже отводится 10 байт. Но если тип double может быть примерно до 1,7*10 308 , тип Extended 1,1*10 4932 , то тип Currency имеет только 19 значащих цифр без экспоненты (без степени числа 10).

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

Тип String.

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

Идентификаторы переменных.

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

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

При первом проходе компилятора создаётся таблица, сопоставляющая набор символов (имя переменной) и адрес, отведённый для хранения значения этой переменной.

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

Таким образом происходит преобразование мнемонических (символьных) имен в адреса переменных.

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

Параметры процедур.

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

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

Стек — это область памяти фиксированного объёма (65 535 байт) типа «очередь», организованная таким образом, что она растёт как лёд в воде, который сверху периодически поливают водой и он замерзает слой за слоем, а нижняя кромка льда опускается всё ниже и ниже, пока не достигнет дна.

Таким образом, последнее записанное значение оказывается на вершине стека, адрес которой зафиксирован.

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

Процедура считывает из стека такое количество значений, которое указано в её шапке. Значения последовательно считываются из стека, начиная с вершины, и присваиваются формальным параметрам процедуры.

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

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

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

Если же процедура должна работать с большим количеством данных, например с массивом, то в процедуру передаётся ссылка на первый элемент массива. В процедуре такие формальные переменные помечены ключевым словом «var».

В этом случае надо быть очень осторожным, так как работа будет проводиться с самим массивом, а не с его копией!

delphi указатели.

Динамическая память. Типы указателей.

При работе программы часто возникает необходимость хранить в памяти полученные результаты (например, результаты расчётов), количество которых заранее не известно.

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

4Гб памяти. Это пространство называется «куча».

Для размещения данных в куче используют особые переменные, называемые «указатель».

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

Указатели бывают двух типов.

В первом случае известен тип данных, на который ссылается указатель. За счет этого при чтении данных, на которые указывает адрес, содержащейся в переменной «указатель», будет прочитано столько байт, сколько соответствует данному типу данных.

Поэтому такие указатели называются «типизированными».

Например, если указатель объявлен как указывающий на целый тип данных, то при чтении из памяти будет извлечено 8 последовательных байт и полученная информация интерпретирована как целое число.

Во втором случае при объявлении указателя указывается не тип данных, а явно количество байт, отводимых под переменную.

Объявление типизированных указателей.

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

type имя_типа=^базовый_тип (или пользовательский тип)

var имя_переменной_указателя: имя_типа;

Или непосредственно var имя_переменной_указателя:^базовый_тип (или пользовательский тип).

Например, объявить указатель на данные целого типа:

type tPInt=^Integer;
var vPInt: tPInt;

var vPInt:^Integer;

Delphi работа с памятью.

Но объявленная переменная-указатель пока не содержит в себе ссылки (адреса). Там находится случайная информация. Чтобы привязать к указателю конкретный адрес в куче, переменную надо инициализировать процедурой: new():

new(vPInt);

Delphi работа с памятью

Теперь в указатель vPInt записан конкретный адрес и по этому адресу можно разместить целое значение (разименовать указатель).

Делается это с помощью нотации:

vPInt^:=12345;

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

dispose(vPInt);

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

Только что объявленный, но ещё не инициализированный указатель можно приравнять предопределённому «пустому» значению nil:

vPInt:=nil;

Это не позволит прочитать «мусор», если вдруг в указателе случайно оказался реальный адрес. Ещё хуже, если этот указатель указывает на область кучи, содержащей данные. Таким образом можно разрушить хранимые значения.

Однако и после «уничтожения» указателя его также можно приравнять nil.

Проверка значения указателя на nil не позволит дважды инициализировать указатель, что приведёт к ошибке времени выполнения и программа остановится.

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

if vPInt=nil then new(vPInt);
или if vPInt <> nil then vPInt^:=12345;

Процедуру dispose(vPInt); надо всегда применять, как только надобность в данных отпадает (во избежании утечки памяти).

Определение адреса и размера переменной.

Иногда необходимо знать адрес начала расположения переменной. Это делается с помощью функции

addr(X)

Применению указанной функции эквивалентна нотация: @X.

Замечание. Если приравнять vPInt:=addr(X); (или vPInt:=@X), то vPInt^ становится эквивалентом X.

Если мы создаём указатель на сложную структуру, например «запись», то бывает полезно знать, сколько байт она занимает в памяти. Для этого применяют функцию:

sizeOf(vPInt);

Нетипизированные указатели.

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

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

var p:pointer;

Далее выделяется блок памяти процедурой

getMem(p,N)

где N – количество требуемых байт памяти.

Освобождается память процедурой:

freeMem(p,N)

Особенности объявления типизированных указателей.

Типизированные указатели, как и простые переменные, можно приравнивать друг к другу. Например:

var p1,p2:^Integer; x:Integer;
p1:=@x;
p2:=p1;

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

При объявлении переменных тип переменной назначается неявно и он является уникальным для группы переменных, перечисленных в одном объявлении (как показано в приведённом примере);

Но если сделать объявление переменных:

то типы, присвоенные для p1 и p2 при компиляции, будут иметь разные имена. Поэтому

вызовет ошибку времени выполнения.

В то же время нотация:

type tPInt=^Integer;
var p1: tPInt; p2:tPInt;
p1:=@x;
p2:=p1;

ошибки не вызовет, так как переменным присвоен один и тот же тип.

Другой способ избежать коллизии — использовать нетипизированный указатель. Нотация:

var p1:^Integer; p2:^Integer; p:pointer;
p1:=@x;
p:=p1;
p2:=p;

Заключение.

Рассмотрены понятия динамической и статической памяти, а также как в delphi динамическая память распределяется. Более подробно рассмотрены типы данных Char, Integer, Double. Добавлены новые типы extended и сurrency. Рассмотрен механизм связи идентификатора и его адреса. Также рассмотрен механизм использования параметров процедур и функций. Даны примеры, как в delphi указатели различного типа используются для организации памяти в куче, а также инструменты работы с ними посредством процедур и функций.

Давайте сначала рассмотрим комментарий от "Борланд", создаваемый мастером динамических библиотек 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

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