【问题标题】:What is the equivalent function for the CONTAINING_RECORD C macro in Delphi?Delphi 中 CONTAINING_RECORD C 宏的等效函数是什么?
【发布时间】:2026-01-30 05:05:02
【问题描述】:

C 中的CONTAINING_RECORD macro 根据结构中字段成员的地址返回结构/记录类型变量的基地址。在我只能将预定义的记录指针传递给触发回调的某些 Windows API 函数的情况下,它非常有用。

例如,如果我有一些类型,例如:

type
   tInnerRecord = record
      x, y : integer;
   end;
   pInnerRecord = ^tInnerRecord

tOuterRecord = record
   field1 : integer;
   inner : tInnerRecord;
   field2 : integer;
end;
pOuterRecord = ^tOuterRecord;

我希望能够做类似的事情:

procedure SomeCallback( pIn : pInnerRecord ); stdcall;
var
   Out : pOuterRecord;
begin
   Out := CONTAINING_RECORD(pIn, tOuterRecord, inner);
   Out.field1 := pIn.x + pIn.y;
end;

在我的具体情况下,我想将我的对象指针与 ReadFileEx(Windows 异步 I/O)的重叠数据指针一起传递,以便我可以在回调中访问该对象。

在 Delphi (2006) 中是否有提供类似功能的等效函数?

【问题讨论】:

  • 另一个可以让 Delphi 程序员羡慕的有用的东西是offsetof 用于记录或对象字段。
  • @Serg:有类似的解决方法,例如 LURD 和我的 offsetof 解决方案 - 不如宏方便,但它们可以工作。
  • @blerontin:你确定你得到了正确的间接级别吗? tOuterRecord 包含一个指向 tInnerRecord 的指针,而 SomeCallback 按值获取 pInnerRecord
  • @UliGerhardt:感谢您的提示。我已将tOuterRecord 声明中的inner 字段从pInnerRecord 更改为tInnerRecord

标签: delphi pointers struct record


【解决方案1】:

我刚刚没有编译器,但这应该可以解决问题:

Out := Pointer( Cardinal(pIn) - Cardinal(@TOuterRecord(nil^).inner));

David 解释了为什么在 Delphi 中没有直接的等效函数。 所以这是一个最接近的函数:

function ContainingRecord( var aField; aFieldOffsetRef : Pointer) : Pointer;
{$IF Declared(NativeUInt) = False}
type
  NativeUInt = Cardinal;
{$IFEND}
begin
  Result := Pointer(NativeUInt(@aField) - NativeUInt(aFieldOffsetRef));
end;

调用示例:

Out := ContainingRecord(pIn^, @pOuterRecord(nil).inner);

【讨论】:

  • 宏是在字段名称上参数化的。这不是。
  • @LU RD,您在pIn 前面缺少@ - 我一开始也犯了同样的错误:-)。否则你的解决方案很好,而且比我的短。
  • 如果这真的是答案,那么问题是*.com/questions/14462103/…的重复项
  • @David:不是真的 - 这是相反的方向:CONTAINING_RECORDoffsetof。当然,在这种情况下,这相当于一个额外的- 符号。 :-)
  • @UliGerhardt 我假设任何能够提出这个问题的人都能理解- 运算符
【解决方案2】:

您应该能够使用一些强制转换和指针算术来做到这一点,但不能在一个封装良好的宏中(正如 David 已经提到的那样)。比如:

procedure SomeCallback(var pIn: pInnerRecord); stdcall;
const
  p = pOuterRecord(nil);
var
  Offset: Integer;
  Out: pOuterRecord;
begin
  Offset := INT_PTR(@p^.inner) - INT_PTR(p);
  Out := Pointer(INT_PTR(@pIn) - Offset);
  Out.field1 := pIn.x + pIn.y;
end;

var
 outer: tOuterRecord;
 inner: tInnerRecord;
begin
  inner.x := 1;
  inner.y := 2;
  outer.inner := @inner;
  SomeCallback(outer.inner);
  Assert(outer.field1 = 3);
end;

有效。请注意,我必须在SomeCallback 的参数列表中添加var

【讨论】:

  • 宏是在字段名称上参数化的。这不是。
  • 这不是一个函数。它是宏 call 的就地替换。 inner 在我的解决方案中与原始 C 代码中的硬编码完全相同。当然,宏“调用”比我的解决方案更具可读性,但至少它有效。
  • 好吧,我猜你是在告诉我“在 Delphi (2006) 中是否有提供类似功能的等效功能?”的答案。是“不”——同意。但是对于我认为是 OP 的 real 问题,有一个解决方案,即“我能做些什么才能让它在 Delphi (2006) 中工作?”
  • @DavidHeffernan:是的,好吧,我只是说“名称作为参数”的一部分是不可能的。
  • @LURD 是的,答案中减法的右侧在编译时是已知的。
【解决方案3】:

在 Delphi 中没有直接的等价物。不可能有这样的事情,因为 Delphi 没有宏和预处理器。

显然在 Delphi 中可以计算出offset for a specific field in a specific record。然后执行获得包含记录基地址所需的减法是微不足道的。但是你不能做的是一次表达该计算并将其重新用于任何常规字段/记录对,就像使用 C 宏所做的那样。

您需要为以这种方式使用的每个字段/记录对编写一个函数。

【讨论】: