DLL библиотеки, строки, {$H+}

Форум для изучающих FPC и их учителей.

Модератор: Модераторы

DLL библиотеки, строки, {$H+}

Сообщение absdjfh » 12.03.2013 20:45:38

Помогите школьнику разобраться с ошибкой.
Краткое содержание: при вызове из библиотеки (.dll) внешней процедуры с разносортными параметрами (var string и [const || по значению] - произвольного типа) при включенной директиве компилятора {$H+} после обработки строки процедурой дальнейшее редактирование строки блокируется, и возникает ошибка времени выполнения.
Подробности:
Код библиотеки:
Код: Выделить всё
library project1;

{$mode objfpc}{$H+}

uses
  SysUtils;

procedure Proc1(var S1, S2: string; i: Integer);
begin
  S1 := 'very good thing' + IntToStr(i);
  S2 := 'myyy' + IntToStr(i*2);
end;

exports
  Proc1;

begin
end.

Код программы:
Код: Выделить всё
program project2;

{$mode objfpc}{$H+}

procedure Proc1(var SA, SB: string; i: Integer); external 'project1.dll';

var
  S1, S2: string;

begin
  S1 := 'something';
  S2 := 'somebody';
  Proc1(S1, S2, 5);
  S1 := 'some body';
  S2 := 'kill';
end.       

При запуске Project2 возникает ошибка:
77268DC9 ff4014 incl 0x14(%eax)
(ассемблер)
Количество string в параметрах произвольно (1, 2...).
Если убрать параметр i (integer), программа работает правильно.
Если вместо i добавить параметр любого другого типа (главное - передача по значению или через const) - ошибка.
Если убрать директиву компилятора {$H+} (длинные строки??) из обоих файлов - все будет работать нормально (ошибки не будет ни в каком случае).
При пошаговом выполнении программы с наблюдением за переменными S1 и S2 выясняется, что строки успешно изменяются, процедура также успешно изменяет обе строки на правильные значения, а ошибка возникает при последующей попытке присвоить строками новые значения, будто бы вызванная процедура заблокировала их изменение.
Только начал разбираться с библиотеками (буквально сегодня), многого не знаю. Вышеописанное поведение - это ошибка в компиляторе или я что-то неправильно делаю? Если ошибка, то как о ней сообщить (или исправить ее?).
absdjfh
новенький
 
Сообщения: 60
Зарегистрирован: 21.01.2012 13:59:00

Re: DLL библиотеки, строки, {$H+}

Сообщение NTFS » 12.03.2013 21:06:51

Решение 1: Не разрабатывать DLL на FreePascalCompiler.

Решение 2: Если все-таки хочется веселья, передавать только короткие строки < 256 символов или использовать PChar для передаваемых длинных строк.

Решение 3: Если хочется веселья, но нужны длинные строки и неохота возиться с гадким PChar, начинать глубоко разбираться в работе динамических библиотек и способах управления памятью (применительно к той ОС, для которой пишется программа).

Удачи.
NTFS
постоялец
 
Сообщения: 388
Зарегистрирован: 05.11.2007 14:57:50
Откуда: Краснодар

Re: DLL библиотеки, строки, {$H+}

Сообщение alexey38 » 12.03.2013 21:40:49

Общая рекомендация по работе с DLL на любых языках: выделять и освобождать память нужно либо в DLL, либо в основном модуле (EXE). Если выделять память в DLL, а освобождать ее в EXE или наоборот, то это чревато ошибкам. В общем случае менеджер памяти в EXE и DLL - разные, в частном случае можно их сделать одинаковыми, но это лучше избегать. Длинные строки в паскале - это динамические элементы со счетчиком ссылок. Выделение памяти происходить при первом присвоении, а освобождение памяти, когда счетчик ссылок стал равен 0. То есть освобождение памяти неочевидно.
alexey38
долгожитель
 
Сообщения: 1627
Зарегистрирован: 27.04.2011 19:42:31

Re: DLL библиотеки, строки, {$H+}

Сообщение absdjfh » 12.03.2013 22:28:12

NTFS писал(а):Решение 2: Если все-таки хочется веселья, передавать только короткие строки < 256 символов

Такое решение следует прямо из моего объяснения задачи. Оно мне подходит, но хотелось бы все-таки разобраться.
alexey38 писал(а):выделять и освобождать память нужно либо в DLL, либо в основном модуле (EXE)

Возможно, я неправильно понимаю, но память в S1 и S2 должна высвобождаться только в конце исполняемого файла. Выделения памяти в dll-библиотеке я не вижу. Не могли бы вы прокомментировать состояние счетчика ссылок в каждый момент времени? Мне представляется, что он все время равен единице (кроме самого начала и самого конца). Даже если это не так, то при повторном присвоении (S2 := 'kill') память должна выделиться заново. Я даже представить не могу, каким образом процедура может блокировать присвоение для переменной.

