Delphiレコードのビットフィールドをシミュレートする方法は?
-
08-07-2019 - |
質問
Delphiで、Cと同じレイアウトを含むレコードを宣言したい。
関心のある方へ:このレコードは、Windows OSのLDT_ENTRYレコードの共用体の一部です。 (DelphiでXboxエミュレーターで作業しているため、Delphiでこのレコードを使用する必要があります-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;
しかし残念なことに、サイズは予想される4バイトではなく10バイトになります。 レコードを宣言する方法を知りたいので、同じレイアウト、同じサイズ、同じメンバーのレコードを取得します。ゲッター/セッターの負荷なしにできます。
TIA。
解決
みんなありがとう!
この情報に基づいて、これを次のように減らしました:
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 <!> lt; = NrBits <!> lt; = 32および0 <!> lt; = BitOffset <!> lt; = 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は、この優れたにこの改訂版を含めました。 <!> quot;変換の落とし穴<!> quot;-記事。
他のヒント
Rudy's Delphi Corner は、DelphiとC / C ++の相互運用性に関して私が知っている最高のリソースです。彼の変換の落とし穴は、DelphiでC / C ++ APIを使用する際に読む必要のあるものです。最も興味がある章は、レコードと配置-<!> gt;です。ビットフィールドですが、すべてを上から下に読むことをお勧めします。 2回。他の記事も時間投資の価値があります。
さて、私のビット操作は少しさびているので、バイトを逆にすることができました。ただし、以下のコードは一般的な考え方を示しています。
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バージョンのコンパイラマジックによってビット操作が追加されることです。 / p>
メモリに多くのレコードがなく、別のプログラムと話す必要がない場合は、自然なDelphi構造を使用します。より高いパフォーマンスとのトレードオフは、より多くのメモリが使用されることです。
しかし、それはすべてあなたの基準に依存します。
いずれにせよ、DelphiコンパイラにCコンパイラと同じ仕事をさせることはできません。
ここで別の人が提案したPACKED RECORDはそれを行わず、意図されていませんでした。 32ビット境界などに整数を配置するためにアライメントパディングを削除するだけですが、複数のフィールドを1バイトにパックすることはありません。
これを行う一般的な方法は、ビットフィールドを使用して内部的に実装しているDelphi SETSを使用することです。繰り返しますが、Cバリアントとは異なるコードになります。