怎么将一个函数的返回值用作另外一个函数的参数
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了怎么将一个函数的返回值用作另外一个函数的参数相关的知识,希望对你有一定的参考价值。
先用一个变量把那个函数的返回值保存下来,然后再把该变量作为实参传递给两一个函数即可。就像这样:#include <stdio.h>
int f()
return 1;
void a(int i)
printf("i = %d\n",i);
int main()
int x;
x = f();
a(x);
return 0;
参考技术A 如果第二个函数的参数是引用,则需要临时变量保存,否则可以直接使用:
int func1();
void func2(int &);
void func3(int);
如果是2:
int i = func1();
func2(i);
如果是3,可以直接:
func3(func1());
为啥不能将 WideString 用作互操作的函数返回值?
【中文标题】为啥不能将 WideString 用作互操作的函数返回值?【英文标题】:Why can a WideString not be used as a function return value for interop?为什么不能将 WideString 用作互操作的函数返回值? 【发布时间】:2012-03-10 02:36:51 【问题描述】:我曾不止一次建议人们将WideString
类型的返回值用于互操作目的。
这个想法是WideString
与BSTR
相同。因为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
的调用,其第一行内容如下:
现在,EAX
是 $00000000
,自然存在访问冲突。
谁能解释一下?难道我做错了什么?我期望WideString
函数值是可行的BSTR
s 是不合理的吗?还是只是 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 你说得对,那部分是一个糟糕的论点,但我坚持我的结论。 WideString
和 BSTR
都具有指针的大小,但这并不意味着它们总是以相同的方式传递。它们足够接近,因此它们以相同的方式传递给过程和函数参数,但是如果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 级别首先传递。以上是关于怎么将一个函数的返回值用作另外一个函数的参数的主要内容,如果未能解决你的问题,请参考以下文章