Добавлено спустя 29 минут 36 секунд:
Понаблюдав за счетчиком ссылок, обнаружил, что, в случае процедуры Proc1(var S1, S2: string; i: Integer) он:
после первого присвоения равен 255
после применения процедуры равен 1
В случае процедуры Proc1(var S1, S2: string) он
после первого присвоения равен 255
после применения процедуры равен 255
(это видимо своеобразные внутренние 1 и 2 в понимании компилятора :) )
Почему ни на что не влияющая добавленная в параметры функции переменная i влияет на механизм обработки строки?
absdjfh
новенький
 
Сообщения: 60
Зарегистрирован: 21.01.2012 13:59:00

Re: DLL библиотеки, строки, {$H+}

Сообщение Sergei I. Gorelkin » 13.03.2013 09:23:52

Наблюдение верное, только 255 следует заменить на -1 (счетчик ссылок представляет собой 4-байтовое значение на 32-битных системах).
Значение -1 указывает на то, что освобождать память строки не нужно. Так происходит при присвоении строке константного выражения.

Собственно, пока присваиваются константные выражения, выделения/освобождения памяти не происходит, и поэтому строку, присвоенную в DLL, удается передать в Exe. При добавлении переменной i строка S2 перестает быть константной, память для нее выделяется в DLL, а присвоение нового значения в exe пытается эту память освободить - приплыли.
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
 
Сообщения: 1405
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Re: DLL библиотеки, строки, {$H+}

Сообщение Vapaamies » 13.03.2013 12:44:32

Решение 4: Предусмотреть процедуру вида:

Код: Выделить всё
procedure InitMyLib(const MM: TMemoryManager);
begin
  SetMemoryManager(MM);
end;
Аватара пользователя
Vapaamies
постоялец
 
Сообщения: 292
Зарегистрирован: 24.07.2012 22:37:59
Откуда: Санкт-Петербург

Re: DLL библиотеки, строки, {$H+}

Сообщение absdjfh » 13.03.2013 14:45:39

Sergei I. Gorelkin писал(а):Наблюдение верное, только 255 следует заменить на -1 (счетчик ссылок представляет собой 4-байтовое значение на 32-битных системах).
Значение -1 указывает на то, что освобождать память строки не нужно. Так происходит при присвоении строке константного выражения.

Собственно, пока присваиваются константные выражения, выделения/освобождения памяти не происходит, и поэтому строку, присвоенную в DLL, удается передать в Exe. При добавлении переменной i строка S2 перестает быть константной, память для нее выделяется в DLL, а присвоение нового значения в exe пытается эту память освободить - приплыли.

Спасибо, теперь стало вроде как все ясно, но до сих пор непонятно, как это "красиво" исправить.
Vapaamies писал(а):Решение 4: Предусмотреть процедуру вида:
Код: Выделить всё
procedure InitMyLib(const MM: TMemoryManager);
begin
  SetMemoryManager(MM);
end;


Что это за магия такая? :) Что она делает? Вызывать это нужно снаружи (в exe) или изнутри библиотеки (в секции begin-end)?
Дайте ссылку или название на более или менее понятно описанную литературу/статью/документацию по этому поводу, пожалуйста.
absdjfh
новенький
 
Сообщения: 60
Зарегистрирован: 21.01.2012 13:59:00

Re: DLL библиотеки, строки, {$H+}

Сообщение Mr.Smart » 13.03.2013 14:58:46

Mr.Smart
долгожитель
 
Сообщения: 1796
Зарегистрирован: 29.03.2008 01:01:11
Откуда: из леса!

Re: DLL библиотеки, строки, {$H+}

Сообщение alexey38 » 13.03.2013 17:13:54

absdjfh писал(а):Спасибо, теперь стало вроде как все ясно, но до сих пор непонятно, как это "красиво" исправить.

Красиво исправить - это при разработке DLL делать так, чтобы память под динамические элементы (строки) выделялась либо только в DLL, либо только в EXE.
Так как у типа String (или подобных) освобождение памяти происходит неявное и не очевидное, то общая рекомендация - не использовать паскалевские строи для передачи параметров в dll. Неким стандартом для этих целей являются типы подобные PChar в разных вариациях. Для них выделение и освобождение памяти нужно осуществлять путем вызова соответствующих функций. Например создали некий буфер в EXE, передали в DLL указатель и максимальную длину, а там уже возвращаете значение используя StrCopy, так как это происходит для всех WinAPI функций.

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

Добавлено спустя 4 минуты 31 секунду:
absdjfh писал(а):Что это за магия такая? Что она делает? Вызывать это нужно снаружи (в exe) или изнутри библиотеки (в секции begin-end)?
Дайте ссылку или название на более или менее понятно описанную литературу/статью/документацию по этому поводу, пожалуйста.

