簡體   English   中英

Delphi - 從由無類型指針填充的動態數組訪問數據

[英]Delphi - Accessing data from dynamic array that is populated from an untyped Pointer

我正在使用Delphi 2009並不是說它對我正在做的事情有很大影響。 如果我還在2007 年,我想我會遇到同樣的情況。

我有一個將數據輸出到指針的 scsi 調用(錯誤的查看方式,但我無法解釋)。

最初我使用Move用返回的數據填充Static 字節數組,但我想切換到在調用時已知長度的動態數組 我已經嘗試了幾件事,結果各不相同,有些得到了數據,但有瘋狂的訪問沖突,有些沒有錯誤,但得到了無效的數據。

setlength添加到數組,然后使用move ,首先導致設置長度的空數組,然后第二個無法像我在 static 時那樣通過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;

輸出數據還有很多其他用途,因為它以字符串和十六進制等形式輸出。

無論如何,我如何使用指針將該數據放入動態數組中,然后以您尋址數組的方式獲取該數據。

謝謝。

要將動態數組與Move過程一起使用,您需要傳遞數組的第一個元素 例如:

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

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

另請注意,第二個參數取消引用指針。 那是因為Move采用您正在復制的,而不是指向該值的指針。 您正在復制指針指向的內容,因此這就是您需要傳遞給Move的內容。

順便說一句,如果Destination也是 static 數組,則相同的語法也有效。 你說得對,這不是 Delphi 2009 所特有的。一直到 Delphi 4 時都是如此,這是引入動態 arrays 的時候。 並且Move永遠具有相同的奇怪的無類型參數語法。


不要使用 GetMem 分配您自己的GetMem然后進行類型轉換以使編譯器認為您擁有的是動態數組。 不是 動態 arrays 具有普通字節緩沖區所沒有的引用計數和長度字段,並且由於您無法控制編譯器為訪問假定的動態數組而生成的所有代碼,因此您的程序存在嘗試訪問的危險數據結構中不存在的數據。

您可以讓 PSP function 將其數據直接存儲到動態數組中。 這是一些代碼:

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

之后無需釋放 memory; Output超出 scope 並且沒有其他對數組的引用時,編譯器會插入代碼以解除分配動態數組。 此代碼接受一個動態數組並將其作為普通緩沖區傳遞。 這有效且安全,因為動態數組實際上是普通舊緩沖區的子類型。 function 將接受指向數組第一個元素的指針,並將指針視為指向一堆字節的指針,因為這正是它的本質。 function 不需要知道與程序用於動態數組簿記的那些字節相鄰的其他內容。


如果您將數據放在緩沖區中,並且希望將該緩沖區視為數組,而不是將數據復制到單獨的數據結構中,那么您有兩種選擇。

  1. 聲明一個靜態數組指針,然后將緩沖區指針類型轉換為該類型。 這是經典的技術,您可以在各處的代碼中看到它,尤其是早於 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。該數組被聲明為具有該大小的部分原因是作為一個線索,你不是真的應該聲明該類型的變量。 只能通過指針使用它。 另一方面,如果您知道合適的最大數據大小,您可以使用該大小來聲明數組類型,然后您可以保持打開范圍檢查。 (如果您通常禁用范圍檢查,那么您只是在自找麻煩。)

  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編譯器指令不需要為PByte啟用此功能,因為該類型是在該指令有效時聲明的。 如果您想對其他指針類型執行類似 C 的指針操作,請將{$POINTERMATH ON}放在使用新擴展語法的代碼之前。


最后一點,您不需要一次構建一個字符的字符串。 浪費在兩個方面。 首先,您正在構建大量字符串,每個字符串僅比前一個字符串大兩個字節。 其次,由於您將字符串結果存儲在編輯控件中,因此您也強制該控件的操作系統實現分配一堆新字符串。 將您的數據放入一個字符串中,然后將 append 一次全部放入您的編輯控件:

var
  OutputString: AnsiString;

SetString(OutputString, PAnsiChar(Buffer), OutputLength.Value);
edtString.Text := edtString.Text + OutputString;

無論如何,output 繪圖需要時間的原因是因為您正在查看 edtString.text 分配。 這應該只分配一次,而不是循環分配。 每次重新分配它時,都必須處理許多級別的內容,從字符串連接一直到屏幕上的操作系統繪圖。 您可以先建立一個字符串,然后在最壞的情況下在最后分配它。

沒關系...哈哈,在搞砸了 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;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM