Почему WideString не может использоваться как возвращаемое значение функции для взаимодействия? - PullRequest
44 голосов
/ 19 февраля 2012

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

Идея состоит в том, что WideString - это то же самое, что и BSTR. Поскольку 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.

и следующий код 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 через p / invoke работает как положено.

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

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

CMP     [EAX],EDX

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

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

Ответы [ 2 ]

22 голосов
/ 19 февраля 2012

В обычных функциях 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;
17 голосов
/ 20 февраля 2012

В 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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...