3

私はDelphi 2009を使用していますが、それが私がしていることに大きな影響を与えているわけではありません。私がまだ2007にいたら、同じことに出くわすと思います。

データをポインターに出力する scsi 呼び出しがあります (間違った見方をしていますが、それを説明するのに苦労しています)。

もともとMoveを使用して、戻ってきたデータをByteの静的配列に入力しましたが、呼び出し時に長さがわかっている動的配列に切り替えたいと思います。私はさまざまな結果でいくつかのことを試しましたが、データを取得するものもあれば、アクセス違反が発生するものもあれば、エラーはありませんが無効なデータを取得するものもあります。

配列にsetlengthを追加してからmoveを使用すると、最初に設定された長さの空の配列があり、次に、移動後のデバッガーで、静的だったときのようにOutputData[0]のようにデータにアクセスできなくなりますすべてが手に負えない値または何でもとして表示されます。

以下は、反対が動的配列を取り、そのアドレスをポインターに与えたという記事を読んだ後に試したものです。データを孤立させるなどの間違いを犯すことについて言及しました。

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;

eoutput データが文字列や 16 進数などで出力されるために使用されるその他のさまざまなものがあります。

とにかく、ポインターを取得してそのデータを動的配列に入れ、配列をアドレス指定する方法でそのデータを取得するにはどうすればよいでしょうか。

ありがとう。

4

4 に答える 4

10

プロシージャで動的配列を使用するには、配列のMove最初の要素を渡す必要があります。例えば:

var
  Source: Pointer;
  SourceSize: Integer;
  Destination: array of Byte;

SetLength(Destination, SourceSize);
Move(Source^, Destination[0], SourceSize);

2 番目のパラメーターがポインターを逆参照することにも注意してください。これは、値へのポインターではなく、コピーしているMoveを取るためです。ポインタが指すものをコピーしているので、それを に渡す必要があります。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スコープ外になり、配列への参照が他にない場合、コンパイラは動的配列の割り当てを解除するコードを挿入します。このコードは、動的配列を取り、通常のバッファーであるかのように渡します。動的配列は事実上、単純な古いバッファのサブタイプであるため、これは機能し、安全です。関数は、配列の最初の要素へのポインターを受け入れ、そのポインターを一連のバイトへのポインターとして扱います。関数は、プログラムが動的配列のブックキーピングに使用するバイトに隣接する追加の要素があることを知る必要はありません。


バッファーにデータがあり、データを別のデータ構造にコピーするのではなく、そのバッファーを配列のように扱いたい場合は、2 つのオプションがあります。

  1. static-array pointerを宣言してから、バッファー ポインターをその型に型キャストします。これは古典的な手法であり、あらゆる場所のコード、特に 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;
    

    ディレクティブは、配列型の長さが1であると宣言されているため、そのコードの$R範囲チェックがオフになっていることを確認することです。配列は、実際に宣言する必要がないという手がかりとして機能するように、そのサイズで宣言されていますその型の変数。ポインターを介してのみ使用してください。一方、データの適切な最大サイズがわかっている場合は、代わりにそのサイズを使用して配列型を宣言し、範囲チェックを有効にしておくことができます。(通常、範囲チェックを無効にしている場合は、トラブルを求めているだけです。)

  2. PByte代わりにバッファーを宣言し、任意のポインター型を配列ポインターとして扱うためPointerの Delphi の新しい (Delphi 2009 以降)サポートを使用します。以前のバージョンでは、、、およびのみがこの構文をサポートしていました。例えば:PCharPAnsiCharPWideChar

    var
      Output: PByte;
    
    for i := 0 to Pred(OutputLength.Value) do begin
      edtString.Text := edtString.Text + Chr(Output[i]);
    end;
    

    そのディレクティブが有効なときにその型が宣言さ$POINTERMATHれるため、この機能を有効にするためにコンパイラ ディレクティブは必要ありません。他のポインター型でC のようなポインター操作を行いたい場合は、新しい拡張構文を使用するコードの前に配置します。PByte{$POINTERMATH ON}


最後に、文字列を一度に 1 文字ずつ作成する必要はありません。2つの意味で無駄です。まず、多数の文字列を作成しています。それぞれの文字列は、前の文字列よりわずか 2 バイト大きくなっています。2 つ目は、文字列の結果をエディット コントロールに格納しているため、そのコントロールの OS 実装に一連の新しい文字列の割り当ても強制しています。データを1 つの文字列に入れ、編集コントロールに一度に追加します。

var
  OutputString: AnsiString;

SetString(OutputString, PAnsiChar(Buffer), OutputLength.Value);
edtString.Text := edtString.Text + OutputString;
于 2009-03-10T07:24:40.370 に答える
0

いずれにせよ、出力の描画に時間がかかる理由は、edtString.text の割り当てを見ているからです。これは、ループではなく、一度だけ割り当てる必要があります。再割り当てするたびに、文字列の連結から画面上の OS の描画に至るまで、多くのレベルのものを処理する必要があります。最初に文字列を作成し、最悪の場合は最後に割り当てることができます。

于 2009-11-02T08:08:17.070 に答える
0

気にしないでください...笑これを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;
于 2009-03-10T05:40:44.173 に答える
0

PByte を使用できます。{$POINTERMATH ON} ディレクティブを使用すると、このポインターをバイト配列として使用できます。

{$POINTERMATH ON}
var
  Output: Pointer;
  ar: PByte;
begin
  GetMem(Output, 100);
  ar:=Output;
  ShowMessage(IntToStr(ar[0])+IntToStr(ar[1])+'...');
end;
于 2009-03-10T06:35:15.900 に答える