How to simulate bit-fields in Delphi records?

前端 未结 4 2051
终归单人心
终归单人心 2020-12-04 18:32

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 OS\'s L

相关标签:
4条回答
  • 2020-12-04 18:49

    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.

    0 讨论(0)
  • 2020-12-04 18:55

    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;
    
    0 讨论(0)
  • 2020-12-04 18:58

    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.

    0 讨论(0)
  • 2020-12-04 19:03

    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.

    0 讨论(0)
提交回复
热议问题