Как имитировать битовые поля в записях Delphi?
-
08-07-2019 - |
Вопрос
Я хотел бы объявить запись в 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.Я хотел бы знать, как я должен объявить запись, чтобы получить запись с тем же макетом, тем же размером и теми же элементами.Предпочтительно без множества геттеров / сеттеров.
ТИА.
Решение
Спасибо всем!
Основываясь на этой информации, я свел это к :
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<=НрБиты<=32 и 0<=Битовое смещение<=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:Руди Велтхейс теперь включил исправленную версию этого в свой превосходный "Подводные камни конвертации"-статья.
Другие советы
Уголок Руди в Дельфи это лучший ресурс, который я знаю относительно совместимости Delphi и C / C ++.Его Подводные камни конверсии это в значительной степени обязательное чтение при использовании C / C ++ API в Delphi.Глава, которая вас больше всего заинтересует, - это Записи и выравнивание -> Битовые поля, но я настоятельно призываю вас прочитать все это сверху донизу, дважды.Другие статьи, безусловно, тоже стоят потраченного времени.
Хорошо, мои битовые манипуляции немного подзабыты, так что я мог бы поменять местами байты.Но приведенный ниже код дает общую идею:
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;
Ну, по сути, вам нужно приступить к грязным манипуляциям с битами.
Почему, конкретно, вам нужно сохранить эту структуру?
Если вам нужно только пообщаться с устаревшей программой, которая либо разговаривает на этом диалекте (TCP / IP или аналогичном), либо хранит данные таким образом (файлы и т.д.), То я бы сопоставил обычную структуру Delphi с совместимой с битовой версией.Другими словами, я бы использовал обычно структурированную структуру Delphi в памяти и написал код для записи и чтения этой структуры совместимым образом.
Если вам нужно сэкономить память, я бы создал геттеры и сеттеры, которые манипулируют битами внутренних целых чисел или чем-то подобным.Это повлияет на производительность, но не намного больше, чем было бы у оригинальной программы на C, единственное отличие заключается в том, что в версии C манипуляция с битами будет добавлена с помощью магии компилятора, тогда как вам придется написать это самостоятельно.
Если у вас не так много записей в памяти и вам не нужно обращаться к другой программе, я бы использовал естественную структуру Delphi.Компромиссом для повышения производительности будет увеличение объема используемой памяти.
Но все зависит от ваших критериев.
В любом случае, вы не сможете уговорить компилятор Delphi выполнить за вас ту же работу, что и компилятор C.
УПАКОВАННАЯ ЗАПИСЬ, предложенная другим здесь, этого не делает, и никогда не предназначалась для этого.Это только удалит выравнивание, чтобы поместить целые числа в 32-разрядные границы и тому подобное, но не будет упаковывать несколько полей в один байт.
Обратите внимание, что распространенным способом сделать это является использование наборов Delphi, которые реализуются внутренне с использованием битовых полей.Опять же, у вас будет код, отличный от варианта C.