Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2021年02月15日] [更新 2021年02月15日] Delphi 一般・その他
   
PChar 型の変数と関数の PChar 型の引数
動作確認等 Windows 7 U64(SP1) + Delphi XE5(UP2) Pro VCL-32
[整理番号 - 0754]







PChar 型の変数と関数や手続きの PChar 型の引数


PChar 型の変数の基本的な動作の確認と,関数や手続きの PChar 型の引数に関するサンプルです.
PChar 型の引数に,Char 型の配列,または String 型の文字列を渡して,関数や手続き内の処理結果としての文字列を PChar 型の引数で受け取る例です.



Windows API の関数類で文字列を扱う場合,PChar 型や Char 型の静的配列で文字列を扱わなければならないことがあります.
Windows API の関数ではなく,自作の関数で関数や手続きの引数で文字列を扱うのあれば,String 型を引数にすると便利です.予約語 var を使用すれば,関数や手続き内での処理の結果を引数から取得することができます.




図1
String 型の引数を持つ手続き
  • 手続き内での処理結果を引数で受け取る

リスト1
//-----------------------------------------------------------------------------
//  String 型の引数を持つ手続きの例
//-----------------------------------------------------------------------------
procedure TestProc(var AStr: string);
begin
  AStr := AStr + ' + 追加文字列';
end;

//=============================================================================
//  関数や手続きの String 型の引数に String 型の変数を渡す例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStr : string;
begin
  Memo1.Lines.Clear;

  LStr := '日本語';
  TestProc(LStr);

  Memo1.Lines.Add(LStr);
end;




01_PChar 型と String 型の変数


String 型の変数は,PChar でキャストすれば PChar 型の変数に代入できます.PChar 型の変数はそのまま String 型の変数に代入できます.




図2
PChar 型の変数と PChar 型の変数の代入操作

リスト2
//=============================================================================
//  PChar 型と String 型は相互に代入可能
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LPChar : PChar;
  LText  : string;
begin
  Memo1.Lines.Clear;

  // String 型の文字列を PChar 型の文字列にする
  LText := 'テスト文字列';
  LPChar := PChar(LText);
  Memo1.Lines.Add(LpChar);

  // PChar 型の文字列を String 型の文字列にする
  LPChar := '日本語の文字列';
  LText  := LPChar;
  Memo1.Lines.Add(LText);
end;


PChar 型の文字列は,String 型と同じように,文字 (エレメント) 単位の取得操作ができます.後述のメモリの確保した PChar の文字列に対しては,代入も可能です.
PChar 型の文字列の要素のインデックスは 0 ベースです.




図3
PChar 型の文字列の 1 文字を取得する例

リスト3
procedure TForm1.Button1Click(Sender: TObject);
var
  LPChar : PChar;
begin
  Memo1.Lines.Clear;

  LPChar := 'テスト文字列';
  Memo1.Lines.Add(LPChar);

  // String 型と違って 0 ベース
  Memo1.Lines.Add(LPChar[0]);
  Memo1.Lines.Add(LPChar[3]);
end;




02_PChar 型の変数におけるポインタの移動


PChar 型は Null 終端文字列のポインタ型です.ポインタ型ですから inc 手続きや dec 手続きを使用してポインタとしての移動ができます.




図4
PChar 型におけるポインタの移動

リスト4
procedure TForm1.Button1Click(Sender: TObject);
var
  LPChar : PChar;
begin
  Memo1.Lines.Clear;

  LPChar := 'テスト文字列';
  Memo1.Lines.Add(LPChar);

  Inc(LPChar, 3);
  Memo1.Lines.Add(LPChar);

  // ポインタを 3 つ戻す (Dec 手続き使用)
  Dec(LPChar, 3);
  Memo1.Lines.Add(LPChar);
end;


ポインタの移動は,次のようなコードでも可能です.




図5
PChar 型におけるポインタの移動

リスト5
procedure TForm1.Button1Click(Sender: TObject);
var
  LPChar : PChar;
begin
  Memo1.Lines.Clear;

  LPChar := 'テスト文字列';
  Memo1.Lines.Add(LPChar);

  // ポインタを 3 つ進める
  LPChar := LPChar + 3;
  Memo1.Lines.Add(LPChar);

  // ポインタを 3 つ戻す
  LPChar := LPChar - 3;
  Memo1.Lines.Add(LPChar);
end;




03_PChar 型の変数の逆参照


PChar 型はポインタ型ですから逆参照の機能が利用できます.ただし,PChar の逆参照は 1 文字 (エレメント) だけの参照です.




図6
PChar 型の逆参照

リスト6
procedure TForm1.Button1Click(Sender: TObject);
var
  LPChar : PChar;
  LChar  : Char;
