Подтвердить что ты не робот

Почему DLL Delphi может использовать WideString без использования ShareMem?

Дэвид отвечает на другой вопрос, показывает DLL-функцию Delphi, возвращающую WideString. Я никогда не думал, что это возможно без использования ShareMem.

Моя тестовая DLL:

function SomeFunction1: Widestring; stdcall;
begin
  Result := 'Hello';
end;

function SomeFunction2(var OutVar: Widestring): BOOL; stdcall;
begin
  OutVar := 'Hello';
  Result := True;
end;

Моя программа вызывающего абонента:

function SomeFunction1: WideString; stdcall; external 'Test.dll';
function SomeFunction2(var OutVar: Widestring): BOOL; stdcall; external 'Test.dll';

procedure TForm1.Button1Click(Sender: TObject);
var
  W: WideString;
begin
  ShowMessage(SomeFunction1);
  SomeFunction2(W);
  ShowMessage(W);
end;

Работает, и я не понимаю, как это сделать. Соглашение, о котором я знаю, - это тот, который используется в Windows API, например Windows GetClassNameW:

function GetClassNameW(hWnd: HWND; lpClassName: PWideChar; nMaxCount: Integer): Integer; stdcall;

Значение вызывающего абонента обеспечивает буфер и максимальную длину. DLL Windows записывает в этот буфер с ограничением длины. Вызывающий абонент выделяет и освобождает память.

Другим вариантом является то, что DLL выделяет память, например, с помощью LocalAlloc, и Caller освобождает память, вызывая LocalFree.

Как распределяется и освобождается память с моим примером DLL? Существует ли "волшебство", потому что результат WideString (BSTR)? И почему не объявлены API Windows с таким удобным соглашением? (Существуют ли какие-либо известные API Win32, которые используют такое соглашение?)


EDIT:

Я протестировал DLL с помощью С#.
Вызов SomeFunction1 вызывает AV (Attempted to read or write protected memory).
SomeFunction2 работает нормально.

[DllImport(@"Test.dll")]
[return: MarshalAs(UnmanagedType.BStr)]
static extern string SomeFunction1();

[DllImport(@"Test.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SomeFunction2([MarshalAs(UnmanagedType.BStr)] out string res);

...

string s;
SomeFunction2(out s);
MessageBox.Show(s); // works ok
MessageBox.Show(SomeFunction1()); // fails with AV!

Ниже приведено followup.

4b9b3361

Ответ 1

A WideString совпадает с BSTR, это просто имя Delphi для него. Распределение памяти обрабатывается общим распределителем COM, CoTaskMemAlloc. Поскольку все стороны используют один и тот же распределитель, вы можете смело выделить один модуль и освободить его в другом.

Итак, причина, по которой вам не нужно использовать Sharemem, заключается в том, что куча Delphi не используется. Вместо этого используется куча COM. И это разделяется между всеми модулями в процессе.

Если вы посмотрите на реализацию WideString в Delphi, вы увидите вызовы следующих API: SysAllocStringLen, SysFreeString и SysReAllocStringLen. Это предоставленная система BSTR API-функций.

Многие из API Windows вы ссылаетесь на предварительную версию изобретения COM. Более того, существуют преимущества производительности при использовании буфера фиксированной длины, выделенного вызывающим. А именно, что он может быть выделен в стеке, а не в кучу. Я также могу представить, что разработчики Windows не хотят, чтобы каждый процесс должен был ссылаться на OleAut32.dll и платить цену за сохранение кучи COM. Помните, что, когда большая часть Windows API была разработана, характеристики производительности типичного оборудования сильно отличались от настоящего момента.

Еще одна возможная причина не использовать BSTR более широко - это то, что Windows API ориентирован на C. И управление временем жизни BSTR из C очень сложнее, чем на языках более высокого уровня, таких как С++, С#, Delphi и др.