Почему WideString не может использоваться как возвращаемое значение функции для взаимодействия?

Я неоднократно советовал людям использовать возвращаемое значение типа WideString для взаимодействия.

Идея в том, что WideString такой же как BSTR, Потому что BSTR размещается в общей куче COM, тогда нет проблем разместить в одном модуле и освободить в другом модуле. Это потому, что все стороны согласились использовать одну и ту же кучу, кучу COM.

Тем не менее, кажется, что WideString не может использоваться как возвращаемое значение функции для взаимодействия.

Рассмотрим следующую Delphi DLL.

library WideStringTest;

uses
  ActiveX;

function TestWideString: WideString; stdcall;
begin
  Result := 'TestWideString';
end;

function TestBSTR: TBstr; stdcall;
begin
  Result := SysAllocString('TestBSTR');
end;

procedure TestWideStringOutParam(out str: WideString); stdcall;
begin
  str := 'TestWideStringOutParam';
end;

exports
  TestWideString, TestBSTR, TestWideStringOutParam;

begin
end.

и следующий код C++:

typedef BSTR (__stdcall *Func)();
typedef void (__stdcall *OutParam)(BSTR &pstr);

HMODULE lib = LoadLibrary(DLLNAME);
Func TestWideString = (Func) GetProcAddress(lib, "TestWideString");
Func TestBSTR = (Func) GetProcAddress(lib, "TestBSTR");
OutParam TestWideStringOutParam = (OutParam) GetProcAddress(lib,
                   "TestWideStringOutParam");

BSTR str = TestBSTR();
wprintf(L"%s\n", str);
SysFreeString(str);
str = NULL;

TestWideStringOutParam(str);
wprintf(L"%s\n", str);
SysFreeString(str);
str = NULL;

str = TestWideString();//fails here
wprintf(L"%s\n", str);
SysFreeString(str);

Призыв к TestWideString не удается с этой ошибкой:

Необработанное исключение в 0x772015de в BSTRtest.exe: 0xC0000005: расположение чтения нарушения доступа 0x00000000.

Точно так же, если мы пытаемся вызвать это из C# с помощью p/invoke, у нас возникает ошибка:

[DllImport(@"path\to\my\dll")]
[return: MarshalAs(UnmanagedType.BStr)]
static extern string TestWideString();

Ошибка:

Произошло необработанное исключение типа "System.Runtime.InteropServices.SEHException" в ConsoleApplication10.exe.

Дополнительная информация: Внешний компонент выдал исключение.

призвание TestWideString via p/invoke работает как положено.

Поэтому используйте передачу по ссылке с параметрами WideString и отображайте их на BSTR кажется, работает отлично. Но не для функции, возвращающей значения. Я проверил это на Delphi 5, 2010 и XE2 и наблюдаю одинаковое поведение на всех версиях.

Выполнение входит в Delphi и завершается неудачей почти сразу. Назначение Result превращается в вызов System._WStrAsg первая строка которого гласит:

CMP [EAX], EDX

Сейчас, EAX является $00000000 и естественно есть нарушение доступа.

Кто-нибудь может объяснить это? Я делаю что-то неправильно? Я неоправдан в ожидании WideString Значения функции должны быть жизнеспособными BSTR s? Или это просто дефект Delphi?

2 ответа

Решение

В обычных функциях Delphi возвращаемая функция на самом деле является параметром, передаваемым по ссылке, хотя синтаксически она выглядит и ощущается как выходной параметр. Вы можете проверить это следующим образом (это может зависеть от версии):

function DoNothing: IInterface;
begin
  if Assigned(Result) then
    ShowMessage('result assigned before invocation')
  else
    ShowMessage('result NOT assigned before invocation');
end;

procedure TestParameterPassingMechanismOfFunctions;
var
  X: IInterface;
begin
  X := TInterfaceObject.Create;
  X := DoNothing; 
end;

Продемонстрировать вызов TestParameterPassingMechanismOfFunctions()

Ваш код терпит неудачу из-за несоответствия между Delphi и C++ в понимании соглашения о вызовах относительно механизма передачи для результатов функции. В C++ функция return действует так, как предполагает синтаксис: out параметр. Но для Delphi это var параметр.

Чтобы исправить, попробуйте это:

function TestWideString: WideString; stdcall;
begin
  Pointer(Result) := nil;
  Result := 'TestWideString';
end;

В C#/C++ вам нужно определить результат как out Параметр, чтобы поддерживать совместимость двоичного кода stdcall соглашения о вызовах:

Возвращение строк и ссылок на интерфейсы из функций DLL

в stdcall Соглашение о вызовах, результат функции передается через процессор EAX регистр. Однако Visual C++ и Delphi генерируют различный двоичный код для этих процедур.

Код Delphi остается прежним:

function TestWideString: WideString; stdcall;
begin
  Result := 'TestWideString';
end;

Код C#:

// declaration
[DllImport(@"Test.dll")]        
static extern void  TestWideString([MarshalAs(UnmanagedType.BStr)] out string Result);
...
string s;
TestWideString(out s); 
MessageBox.Show(s);
Другие вопросы по тегам