begin
  Memo1.Lines.Clear;

  LPChar := 'テスト文字列';
  Memo1.Lines.Add(LPChar);

  // 逆参照は Char 型の文字 ( Char 型は 1 文字の型)
  // 逆参照で PChar 型の変数には代入できない
  LChar := LPChar^;
  Memo1.Lines.Add(LChar);

  LPChar := LPChar + 5;
  LChar := LPChar^;
  Memo1.Lines.Add(LChar);
end;


PString 型は String 型のポインタ型です.PString 型の逆参照は文字列全体です.




P 図7
PString 型の逆参照

リスト7
procedure TForm1.Button1Click(Sender: TObject);
var
  LText    : string;
  LPString : PString;
begin
  Memo1.Lines.Clear;
  LText := 'テスト文字列';
  Memo1.Lines.Add(LText);

  // LPString := PString(LText); は不可.LPString 用のメモリの確保が必要.
  // LPString^ := 'テスト文字列'; も不可.
  LPString := @LText;
  Memo1.Lines.Add(LPString^);
end;




04_PChar 型の変数用のメモリ確保


PChar 型の文字列を記憶するメモリ領域 (メモリブロック) を確保して PChar 型の文字列を処理する例です.Windows API の PChar 型の引数に文字列データを渡す場合に必要となることがあります.
確保したメモリブロックのサイズを超える長さの文字列は扱えません.確保したメモリは解放処理が必要です.
確保したメモリを解放しても,PChar 型の変数は nil にはなりません.明示的に nil にするには, PChar 型の変数に nil を代入します.




図8
PChar 型の変数用のメモリを確保して操作する例
  • 文字列の書き込みは StrPCopy 関数等を使用する

リスト8
procedure TForm1.Button1Click(Sender: TObject);
var
  LText  : string;
  LPChar : PChar;
begin
  Memo1.Lines.Clear;

  LText := 'テスト文字列';

  // メモリの確保 (バイト単位)
  // GetMem てもよいが GetMem は確保したメモリを初期化しない
  LPChar := AllocMem(100);
  try
    // PChar の文字列に文字列をコピー
    StrPCopy(LPChar, LText);

    // 固定したメモリアドレスを使用する PChar 型の変数への直接代入
    // 代入そのものは可能だが確保したメモリが解放できなくなる
    // 文字列を直接代入するとデータの格納アドレスが変わる
    // LPChar := '別の文字列';
    Memo1.Lines.Add(LPChar);

    // 途中から追加する例 (挿入ではない)
    StrPCopy(LPChar + 3, '日本');
    Memo1.Lines.Add(LPChar);
  finally
    // 確保したメモリを解放
    FreeMem(LPChar);
  end;
end;


PChr 型の文字列は,代入する文字列が変わると,文字列のデータを記憶するメモリアドレスも変わります.PChar 型の変数用のメモリを確保すると,固定したアドレスに対する操作となります.

固定したメモリアドレスを使用する PChar 型の変数に文字列を直接代入したり,String 型の文字列を PChar でキャストして代入してしまうと,確保したメモリとは別の場所に文字列が書き込まれてしまいます.その結果,確保したメモリ領域が解放できなくなり,メモリリークが発生します.
この時,メモリの解放処理の部分で「無効なポインタ操作」のエラーが発生します.
確保したメモリを解放した後であれば,文字列を直接代入しても構いません.




図9
メモリ確保の PChar 型の変数に文字列を直接代入
  • 確保したメモリが解放できなくなる

メモリを確保した PChar 型の変数に文字列を書き込むには,上のサンプルように,StrPCopy 関数等を使用します.StrPCopy 関数では Char 型の文字配列の文字列も書き込めます.




図10
Char 型の文字配列の文字列を書き込む

リスト9
procedure TForm1.Button1Click(Sender: TObject);
var
  LPChar     : PChar;
  LCharArray : array [0..100] of Char;
