Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Общие вопросы > WideString в DLL


Автор: aktuba 26.2.2007, 08:32
Не так давно я описывал http://forum.vingrad.ru/topic-136180/view-all.html. Сегодня вот нашел немного инфы на эту тему, точнее на тему использования WideString в DLL. Думаю, это многим пригодится, т.к. ни в одной книге, ни на других форумах подобного не встречал.
Итак, вкратце: при использовании WideString НЕ НАДО никакого выделения памяти под строки, НЕ НАДО подключать никакие доп. модули и т.д. В общем не надо НИЧЕГО!!! Не верите? Попробуйте:

Код DLL:
Код

library tstdll;

function GetWideString: WideString; stdcall; export;
begin
  Result := 'Какой-то текст.';
end;

exports
  GetWideString;

begin
end.


Код программы:
Код

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  function GetWideString: WideString; stdcall; external 'tstdll.dll';

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowMessage(GetWideString);
end;

end.


Все. Компилируем и проверяем. Странно... Работает  smile.

Теперь небольшое объяснение. Все прекрасно знают, что в EXE и в DLL менеджеры памяти разные и именно из-за этого и возникают проблемы при передаче строк из программы в dll и обратно. Но, при использовании WideString, используется не обычный менеджер памяти, а специальный - системный. Специальный менеджер сделан для совместимости с COM. И этот менеджер памяти используется один на всех, поэтому WideString можно безопасно передавать и без использования ShareMem, FastShareMem и т.д. - просто в этом случае вы будете терять время на перекодировку в Unicode и обратно. Теперь по поводу выделения/освобождения памяти под WideString. При работе с WideString используются функции SysAllocString/SysReallocString/SysFreeString, которые можно вызвать явно или, как делает Delphi, неявно. Плюс к этому, в отличии от AnsiString, где для определения момента освобождения памяти, занимаемой строкой, используется механизм подсчета ссылок, в WideString всегда используется явно. Т.е. при присваивании wstr1 := wstr2, для AnsiString это будет просто копирование указателя, а для WideString создание новой строки и копирование содержимого wstr2 в созданную строку.

Думаю, это многим поможет...

Автор: Alexeis 26.2.2007, 12:04
Цитата(aktuba @  26.2.2007,  08:32 Найти цитируемый пост)
в WideString всегда используется явно. Т.е. при присваивании wstr1 := wstr2, для AnsiString это будет просто копирование указателя, а для WideString создание новой строки и копирование содержимого wstr2 в созданную строку.


Мне кажется ты ошибаешся. Посмотрим реализацию следующего кода.
Код

procedure TForm1.FormCreate(Sender: TObject);
var
  s1, s2 : WideString;
  i, j   : Integer;
begin
  S1 := 'Some Text';
  S2 := S1;
end;


Смотрим, что же у нас выходит в ассемблере
Код

Unit1.pas.30: S1 := 'Some Text';
0044C896 8D45F8           lea eax,[ebp-$08]
0044C899 BAE0C84400       mov edx,$0044c8e0
0044C89E E8AD7CFBFF       call @WStrLAsg
Unit1.pas.31: S2 := S1;
0044C8A3 8D45F4           lea eax,[ebp-$0c]
0044C8A6 8B55F8           mov edx,[ebp-$08]
0044C8A9 E8A27CFBFF       call @WStrLAsg


Вызов WStrLAsg пустой переход к _LStrLAsg

Смотрим теперь на паскалевский вариант _LStrLAsg

Код

procedure _LStrLAsg(var dest; const source);
{$IFDEF PUREPASCAL}
var
  P: Pointer;
begin
  P := Pointer(source);
  _LStrAddRef(P);
  P := Pointer(dest);
  Pointer(dest) := Pointer(source);
  _LStrClr(P);
end;    


Что же мы видим? Увеличение счетчика ссылок на строку и копирование указателя на строку. Нет ни какого копирования содержимого строки. Просто ссылка и все. Причем используется стандарный менеджер памяти.

В справку написано использовать модуль ShareMem при работе с длинными строками WideString тоже относится к длинным строкам.

Автор: Quadr0 26.2.2007, 12:29
...

Автор: aktuba 26.2.2007, 12:31
Цитата

Мне кажется ты ошибаешся. Посмотрим реализацию следующего кода.


Да нет, мне кажется ты немного заблуждаешься. Или Delphi умнее, чем мы думаем и использует то, что необходимо в данной ситуации =).
Для примера:
Код

procedure TForm1.FormCreate(Sender: TObject);
var
  str1, str2: WideString;
begin
  str1 := 'Это текст';
  str2 := str1;
end;


В отличии от твоего кода - я использую русский текст. Вот что дает дебагер:
user posted image

Здесь видно, что не только адреса ссылок на переменные разные, но и адрес нахождения самих данных различается. Но, пойдем дальше...

Цитата

Вызов WStrLAsg пустой переход к _LStrLAsg


Снова не верно. Смотрим скрин:
user posted image

Снова видим, что никакого перехода к _LStrLAsg нет в природе. Все на уровне работы с WideString, ну и соответственно, используется SysRealloc, как я и написал в начале.

Цитата

Причем используется стандарный менеджер памяти.


