如何模拟Delphi记录中的位字段?

问题描述 投票:0回答:4

我想在 Delphi 中声明一条记录,其中包含与 C 中相同的布局。

对于那些感兴趣的人:此记录是 Windows API

LDT_ENTRY
记录中联合的一部分(我需要在 Delphi 中使用此记录,因为我正在 Delphi 中开发 Xbox 模拟器 - 请参阅 sourceforge 上的项目 Dxbx)。

无论如何,相关记录定义为:

struct
{
    DWORD   BaseMid : 8;
    DWORD   Type : 5;
    DWORD   Dpl : 2;
    DWORD   Pres : 1;
    DWORD   LimitHi : 4;
    DWORD   Sys : 1;
    DWORD   Reserved_0 : 1;
    DWORD   Default_Big : 1;
    DWORD   Granularity : 1;
    DWORD   BaseHi : 8;
}
Bits;

据我所知,Delphi 中没有可能的位字段。我确实尝试过这个:

Bits = record
  BaseMid: Byte; // 8 bits
  _Type: 0..31; // 5 bits
  Dpl: 0..3; // 2 bits
  Pres: Boolean; // 1 bit
  LimitHi: 0..15; // 4 bits
  Sys: Boolean; // 1 bit
  Reserved_0: Boolean; // 1 bit
  Default_Big: Boolean; // 1 bit
  Granularity: Boolean; // 1 bit
  BaseHi: Byte; // 8 bits
end;

但是可惜:它的大小变成了 10 个字节,而不是预期的 4 个字节。

我想知道如何声明记录,以便获得具有相同布局、相同大小和相同成员的记录。最好没有大量的 getter/setter。

delphi record bit-fields
4个回答
33
投票

谢谢大家!

根据这些信息,我将其简化为:

RBits = record
public
  BaseMid: BYTE;
private
  Flags: WORD;
  function GetBits(const aIndex: Integer): Integer;
  procedure SetBits(const aIndex: Integer; const aValue: Integer);
public
  BaseHi: BYTE;
  property _Type: Integer index $0005 read GetBits write SetBits; // 5 bits at offset 0
  property Dpl: Integer index $0502 read GetBits write SetBits; // 2 bits at offset 5
  property Pres: Integer index $0701 read GetBits write SetBits; // 1 bit at offset 7
  property LimitHi: Integer index $0804 read GetBits write SetBits; // 4 bits at offset 8
  property Sys: Integer index $0C01 read GetBits write SetBits; // 1 bit at offset 12
  property Reserved_0: Integer index $0D01 read GetBits write SetBits; // 1 bit at offset 13
  property Default_Big: Integer index $0E01 read GetBits write SetBits; // 1 bit at offset 14
  property Granularity: Integer index $0F01 read GetBits write SetBits; // 1 bit at offset 15
end;

索引编码如下:

(BitOffset shl 8) + NrBits
。哪里 1<=NrBits<=32 and 0<=BitOffset<=31

现在,我可以按如下方式获取和设置这些位:

{$OPTIMIZATION ON}
{$OVERFLOWCHECKS OFF}
function RBits.GetBits(const aIndex: Integer): Integer;
var
  Offset: Integer;
  NrBits: Integer;
  Mask: Integer;
begin
  NrBits := aIndex and $FF;
  Offset := aIndex shr 8;

  Mask := ((1 shl NrBits) - 1);

  Result := (Flags shr Offset) and Mask;
end;

procedure RBits.SetBits(const aIndex: Integer; const aValue: Integer);
var
  Offset: Integer;
  NrBits: Integer;
  Mask: Integer;
begin
  NrBits := aIndex and $FF;
  Offset := aIndex shr 8;

  Mask := ((1 shl NrBits) - 1);
  Assert(aValue <= Mask);

  Flags := (Flags and (not (Mask shl Offset))) or (aValue shl Offset);
end;

非常漂亮,你不觉得吗?!?!

PS:Rudy Velthuis 现在在他出色的“转换的陷阱”文章中包含了此内容的修订版本。


16
投票

Rudy 的 Delphi Corner 是我所知道的有关 Delphi 和 C/C++ 互操作性的最佳资源。在 Delphi 中使用 C/C++ API 时,他的《转换的陷阱》几乎是必读之作。您最感兴趣的章节是记录和对齐 -> 位字段,但我强烈建议您从上到下阅读整个内容,两次。其他文章也绝对值得投入时间。


