为啥不能将 WideString 用作互操作的函数返回值?

     2023-02-15     287

关键词:

【中文标题】为啥不能将 WideString 用作互操作的函数返回值?【英文标题】:Why can a WideString not be used as a function return value for interop?为什么不能将 WideString 用作互操作的函数返回值? 【发布时间】:2012-03-10 02:36:51 【问题描述】:

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

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?

这个想法是WideStringBSTR 相同。因为BSTR 是在共享COM 堆上分配的,所以在一个模块中分配并在另一个模块中释放是没有问题的。这是因为各方都同意使用同一个堆,即 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。

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

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

错误是:

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

附加信息:外部组件已抛出异常。

通过 p/invoke 调用 TestWideString 可以正常工作。

因此,对 WideString 参数使用传递引用并将它们映射到 BSTR 似乎工作得很好。但不适用于函数返回值。我在 Delphi 5、2010 和 XE2 上对此进行了测试,并在所有版本上观察到相同的行为。

执行进入 Delphi 并几乎立即失败。对Result 的赋值变成了对System._WStrAsg 的调用,其第一行内容如下:

CMP [EAX],EDX

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

谁能解释一下?难道我做错了什么?我期望WideString 函数值是可行的BSTRs 是不合理的吗?还是只是 Delphi 的缺陷?

【问题讨论】:

大卫,也许还添加C++C# 标签? @kobik 我相信这确实是一个关于Delphi如何实现返回值的问题。我认为 Delphi 是一个奇怪的问题。 @J... 我从未见过不返回 HRESULT 的 COM 方法。我不是在谈论在 COM 中使用 BSTR。我说它是一种在不同模块之间共享堆的便捷方式。 @J... 分配给 WideString,它确实调用了 SysAllocString。或者它可能是 SysReallocString,但这在道德上是等价的。 @DavidHeffernan,所以procedure TestWideStringOutParam(var str: WideString); stdcall(注意var)不起作用?还是我仍然弄错了? (因为它确实有效) 【参考方案1】:

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

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;

【讨论】:

这听起来有道理,但Pointer(result) := nil 本身就引发了 AV。 对于函数,Delphi 将指向结果的指针存储在 EAX 中。这几乎解释了它。从 Delphi 的角度来看,您不能将“无变量”作为 var 参数传递。 Pointer(Result) := nil 会抛出 AV,因为返回类型实际上是指向 WideString 的指针(隐藏参数)。通过将其分配为零,指针(从未被 C++ 处理过)被推迟:mov eax,[ebp+$08]; xor edx,edx; mov [eax],edx。换句话说:WideString 返回值总是作为隐藏参数传递。 Delphi 不允许你改变这种行为。 然而,有可能通过返回PWideChar: (未经测试)function TestWideString: PWideChar; stdcall; var RealResult: WideString absolute Result; begin Initialize(RealResult); RealResult := 'TestWideString'; end;来欺骗Delphi @DavidHeffernan 你说得对,那部分是一个糟糕的论点,但我坚持我的结论。 WideStringBSTR 都具有指针的大小,但这并不意味着它们总是以相同的方式传递。它们足够接近,因此它们以相同的方式传递给过程和函数参数,但是如果stdcall 调用约定通过隐藏的out 参数返回结构,并且WideString 被视为结构,那么它赢了'返回的方式与 BSTR (PWideChar) 不同。【参考方案2】:

在 C#/C++ 中,您需要将 Result 定义为 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);

【讨论】:

+1 是的。不过,我仍然无法理解这里到底发生了什么! 请注意,从我的测试来看,如果您有多个参数,Result 参数似乎总是列表中的第一个,而不是可能假设的最后一个。 @JamieKitson 我不明白那个评论。如果您的意思是用于返回函数返回值的 Delphi 隐式 var 参数,则额外的参数将在其他参数之后传递。它在这里清楚地记录:docwiki.embarcadero.com/RADStudio/en/… @DavidHeffernan 也许 Jamie 观察到的是参数以与stdcall 相反的顺序传递,正如您的链接也指出的那样(最后一个)。所以“result”参数是声明/Delphi 端的最后一个参数,在存根/asm 级别首先传递。

为啥成员函数不能用作模板参数?

】为啥成员函数不能用作模板参数?【英文标题】:Whymemberfunctionscan\'tbeusedastemplatearguments?为什么成员函数不能用作模板参数?【发布时间】:2015-09-0423:26:33【问题描述】:为什么成员函数不能用作模板参数?例如,我想做这样... 查看详情

如何在vb6中注册一个不能用作com互操作的.netdll?

...请确保它是有效的DLL或OCX文件,然后再试一次。”答案你不能直接做你想要的。VB6可以“注册”的唯一东西是定义为COMDLL。如果您无法修改C#DLL 查看详情

为啥表达式'I_VALUE'不能用作函数中的赋值目标

】为啥表达式\\\'I_VALUE\\\'不能用作函数中的赋值目标【英文标题】:whyexpression\'I_VALUE\'cannotbeusedasanassignmenttargetinFunction为什么表达式\'I_VALUE\'不能用作函数中的赋值目标【发布时间】:2016-07-2105:35:39【问题描述】:我在oracle中创... 查看详情

为啥我们不能使用 CloudFormation 中的参数将 AllowedValues 用作字符串?

】为啥我们不能使用CloudFormation中的参数将AllowedValues用作字符串?【英文标题】:WhycantweuseAllowedValuesforkeypairasStringusingtheParameterinCloudFormation?为什么我们不能使用CloudFormation中的参数将AllowedValues用作字符串?【发布时间】:2021-06... 查看详情