begin
  Memo1.Lines.Clear;

  // メモリの確保 (バイト単位)
  // GetMem てもよいが GetMem は確保したメモリを初期化しない
  LPChar := AllocMem(100);
  try
    // 文字配列に文字列をセット
    FillChar(LCharArray, Sizeof(LCharArray), #0);
    StrCopy(LCharArray, '日本語 ABC 文字列');

    // Char 型の文字配列の文字゛を PChar 型の変数にコピー
    StrPCopy(LPChar, LCharArray);
    Memo1.Lines.Add(LPChar);
  finally
    // 確保したメモリを解放
    FreeMem(LPChar);
  end;
end;


メモリを確保した PChar 型の変数には,文字 (エレメント) 単位で文字を書き込むこともできます.
String 型の変数は,メモリを確保しなくても任意のインデックスの文字を変更できます.




図11
任意の位置の文字を変更
  • 4 番目の文字だけを変更

リスト10
procedure TForm1.Button1Click(Sender: TObject);
var
  LPChar : PChar;
begin
  Memo1.Lines.Clear;

  // メモリの確保 (バイト単位)
  LPChar := AllocMem(100);
  try
    StrPCopy(LPChar,  'テスト文字列');
    Memo1.Lines.Add(LPChar);

    LPChar[3] := '日';
    Memo1.Lines.Add(LPChar);

    // ポインタ型の逆参照で文字を代入
    PChar(LPChar + 3)^ := '東';
    Memo1.Lines.Add(LPChar);
  finally
    // 確保したメモリを解放
    FreeMem(LPChar);
  end;
end;




05_関数や手続きの PChar 型の引数


PChar 型の変数も,数値型や String 型と同じように関数や手続きの引数として使用できます.以下はその例です.関数や手続き内では,引数である PChar 型の値を使用した各種の処理ができます.
PChar 型はポインタ型ですから,予約語 var を使用しなくても,関数や手続き内の処理結果を引数から受け取ることができますが,このサンプルではできません.




図12
PChar 型の引数を持つ手続き

リスト11
//-----------------------------------------------------------------------------
//  PChar 型の変数を引数に持つ手続きの例
//-----------------------------------------------------------------------------
procedure TestProc(ApChar: PChar);
begin
  ApChar := '手続き内で取得した文字列';
end;

//=============================================================================
//  関数や手続きに PChar 型の変数を渡す例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LPChar : PChar;
begin
  Memo1.Lines.Clear;

  LPChar := '関数に渡す文字列';

  TestProc(LPChar);
  Memo1.Lines.Add(LPChar);
end;




06_関数や手続き内の処理結果を PChar 型の引数で受け取る


関数や手続き内の処理結果としての文字列を PChar 型の引数で受け取るには,引数の PChar 型の変数のメモリ領域を確保します.その例です.
メモリを確保した PChar 型の変数の扱いについては,メモリの確保で述べた通りです.




図13
手続き内の処理結果を PChar 型の引数で受け取る
  • メモリを確保した PChar 型の変数を引数にする

リスト12
//-----------------------------------------------------------------------------
//  PChar 型の変数を引数に持つ手続きの例
//-----------------------------------------------------------------------------
procedure TestProc(ApChar: PChar);
var
  LText  : string;
  LPChar : PChar;
begin
  LText  := ' + 手続き内で追加';
  LPChar := '文字列';

  ApChar := ApChar + 3;
  StrPCopy(ApChar, LText);

  StrCat(ApChar, LPChar);
end;

//=============================================================================
//  関数や手続きの処理結果を PChar 型の引数から取得する例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LPChar : PChar;
begin
  Memo1.Lines.Clear;

  // メモリの確保 (バイト単位)
  LPChar := AllocMem(100);
  try
    StrPCopy(LPChar, 'テスト文字列');

    // 関数を実行
    TestProc(LPChar);
    Memo1.Lines.Add(LPChar);
  finally
    // 確保したメモリを解放
    FreeMem(LPChar);
  end;
end;


PChar 型のメモリを確保しても,その変数に直接文字列を代入すると,文字列データを書き込むアドレスが変わってしまいます.その結果,確保したメモリ領域には文字列は書き込まれません.




図14
メモリを確保した PChr 型の変数に直接文字列を代入した場合

リスト13
//-----------------------------------------------------------------------------
//  PChar 型の変数を引数に持つ手続きの例
//-----------------------------------------------------------------------------
procedure TestProc(ApChar: PChar);
var
  LText : string;
begin
  LText := '手続き内で取得した文字列';
  ApChar := PChar(LText);
end;

//=============================================================================
//  関数や手続きの処理結果を PChar 型の引数から取得する例
//  戻り値として手続きからの出力がない例 (引数は入力専用)
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LPChar : PChar;
begin
  Memo1.Lines.Clear;

  LPChar := AllocMem(100);
  try
    TestProc(LPChar);
    Memo1.Lines.Add(LPChar);
  finally
    FreeMem(LPChar);
  end;
end;


メモリを確保していない PChar 型の変数に対じて,StrPCopy 等の Null 終端文字列用のルーチンを実行すると例外が発生します.
PChar 型の引数で結果を受け取るようになっている Windows API の関数を使用する時は,その変数のメモリの確保が必要です.



リスト14
//-----------------------------------------------------------------------------
//  PChar 型の変数を引数に持つ手続きの例
//-----------------------------------------------------------------------------
procedure TestProc(ApChar: PChar);
var
  LText : string;
begin
  LText := '手続き内の文字列';
  // 以下で例外が発生する
  StrPCopy(ApChar, LText);
end;

//=============================================================================
//  関数や手続きの処理結果を PChar 型の引数から取得する例
//  例外発生
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LPChar : PChar;
begin
  Memo1.Lines.Clear;

  LPChar := '手続きに渡す文字列';
  TestProc(LPChar);
  Memo1.Lines.Add(LPChar);
end;




07_関数や手続きの PChar 型の引数に Char 型の静的配列を渡す


関数や手続きの PChar 型の引数に Char 型の静的配列を渡す例です.




図15
PChar 型の引数に Char 型の静的配列を渡す

リスト15
//-----------------------------------------------------------------------------
//  PChar 型の変数を引数に持つ手続きの例
//-----------------------------------------------------------------------------
procedure TestProc(ApChar: PChar);
var
  LText : string;
begin
  LText := '手続き内で取得した文字列';

  // PChar 型の文字列 (データ格納場所のメモリ) に文字列をコピー
  StrPCopy(ApChar, LText);
end;

//=============================================================================
//  関数や手続きの処理結果を PChar 型の引数から取得する例
//  引数に Char 型の静的配列を渡す例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LBuffer : array [0..100] of Char;
begin
  Memo1.Lines.Clear;

  // 初期化
  FillChar(LBuffer, Sizeof(LBuffer), #0);

  TestProc(@LBuffer);
  Memo1.Lines.Add(LBuffer);
end;




08_関数や手続きの PChar 型の引数に Char 型の動的配列を渡す


関数や手続きの PChar 型の引数に Char 型の動的配列を渡す例です.
動的配列はポインタです.先頭の文字が書き込まれているアドレスを引数に渡します.




図16
PChar 型の引数に Char 型の動的配列を渡す

リスト16
//-----------------------------------------------------------------------------
//  PChar 型の変数を引数に持つ手続きの例
//-----------------------------------------------------------------------------
procedure TestProc(ApChar: PChar);
var
  LText : string;
begin
  LText := '手続き内で取得した文字列';

  // PChar 型の文字列 (データ格納場所のメモリ) に文字列をコピー
  StrPCopy(ApChar, LText);
end;

//=============================================================================
//  関数や手続きの処理結果を PChar 型の引数から取得する例
//  引数に Char 型の動的配列を渡す例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LBuffer : array of Char;
begin
  Memo1.Lines.Clear;

  SetLength(LBuffer, 100);

  // 動的な Char 型配列の場合インデックスの指定が必要
  TestProc(@LBuffer[0]);

  // Memo1.Lines.Add(PChar(LBuffer)); でもよい
  Memo1.Lines.Add(string(LBuffer));
end;




09_関数や手続きの PChar 型の引数に String 型の変数を渡す


関数や手続きの PChar 型の引数に String 型の文字列を渡す例です.
Delphi の String 型はポインタです.先頭の文字のアドレスを渡すか,あるいは PChar でキャストして渡します.




図17
PChar 型の引数に String 型の文字列を渡す

リスト17
//-----------------------------------------------------------------------------
//  PChar 型の変数を引数に持つ手続きの例
//-----------------------------------------------------------------------------
procedure TestProc(ApChar: PChar);
var
  LText : string;
begin
  LText := '手続き内で取得した文字列';

  // PChar 型の文字列 (データ格納場所のメモリ) に文字列をコピー
  StrPCopy(ApChar, LText);
end;

//=============================================================================
//  関数や手続きの処理結果を PChar 型の引数から取得する例
//  引数に String 型の変数を渡す例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LBuffer  : string;
begin
  Memo1.Lines.Clear;

  SetLength(LBuffer, 100);

  // TestProc(@LBuffer[1]); でもよい
  TestProc(PChar(LBuffer));     
  Memo1.Lines.Add(LBuffer);
end;


SetLength で確保した String 型のメモリアドレスに文字列を書き込むには,MoveChars 手続きが利用できます.SetLength でメモリ領域を確保した直後に文字列データを書き込み時は,末尾に Null 終端文字が必要です.




図18
SetLength で確保したメモリアドレスに文字列データを書き込む

リスト18
//-----------------------------------------------------------------------------
//  PChar 型の変数を引数に持つ手続きの例
//-----------------------------------------------------------------------------
procedure TestProc(ApChar: PChar);
var
  LText : PChar;
begin
  LText := ' + 手続き内の文字列';
  StrCat(ApChar, PChar(LText));
end;

//=============================================================================
//  関数や手続きの処理結果を PChar 型の引数から取得する例
//  引数に String 型の変数を渡す例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LBuffer  : string;
  LText    : string;
begin

  Memo1.Lines.Clear;

  SetLength(LBuffer, 100);

  // 末尾に Null 文字が必要
  LText := 'テスト文字列'+ #0;
  MoveChars(LText[1], LBuffer[1], Length(LText));

  TestProc(@LBuffer[1]);
  Memo1.Lines.Add(LBuffer);
end;