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

发布于 2024-07-08 21:54:07 字数 1050 浏览 3 评论 0原文

我想在 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。

I would like to declare a record in Delphi that contains the same layout as it has in C.

For those interested : This record is part of a union in the Windows API LDT_ENTRY record (I need to use this record in Delphi because I'm working on an Xbox emulator in Delphi - see project Dxbx on sourceforge).

Anyway, the record in question is defined as:

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;

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;

But alas: it's size becomes 10 bytes, instead of the expected 4.

I would like to know how I should declare the record, so that I get a record with the same layout, the same size, and the same members. Preferably without loads of getters/setters.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

几味少女 2024-07-15 21:54:07

感谢大家!

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

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;

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

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

Thanks everyone!

Based on this information, I reduced this to :

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;

The index is encoded as follows : (BitOffset shl 8) + NrBits. Where 1<=NrBits<=32 and 0<=BitOffset<=31

Now, I can get and set these bits as follows :

{$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;

Pretty nifty, don't you think?!?!

PS: Rudy Velthuis now included a revised version of this in his excellent "Pitfalls of converting"-article.

薄情伤 2024-07-15 21:54:07

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

Rudy's Delphi Corner is the best resource I know of regarding Delphi and C/C++ interoperability. His Pitfalls of conversion is pretty much a must read when using C/C++ APIs in Delphi. The chapter you'll be most interested in is Records and alignment -> Bitfields, but I urge you to read the entire thing top to bottom, twice. The other articles are definitely worth the time investment, too.

树深时见影 2024-07-15 21:54:07

好吧,我的位操作有点生疏,所以我可以反转字节。 但下面的代码给出了总体思路:

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;

Ok, my bit manipulation is a bit rusty, so I could have reversed the bytes. But the code below gives the general idea:

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;
云归处 2024-07-15 21:54:07

好吧,你基本上需要深入了解位操作。

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

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

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

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

但这完全取决于您的标准。

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

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

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

Well, you basically need to get down to the dirty with bit-manipulation.

Why, specifically, do you need to retain that structure?

If you only need to talk to a legacy program that either talks in this dialect (TCP/IP or similar), or stores data in this manner (files, etc.), then I would map a normal Delphi structure to a bit-version compatible. In other words, I would use a normally structured Delphi structure in memory, and write code to write and read that structure in a compatible manner.

If you need to save memory, I would make getters and setters that manipulate bits of internal integers or similar. This will have a performance impact, but not much more than what the original C program would have, the only difference is that the bit-manipulation would be added by compiler magic in the C version, whereas you will have to write it yourself.

If you don't have many records in memory, and don't need to talk to another program, I'd use a natural Delphi structure. Trade-off for higher performance will be more memory used.

But it all depends on your criteria.

In any case, you won't be able to talk the Delphi compiler into doing the same job for you as the C compiler.

PACKED RECORD, suggested by another here, doesn't do that, and was never meant to. It will only remove alignment padding to put integers on 32-bit boundaries and similar, but won't pack multiple fields into one byte.

Note that a common way to do this is through Delphi SETS, which are implementing internally using bit-fields. Again, you will have different code than the C variant.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文