【发布时间】:2014-08-29 22:03:07
【问题描述】:
我正在将 TaskSchedule API 相关代码从 Delphi 5 移植到 Delphi XE6。我遇到了结构对齐和sizeof 的问题。
实际的TASK_TRIGGER结构声明为:
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
我正在使用的MsTask.pas 的旧翻译(以及 MsTask 的当前 JCL 事务)将其翻译为:
_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;
sizeof 这个记录在 Delphi 5 和 XE6 之间有所不同:
- 德尔福5:
SizeOf(TASK_TRIGGER) = 48 - 德尔福 XE6
SizeOf(TASK_TRIGGER) = 47
ITaskTrigger.SetTrigger(TASK_TRIGGER) 的函数调用在 Dephi5 中成功,但在 Delphi XE6 中调用 The parameters are incorrect 失败。
布局
如果我天真地猜测记录的布局,我会是:
□□□□ □□□□ //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)
但是当我实际检查 Delphi 5 调试器中填充的结构时,实际结构是 48 字节,在 TriggerType 和 Type_ 之间有额外的 4 字节填充:
□□□□ □□□□ //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)
好的,如果这就是 Delphi 5 想要这样做的方式,我该争论谁。它肯定比我更了解 Windows 结构打包。
我检查布局的方式是在记录中放置已知的标记值:
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;
并在 CPU 窗口中查看生成的内存布局:
(交替的成员红色和绿色,红色是填充);
在 Delphi 5 中总共有 48 bytes。
输入 XE6
当我在 Delphi XE6 中进行相同的测试时,它的包装方式不同(而且非常可怕):
首先,它无法在 32 位边界上分配堆栈变量;不过没关系。
CPU 窗口拒绝完全在结构上启动视图 - 坚持它开始在 DWORD 边界上显示内存;不过没关系。
记录在$18EB31处确实没有对齐:
所以我们会继续这样做。
□□□□ □□□□ //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)
这是设计上的怪物,还是编译器代码生成错误?
你试过 {$ALIGN ON} 吗?
当然。
sizeof(TASK_TRIGGER) = 52
失败。
{$MINENUMSIZE 4} 怎么样?
好的。
sizeof(TASK_TRIGGER) = 50
失败。
是的,但是您是否同时尝试了这两种方法?
触摸。
sizeof(TASK_TRIGGER) = 52
失败。
这几乎就像 Delphi 拒绝相信它是 Windows 编译器。
总结
$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
等我有耐心再补充。
【问题讨论】:
-
获取 C 编译器并查看布局。使用和
offsetof()宏。 -
@DavidHeffernan 我这里有一个功能编译器(Delphi 5)。我只需要 Delphi XE6 来创建与 Windows ABI 兼容的结构。
-
@Ian D5 在结构布局上很烂。认真获得一个真正的编译器!一种理解windows头文件的人。从马嘴里获取你的信息。
-
@Ian 我们已经看到了 ;-)
标签: delphi delphi-xe6