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

39

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

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

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

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

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.

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

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.

Аналогично, если мы попытаемся вызывать это из С# с p/invoke, у нас есть сбой:

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

Ошибка:

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

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

Вызов TestWideString через p/invoke работает как ожидалось.

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

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

CMP     [EAX],EDX

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

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

  • 5
    Дэвид, Может быть, добавить теги C++ , C# тоже?
  • 0
    @kobik Я считаю, что это действительно вопрос о том, как Delphi реализует возвращаемые значения. Я думаю, что Delphi является странным.
Показать ещё 19 комментариев
Теги:

2 ответа

20
Лучший ответ

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

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 и С++ вызывающего соглашения в отношении механизма передачи результатов функции. В С++ функция return действует как синтаксис: a out. Но для Delphi это параметр var.

Чтобы исправить ошибку, попробуйте следующее:

function TestWideString: WideString; stdcall;
begin
  Pointer(Result) := nil;
  Result := 'TestWideString';
end;
  • 5
    Это звучит правдоподобно, но Pointer(result) := nil сам вызывает AV.
  • 0
    Для функций Delphi сохраняет указатель на результат в EAX. Это в значительной степени объясняет это. С точки зрения Delphi, вы не можете передать "no variable" в качестве параметра var.
Показать ещё 15 комментариев
17

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

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

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

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

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

Код С#:

// declaration
[DllImport(@"Test.dll")]        
static extern void  TestWideString([MarshalAs(UnmanagedType.BStr)] out string Result);
...
string s;
TestWideString(out s); 
MessageBox.Show(s);
  • 5
    +1 Да, это так. Я все еще не могу понять, что здесь происходит на самом деле !!
  • 0
    Обратите внимание, что из моего тестирования кажется, что параметр Result всегда первый в списке, если у вас несколько параметров, а не последний, как можно предположить.
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню