如何在Delphi记录中模拟位域?

26

我希望在Delphi中声明一个记录(record),其布局与C语言中的一样。

对于那些感兴趣的人:这个记录是Windows操作系统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不支持位域。我已尝试过以下代码:

As far as I know, there are no bit-fields possible in Delphi. I did try this:

 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。

TIA.

4个回答

32
感谢大家!根据这些信息,我将其简化为:
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且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;

挺不错的,你觉得呢?!?!

附注:Rudy Velthuis现在在他出色的“转换陷阱”文章中包含了修订版。


谢谢夸奖。我的代码确实犯了一些错误,现在已经修复了,干杯! - PatrickvL
谢谢,这非常有帮助。"Flags" 不应该是整数类型吗? - JustMe
3
非常巧妙。只有几个观察:1)记录应该“紧凑”吗?我认为这取决于C/C++编译器的实现,但等效的结构可能是紧密地打包的。2)我在第一眼看到公共/私有/公共部分时感到有些困惑。也许将所有记录值保持为私有(fBaseMid、fFlags、fBaseHi),然后具有公共属性以公开“Base”字节(property BaseMid read fBaseMid write fBaseMid等)会更清晰一些? - Deltics

16

Rudy's 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结构,并编写代码以兼容的方式读写该结构。

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

如果内存中没有太多记录,并且不需要与其他程序交互,我会使用自然的Delphi结构。更高性能的折衷方案将使用更多的内存。

但这都取决于你的标准。

无论如何,你都不能让Delphi编译器像C编译器一样为你完成相同的工作。

PACKED RECORD,正如其他人所建议的那样,不会这样做,也从未打算这样做。它只会删除对齐填充以将整数放置在32位边界上等类似操作,但不会将多个字段打包成一个字节。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接