delphiclangc++builderdelphi-10.1-berlinc++builder-10.1-berlin

C++ error when calling a Delphi function with Extended parameters


In a new Win32 project, I have the following Delphi function:

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

In the same project but from a C++ unit, I call this function:

SetValue(10, 40);

When I check value1 when compiling with BCC32C (CLang), I get 1.68132090507504896E-4932, that is not correct.

Compiling with BCC32 (classic), I get 10.

The second parameter is 40 in both cases.

It seems that there is a problem with Extended values and the parameter stack loading.

I use RAD Studio 10.1 Berlin.

How can I solve this?

UPDATE

I did not include the declaration because the hpp is created automatically when compiling. In any case, the declaration is:

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

To replicate the project:

1-Create a C++ project in Rad Studio

2-Add a Delphi unit with the above SetValue function

3-From C++ unit, add the hpp header with #include and call SetValue

It is all.

I need to use Extended type. I am using an external Delphi library, so I cannot change the types. The above code is a simplification of the problem. In reality, the problem is calling a function of this library that uses Extended in the parameters. Extended is a native type in Delphi but in C++ it is mapped as long double, 10 bytes (for Win32).


Solution

  • This seems to be an error in the BCC32C compiler. I guess it was not extended properly to handle Extended as Delphi needs it.

    If you look at the CPU window, then the BCC32 compiler generates:

    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)
    

    and that is correct. It first pushes 10, then 40 in Extended format. Note that each Extended occupies 12 byte on the stack.

    But now look at the output for the BCC32C compiler:

    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
    

    It first reads the 32 single precision float 40 and stores it as Extended at [ESP]. So far, so good. But then it reads in the next 32 bit single precision float 10 (still OK) but then stores it at [ESP+$0A], which is clearly wrong (for Delphi)! It should be stored at [ESP+$0C]! That is why the first value, which is read by the Pascal function at [ESP+$0C], but which is stored by BCC32C at [ESP+$0A], is wrong.

    So this seems to be a bug. Reported as https://quality.embarcadero.com/browse/RSP-15737

    Note that this is the normal way BCC32C pushes and expects such values. In a C++ function in the same module, i.e. compiled with BCC32C too, this works nicely:

    void __fastcall Bla(long double a, long double b)
    {
        printf("%Lf %Lf\n", a, b);
    }
    

    But Delphi expects a 10 byte Extended to occupy 12 bytes on the stack, not 10 bytes as BCC32C does.

    Weirdly enough, if the function to be called is not a Delphi __fastcall function, but a normal C++ (cdecl) function, the BCC32C compiler will store the Extendeds (long doubles) in [ESP+$0C] and [ESP] respectively.

    Workarounds

    As David Heffernan commented, you can pass multiple extendeds in a record. Alternatively, you could pass them as var parameters. In both cases, it is not as simple as calling SetVal(10.0, 40.0);.