我不止一次建议人们使用WideString
类型的返回值进行互操作。
我们的想法是WideString
与BSTR
相同。因为在共享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缺陷?
答案 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);