Why can a WideString not be used as a function return value for interop?

delphi

Solution

In regular Delphi functions, the function return is actually a parameter passed by reference, even though syntactically it looks and feels like an 'out' parameter. You can test this out like so (this may be version dependent):

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;

To demonstrate call `TestParameterPassingMechanismOfFunctions()`

Your code is failing because of a mismatch between Delphi and C++'s understanding of the calling convention in relation to the passing mechanism for function results. In C++ a function return acts like the syntax suggests: an `out` parameter. But for Delphi it is a `var` parameter.

To fix, try this:

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

Problem

I have, on more than one occasion, advised people to use a return value of type `WideString` for interop purposes. - Accessing Delphi DLL throwing ocasional exception - ASP.NET web app calling Delphi DLL on IIS webserver, locks up when returning PChar string - Why can Delphi DLLs use WideString without using ShareMem? The idea is that a `WideString` is the same as a `BSTR`. Because a `BSTR` is allocated on the shared COM heap then it is no problem to allocate in one module and deallocate in a different module. This is because all parties have agreed to use the same heap, the COM heap. However, it seems that `WideString` cannot be used as a function return value for interop. Consider the following 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. ``` and the following C++ code: ``` 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); ``` The call to `TestWideString` fails with this error: Unhandled exception at 0x772015de in BSTRtest.exe: 0xC0000005: Access violation reading location 0x00000000. Similarly, if we try to call this from C# with p/invoke, we have a failure: ``` [DllImport(@"path\to\my\dll")] [return: MarshalAs(UnmanagedType.BStr)] static extern string TestWideString(); ``` The error is: An unhandled exception of type 'System.Runtime.InteropServices.SEHException' occurred in ConsoleApplication10.exe Additional information: External component has thrown an exception. Calling `TestWideString` via p/invoke works as expected. So, use pass-by-reference with WideString parameters and mapping them onto `BSTR` appears to work perfectly well. But not for function return values. I have tested this on Delphi 5, 2010 and XE2 and observe the same behaviour on all versions. Execution enters the Delphi and fails almost immediately. The assignment to `Result` turns into a call to `System._WStrAsg`, the first line of which reads: ``` CMP [EAX],EDX ``` Now, `EAX` is `$00000000` and naturally there is an access violation. Can anyone explain this? Am I doing something wrong? Am I unreasonable in expecting `WideString` function values to be viable `BSTR`s? Or is it just a Delphi defect?

Original source

Related problems