Это работоспособная магия, но важно понимать, что она работоспособна будет только если EXE и DLL написаны и скомпилированы на одном языке и компиляторе. Если это так, то для чего Вы вообще используете DLL, не проще было бы перейти на Unit, вместо library?
Обычно DLL делаются для того, чтобы можно было связать код написанный на разных языках программирования, когда это разные разработчики, которые не хотят делится исходным кодом. Соответственно для таких случаев магия с менеджером памяти не применима в принципе. Поэтому либо отказывайтесь от DLL, либо используйте советы выше про PChar.
alexey38
долгожитель
 
Сообщения: 1627
Зарегистрирован: 27.04.2011 19:42:31

Re: DLL библиотеки, строки, {$H+}

Сообщение absdjfh » 13.03.2013 17:25:16

Подумать только, и все эти сложности лишь из-за того, "что Free Pascal для распределения динамической памяти использует не функции операционной системы, а свои собственные, что позволяет значительно ускорить процесс". Хоть бы hint выдавал, что-то вроде "ansistring declaration found" при условии, что она есть в экспортируемой функции.
Вообщем, я решил воспользоваться самым разумным решением.
NTFS писал(а):Решение 1: Не разрабатывать DLL на FreePascalCompiler.


Добавлено спустя 3 минуты 11 секунд:
alexey38 писал(а):Обычно DLL делаются для того, чтобы можно было связать код написанный на разных языках программирования, когда это разные разработчики, которые не хотят делится исходным кодом.

Насколько я знаю, еще одно применение - это предотвращение многократной загрузки в память одной и той же библиотеки, если она используется в разных программах. Но для меня это не нужно.
absdjfh
новенький
 
Сообщения: 60
Зарегистрирован: 21.01.2012 13:59:00

Re: DLL библиотеки, строки, {$H+}

Сообщение Mr.Smart » 13.03.2013 17:32:45

absdjfh писал(а):Подумать только, и все эти сложности лишь из-за того, "что Free Pascal для распределения динамической памяти использует не функции операционной системы, а свои собственные, что позволяет значительно ускорить процесс".

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

Кстати, такие же точно проблемы есть и в Delphi.
Mr.Smart
долгожитель
 
Сообщения: 1796
Зарегистрирован: 29.03.2008 01:01:11
Откуда: из леса!

Re: DLL библиотеки, строки, {$H+}

Сообщение alexey38 » 13.03.2013 20:56:59

absdjfh писал(а):Насколько я знаю, еще одно применение - это предотвращение многократной загрузки в память одной и той же библиотеки, если она используется в разных программах.

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

Добавлено спустя 21 минуту 18 секунд:
absdjfh писал(а):Вообщем, я решил воспользоваться самым разумным решением.
NTFS писал(а): Решение 1: Не разрабатывать DLL на FreePascalCompiler.

Mr.Smart писал(а):Кстати, такие же точно проблемы есть и в Delphi.


Для чего пытаетесь эту классическую проблему привязать как недостаток паскаля? Проблема в принципе не связана с паскалем. Она есть во всех языках. DLL, на то и DLL, что она не ограничивает вас в способах передачи данных.
Выше приведено не совсем точное утверждение "Free Pascal для распределения динамической памяти использует не функции операционной системы", т.к. любая программа использует только средства ОС для выделения памяти, т.к. другой возможности нет в принципе. Чтобы освободить память нужно знать, как она выделена. А выделять и распределять память можно по разному. И DLL со своим синтаксисом и стандартами ни как не утверждает, какой из 1000 способов ОС был применен. Начиная с того, что паскалевеская строка передает не указатель на начало блока, а адрес со смещением. В одной версии компилятора служебный блок, предшествующий адресу не содержит номер кодовой страницы, а в другой версии содержит. Поэтому даже при использовании одного менеджера памяти, но в разных версиях компилятора собрав DLL и EXE, можно получить глюки. А кто не получал в жизни глюков одного софта, после установки другого софта? Одна софтина в виндовую папку набрасала DLL одних версий, а другая туда с тем же именем других версий, и эти глюки не зависят от языка.

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

Кстати, в свое время МС придумал COM-объекты как раз в качестве способа более строго взаимодействия. COM-объекты, которые в DLL - используют одни способы передачи, а которые в EXE - уже используют маршалинг. Система навороченная, но все же наводила некоторый порядок, и не давала возможность делать то, что нельзя делать.
alexey38
долгожитель
 
Сообщения: 1627
Зарегистрирован: 27.04.2011 19:42:31


Вернуться в Обучение Free Pascal

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 4

Рейтинг@Mail.ru