Delphi-型なしポインタから入力された動的配列からデータにアクセス
質問
私は Delphi 2009 を使用していますが、それは私がやっていることに大きな影響を与えるということではありません。まだ 2007 を使用していた場合、同じ問題に遭遇するでしょう。
ポインターにデータを出力するscsi呼び出しがあります(見方が間違っていますが、説明に問題があります)。
元々は Move を使用して Static Array of Byte に戻ってきたデータを入力していましたが、 Dynamic Array の長さは通話時にわかっています。さまざまな結果でいくつかのことを試しましたが、いくつかはデータを取得しますが、不正なアクセス違反があり、他はエラーがありませんが無効なデータを取得します。
配列に setlength を追加してから move を使用すると、最初に設定された長さの空の配列が作成され、次に<のような方法でデータにアクセスできなくなりますstrong> OutputData [0] 静的なときと同じように、移動後のデバッガーでは、すべてがアクセス不可能な値などとして表示されます。
以下は、opositが動的配列を取り、そのアドレスにポインターを与えた記事を読んだ後に試したものです。データを孤立させるような間違いを犯すことについて言及しました。
var
Output: Pointer;
OutputData: Array of byte;
I: Integer;
begin
GetMem(Output, OutputLength.Value);
if SendPSPQuery(Char(DriveLetter[1]), cbxQuery.Items.IndexOf(cbxQuery.Text), Output, OutputLength.Value) = 0 then
begin
OutputData := @Output;
for I := 0 to OutputLength.Value - 1 do
begin
edtString.Text := edtString.Text + Char(OutputData[I]);
end;
他にも、出力データを使用して文字列や16進数などで出力されるさまざまなものがあります。
とにかく、どのようにしてポインターを取得してそのデータを動的配列に入れてから、配列をアドレス指定する方法でそのデータを取得できますか。
ありがとう。
解決
Move
プロシージャで動的配列を使用するには、配列の最初の要素を渡す必要があります。例:
var
Source: Pointer;
SourceSize: Integer;
Destination: array of Byte;
SetLength(Destination, SourceSize);
Move(Source^, Destination[0], SourceSize);
2番目のパラメーターがポインターを逆参照することにも注意してください。これは、 Move
が値へのポインタではなく、コピーする value を取るためです。ポインターが指すものをコピーしているので、 Move
に渡す必要があります。
ちなみに、 Destination
が静的配列でも同じ構文が機能します。そして、これはDelphi 2009に固有のものではないことは確かです。動的配列が導入されたDelphi 4にまでさかのぼります。また、 Move
は、同じ奇妙な型指定されていないパラメーター構文を永遠に保持しています。 。
GetMem
を使用して独自のメモリを割り当ててから、型キャストしてコンパイラに自分の持っているものが動的配列であると認識させないでください。 違います。動的配列には、通常のバイトバッファーにはない参照カウントと長さフィールドがあり、想定される動的配列にアクセスするためにコンパイラーが生成するすべてのコードを制御できないため、プログラムがアクセスしようとする危険がありますデータ構造の存在しないデータ。
PSP関数にそのデータを動的配列に直接保存させることができます。これを行うコードを次に示します。
var
Output: array of Byte;
SetLength(Output, OutputLength.Value);
if SendPSPQuery(Char(DriveLetter[1]),
cbxQuery.Items.IndexOf(cbxQuery.Text),
@Output[0],
OutputLength.Value) = 0
then
後でメモリを解放する必要はありません。コンパイラは、 Output
がスコープ外になり、配列への他の参照がない場合、動的配列の割り当てを解除するコードを挿入します。このコードは動的配列を取り、通常のバッファーであるかのように渡します。これは、動的配列が実際には単純な古いバッファのサブタイプであるため、機能し、安全です。この関数は、配列の最初の要素へのポインタを受け入れ、それがまさにそれであるため、ポインタをバイトの束へのポインタとして扱います。この関数は、プログラムが動的配列のブックキーピングに使用するバイトに隣接して追加のものがあることを知る必要はありません。
データをバッファに格納し、そのバッファを it が配列であるかのように扱いたい場合、データを別のデータ構造にコピーする代わりに、2つのオプションがあります。
-
静的配列ポインターを宣言し、バッファーポインターをその型に型キャストします。これは古典的な手法であり、あらゆる場所のコード、特にDelphi 4より前のコードで使用されていることがわかります。次に例を示します。
type PByteArray = ^TByteArray; TByteArray = array[0..0] of Byte; var ByteArray: PByteArray; ByteArray := PByteArray(Output); for i := 0 to Pred(OutputLength.Value) do begin {$R-} edtString.Text := edtString.Text + Chr(ByteArray[i]); {$R+} end;
$ R
ディレクティブは、配列タイプの長さが1であると宣言されているため、そのコードの範囲チェックがオフになっていることを確認するためのものです。そのタイプの変数を実際に宣言することになっていないことの手がかりとして。ポインタを介してのみ使用してください。一方、データの適切な最大サイズがわかっている場合は、代わりにそのサイズを使用して配列型を宣言し、範囲チェックをオンのままにすることができます。 (通常、範囲チェックを無効のままにすると、単にトラブルを求めていることになります。) -
バッファを
Pointer
ではなくPByte
として宣言し、Delphiの新しい(Delphi 2009以降)任意のポインタータイプを配列ポインターとして処理するためのサポート。以前のバージョンでは、PChar
、PAnsiChar
、およびPWideChar
のみがこの構文をサポートしていました。例:var Output: PByte; for i := 0 to Pred(OutputLength.Value) do begin edtString.Text := edtString.Text + Chr(Output[i]); end;
$ POINTERMATH
コンパイラディレクティブは、PByte
のこの機能を有効にするために必要ではありません。なぜなら、そのタイプは、ディレクティブが有効な間は宣言するからです。 Cのようなポインター操作をで行いたい場合
他のヒント
ネバーマインド... 2時間半のこれをいじってから、最終的に何かを見つけました....
type
PDynByteArray = ^TDynByteArray;
TDynByteArray = array of byte;
procedure TfrmMain.btnQueryClick(Sender: TObject);
var
Output: Pointer;
OutputData: PDynByteArray;
WorkingData: Array of byte;
DriveLetter: ShortString;
I: Integer;
HexOutput: String;
begin
edtSTRING.Clear;
memHEX.Clear;
GetMem(Output, OutputLength.Value);
DriveLetter := edtDrive.Text;
if SendPSPQuery(Char(DriveLetter[1]), cbxQuery.Items.IndexOf(cbxQuery.Text), Output, OutputLength.Value) = 0 then
begin
//Move(Output^,OutputData,56);
OutputData := PDynByteArray(@Output);
for I := 0 to OutputLength.Value - 1 do
begin
edtString.Text := edtString.Text + Char(OutputData^[I]);
end;
for I := 0 to OutputLength.Value - 1 do
begin
HexOutput := HexOutput + InttoHex(OutputData^[I],2) + ' ';
end;
memHex.Lines.Append(HexOutput);
FreeMem(Output);
memHex.SelStart := 0;
end
else edtSTRING.Text := 'SCSI Command Failed';
end;
PByteを使用できます。 {$ POINTERMATH ON}ディレクティブを使用すると、このポインターをバイトの配列として使用できます。
{$POINTERMATH ON}
var
Output: Pointer;
ar: PByte;
begin
GetMem(Output, 100);
ar:=Output;
ShowMessage(IntToStr(ar[0])+IntToStr(ar[1])+'...');
end;
とにかく、出力図面に時間がかかる理由は、edtString.text割り当てを見ているためです。これは、ループではなく、1回だけ割り当てる必要があります。それを再割り当てするたびに、文字列の連結から画面上のOS描画まで、多くのレベルの処理を行う必要があります。最初に文字列を作成し、最悪の場合は最後に文字列を割り当てることができます。