Вопросы по плагинам: память и версии Delphi

Больше
14 года 4 мес. назад - 14 года 4 мес. назад #1 от cy6
Заинтересовалась устройством системы плагинов в RnQ, и возникло пара вопросиков. :blush:

1) Как лучше выделять память в плагине (так как он пользуется адресным пространством и кучами процесса, то есть RnQ.exe)? Вообще, и особенно для возврата значений из экспорт пойнт PluginFun (когда плагин потерял управление, кто будет освобождать память и когда). Логично было бы предположить, что при одинаковых версиях Delphi, система управления памятью сама занимается освобождением памяти (строк, которые в семействе &RQ используются для хранения произвольных байт).

2) Совместимость разных версий Delphi 2007/2009/2010 (если писать не на C++), в каждой версии свои системные библиотеки, в том числе и система управления памятью, которые однако загружаются (одновременно) в адресное пространство процесса при запуске.

Понимаю, что вопросы не из простых, но надеюсь на информацию. Тем более, что здесь есть программисты и на C++ (OverQuantum, создавший HistoryLog, единственный плагин, которым я пользуюсь ^_^).

Поискала немного в интернете о динамическом выделении памяти в Delphi, и вот что любопытное

Код менеджера памяти находится в модулях System.pas и GetMem.inc, так что компилируется с каждой программой. В общем случае это не является проблемой, но в приложениях, использующих библиотеки, также написанные в "Дельфи", это имеет некоторое значение. Так как DLL — отдельно компилируемое приложение, библиотека получает свою собственную копию менеджера памяти и, таким образом, отдельную кучу. Это — самая важная вещь, которую необходимо помнить: каждое отдельное приложение, будь то исполняемый .exe-файл или динамическая библиотека .dll, управляется его собственным менеджером памяти, обладающим своей кучей. Все последующие проблемы просто возникают из-за наличия одного приложения, .exe либо .dll, которое по ошибке управляет частью памяти, совершенно не принадлежащей его собственной куче.


А вот тут еще интереснее.

В 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 ); // безвредное для кучи освобождение ресурсов.

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

А что тогда с плагинами на C++, DLL должна самостоятельно освобождать всю выделенную память по DLL_PROCESS_DETACH?
И какие функции WinAPI лучше использовать в плагине на C++, родные функции new delete (которые наверное полезут в кучу процесса?), создание своей собственной кучи или функциями глобального выделения памяти?

:unsure:
Последнее редактирование: 14 года 4 мес. назад пользователем cy6.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
14 года 4 мес. назад - 14 года 4 мес. назад #2 от Rapid D
Мне кажеца у вас была не самая лучшая книжка по делфи :)
Я тоже довольно долго не мог понять как это всё работает вообще :)

Но в andrq всё было придумано довольно правильно. И всё на самом деле просто.
Плагин - это фактически всего одна ф-я:
function pluginFun(data:pointer):pointer; stdcall;

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

особенно для возврата значений из экспорт пойнт PluginFun (когда плагин потерял управление, кто будет освобождать память и когда)

Для этого в плагине обычно имеется буфер:
var
outBuffer:string;
Теперь лучше ему выглядить так:
outBuffer: RawByteString;
Смысл её в том, что ф-я возвращает адрес это строки. В принципе она не затрётся, пока ещё раз не вызовут pluginFun.
Последнее редактирование: 14 года 4 мес. назад пользователем Rapid D.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
14 года 4 мес. назад - 14 года 4 мес. назад #3 от cy6
Rapid D писал(а):

Мне кажеца у вас была не самая лучшая книжка по делфи :)

Если вы про цитаты, то это из довольно серьезной и очень полезной статьи www.nestor.minsk.by/kg/2003/37/kg33703.html

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

Для этого в плагине обычно имеется буфер:
var
outBuffer:string;
Теперь лучше ему выглядить так:
outBuffer: RawByteString;
Смысл её в том, что ф-я возвращает адрес это строки. В принципе она не затрётся, пока ещё раз не вызовут pluginFun.

В том и дело, что адрес передаваемый из плагина, где то хранится. И, насколько я поняла:
1) Нужно чтобы он был валидным ДО следующего вызова экспорт пойнт pluginFun.
2) DLL плагина сама заботиться о валидности адреса и об освобождении памяти (в Delhi память для строк класса string освобождается автоматически, прилинкованным к DLL менеджером памяти соответствующей версии).

outBuffer на самом деле не сам буфер, а указатель на него, который физически болтается где то в куче, и управляется ссылочной системой распределения памяти строк Delhi. Причем бывает так, что два указателя (типа string, AnsiString, WideString) указывают на один и тот же строковый буфер, когда мы пишем строку типа str2 := str1, но при любом изменении str2, создается копия и адрес указателя меняется.

Вообщем, с плагинами на Delphi все более ли менее понятно.

