Как имитировать битовые поля в записях Delphi?

Я хотел бы объявить запись в Delphi, которая содержит тот же макет, что и в C.

для заинтересованных: эта запись является частью объединения в записи LDT_ENTRY ОС Windows. (Мне нужно использовать эту запись в Delphi, потому что я работаю над эмулятором Xbox в Delphi - см. Проект Dxbx на sourceforge).

в любом случае, рассматриваемая запись определяется как:

    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. Я хотел бы знать, как я должен объявить запись, чтобы я получил запись с тем же макетом, тем же размером и теми же членами. Желательно без нагрузок геттеров/сеттеров.

ТИА.

4 ответов


спасибо всем!

основываясь на этой информации, я уменьшил это :

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 05 read GetBits write SetBits; // 5 bits at offset 0
  property Dpl: Integer index 02 read GetBits write SetBits; // 2 bits at offset 5
  property Pres: Integer index 01 read GetBits write SetBits; // 1 bit at offset 7
  property LimitHi: Integer index 04 read GetBits write SetBits; // 4 bits at offset 8
  property Sys: Integer index C01 read GetBits write SetBits; // 1 bit at offset 12
  property Reserved_0: Integer index D01 read GetBits write SetBits; // 1 bit at offset 13
  property Default_Big: Integer index E01 read GetBits write SetBits; // 1 bit at offset 14
  property Granularity: Integer index F01 read GetBits write SetBits; // 1 bit at offset 15
end;

индекс кодируется следующим образом : (BitOffset shl 8) + NrBits. Где 1

Теперь, я могу получить и установить эти биты следующим образом :

{$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: Руди Velthuis теперь включил пересмотренную версию этого в своем превосходном "подводные камни преобразования"-статьи.


Delphi Corner Руди это лучший ресурс, который я знаю о совместимости Delphi и C/C++. Его ловушки преобразования в значительной степени необходимо читать при использовании API C/C++ в Delphi. Глава, которая вас больше всего заинтересует, это записи и выравнивание - > Bitfields, но я призываю вас прочитать все это сверху донизу,два раза. Другие статьи, безусловно, стоит время инвестиций, тоже.


хорошо, моя битовая манипуляция немного ржавая, поэтому я мог бы отменить байты. Но приведенный ниже код дает общее представление:

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 F;
  end;

  procedure TBits.SetType(const AType: Byte);
  begin
    FTypeDplPres := (FTypeDplPres and ) + ((AType and F) shr 3);
  end;

  function TBits.GetDpl: Byte;
  begin
    Result := (FTypeDplPres and ) shr 1;
  end;

  procedure TBits.SetDbl(const ADpl: Byte);
  begin
    FTypeDblPres := (FTypeDblPres and $F9) + ((ADpl and ) 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 F;
  end;

  procedure TBits.SetLimitHi(const AValue: Byte);
  begin
    FLimitHiSysEa := (FLimitHiSysEa and F) + ((AValue and F) 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;

Ну, вам в основном нужно спуститься к грязному с Бит-манипуляцией.

Почему, в частности, вам нужно сохранить эту структуру?

Если вам нужно только поговорить с устаревшей программой, которая либо говорит на этом диалекте (TCP/IP или аналогичный), либо хранит данные таким образом (файлы и т. д.), затем я бы сопоставил нормальную структуру Delphi с совместимой с битовой версией. Другими словами, Я бы использовал нормально структурированную структуру Delphi в памяти и писал код для записи и прочтите эту структуру совместимым образом.

Если вам нужно сохранить память, я бы сделал геттеры и сеттеры, которые управляют битами внутренних целых чисел или тому подобное. Это будет иметь влияние на производительность, но не намного больше, чем оригинальная программа C, единственная разница заключается в том, что бит-манипуляция будет добавлена компилятором magic в версии C, тогда как вам придется написать ее самостоятельно.

Если у вас не так много записей в памяти, и вам не нужно поговорите с другой программой, я бы использовал естественную структуру Delphi. Компромисс для более высокой производительности будет больше используемой памяти.

но все зависит от ваших критериев.

в любом случае, вы не сможете уговорить компилятор Delphi выполнить ту же работу для вас, что и компилятор C.

упакованная запись, предложенная другим здесь, не делает этого и никогда не должна была. Он удалит только заполнение выравнивания, чтобы поместить целые числа на 32-битные границы и аналогичные, но не будет упаковать несколько полей в один байт.

обратите внимание, что общий способ сделать это-через наборы Delphi, которые реализуют внутренне, используя битовые поля. Опять же, у вас будет другой код, чем c вариант.