gpt4 book ai didi

delphi - XE6下如何正确对齐TASK_TRIGGER记录?

转载 作者:行者123 更新时间:2023-12-03 18:21:33 25 4
gpt4 key购买 nike

我正在将 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之间有所不同:

  • Delphi 5:SizeOf(TASK_TRIGGER) = 48
  • Delphi XE6 SizeOf(TASK_TRIGGER) = 47

在Dephi5中调用ITaskTrigger.SetTrigger(TASK_TRIGGER)成功,但在Delphi XE6中调用失败,参数不正确

布局

如果我天真地猜测记录的布局,我会是:

□□□□ □□□□       //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 个字节,在 TriggerTypeType_ 之间有额外的 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 窗口中查看最终的内存布局:

enter image description here

(交替成员红色和绿色,红色是填充);

在 Delphi 5 中总共 48 字节

输入XE6

当我在 Delphi XE6 中进行相同的测试时,它的包装方式不同(而且可怕):

enter image description here

首先,它无法在 32 位边界上分配堆栈变量;不过没关系。
CPU 窗口拒绝在结构上精确地启动 View - 坚持它开始在 DWORD 边界上显示内存;不过没关系。
该记录确实未在 $18EB31 处对齐:

enter image description here

所以我们就这么做。

□□□□ □□□□       //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

enter image description here

失败。

{$MINENUMSIZE 4}怎么样?

好的。

sizeof(TASK_TRIGGER) = 50

enter image description here

失败。

是的,但是你同时尝试过两者吗?

触摸。

sizeof(TASK_TRIGGER) = 52

enter image description here

失败。

这几乎就像 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

当我有耐心时,我会添加更多内容。

最佳答案

正确的布局如下:

00-01 cbTriggerSize: WORD;
02-03 Reserved1: WORD;
04-05 wBeginYear: WORD;
06-07 wBeginMonth: WORD;
08-09 wBeginDay: WORD;
10-11 wEndYear: WORD;
12-13 wEndMonth: WORD;
14-15 EndDay: WORD;
16-17 wStartHour: WORD;
18-19 wStartMinute: WORD;
20-23 MinutesDuration: DWORD;
24-27 MinutesInterval: DWORD;
28-31 rgFlags: DWORD;
32-35 TriggerType: TTaskTriggerType;
36-43 Type_: TTriggerTypeUnion;
44-45 Reserved2: WORD;
46-47 wRandomMinutesInterval: WORD;

让我们逐点讨论这个问题:

  • 前 10 个单词没有填充地排列在一起,全部自然对齐。
  • 然后是 3 个双字,同样不需要填充来在 4 字节边界上对齐。
  • 接下来是 C 枚举,它实际上是一个 int。同样,对齐 4,不需要填充。
  • 现在是工会。结构体的联合,其中最大的是 MONTHLYDATE,由于对齐,其大小为 8。
  • 另外两个单词可以自然偏移,无需填充。

假设 C 头文件指定了对齐结构,您需要使用 {$MINENUMSIZE 4}{$ALIGN ON} 进行编译。

您省略的详细信息是 JEDI 单元的编译器选项以及枚举和联合的声明。查看 github 存储库中的单元,我看到 {$MINENUMSIZE 4}{$ALIGN ON} 这很好。枚举是一个普通的 Delphi 枚举类型。也不错。但联合体及其包含的记录都已打包。这是错误的,会导致工会的规模错误。

我也从 JEDI 来源看到了这一点:

_TASK_TRIGGER = record 
// SP: removed packed record statement as seemed to affect SetTrigger

本单元的作者似乎对打包和对齐有点困惑。

XE6 怎么认为这可能是 47 个字节,我无法理解。尤其是因为我看不到所有细节,因为不幸的是这个问题省略了一些细节。无论如何,您确实需要枚举大小为 4,并对齐记录,因此 47 个数据点可能不是关键点。我建议我们忽略它。

适当的 XE6 数据点是大小为 52 的 {$MINENUMSIZE 4}{$ALIGN ON} 情况。在这里我们看到联合消耗了 12 个字节出于某种难以理解的原因。我假设您的工会符合 JEDI github 存储库中的声明。是吗?

从你提供的事实来看,这对我来说就像是一个 Delphi XE6 编译器错误。旧的 Delphi 版本在对齐结构方面是出了名的差劲。我认为现代版本是正确的,但也许不是。然而,您正在使用的 header 翻译可能会混淆所有这些。当然,它似乎对包装感到困惑。我们还无法看到您的所有代码。我只在github上看到过最新的。也许问题出在那里而不是编译器。 @LURD 的调查表明 XE6 编译器正确地布置了结构。


处理此类问题的方法是使用 MS 编译器转到原始头文件。包含标题并使用 sizeofoffsetof 转储布局。可以说是从马嘴里说出来的。

然后用 Delphi 编译器执行相同的操作并比较布局。使用我在这里展示的技巧来代替 C++ offsetof:Can we implement ANSI C's `offsetof` in Delphi?

至于如何进行,一旦您知道正确的布局,就应该很容易说服编译器以相同的方式布置记录。从 github 存储库中的 JEDI 代码开始,删除所有使用的 Packed。尝试一下尺寸。如果这不起作用,请调查联合体的布局。作为最后的手段,您可以打包所有元素并手动填充。如果问题确实存在的话,也许与工会一起这样做就足够了。

更新: LURD 的答案似乎表明,删除联合和包含结构中打包的使用可以提供正确的布局。

注意:我手头没有任何编译器,因此以上所有内容都是我的脑海中生成的。我可能在细节上犯了错误。然而,我相信,使用 MS 编译器向您显示正确布局的一般建议是解决对 Win32 结构布局的所有疑问的一般建议。有了这个工具,您就可以解决任何此类性质的问题。

关于delphi - XE6下如何正确对齐TASK_TRIGGER记录?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25576852/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com