На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: jack128, Rouse_, Krid
  
    > Передача строк между приложением и DLL
      При создании проекта DLL, автоматически генерируемый средой комментарий предупреждает, что передавать между приложением и библиотекой параметры типа String так просто нельзя, и предлагает объявлять ShareMem первым в списке юнитов проекта либо использовать типы PChar или ShortString. Постараюсь подробнее описать, из-за чего возникает такая необходимость, и какие тут есть проблемы, а также методы их решения, однако все тонкости типа String рассматриваться не будут.

      Суть проблемы:
      1. Приложение и библиотека содержат свой собственный экземпляр менеджера памяти, который представляет собой структуры данных, хранящие информацию о выделенных и свободных блоках памяти, и набор функций для выделения/освобождения памяти, которые с этими данными работают.
      2. Менеджер памяти не может освободить или перераспределить блок, который он не выделял, так как в его записях нет информации об этом блоке.
      3. При работе с типом String неявно происходит интенсивная работа с менеджером памяти, компилятор автоматически вставляет код для выделения, перераспределения и освобождения памяти под строку, а сама переменная типа String является лишь указателем на память, где строка хранится. Для работы с памятью используются функции, предоставляемые менеджером. В результате при передаче String-ов между приложением и библиотекой зачастую получается, что одна из сторон имеет дело с памятью, информации о которой нет в записях менеджера. Первое же обращение к менеджеру памяти в этом случае приведет к исключению.

      Методы решения:
      1. Передавать ShortString или String[x] вместо String. ShortString - это обыкновенный статический массив (максимальная длина - 255 символов). Динамическая память не используется, неявных обращений к менеджеру памяти нет. Недостаток – ограничение длины строки 255 символами.

      2. Передавать PChar, но если планируется передавать String под видом PChar, есть некоторые оговорки. Нужно помнить, что String – это финализируемый тип, память под строку автоматически освобождается при потери последней ссылки на строку, а когда мы переходим к PChar компилятор перестает следить за ссылками. Вот так, например, в экспортной функции DLL делать нельзя:
      ExpandedWrap disabled
        function MyFunc: PChar;
        var
          s: String;
        begin
          s:=DateTimeToStr(Now);  // Тут память под строку выделяется
          Result:=PChar(s);
        end;  { Вот тут происходит финализация s и память под строку освобождается. Указатель, возвращаемый функцией невалиден }

      Когда придет время использовать указатель, возвращенный MyFunc, если повезет, то он все еще будет указывать на корректный буфер строки, однако не факт, в этой области памяти могут уже быть совсем другие данные или память может быть возвращена системе (в этом случае получим AV).
      Однако нижеприведенный код, будет работать (если конечно приложение не захочет использовать полученный указатель после выгрузки библиотеки с помощью FreeLibrary):
      ExpandedWrap disabled
        function MyFunc: PChar;
        var
          s: String;
        begin
          s:='Some String';
          Result:=PChar(s);
        end;

      Разница в том, что строковые литералы, коим является 'Some String', хранятся не в динамической памяти, а в статической памяти исполняемого модуля (exe или dll), и указатель на строку валиден все время пока исполняемый модуль загружен в адресное пространство процесса.
      При передаче строк типа PChar важно следить, чтобы выделял и освобождал память один и тот же менеджер. Если для работы с памятью используется менеджер Delphi (функции GetMem/FreeMem), то лучше поступить так, как обычно принято в WinAPI: выделяет и освобождает память вызывающая сторона (приложение), а библиотека работает с уже подготовленным буфером. Либо отказаться от менеджера памяти Delphi и работать с памятью через системные функции LocalAlloc/LocalFree или HeapAlloc/HeapFree. В этом случае не важно кто выделяет и освобождает память, так как в любом случае менеджер памяти общий – не дельфийский, а системный.

      3. Передавать WideString. Очень простой вариант, работать с WideString так же просто, как и со String, за памятью следить не нужно, она автоматически выделяется и освобождается. Однако при работе с этим типом дельфийский менеджер памяти не используется, используются системные функции. Единственный недостаток в том, что при присвоении String-у WideString-а и наоборот происходит автоматически перекодировка ANSI<->UNICODE. При передаче String-ов между библиотекой и приложением в виде WideString будет тратиться процессорное время на перекодировку в UNICODE и обратно.

      4. Использование модуля ShareMem. Этот юнит необходимо объявить первым в списке uses проекта exe (именно проекта, а не главной формы) и dll. ShareMem содержит в секции инициализации код, который присваивает переменной MemoryManager значение нового менеджера, функции которого находятся во внешней библиотеке BORLNDMM.DLL. Это заставит и приложение и библиотеку использовать общий внешний менеджер памяти. Почему необходимо объявить первым – потому что секции initialization юнитов выполняются в том порядке, в котором юниты объявлены в проекте, и если до выполнения кода инициализации ShareMem будет выделена память, освобождать ее придется уже новому менеджеру (а ведь он ее не выделял).
      Начиная с BDS 2006 библиотеку BORLNDMM.DLL уже таскать с собой не нужно, там новый менеджер памяти, только вместо ShareMem нужно использовать SimpleShareMem. Если у вас более ранняя версия и необходимость распространения BORLNDMM.DLL вызывает неудобство, можно использовать сторонние менеджеры. Наиболее простое и изящное решения на мой взгляд предоставляет FastShareMem:
      http://www.codexterity.com/fastsharemem.htm
      Код юнита прост и гениален (~60 строчек за вычетом комментариев!). Если кому интересно, посмотрите, я получил немалое эстетическое удовольствие. :)
      0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
      0 пользователей:


      Рейтинг@Mail.ru
      [ Script execution time: 0,0220 ]   [ 16 queries used ]   [ Generated: 19.04.24, 21:00 GMT ]