5
投票

type TBits = record private FBaseMid : Byte; FTypeDplPres : Byte; FLimitHiSysEa: Byte; FBaseHi : Byte; function GetType: Byte; procedure SetType(const AType: Byte); function GetDpl: Byte; procedure SetDbl(const ADpl: Byte); function GetBit1(const AIndex: Integer): Boolean; procedure SetBit1(const AIndex: Integer; const AValue: Boolean); function GetLimitHi: Byte; procedure SetLimitHi(const AValue: Byte); function GetBit2(const AIndex: Integer): Boolean; procedure SetBit2(const AIndex: Integer; const AValue: Boolean); public property BaseMid: Byte read FBaseMid write FBaseMid; property &Type: Byte read GetType write SetType; // 0..31 property Dpl: Byte read GetDpl write SetDbl; // 0..3 property Pres: Boolean index 128 read GetBit1 write SetBit1; property LimitHi: Byte read GetLimitHi write SetLimitHi; // 0..15 property Sys: Boolean index 16 read GetBit2 write SetBit2; property Reserved0: Boolean index 32 read GetBit2 write SetBit2; property DefaultBig: Boolean index 64 read GetBit2 write SetBit2; property Granularity: Boolean index 128 read GetBit2 write SetBit2; property BaseHi: Byte read FBaseHi write FBaseHi; end; function TBits.GetType: Byte; begin Result := (FTypeDplPres shr 3) and $1F; end; procedure TBits.SetType(const AType: Byte); begin FTypeDplPres := (FTypeDplPres and $07) + ((AType and $1F) shr 3); end; function TBits.GetDpl: Byte; begin Result := (FTypeDplPres and $06) shr 1; end; procedure TBits.SetDbl(const ADpl: Byte); begin FTypeDblPres := (FTypeDblPres and $F9) + ((ADpl and $3) shl 1); end; function TBits.GetBit1(const AIndex: Integer): Boolean; begin Result := FTypeDplPres and AIndex = AIndex; end; procedure TBits.SetBit1(const AIndex: Integer; const AValue: Boolean); begin if AValue then FTypeDblPres := FTypeDblPres or AIndex else FTypeDblPres := FTypeDblPres and not AIndex; end; function TBits.GetLimitHi: Byte; begin Result := (FLimitHiSysEa shr 4) and $0F; end; procedure TBits.SetLimitHi(const AValue: Byte); begin FLimitHiSysEa := (FLimitHiSysEa and $0F) + ((AValue and $0F) shr 4); end; function TBits.GetBit2(const AIndex: Integer): Boolean; begin Result := FLimitHiSysEa and AIndex = AIndex; end; procedure TBits.SetBit2(const AIndex: Integer; const AValue: Boolean); begin if AValue then FLimitHiSysEa := FLimitHiSysEa or AIndex else FLimitHiSysEa := FLimitHiSysEa and not AIndex; end;



0
投票

具体来说,为什么您需要保留该结构?

如果您只需要与使用这种方言(TCP/IP 或类似)进行对话的遗留程序进行对话,或者以这种方式存储数据(文件等),那么我会将普通的 Delphi 结构映射到位-版本兼容。换句话说,我将在内存中使用正常结构的 Delphi 结构,并编写代码以兼容的方式写入和读取该结构。

如果您需要节省内存,我会制作操作内部整数或类似位的 getter 和 setter。这会对性能产生影响,但不会比原始 C 程序的影响大多少,唯一的区别是位操作将由 C 版本中的编译器魔法添加,而你必须自己编写它。

如果内存中没有很多记录,并且不需要与另一个程序对话,我会使用自然的 Delphi 结构。更高性能的代价是使用更多内存。

但这一切都取决于您的标准。

无论如何,您都无法让 Delphi 编译器为您完成与 C 编译器相同的工作。

PACKED RECORD,由另一个人在这里建议的,并没有这样做,也从来没有打算这样做。它只会删除对齐填充以将整数放在 32 位边界和类似的位置上,但不会将多个字段打包到一个字节中。

请注意,执行此操作的常见方法是通过 Delphi SETS,它使用位字段在内部实现。同样,您将拥有与 C 变体不同的代码。

© www.soinside.com 2019 - 2024. All rights reserved.