<?xml version='1.0' encoding="utf-8"?>
      <rss version='2.0'>
      <channel>
      <title>Форум на Исходниках.RU</title>
      <link>https://forum.sources.ru</link>
      <description>Форум на Исходниках.RU</description>
      <generator>Форум на Исходниках.RU</generator>
  	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=210450&amp;view=findpost&amp;p=1764366</guid>
        <pubDate>Wed, 14 Nov 2007 12:45:12 +0000</pubDate>
        <title>Передача строк между приложением и DLL</title>
        <link>https://forum.sources.ru/index.php?showtopic=210450&amp;view=findpost&amp;p=1764366</link>
        <description><![CDATA[Rose: При создании проекта DLL, автоматически генерируемый средой комментарий предупреждает, что передавать между приложением и библиотекой параметры типа String так просто нельзя, и предлагает объявлять ShareMem первым в списке юнитов проекта либо использовать типы PChar или ShortString. Постараюсь подробнее описать, из-за чего возникает такая необходимость, и какие тут есть проблемы, а также методы их решения, однако все тонкости типа String рассматриваться не будут.<br>
<br>
<strong class='tag-b'>Суть проблемы:</strong><br>
1. Приложение и библиотека содержат свой собственный экземпляр менеджера памяти, который представляет собой структуры данных, хранящие информацию о выделенных и свободных блоках памяти, и набор функций для выделения/освобождения памяти, которые с этими данными работают.<br>
2. Менеджер памяти не может освободить или перераспределить блок, который он не выделял, так как в его записях нет информации об этом блоке.<br>
3. При работе с типом String неявно происходит интенсивная работа с менеджером памяти, компилятор автоматически вставляет код для выделения, перераспределения и освобождения памяти под строку, а сама переменная типа String является лишь указателем на память, где строка хранится. Для работы с памятью используются функции, предоставляемые менеджером. В результате при передаче String-ов между приложением и библиотекой зачастую получается, что одна из сторон имеет дело с памятью, информации о которой нет в записях менеджера. Первое же обращение к менеджеру памяти в этом случае приведет к исключению.<br>
<br>
<strong class='tag-b'>Методы решения:</strong><br>
1. Передавать ShortString или String[x] вместо String. ShortString - это обыкновенный статический массив (максимальная длина - 255 символов). Динамическая память не используется, неявных обращений к менеджеру памяти нет. Недостаток – ограничение длины строки 255 символами.<br>
<br>
2. Передавать PChar, но если планируется передавать String под видом PChar, есть некоторые оговорки. Нужно помнить, что String – это финализируемый тип, память под строку автоматически освобождается при потери последней ссылки на строку, а когда мы переходим к PChar компилятор перестает следить за ссылками. Вот так, например, в экспортной функции DLL делать нельзя:<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">function MyFunc: PChar;</div><div class="code_line">var</div><div class="code_line">&nbsp;&nbsp;s: String;</div><div class="code_line">begin</div><div class="code_line">&nbsp;&nbsp;s:=DateTimeToStr(Now); &nbsp;// Тут память под строку выделяется</div><div class="code_line">&nbsp;&nbsp;Result:=PChar(s);</div><div class="code_line">end; &nbsp;{ Вот тут происходит финализация s и память под строку освобождается. Указатель, возвращаемый функцией невалиден }</div></ol></div></div></div></div><script>preloadCodeButtons('1');</script><br>
Когда придет время использовать указатель, возвращенный MyFunc, если повезет, то он все еще будет указывать на корректный буфер строки, однако не факт, в этой области памяти могут уже быть совсем другие данные или память может быть возвращена системе (в этом случае получим AV).<br>
Однако нижеприведенный код, будет работать (если конечно приложение не захочет использовать полученный указатель после выгрузки библиотеки с помощью FreeLibrary):<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">function MyFunc: PChar;</div><div class="code_line">var</div><div class="code_line">&nbsp;&nbsp;s: String;</div><div class="code_line">begin</div><div class="code_line">&nbsp;&nbsp;s:=&#39;Some String&#39;;</div><div class="code_line">&nbsp;&nbsp;Result:=PChar(s);</div><div class="code_line">end;</div></ol></div></div></div></div><br>
Разница в том, что строковые литералы, коим является &#39;Some String&#39;, хранятся не в динамической памяти, а в статической памяти исполняемого модуля (exe или dll), и указатель на строку валиден все время пока исполняемый модуль загружен в адресное пространство процесса.<br>
При передаче строк типа PChar важно следить, чтобы выделял и освобождал память один и тот же менеджер. Если для работы с памятью используется менеджер Delphi (функции GetMem/FreeMem), то лучше поступить так, как обычно принято в WinAPI: выделяет и освобождает память вызывающая сторона (приложение), а библиотека работает с уже подготовленным буфером. Либо отказаться от менеджера памяти Delphi и работать с памятью через системные функции LocalAlloc/LocalFree или HeapAlloc/HeapFree. В этом случае не важно кто выделяет и освобождает память, так как в любом случае менеджер памяти общий – не дельфийский, а системный.<br>
<br>
3. Передавать WideString. Очень простой вариант, работать с WideString так же просто, как и со String, за памятью следить не нужно, она автоматически выделяется и освобождается. Однако при работе с этим типом дельфийский менеджер памяти не используется, используются системные функции. Единственный недостаток в том, что при присвоении String-у WideString-а и наоборот происходит автоматически перекодировка ANSI&lt;-&gt;UNICODE. При передаче String-ов между библиотекой и приложением в виде WideString будет тратиться процессорное время на перекодировку в UNICODE и обратно.<br>
<br>
4. Использование модуля ShareMem. Этот юнит необходимо объявить первым в списке uses проекта exe (именно проекта, а не главной формы) и dll. ShareMem содержит в секции инициализации код, который присваивает переменной MemoryManager значение нового менеджера, функции которого находятся во внешней библиотеке BORLNDMM.DLL. Это заставит и приложение и библиотеку использовать общий внешний менеджер памяти. Почему необходимо объявить первым – потому что секции initialization юнитов выполняются в том порядке, в котором юниты объявлены в проекте, и если до выполнения кода инициализации ShareMem будет выделена память, освобождать ее придется уже новому менеджеру (а ведь он ее не выделял).<br>
Начиная с BDS 2006 библиотеку BORLNDMM.DLL уже таскать с собой не нужно, там новый менеджер памяти, только вместо ShareMem нужно использовать SimpleShareMem. Если у вас более ранняя версия и необходимость распространения BORLNDMM.DLL вызывает неудобство, можно использовать сторонние менеджеры. Наиболее простое и изящное решения на мой взгляд предоставляет FastShareMem:<br>
http://www.codexterity.com/fastsharemem.htm<br>
Код юнита прост и гениален (~60 строчек за вычетом комментариев&#33;). Если кому интересно, посмотрите, я получил немалое эстетическое удовольствие. :)]]></description>
        <author>Rose</author>
        <category>Общие вопросы</category>
      </item>
	
      </channel>
      </rss>
	