48 votes

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

J'ai, plus d'une fois, conseillé aux gens d'utiliser une valeur de retour de type WideString à des fins d'interopérabilité.

L'idée est qu'un WideString est la même chose qu'un BSTR . Parce qu'un BSTR est allouée sur le tas partagé de la COM, il n'y a aucun problème à allouer dans un module et à désallouer dans un autre module. En effet, toutes les parties ont convenu d'utiliser le même tas, le tas COM.

Cependant, il semble que WideString ne peut pas être utilisé comme valeur de retour de fonction pour l'interopérabilité.

Considérons 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"%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);

L'appel à TestWideString échoue avec cette erreur :

Exception non gérée à 0x772015de dans BSTRtest.exe : 0xC0000005 : Violation d'accès à la lecture de l'emplacement 0x00000000.

De même, si nous essayons de l'appeler depuis C# avec p/invoke, nous avons un échec :

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

L'erreur est :

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

Informations complémentaires : Un composant externe a déclenché une exception.

Appel à TestWideString via p/invoke fonctionne comme prévu.

Il faut donc utiliser la méthode de pass-by-reference avec les paramètres WideString et les faire correspondre à BSTR semble fonctionner parfaitement bien. Mais pas pour les valeurs de retour des fonctions. J'ai testé ceci sur Delphi 5, 2010 et XE2 et 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 comme suit :

CMP     \[EAX\],EDX

Maintenant, EAX es $00000000 et naturellement il y a une violation d'accès.

Quelqu'un peut-il l'expliquer ? Est-ce que je fais quelque chose de mal ? Suis-je déraisonnable en attendant WideString les valeurs de la fonction pour être viable BSTR s ? Ou s'agit-il simplement d'un défaut de Delphi ?

25voto

Sean B. Durkin Points 7723

Dans les fonctions Delphi classiques, le retour de la fonction est en fait un paramètre passé par référence, même si, syntaxiquement, il ressemble à un paramètre "out". Vous pouvez tester cela comme suit (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 échoue en raison d'un décalage entre la compréhension de Delphi et de C++ de la convention d'appel par rapport au mécanisme de passage des résultats de la fonction. En C++, le retour d'une fonction se comporte comme la syntaxe le suggère : une fonction out paramètre. Mais pour Delphi, c'est un var paramètre.

Pour réparer, essayez ceci :

function TestWideString: WideString; stdcall;
begin
  Pointer(Result) := nil;
  Result := 'TestWideString';
end;

19voto

kobik Points 10922

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

Renvoyer des chaînes de caractères et des références d'interface à partir de fonctions DLL

En el stdcall la convention d'appel, le résultat de la fonction est transmis par l'intermédiaire du processeur EAX registre. Cependant, Visual C++ et Delphi génèrent un code binaire différent pour ces routines.

Le code Delphi reste le même :

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

Code C# :

// declaration
[DllImport(@"Test.dll")]        
static extern void  TestWideString([MarshalAs(UnmanagedType.BStr)] out string Result);
...
string s;
TestWideString(out s); 
MessageBox.Show(s);

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X