Почему библиотеки Delphi DLL могут использовать 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;

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

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

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


РЕДАКТИРОВАТЬ:

Я проверил DLL с C#.
призвание 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!

Вот продолжение.

1 ответ

Решение

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 намного сложнее, чем из языков более высокого уровня, таких как C++, C#, Delphi и т. д.

Однако есть дополнительное осложнение. Delphi ABI для WideString возвращаемые значения не совместимы с инструментами Microsoft. Вы не должны использовать WideString в качестве типа возврата, вместо этого верните его через out параметр. Для получения дополнительной информации см. Почему WideString не может использоваться как возвращаемое значение функции для взаимодействия?

Другие вопросы по тегам