为啥 ref 结构不能用作类型参数?

】为啥ref结构不能用作类型参数?【英文标题】:Whyrefstructscannotbeusedastypearguments?为什么ref结构不能用作类型参数?【发布时间】:2018-11-2500:53:16【问题描述】:C#7.2introducedrefstructs。但是,给定一个像这样的refstruct:publicrefstruct... 查看详情

为啥这个互操作会使 .NET 运行时崩溃?

】为啥这个互操作会使.NET运行时崩溃?【英文标题】:Whydoesthisinteropcrashthe.NETruntime?为什么这个互操作会使.NET运行时崩溃?【发布时间】:2014-07-2517:48:03【问题描述】:我正在尝试遍历一些文件并获取它们的shell图标;为此,我... 查看详情

为啥我可以将 Web 服务用作 Java 应用程序,但不能使用 struts2

】为啥我可以将Web服务用作Java应用程序,但不能使用struts2【英文标题】:WhyamIabletousewebserviceasajavaapplicationbutnotpossiblewithstruts2为什么我可以将Web服务用作Java应用程序,但不能使用struts2【发布时间】:2013-11-2411:14:29【问题描述】... 查看详情

为啥当我将熊猫数据框用作具有多处理功能的函数的输入时它不会改变

】为啥当我将熊猫数据框用作具有多处理功能的函数的输入时它不会改变【英文标题】:Whypandasdataframedoesn\'tchangewheniuseditasainputofafunctionwithmultiprocessing为什么当我将熊猫数据框用作具有多处理功能的函数的输入时它不会改变【发... 查看详情

为啥我不能操作我的 xib 文件中的对象?

】为啥我不能操作我的xib文件中的对象?【英文标题】:WhycanInotmanipulatetheobjectsinmyxibfile?为什么我不能操作我的xib文件中的对象?【发布时间】:2013-11-0622:14:46【问题描述】:我有一个文件,View.xib,其中包含三个UIViews,每个都... 查看详情

Excel VBA 函数不能将参数用作范围

】ExcelVBA函数不能将参数用作范围【英文标题】:ExcelVBAFunctionwillnotworkwithanargumentasaRange【发布时间】:2013-09-1700:05:57【问题描述】:我编写了一个相当简单的VBA函数,我想在我的工作表和链接到宏工作表的其他函数中使用它。我... 查看详情

将 C# 与 C 函数互操作的性能损失 [关闭]

】将C#与C函数互操作的性能损失[关闭]【英文标题】:Performancepenaltiesofinterop\'ingC#withCfunctions[closed]【发布时间】:2012-09-2914:02:18【问题描述】:除非对我的代码进行实际性能测试(我正处于设计阶段),将C代码连接到C#的普遍共... 查看详情

将 C# 函数指针传递到 C++/CLI 互操作 dll

】将C#函数指针传递到C++/CLI互操作dll【英文标题】:PassingC#functionpointersintoC++/CLIinteropdll【发布时间】:2011-11-2318:32:55【问题描述】:我正在尝试将函数指针从C#传递到C++/CLI,并得到一个Windows编译器错误,指出该语言(C#)不支持Man... 查看详情

为啥 $ 符号不能用作标识符的一部分? [复制]

】为啥$符号不能用作标识符的一部分?[复制]【英文标题】:Whythe$symbolcannotbeusedasapartofanidentifier?[duplicate]为什么$符号不能用作标识符的一部分?[复制]【发布时间】:2017-09-0808:27:33【问题描述】:我是C++的新手。我在一本C++的... 查看详情

为啥不能将继承的受保护构造函数公开?

】为啥不能将继承的受保护构造函数公开?【英文标题】:Whycan\'taninheritedprotectedconstructorbemadepublic?为什么不能将继承的受保护构造函数公开?【发布时间】:2020-09-1719:05:02【问题描述】:考虑:classAprotected:A(int)voidf(int)public:A();... 查看详情

为啥我在设置中收到“'Bars' 不能用作实体类型 'Foo' 的属性”?

】为啥我在设置中收到“\\\'Bars\\\'不能用作实体类型\\\'Foo\\\'的属性”?【英文标题】:WhyIamIgetting"\'Bars\'cannotbeusedasapropertyonentitytype\'Foo\'"inmysetup?为什么我在设置中收到“\'Bars\'不能用作实体类型\'Foo\'的属性”?【发布... 查看详情

为啥我不能将指令更改为以前的值(函数调用)OllyDbg

】为啥我不能将指令更改为以前的值(函数调用)OllyDbg【英文标题】:WhyIcan\'tchangeinstructiontoit\'spreviousvalues(functioncall)OllyDbg为什么我不能将指令更改为以前的值(函数调用)OllyDbg【发布时间】:2020-09-1023:48:01【问题描述】:我... 查看详情

为啥我不能将字符串传递给函数 GetDriveTypesA()?

】为啥我不能将字符串传递给函数GetDriveTypesA()?【英文标题】:Whycan\'tIpassastringintothefunctionGetDriveTypesA()?为什么我不能将字符串传递给函数GetDriveTypesA()?【发布时间】:2019-03-1322:25:13【问题描述】:因此,该计划的目的是找出... 查看详情

为啥我不能将 lambda 传递给这个需要 std::function 的函数? [复制]

】为啥我不能将lambda传递给这个需要std::function的函数?[复制]【英文标题】:Whycan\'tIpassalambdatothisfunctionwhichtakesastd::function?[duplicate]为什么我不能将lambda传递给这个需要std::function的函数?[复制]【发布时间】:2015-01-0823:01:14【问... 查看详情