为什么WideString不能用作互操作的函数返回值?

时间:2012-02-19 13:23:31

标签: delphi

我不止一次建议人们使用WideString类型的返回值进行互操作。

我们的想法是WideStringBSTR相同。因为在共享COM堆上分配了BSTR,所以在一个模块中分配并在另一个模块中解除分配是没有问题的。这是因为所有各方都同意使用相同的堆,即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的调用因此错误而失败:

  

BSTRtest.exe中0x772015de处的未处理异常:0xC0000005:访问冲突读取位置0x00000000。

同样,如果我们尝试使用p / invoke从C#调用它,我们就会失败:

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

错误是:

  

ConsoleApplication10.exe中发生未处理的“System.Runtime.InteropServices.SEHException”类型异常

     

其他信息:外部组件引发了异常。

通过p / invoke调用TestWideString按预期工作。

因此,使用带有WideString参数的pass-by-reference并将它们映射到BSTR似乎非常有效。但不是函数返回值。我已经在Delphi 5,2010和XE2上对此进行了测试,并在所有版本上观察到相同的行为。

执行进入Delphi并几乎立即失败。对Result的分配变为对System._WStrAsg的调用,其第一行为:

CMP     [EAX],EDX

现在,EAX$00000000,自然会出现访问冲突。

任何人都能解释一下吗?难道我做错了什么?我期望WideString函数值可行BSTR是不合理的吗?或者它只是一个Delphi缺陷?

2 个答案:

答案 0 :(得分:22)

在常规Delphi函数中,函数return实际上是一个通过引用传递的参数,即使在语法上它看起来和感觉就像一个'out'参数。您可以这样测试(这可能取决于版本):

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 ++中,函数返回的行为类似于语法建议:out参数。但对于Delphi,它是var参数。

要修复,请尝试以下方法:

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

答案 1 :(得分:17)

在C#/ C ++中,您需要将结果定义为out参数,以保持stdcall调用约定的二进制代码兼容性:

Returning Strings and Interface References From DLL Functions

  

stdcall调用约定中,函数的结果通过CPU的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);