Dlaczego WideString nie może być używany jako wartość zwracana funkcji interop?

Wielokrotnie doradzałem ludziom, aby używali wartości zwracanej typu WideString do celów interop.

Chodzi o to, że a WideString jest tym samym, co BSTR. Ponieważ BSTR jest przydziela się na współdzielonej stercie COM wtedy nie ma problemu przydziela się w jednym module i dealokuje w innym module. Dzieje się tak dlatego, że wszystkie strony zgodziły się używać tej samej sterty, sterty COM.

Wydaje się jednak, że WideString nie może być użyta jako wartość zwracana funkcji interop.

Rozważ następującą bibliotekę 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.

Oraz następujący kod 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);
Nie jest to jednak możliwe, ponieważ nie jest to możliwe.]}

Nieobsługiwany wyjątek w 0x772015de w Bsttest.exe: 0xc0000005: lokalizacja odczytu naruszenia dostępu 0x0000000.

Podobnie, jeśli spróbujemy wywołać to z C # za pomocą P / invoke, mamy błąd:

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

Błąd to:

Nieobsługiwany wyjątek typu ' System.Runtime.InteropServices.SEHException " wystąpił w ConsoleApplication10.exe

Dodatkowe informacje: zewnętrzny komponent wyrzucił wyjątek.

Wywołanie TestWideString przez P / invoke działa jako oczekiwane.

Więc, użycie pass-by-reference z parametrami WideString i mapowanie ich na BSTR wydaje się działać doskonale. Ale nie dla wartości zwracanych przez funkcję. Testowałem to na Delphi 5, 2010 i XE2 i obserwowałem to samo zachowanie we wszystkich wersjach.

Egzekucja wchodzi do Delphi i kończy się niemal natychmiast. Przypisanie do Result zamienia się w wywołanie do System._WStrAsg, którego pierwsza linia brzmi:

CMP     [EAX],EDX

Teraz, EAX jest $00000000 i naturalnie jest dostęp naruszenie.

Czy ktoś może to wyjaśnić? Czy robię coś nie tak? Czy jestem nierozsądny w oczekiwaniu, że wartości funkcji WideString będą żywotne BSTRs? Czy to tylko wada Delphi?
 41
Author: Community, 2012-02-19

2 answers

W zwykłych funkcjach Delphi, zwracanie funkcji jest w rzeczywistości parametrem przekazywanym przez referencję, mimo że składniowo wygląda i wygląda jak parametr 'out'. Możesz to przetestować w ten sposób (może to być zależne od wersji):

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;

Aby zademonstrować wywołanie TestParameterPassingMechanismOfFunctions()

Twój kod zawodzi z powodu niedopasowania między Delphi A C++rozumieniem konwencji wywołania w odniesieniu do mechanizmu przekazywania wyników funkcji. W C++ funkcja return działa jak składnia sugeruje: parametr out. Ale dla Delphi jest to parametr var.

Aby naprawić, spróbuj tego:

function TestWideString: WideString; stdcall;
begin
  Pointer(Result) := nil;
  Result := 'TestWideString';
end;
 21
Author: Sean B. Durkin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-10 10:39:20

W C#/C++ musisz zdefiniować wynik jako parametr out, aby zachować zgodność kodu binarnego z stdcall wywołując konwencje:

Zwracanie łańcuchów i odniesień interfejsu z funkcji DLL

W konwencji wywołania stdcall wynik funkcji jest przekazywany przez rejestr EAX procesora. Jednak Visual C++ i Delphi generują inny kod binarny dla tych procedur.

Kod Delphi pozostaje taki sam:

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

C# kod:

// declaration
[DllImport(@"Test.dll")]        
static extern void  TestWideString([MarshalAs(UnmanagedType.BStr)] out string Result);
...
string s;
TestWideString(out s); 
MessageBox.Show(s);
 17
Author: kobik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-24 23:08:25