I am experiencing some very strange things right now.
When I am passing a struct from C++ to a Delphi DLL as a parameter everything works fine.
However, as soon as I want to receive a record as a result I either get wrong values or exceptions.
I deactivated the alignment of the record so that passing them should work!
Heres the code!
Delphi DLL:
TSimpleRecord = packed record
Nr1 : Integer;
Nr2 : Integer;
end;
//...
function TTest() : TSimpleRecord; cdecl;
begin
Result.Nr1 := 1;
Result.Nr2 := 201;
ShowMessage(IntToStr(SizeOf(Result)));
end;
C++ call :
#pragma pack(1)
struct TSimpleRecord
{
int Nr1;
int Nr2;
};
//...
typedef TSimpleRecord (__cdecl TestFunc)(void);
TestFunc* Function;
HINSTANCE hInstLibrary = LoadLibrary("Reactions.dll");
if (hInstLibrary)
{
Function = (TestFunc*)GetProcAddress(hInstLibrary, "TTest");
if (Function)
{
TSimpleRecord Result = {0};
Result = Function();
printf("%d - %d - %d", sizeof(Result), Result.Nr1, Result.Nr2);
cin.get();
}
}
I have got no idea why passing this record as a parameter works but not as a result of a function!?
Can anybody help me?`
Thanks
PS: As I said, both C++ and Delphi show that the record is 8 bytes large.
Some compilers will return struct
types (possibly depending on the size) in registers, others will add a hidden extra parameter where the result should be stored. Unfortunately, it looks like you're dealing two compilers that do not agree on how to return these.
You should be able to avoid the problem by explicitly using an out
parameter instead.
procedure TTest(out Result: TSimpleRecord); cdecl;
begin
Result.Nr1 := 1;
Result.Nr2 := 201;
end;
Do not forget to update the C++ code accordingly.
Rudy Velthuis has written about this:
This showed me that the ABCVar struct was returned in the registers EDX:EAX (EDX with the top 32 bits, and EAX with the lower ones). This is not what Delphi does with records at all, not even with records of this size. Delphi treats such return types as extra var parameters, and does not return anything (so the function is actually a procedure).
[...]
The only type which Delphi returns as EDX:EAX combination is Int64.
which suggests that an alternative way to avoid the problem is
function TTest() : Int64; cdecl;
begin
TSimpleRecord(Result).Nr1 := 1;
TSimpleRecord(Result).Nr2 := 201;
end;
Note that Delphi allows such type punning even in situations where the behaviour would be undefined in C++.
Delphi does not follow the platform standard ABI for return values. The standard ABI passes return values to the caller by value. Delphi treats the return value as an implicit extra var parameter, passed after all other parameters. The documentation describes the rules.
You can change your calling code to match that. Pass an extra reference to struct parameter in your C++ function.
typedef void (__cdecl TestFunc)(TSimpleRecord&);
If you are going to do this on the C++ side, you would be best doing the same change on the Delphi side for clarity.
Since Delphi does not follow the platform standards for return values I suggest you restrict yourself to types that are compatible with other tools. That means integral values up to 32 bits, pointers and floating point values.
As a general rule of thumb, do not pack records. If you do so you will have mis-alignment which affects performance. For the record in the question, there will be no padding anyway since both fields are the same size.