Pourquoi un WideString ne peut-il pas être utilisé comme valeur de retour de fonction pour interop?

j'ai, en plus d'une occasion, conseillé aux gens d'utiliser une valeur de retour de type WideString pour l'interopérabilité des fins.

l'idée est qu'un WideString est la même chose qu'un BSTR . Parce qu'un BSTR est alloué sur le tas de COM partagée alors il n'y a pas de problème pour allouer dans un module et désallouer dans un autre module. C'est parce que toutes les parties ont convenu d'utiliser le même tas, la COM tas.

cependant, il semble que WideString ne puisse pas être utilisé comme valeur de retour de fonction pour Interop.

tenir compte de la DLL Delphi suivante.

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.

et le code C++ suivant:

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"%sn", str);
SysFreeString(str);
str = NULL;

TestWideStringOutParam(str);
wprintf(L"%sn", str);
SysFreeString(str);
str = NULL;

str = TestWideString();//fails here
wprintf(L"%sn", str);
SysFreeString(str);

l'appel à TestWideString échoue avec cette erreur:

exception non Gérée à 0x772015de dans BSTRtest.exe: 0xC0000005: lieu de lecture de la violation de L'accès 0x00000000.

de même, si nous essayons d'appeler cela de C# avec P/invoquer, nous avoir un échec:

[DllImport(@"pathtomydll")]
[return: MarshalAs(UnmanagedType.BStr)]
static extern string TestWideString();

l'erreur est:

Une exception non gérée du type 'System.Runtime.InteropServices.La SEHException s'est produite dans la ConsoleApplication10.exe

information supplémentaire: le volet externe a fait exception.

Appel TestWideString par l'intermédiaire de p/invoke fonctionne comme prévu.

Ainsi, l'utilisation par référence avec Élargir les paramètres et les cartographier sur BSTR semble fonctionner parfaitement. Mais pas pour les valeurs de retour de fonction. J'ai testé cela sur Delphi 5, 2010 et XE2 et j'observe le même comportement sur toutes les versions.

L'exécution entre dans le Delphi et échoue presque immédiatement. L'affectation à Result se transforme en un appel à System._WStrAsg , dont la première ligne se lit:

CMP     [EAX],EDX

maintenant, EAX est "1519150920"000000 et naturellement, il y a une violation d'accès.

quelqu'un Peut-il expliquer cela? Suis-je en train de faire quelque chose de mal? Suis-je déraisonnable en m'attendant à ce que les valeurs de fonction WideString soient viables BSTR s? Ou est-ce juste un défaut de Delphi?

41
demandé sur Community 2012-02-19 17:23:30

2 réponses

dans les fonctions Delphi régulières, le retour de la fonction est en fait un paramètre passé par référence, même si syntaxiquement il ressemble et se sent comme un paramètre 'out'. Vous pouvez le tester comme ceci (cela peut dépendre de la version):

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;

pour démontrer l'appel TestParameterPassingMechanismOfFunctions()

votre code est défaillant en raison d'un décalage entre la compréhension de Delphi et C++de la convention d'appel en relation avec le mécanisme de passage pour résultats de la fonction. Dans C++ Un Retour de fonction agit comme la syntaxe suggère: un paramètre out . Mais pour Delphi, c'est un paramètre var .

pour corriger, essayez ceci:

function TestWideString: WideString; stdcall;
begin
  Pointer(Result) := nil;
  Result := 'TestWideString';
end;
21
répondu Sean B. Durkin 2014-12-10 10:39:20

en C# / C++ vous devrez définir le résultat comme out paramètre, afin de maintenir la compatibilité de code binaire de stdcall conventions d'appel:

renvoi de chaînes et de références D'Interface des fonctions DLL

dans la convention d'appel stdcall , le résultat de la fonction est transmis via le registre EAX du CPU. Cependant, Visual C++ et Delphi génèrent des code binaire pour ces routines.

le code Delphi reste le même:

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

C # code:

// declaration
[DllImport(@"Test.dll")]        
static extern void  TestWideString([MarshalAs(UnmanagedType.BStr)] out string Result);
...
string s;
TestWideString(out s); 
MessageBox.Show(s);
17
répondu kobik 2012-02-24 23:08:25