А вот с плагинами на C++ еще вопрос, так как менеджера памяти с автоматическим выделением памяти там нет (C++ Builder к использованию не предполагается), есть стандартные функции new и delete, которые можно перегрузить для своих типов данных. Например, при создании класса плагина 1С 7.7 я перегружала данные функции, для выделения памяти совместимой с моделью COM, чтобы не лезть в системную кучу процесса, что может вызвать неопределенные последствия. Для плагинов RnQ предполагаю использовать создание собственной кучи, с помощью стандартных функций WinAPI. Создавать при загрузке DLL, и освобождать при выгрузке.
Ибо, создание глобальной переменной типа array of bytes в сегменте статических данных не кажется мне хорошей идеей. При любой программной ошибке, произойдет порча других данных из этого сегмента. Как и есть сомнения в том, стоит ли лезть в системную кучу RnQ.exe (куча по умолчанию создается для любого процесса). Поправьте меня, если я не права, пожалуйста.

Update: Нашла в исходнике от OverQuantum - rqplugutils.h пример выделения буфера для передачи пакета из плагина.
#define ANSW_LEN        5000

BYTE bAnswer[ANSW_LEN];     //Answer array
То есть, просто статическая переменная в сегменте статических данных, с фиксированной длиной. Длина взята видимо приблизительно такая, чтобы быть гораздо больше, чем объем передаваемых данных.

P.S. В восторге от Splicing.cpp ^_^
Последнее редактирование: 14 года 4 мес. назад пользователем cy6.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
14 года 4 мес. назад #4 от Пушкожук
Добавлю ещё немного :)
Про Delphi. Там действительно свой менеджер памяти, который работает через VirtualAlloc. У каждой dll свой менеджер. Правда, можно заставить dll использовать менеджер памяти от exe (как - написано в той статье). Проблема в том, что юнит ShareMem должен быть и в exe тоже, а в R&Q его нет :) Так что использовать менеджер памяти R&Q не получится. Да и не нужно, наверное - ведь при выгрузке dll её менеджер памяти удаляется вместе со всеми данными, так что можно даже не освобождать память при выгрузке.
Про C/C++. msvcrt.dll по DLL_PROCESS_ATTACH создаёт новую кучу вызовом HeapCreate, и malloc выделяет память именно из неё (надеюсь, new работает через malloc). Получается, что все dll получают память из этой кучи. Поэтому при выгрузке dll надо освобождать всю выделенную ей память, иначе она так и останется "висеть".
А почему возник такой вопрос, где хранить данные? Выделить память по DLL_PROCESS_ATTACH или PE_INITIALIZE, удалить по DLL_PROCESS_DETACH или PE_FINALIZE... И какая разница, где эта память находится? Или нужно избежать фрагментации кучи? :) Используйте тогда VirtualAlloc :) Кстати, в ответ можно и nil вернуть (например, на PE_FINALIZE).
cy6 писал(а):

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

А если выделять память в куче, при программной ошибке произойдёт порча этой самой кучи :) Программных ошибок лучше вообще не допускать :laugh:

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
14 года 4 мес. назад - 14 года 4 мес. назад #5 от cy6
Пушкожук писал(а):

А почему возник такой вопрос, где хранить данные? Выделить память по DLL_PROCESS_ATTACH или PE_INITIALIZE, удалить по DLL_PROCESS_DETACH или PE_FINALIZE... И какая разница, где эта память находится?

Причина - моя паранойя по поводу надежности моего кода. B)
Особенно пугают ошибки связанные с памятью и стеком. :laugh:
Причем, иногда в DLL все нормально, но без дополнительной подстраховки вдруг возникает фатальная ошибка в EXE. Помнится когда писала страницу свойств плагина в 1С, как и остальной код на чистом WinAPI, и exe 1эса вываливался при переходе на мою вкладку. :ohmy: Оказалось все просто, где то в коде самой 1С просчет, вызывающий фатальную ошибку, если я вдруг не создала дочернее окно, которое в документации скромно не упоминается. :blink:
Боюсь я в общем плохо написанных DLL, которую юзают общее адресное пространство не так как батька (EXE) хочет. :silly: Опыт юзанья глюкавых альфа плагинов (DLL) в Миранде тоже этот страх подпитывает. :)

А если выделять память в куче, при программной ошибке произойдёт порча этой самой кучи :) Программных ошибок лучше вообще не допускать :laugh:

Точно. :laugh:

Зато в куче можно менять размер блоков, вместо того чтобы делать несоразмерно огромные фиксированные буфера. :P

Благодарю за полезные советы. :)
Последнее редактирование: 14 года 4 мес. назад пользователем cy6.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Модераторы: bassvazoozaDelphukdekRapid D
Время создания страницы: 0.301 секунд
Работает на Kunena форум