Снова повторю - неверно. Для WideString используется системный менеджер памяти, что наглядно видно из скринов. Правда есть подозрение, что в предыдущих версиях Delphi используется стандартный... Я же говорю про BDS2006/TurboDelphi...

P.S.: пока писал - Quadr0 откоментил, но думаю скрины и пояснения многим помогут...

Автор: Alexeis 26.2.2007, 12:54
Цитата(aktuba @  26.2.2007,  12:31 Найти цитируемый пост)
Снова видим, что никакого перехода к _LStrLAsg нет в природе. Все на уровне работы с WideString, ну и соответственно, используется SysRealloc, как я и написал в начале.

  Все тестил на Delphi7. Там используется именно _LStrLAsg. В любом случае использование недокументированых возможностей сопрежено с большими проблемами совместимости версии. Раз в документации этого нет, значит Borland не обязяна сохранять эту систему в будущем. Непойму чем так усложняет жизнь простое указание модуля FastShareMem?

Автор: aktuba 26.2.2007, 13:10
Цитата

Все тестил на Delphi7. Там используется именно _LStrLAsg. В любом случае использование недокументированых возможностей сопрежено с большими проблемами совместимости версии. Раз в документации этого нет, значит Borland не обязяна сохранять эту систему в будущем. Непойму чем так усложняет жизнь простое указание модуля FastShareMem?


Сейчас посмотрел на Delphi 7, там используется тоже самое, что и на BDS, т.е. к _LStrLAsg перехода нет. К сожалению не могу выложить скрин, т.к. D7 установлен на ноуте без инета... 

Цитата

Раз в документации этого нет, значит Borland не обязяна сохранять эту систему в будущем.


Вот выдержка из хелпа D7:
Цитата

... On Win32, WideString is compatible with the COM BSTR type.

Note
Under Win32, WideString values are not reference-counter. ...

Автор: Alexeis 26.2.2007, 13:23
Упс не догядел. Это был фрагмент линуксового кода  smile 
Да уж и в семерке
Код

procedure TForm1.btn1Click(Sender: TObject);
var
 // str1, str2: AnsiString;
  str1, str2: WideString;
  p1, p2, p3  : Pointer;
begin
  str1 := 'Это текст';
  str2 := str1;
  p1 :=  PWideChar(str1);
  p2 :=  PWideChar(str2);


p1 и p2 указывают на разные области памяти, тогда как

Код

procedure TForm1.btn1Click(Sender: TObject);
var
  str1, str2: AnsiString;
  p1, p2, p3  : Pointer;
begin
  str1 := 'Это текст';
  str2 := str1;
  p1 :=  PChar(str1);
  p2 :=  PChar(str2);


Указывают на одну.

Добавлено @ 13:31 
Да пожалуй основываясь на 
Цитата

On Win32, if a DLL exports routines that pass long strings or dynamic arrays as parameters or function results (whether directly or nested in records or objects), then the DLL and its client applications (or DLLs) must all use the ShareMem unit. The same is true if one application or DLL allocates memory with New or GetMem which is deallocated by a call to Dispose or FreeMem in another module. ShareMem should always be the first unit listed in any program or library uses clause where it occurs.


Можно смело не использовать ни каких FastshareMem.

Автор: aktuba 26.2.2007, 13:36
Alexeis, не хочется снова указывать ошибку, а приходится =((( - во втором коде замени AnsiString на WideString и все встанет на места. p1 и p2 будут указывать на разные области памяти....

Автор: Alexeis 26.2.2007, 13:39
Тогда еще небольшое дополнение к
Цитата(aktuba @  26.2.2007,  08:32 Найти цитируемый пост)
Но, при использовании WideString, используется не обычный менеджер памяти, а специальный - системный. Специальный менеджер сделан для совместимости с COM. И этот менеджер памяти используется один на всех, поэтому WideString можно безопасно передавать и без использования ShareMem, FastShareMem и т.д.


Функция SysReAllocStringLen вообще виндовая и не принадлежит самому приложению. Память выделяется
и уничтожается внешним, как для DLL так и для программы, менеджером памяти. 
Код

Const
  oleaut = 'oleaut32.dll';

function SysReAllocStringLen(var S: WideString; P: PWideChar;
  Len: Integer): LongBool; stdcall;
  external oleaut name 'SysReAllocStringLen';



Автор: Alexeis 26.2.2007, 14:15
В связи с этим хочется развеять еще один скользкий вопрос. Считается, что использование Pchar вместо AnsiString позволяет также избавиться от проблем с менеджером памяти. Однако часто можно увидеть ситуацию, когда память для Pchar выделяется при помощи GetMem или New. Такой ход является очень опасным и допустим, только в случае если память выделенная GetMem освобождается в том же модуле, что и была выделена иначе нужно использовать HeapAlloc, GlobalAlloc.

Автор: Alexeyt 26.2.2007, 16:21
aktuba, 
Alexeis, 
Спасибо за информацию.
Очень полезно (у меня есть кое-какие DLL).

Автор: MetalFan 23.3.2007, 08:57
Цитата(aktuba @  26.2.2007,  08:32 Найти цитируемый пост)
ни в одной книге, ни на других форумах подобного не встречал.

http://delphiworld.narod.ru/base/delphi_com.html
мотаем до самого конца....
ы?

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)