Почему WideString не может использоваться как возвращаемое значение функции для взаимодействия?
Я неоднократно советовал людям использовать возвращаемое значение типа WideString
для взаимодействия.
- Доступ к Delphi DLL вызывает исключение иногда
- Веб-приложение ASP.NET, вызывающее библиотеку Delphi DLL на веб-сервере IIS, блокируется при возврате строки PChar
- Почему библиотеки Delphi DLL могут использовать WideString без использования ShareMem?
Идея в том, что 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);