使用扩展参数调用 Delphi 函数时出现 C++ 错误

Posted

技术标签:

【中文标题】使用扩展参数调用 Delphi 函数时出现 C++ 错误【英文标题】:C++ error when calling a Delphi function with Extended parameters 【发布时间】:2016-08-30 15:46:59 【问题描述】:

在一个新的 Win32 项目中,我有以下 Delphi 函数:

procedure SetValue(value1, value2 : Extended);
begin
end;

在同一个项目中,但来自 C++ 单元,我调用了这个函数:

SetValue(10, 40);

当我使用 BCC32C (CLang) 编译时检查 value1 时,我得到 1.68132090507504896E-4932,这是不正确的。

用 BCC32(经典)编译,我得到 10。

在这两种情况下,第二个参数都是 40。

Extended 值和参数堆栈加载似乎有问题。

我使用 RAD Studio 10.1 Berlin。

我该如何解决这个问题?

更新

我没有包含声明,因为 hpp 是在编译时自动创建的。无论如何,声明是:

extern DELPHI_PACKAGE void __fastcall SetValue(System::Extended value1, System::Extended value2);

复制项目:

1-在 Rad Studio 中创建 C++ 项目

2-用上面的SetValue函数添加一个Delphi单元

3-从C++单元中,用#include添加hpp头并调用SetValue

就是这样。

我需要使用扩展类型。我正在使用外部 Delphi 库,因此无法更改类型。上面的代码是对问题的简化。实际上,问题在于调用该库的函数,该函数在参数中使用了 Extended。 Extended 在 Delphi 中是本机类型,但在 C++ 中它映射为 long double、10 字节(对于 Win32)。

【问题讨论】:

从外部应用程序调用该函数时不应该将其标记为cdecl吗? 如何在 C++ 代码中声明函数? 停止使用Extended。这是个坏主意。使用DoubleRealDouble 的别名)。您只需要展示调用约定和您的 C++ 代码,我们就有机会。为什么我们不能有minimal reproducible example?我在我见过的所有问题中只问了一半。当然,消息正在传递....... @JoachimPileborg 当为 C++ 编译 Delphi 单元时,Delphi 编译器会自动生成 C++ .hpp 头文件。希望 OP 使用该标头而不是手动声明该函数。 @GuidoG 不,调用约定不需要是cdecl。并且该函数没有被外部应用程序调用。 Delphi 代码可以直接在 C++Builder 项目中使用。 【参考方案1】:

这似乎是 BCC32C 编译器中的错误。我猜它没有正确扩展来处理Extended,因为 Delphi 需要它。

如果你查看 CPU 窗口,那么 BCC32 编译器会生成:

File5.cpp.14: SetVal(10.0L, 40.0L);
00401B8F 6802400000       push $00004002
00401B94 68000000A0       push $a0000000
00401B99 6A00             push $00
00401B9B 6804400000       push $00004004
00401BA0 68000000A0       push $a0000000
00401BA5 6A00             push $00
00401BA7 E80C000000       call Unit12::SetVal(long double,long double)

这是正确的。它首先推送10,然后以Extended 格式推送40。注意每个Extended在栈上占用12个字节。

但现在看看 BCC32C 编译器的输出:

File5.cpp.14: SetVal(10.0L, 40.0L);
00401B5D 89E0             mov eax,esp
00401B5F D90554F14E00     fld dword ptr [$004ef154]
00401B65 DB38             fstp tbyte ptr [eax]
00401B67 D90558F14E00     fld dword ptr [$004ef158]
00401B6D DB780A           fstp tbyte ptr [eax+$0a]
00401B70 E81F000000       call Unit12::SetVal(long double,long double)
00401B75 83EC18           sub esp,$18

它首先读取 32 个单精度浮点数 40 并将其存储为 Extended [ESP]。到现在为止还挺好。但随后它会读取下一个 32 位单精度浮点数 10(仍然可以),然后 将其存储在 [ESP+$0A],这显然是错误的(对于 Delphi)!它应该存储在[ESP+$0C]!这就是为什么 first 值是错误的,Pascal 函数在 [ESP+$0C] 处读取,但 BCC32C 将其存储在 [ESP+$0A] 处。

所以这似乎是一个错误。举报为https://quality.embarcadero.com/browse/RSP-15737

请注意,这是 BCC32C 推送并期望此类值的正常方式。在同一模块中的 C++ 函数中,即也使用 BCC32C 编译,这很好用:

void __fastcall Bla(long double a, long double b)

    printf("%Lf %Lf\n", a, b);

但是 Delphi 期望一个 10 字节的 Extended 占用堆栈上的 12 个字节,而不是像 BCC32C 那样的 10 个字节。

很奇怪,如果要调用的函数不是 Delphi __fastcall 函数,而是普通的 C++ (cdecl) 函数,BCC32C 编译器将存储 Extendeds (long doubles ) 分别在[ESP+$0C][ESP] 中。

解决方法

正如 David Heffernan 评论的那样,您可以在记录中传递多个扩展。或者,您可以将它们作为var 参数传递。这两种情况,都不是调用SetVal(10.0, 40.0);那么简单。

【讨论】:

@David:包装确实会得到正确的对齐。 @David:很遗憾,包装不是一种选择。这个库真的很大,有数百个类,其中许多是派生类、它们之间的链接、接口等。构建一个包装器确实需要付出巨大的努力。我会考虑购买源并用另一种类型替换扩展类型。感谢您的帮助。 什么是图书馆不感兴趣? 我知道这是一个延伸,但你可以用 Delphi(可能更容易)或 C++ 的内置汇编器编写一个函数,正确对齐 Extended,然后调用你想要的函数直接调用。那么你可以称它为CallWith2Extendeds(&Unit12::SetValue, 10.0L, 40.0L);。而不是SetValue(10.0L, 40.0L); 终于解决了。我可以直接使用 f->Height、f->Width 等属性,而不是调用 f->SetSize(Height, Width) 等因扩展对齐而失败的函数。谢谢你。你拯救了我的一周!。

以上是关于使用扩展参数调用 Delphi 函数时出现 C++ 错误的主要内容,如果未能解决你的问题,请参考以下文章

构建 Numpy C++ 扩展;调用 PyArray_FROM_OTF 时出现段错误

我的delphi7在调试程序时出现问题啦,下面是截图,哪位大侠帮帮忙啊

在 Python 和 C++ 中使用 ctypes 时出现 Seg 错误

如何使用 C++ 挂钩 Delphi 函数

要从 Delphi 调用的 C++ dll 函数 - 数组参数

用另一个函数(都在类中)调用函数时出现问题。没有错误代码 C++