I am porting TaskSchedule API related code from Delphi 5 to Delphi XE6. I'm having an issue with structure alignment and sizeof
.
The actual TASK_TRIGGER
structure is declared as:
typedef struct _TASK_TRIGGER {
WORD cbTriggerSize;
WORD Reserved1;
WORD wBeginYear;
WORD wBeginMonth;
WORD wBeginDay;
WORD wEndYear;
WORD wEndMonth;
WORD wEndDay;
WORD wStartHour;
WORD wStartMinute;
DWORD MinutesDuration;
DWORD MinutesInterval;
DWORD rgFlags;
TASK_TRIGGER_TYPE TriggerType;
TRIGGER_TYPE_UNION Type;
WORD Reserved2;
WORD wRandomMinutesInterval;
} TASK_TRIGGER
The old translation of MsTask.pas
i'm using (and current JCL transactions of MsTask) translate it as:
_TASK_TRIGGER = record
cbTriggerSize: WORD;
Reserved1: WORD;
wBeginYear: WORD;
wBeginMonth: WORD;
wBeginDay: WORD;
wEndYear: WORD;
wEndMonth: WORD;
wEndDay: WORD;
wStartHour: WORD;
wStartMinute: WORD;
MinutesDuration: DWORD;
MinutesInterval: DWORD;
rgFlags: DWORD;
TriggerType: TTaskTriggerType;
Type_: TTriggerTypeUnion;
Reserved2: WORD;
wRandomMinutesInterval: WORD;
end;
The sizeof
this record differs between Delphi 5 and XE6:
- Delphi 5:
SizeOf(TASK_TRIGGER) = 48
- Delphi XE6
SizeOf(TASK_TRIGGER) = 47
The function call of ITaskTrigger.SetTrigger(TASK_TRIGGER)
succeeds in Dephi5, but fails with Delphi XE6 with The parameters are incorrect
.
The layouts
If i were to naively guess the layout of the record, i would it is:
□□□□ □□□□ //cbTriggerSize, Reserved1 (4 bytes)
□□□□ □□□□ //wBeginYear, wBeginMonth (8 bytes)
□□□□ □□□□ //wBeginDay, wEndYear (12 bytes)
□□□□ □□□□ //wEndMonth, wEndDay (16 bytes)
□□□□ □□□□ //wStartHour, wStartMinute (20 bytes)
□□□□□□□□ //MinutesDuration (24 bytes)
□□□□□□□□ //MinutesInterval (28 bytes)
□□□□□□□□ //rgFlags (32 bytes)
□□□□□□□□ //TriggerType (36 bytes)
□□□□□□□□ //Type_ (40 bytes)
□□□□ □□□□ //Reserved2 wRandomMinutesInterval (44 bytes)
But when i actually examine the populated structure inside the Delphi 5 debugger, the actual structure is 48 bytes, with extra 4 bytes padding between TriggerType
and Type_
:
□□□□ □□□□ //cbTriggerSize, Reserved1 (4 bytes)
□□□□ □□□□ //wBeginYear, wBeginMonth (8 bytes)
□□□□ □□□□ //wBeginDay, wEndYear (12 bytes)
□□□□ □□□□ //wEndMonth, wEndDay (16 bytes)
□□□□ □□□□ //wStartHour, wStartMinute (20 bytes)
□□□□□□□□ //MinutesDuration (24 bytes)
□□□□□□□□ //MinutesInterval (28 bytes)
□□□□□□□□ //rgFlags (32 bytes)
□□□□□□□□ //TriggerType (36 bytes)
□□□□□□□□ //4 bytes padding
□□□□□□□□ //Type_ (44 bytes)
□□□□ □□□□ //Reserved2 wRandomMinutesInterval (48 bytes)
Ok, if that's how Delphi 5 wants to do it who am i to argue. It certainly knows more about Windows structure packing than i do.
The way i examined the layout was to place known sentinel values in the record:
trigger.cbTriggerSize := $1111; // WORD;
trigger.Reserved1 := $2222; // WORD;
trigger.wBeginYear := $3333; // WORD;
trigger.wBeginMonth := $4444; // WORD;
trigger.wBeginDay := $5555; // WORD;
trigger.wEndYear := $6666; // WORD;
trigger.wEndMonth := $7777; // WORD;
trigger.wEndDay := $8888; // WORD;
trigger.wStartHour := $9999; // WORD;
trigger.wStartMinute := $aaaa; // WORD;
trigger.MinutesDuration := $bbbbbbbb; // DWORD;
trigger.MinutesInterval := $cccccccc; // DWORD;
trigger.rgFlags := $dddddddd; // DWORD;
trigger.TriggerType := TASK_TIME_TRIGGER_DAILY; // TTaskTriggerType;
trigger.Type_.Daily.DaysInterval := $ffff; // TTriggerTypeUnion;
trigger.Reserved2 := $1111; // WORD;
trigger.wRandomMinutesInterval := $2222; // WORD;
And look at the resulting memory layout in the CPU window:
(alternating members red and green, red is the padding);
For a total of 48 bytes
in Delphi 5.
Enter XE6
When i do the same test in Delphi XE6, it is packed differently (and terrifyingly):
First, it couldn't manage to allocate a stack variable the on 32-bit boundary; but that's fine.
The CPU window refused to start the view exactly on the structure - insisting it start showing memory on a DWORD boundary; but that's fine.
The record really is not aligned at $18EB31
:
so we'll go with that.
□□□□ □□□□ //cbTriggerSize, Reserved1 (4 bytes)
□□□□ □□□□ //wBeginYear, wBeginMonth (8 bytes)
□□□□ □□□□ //wBeginDay, wEndYear (12 bytes)
□□□□ □□□□ //wEndMonth, wEndDay (16 bytes)
□□□□ □□□□ //wStartHour, wStartMinute (20 bytes)
□□□□□□□□ //MinutesDuration (24 bytes)
□□□□□□□□ //MinutesInterval (28 bytes)
□□□□□□□□ //rgFlags (32 bytes)
□□ □□□□ □□ //TriggerType, Type_, 1 byte padding (36 bytes)
□□□□□□□□ //4 bytes padding (40 bytes)
□□□□□□ □□ //3 bytes padding, part of Reserved2 (44 bytes)
□□ □□□□ //Remainnder of Reserved2, wRandomMinutesInterval (47 bytes)
Is this monstrosity by design, or a compiler code-gen bug?
Did you try {$ALIGN ON}?
Sure.
sizeof(TASK_TRIGGER) = 52
Fails.
What about {$MINENUMSIZE 4}?
Okay.
sizeof(TASK_TRIGGER) = 50
Fails.
Yeah, but did you try both together?
Touche.
sizeof(TASK_TRIGGER) = 52
Fails.
It's almost like Delphi refuses to believe that it is Windows compiler.
Summary
$ALIGN $MINENUMSIZE $OLDTYPELAYOUT "packed" | sizeof
====== ============ ================ ======== ======
ON 4 ON yes 57
ON 4 OFF yes 50
ON 4 ON no 52
ON 4 OFF no 52
ON 2 ON yes 50
ON 2 OFF yes 50
ON 2 ON no 52
ON 2 OFF no 52
ON ON yes 49
ON OFF yes 49
ON ON no 52
ON OFF no 52
i'll add more as i get the patience.