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

Delphi 为什么WideString不能用作互操作的函数返回值?,delphi,Delphi,我不止一次建议人们出于互操作目的使用WideString类型的返回值 其思想是,WideString与。因为BSTR是在共享COM堆上分配的,所以在一个模块中分配并在另一个模块中取消分配是没有问题的。这是因为各方都同意使用相同的堆,即COM堆 但是,WideString似乎不能用作互操作的函数返回值 考虑下面的delphidll library-WideStringTest; 使用 ActiveX; 函数TestWideString:WideString;stdcall; 开始 结果:

我不止一次建议人们出于互操作目的使用
WideString
类型的返回值

其思想是,
WideString
与。因为
BSTR
是在共享COM堆上分配的,所以在一个模块中分配并在另一个模块中取消分配是没有问题的。这是因为各方都同意使用相同的堆,即COM堆

但是,
WideString
似乎不能用作互操作的函数返回值

考虑下面的delphidll

library-WideStringTest;
使用
ActiveX;
函数TestWideString:WideString;stdcall;
开始
结果:='TestWideString';
结束;
功能测试bstr:TBstr;stdcall;
开始
结果:=SysAllocString('TestBSTR');
结束;
程序TestWideString-GoutParam(输出str:WideString);stdcall;
开始
str:=“TestWideStringOutParam”;
结束;
出口
TestWideString、TestBSTR、TestWideStringOutParam;
开始
结束。

和下面的C++代码:

typedef BSTR(u stdcall*Func)();
类型定义无效(uu stdcall*OutParam)(BSTR和pstr);
HMODULE lib=LoadLibrary(DLLNAME);
Func TestWideString=(Func)GetProcAddress(lib,“TestWideString”);
Func TestBSTR=(Func)GetProcAddress(lib,“TestBSTR”);
OutParam TestWideTringOutParam=(OutParam)GetProcAddress(lib,
“测试图”);
BSTR str=TestBSTR();
wprintf(L“%s\n”,str);
SysFreeString(str);
str=NULL;
测试曲线(str);
wprintf(L“%s\n”,str);
SysFreeString(str);
str=NULL;
str=TestWideString()//这里失败了
wprintf(L“%s\n”,str);
SysFreeString(str);
调用
TestWideString
失败,出现以下错误:

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

类似地,如果我们尝试使用p/invoke从C#调用此函数,则会失败:

[DllImport(@“path\to\my\dll”)]
[返回:Marshallas(UnmanagedType.BStr)]
静态外部字符串TestWideString();
错误是:

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

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

通过p/invoke调用
TestWideString
,工作正常

因此,对宽字符串参数使用pass-by-reference,并将它们映射到
BSTR
似乎可以很好地工作。但不适用于函数返回值。我已经在Delphi5、2010和XE2上测试了这一点,并在所有版本上观察到了相同的行为

执行进入Delphi,几乎立即失败。对
Result
的赋值将变成对
系统的调用。\u WStrAsg
,其第一行内容如下:

CMP [EAX],EDX CMP[EAX],EDX 现在,
EAX
$00000000
,自然存在访问冲突


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

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

演示调用TestParameterPassingMechanismOfFunctions()

您的代码失败是因为Delphi和C++对函数结果传递机制的调用约定的理解不匹配。在C++中,函数返回行为类似于语法建议:<代码>输出>代码>参数。但对于Delphi来说,它是一个
var
参数

要修复此问题,请尝试以下操作:

function TestWideString: WideString; stdcall;
begin
  Pointer(Result) := nil;
  Result := 'TestWideString';
end;
在C#/C++中,需要将结果定义为
out
参数,以保持
stdcall
调用约定的二进制代码兼容性:

stdcall
调用约定中,函数的结果通过CPU的
EAX
寄存器传递。但是,Visual C++和Delphi为这些例程生成了不同的二进制代码。p> Delphi代码保持不变:

function TestWideString: WideString; stdcall;
begin
  Result := 'TestWideString';
end;
C#代码:


这听起来似乎有道理,但是
指针(result):=nil
本身会引发一个AV。对于函数,Delphi将指向结果的指针存储在EAX中。这很好地解释了这一点。从Delphi的角度来看,您不能将“no variable”作为var参数传入。
指针(结果):=nil
抛出一个AV,因为返回类型实际上是指向宽字符串(隐藏参数)的指针。通过将其赋值为nil,指针(C++从未处理过)被延迟:
moveax,[ebp+$08];xor-edx,edx;mov[eax],edx
。换句话说:WideString返回值始终作为隐藏参数传递。Delphi不允许您更改该行为。但是,可以通过返回
PWideChar
:(未测试)
函数TestWideString:PWideChar;stdcall;var RealResult:宽字符串绝对结果;开始初始化(realsult);RealResult:=“TestWideString”;结束@DavidHeffernan你是对的,那部分是一个糟糕的论点,但我坚持我的结论。
WideString
BSTR
都有指针大小,但这并不意味着它们总是以相同的方式传递。它们非常接近,因此传递过程和函数参数的方式与传递过程和函数参数的方式相同,但是如果调用约定通过隐藏的
out
参数返回结构,并且
WideString
被视为结构,那么它将不会以与
BSTR
相同的方式返回(
PWideChar
)是的,就是这样。但我仍然无法了解这里到底发生了什么!!注意,从我的测试来看,Result参数似乎总是列表i中的第一个
function TestWideString: WideString; stdcall;
begin
  Result := 'TestWideString';
end;
// declaration
[DllImport(@"Test.dll")]        
static extern void  TestWideString([MarshalAs(UnmanagedType.BStr)] out string Result);
...
string s;
TestWideString(out s); 
MessageBox.Show(s);