Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2011年12月24日] [更新 2012年03月09日] Delphi サンプルプログラム集
832_コールバック関数の引数
動作確認等 Windows XP(SP3) + Delphi XE Pro
Windows 7 U64(SP1) + Delphi XE Pro
832_CallbacArgument.zip [1,540 KB] 2011年12月24日版 (EXE 同梱)







コールバック関数の引数


コールバック関数とは、ある関数が実行中に呼び出す関数、あるいは呼出される関数のことです。この機能により、その「ある関数」の実行中の状況等が把握できることがあります。
コールバック関数は、多くの場合、その「ある関数」の引数で指定します。

フックのコールバック関数やその他のコールバック関数には、ユーザが定義して利用可能な引数がある場合があります。その使用例です。
この引数の型は 32 ビット版では、4 バイト (32 ビット) の整数値となっています。したがって、この引数に変数類のメモリアドレスを指定すれば、このメモリアドレスを介して、引数への代入、引数の値の取得操作ができることになります。32 ビット版の Delphi では、メモリアドレスを指定するポインタ型の値 (サイズ) は 32 ビットです。そのまま使用できます。64 ビット版の Delphi では、ポインタのサイズは 64 ビットす。64 ビット版へのコード移行には注意が必要です。





01_コールバック関数の引数 - 整数型


引数に整数型を使用する場合です。ここでは、サンプルプログラムの Form1 の子ウィンドウを列挙関数の EnumChildWindows 関数で列挙し、コールバック関数に渡したハンドル値と等しいウィンドウを検出したら検索を終了します。
コールバックに渡す時は、変数に @ (Addrでも同じ) をつけて変数のアドレスとします。コールバック関数の引数の型を PInteger と、ポインタ型としておきます。そうすると、メモリアドレスを示すポインタの逆参照 ( ^ ) で、その変数にアクセスできます。

整数型そのものについては、上の参考リンクの記事内にもリンクがありますが、以下は、その内の一部の個別の記事へのリンクです。



図1
設計時画面
  • このフォームの子ウィンドウを検索する
図2
実行結果

リスト1
コールバック関数の引数に整数型を使用する例
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    Panel1: TPanel;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//-----------------------------------------------------------------------------
//  EnumChildWindowsのコールバック関数
//  ここでは、このフォームの子ウィンドウを列挙
//-----------------------------------------------------------------------------
function EnumChildWndTestProc(hWindow: hWnd; lPar: PNativeInt):
  Boolean; Stdcall;
begin
  Result := True;

  //子ウィンドウのハンドル値が引数の値と同じだったら検索終了
  if hWindow = lPar^ then begin
    //10倍にして返す
    lPar^  := lPar^ * 10;
    Result := False;
  end;
end;

//=============================================================================
//  コールバック関数の引数に整数を使用する例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  TestValue : NativeInt;
begin
  //コールバックの関数に、Edit1のハンドル値を与えて子ウィンドウを検索
  TestValue := Edit1.Handle;
  EnumChildWindows(Handle, @EnumChildWndTestProc, LPARAM(@TestValue));

  //戻ってきた値は10倍になっているので元に戻して比較
  if (TestValue div 10) = Edit1.Handle then begin
    MessageBox(Handle, '同じ値です', 'テスト結果', MB_OK);
  end;
end;

end.


変数をポインタ型で定義して使用する場合は、以下のように、メモリ領域の確保が必要です。確保しないと、下図のようなエラーが発生します。
確保したポインタは必要なくなったら解放します。解放しないとメモリリークが発生します。


図3
メモリ確保しないとエラー

リスト2
変数をポインタ型で定義した場合は、メモリの確保が必要
//=============================================================================
//  コールバック関数の引数に整数を使用する例
//  変数をポイント型で定義した場合
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  pTestValue : PNativeInt;
begin
  //変数をポインタ型で定義した場合は、メモリを確保する
  New(pTestValue);

  //コールバックの関数に、Edit1のハンドル値を与えて子ウィンドウを検索
  pTestValue^ := Edit1.Handle;
  EnumChildWindows(Handle, @EnumChildWndTestProc, LPARAM(pTestValue));

  //戻ってきた値は10倍になっているので元に戻して比較
  if (pTestValue^ div 10) = Edit1.Handle then begin
    MessageBox(Handle, '同じ値です', 'テスト結果', MB_OK);
  end;

  //確保したポインタ変数領域のメモリは必ず破棄する
  Dispose(pTestValue);
end;


コールバック関数の引数を Integer, NativeInt, NativeUInt あるいは Cardinal 等にした場合は、PInteger, PNativeInt, PNativeUInt, PCardinal 等でキャストした値を逆参照して、その変数にアクセスすることになります。


リスト3
コールバック関数の引数に整数型を使用する例
//-----------------------------------------------------------------------------
//  EnumChildWindowsのコールバック関数
//  ここでは、このフォームの子ウィンドウを列挙
//-----------------------------------------------------------------------------
function EnumChildWndTestProc(hWindow: hWnd; lPar: NativeInt):
  Boolean; Stdcall;
begin
  Result := True;

  //子ウィンドウのハンドル値が引数の値と同じだったら検索終了
  if hWindow = PNativeInt(lPar)^ then begin
    //10倍にして返す
    PNativeInt(lPar)^  := PNativeInt(lPar)^ * 10;
    Result := False;
  end;
end;

//=============================================================================
//  コールバック関数の引数に整数を使用する例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  TestValue : NativeInt;
begin
  //コールバックの関数に、Edit1のハンドル値を与えて子ウィンドウを検索
  TestValue := Edit1.Handle;
  EnumChildWindows(Handle, @EnumChildWndTestProc, LPARAM(@TestValue));

  //戻ってきた値は10倍になっているので元に戻して比較
  if (TestValue div 10) = Edit1.Handle then begin
    MessageBox(Handle, '同じ値です', 'テスト結果', MB_OK);
  end;
end;




02_コールバック関数の引数 - 文字列型


引数に文字列型を使用する場合です。ここでは、サンプルプログラムの Form1 の子ウィンドウを列挙関数の EnumChildWindows 関数で列挙し、コールバック関数に渡した文字列と同じウィンドウのタイトルを検出したら検索を終了します。
コールバックに渡す時は、変数に @ (Addr でも同じ) をつけて変数のアドレスとします。コールバック関数の引数の型を PString と、ポインタ型としておきます。そうすると、メモリアドレスを示すポインタの逆参照 ( ^ ) で、その変数にアクセスできます。


図4
設計時画面
図5
実行結果

リスト4
コールバック関数の引数に文字列型を使用する例
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    Panel1: TPanel;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//-----------------------------------------------------------------------------
//  ウィンドウのタイトルを取得する関数
//-----------------------------------------------------------------------------
function GetWindowTitleStr(hWindow: HWND): String;
var
  Buffer : array[0..MAX_PATH-1] of Char;
  Len    : Integer;
begin
  FillChar(Buffer, SizeOf(Buffer), #0);
  Len := GetWindowText(hWindow, Buffer, Length(Buffer));
  if Len > 0 then Result := Buffer else Result := '';
end;

//-----------------------------------------------------------------------------
//  EnumChildWindowsのコールバック関数
//  このフォームの子ウィンドウの列挙
//-----------------------------------------------------------------------------
function EnumChildWndTestProc(hWindow: hWnd; lPar: PString):
  Boolean; Stdcall;
var
  hWndTitle : String;
begin
  Result := True;

  //ウィンドウのタイトルが引数と同じだったら検索終了
  hWndTitle := GetWindowTitleStr(hWindow);
  if hWndTitle = lPar^ then begin
    //適当に文字列を追加
    lPar^  := lPar^ + '  成功しました';
    Result := False;
  end;
end;

//=============================================================================
//  コールバック関数の引数に文字列を使用する例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  TestString : String;
begin
  //コールバックの関数に、Edit1の文字列値を与えて子ウィンドウを検索
  TestString := Edit1.Text;
  EnumChildWindows(Handle, @EnumChildWndTestProc, LPARAM(@TestString));

  //戻ってきた値を表示
  MessageBox(Handle, PChar(TestString), 'テスト結果', MB_OK);
end;

end.


文字列変数をポインタ型で定義して使用する場合は、以下のように、メモリ領域の確保が必要です。確保しないと、下図のようなエラーが発生します。
確保したポインタは必要なくなったら解放します。解放しないとメモリリークが発生します。


図6
メモリ確保しないとエラー

リスト5
文字列変数をポインタ型で定義した場合は、メモリの確保が必要
//=============================================================================
//  コールバック関数の引数に文字列を使用する例
//  文字列変数をポイント型で定義した場合
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  pTestString : PString;
begin
  //変数をポインタ型で定義した場合は、メモリを確保する
  New(pTestString);

  //コールバックの関数に、Edit1の文字列値を与えて子ウィンドウを検索
  pTestString^ := Edit1.Text;
  EnumChildWindows(Handle, @EnumChildWndTestProc, LPARAM(pTestString));

  //戻ってきた値を表示
  MessageBox(Handle, PChar(pTestString^), 'テスト結果', MB_OK);

  //確保したポインタのメモリは必ず破棄する
  Dispose(pTestString);
end;


コールバック関数の引数を 整数型にした場合は、PString でキャストした値を逆参照して、その変数にアクセスすることになります。


リスト6
コールバック関数の引数に整数型を使用する例
//-----------------------------------------------------------------------------
//  ウィンドウのタイトルを取得する関数
//-----------------------------------------------------------------------------
function GetWindowTitleStr(hWindow: HWND): String;
var
  Buffer : array[0..MAX_PATH - 1] of Char;
  Len    : Integer;
begin
  FillChar(Buffer, SizeOf(Buffer), #0);
  Len := GetWindowText(hWindow, Buffer, Length(Buffer));
  if Len > 0 then Result := Buffer else Result := '';
end;

//-----------------------------------------------------------------------------
//  EnumChildWindowsのコールバック関数
//  このフォームの子ウィンドウの列挙
//-----------------------------------------------------------------------------
function EnumChildWndTestProc(hWindow: hWnd; lPar: NativeInt):
  Boolean; Stdcall;
var
  hWndTitle : String;
begin
  Result := True;

  //ウィンドウのタイトルが引数と同じだったら検索終了
  hWndTitle := GetWindowTitleStr(hWindow);
  if hWndTitle = PString(lPar)^ then begin
    //適当に文字列を追加
    PString(lPar)^  := PString(lPar)^ + '  成功しました';
    Result := False;
  end;
end;

//=============================================================================
//  コールバック関数の引数に文字列を使用する例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  TestString : String;
begin
  //コールバックの関数に、Edit1の文字列値を与えて子ウィンドウを検索
  TestString := Edit1.Text;
  EnumChildWindows(Handle, @EnumChildWndTestProc, LPARAM(@TestString));

  //戻ってきた値を表示
  MessageBox(Handle, PChar(TestString), 'テスト結果', MB_OK);
end;




03_コールバック関数の引数 - レコード型


引数にレコード型 (構造体)を 使用する場合です。ここでは、サンプルプログラムの Form1 の子ウィンドウを列挙関数の EnumChildWindows 関数で列挙し、コールバック関数に渡したハンドル値と等しいウィンドウを検出したら検索を終了します。その際、レコード型の文字列に適当な文字列を代入して返しています。
レコード型を定義する際、このサンプルのように、そのレコード型のポイント型を定義しておくと、後の操作に便利です。
コールバックに渡す時は、@ (Addr でも同じ) をつけてレコード型の変数のアドレスとします。コールバック関数の引数の型をレコード型のポインタ型としておきます。そうすると、メモリアドレスを示すポインタの逆参照(^)で、レコード型の各変数にアクセスできます。


図7
設計時画面
図8
実行結果

リスト7
コールバック関数の引数にレコード型を使用する例
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    Panel1: TPanel;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

// レコード型の定義
// PWndInfoの様にポインタ型を定義しておくと便利
type
  TWndInfo = record
    hWindow : HWND;
    Title   : String;
  end;
  PWndInfo= ^TWndInfo;

//-----------------------------------------------------------------------------
//  EnumChildWindowsのコールバック関数
//  このフォームの子ウィンドウの列挙
//-----------------------------------------------------------------------------
function EnumChildWndTestProc(hWindow: hWnd; lPar: PWndInfo):
  Boolean; Stdcall;
begin
  Result := True;

  //子ウィンドウのハンドル値が引数の値と同じだったら検索終了
  //ハンドル値は10倍にして返す。文字列は適当
  if hWindow = lPar^.hWindow then begin
    lPar^.hWindow := lPar^.hWindow * 10;
    lPar^.Title   := 'コールバック関数の戻り値';
    Result := False;
  end;
end;

//=============================================================================
//  コールバック関数の引数にレコード型を使用する例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  WndInfo  : TWndInfo;
  StrText  : String;
begin
  //コールバックの関数に、Edit1のハンドル値を与えて子ウィンドウを検索
  WndInfo.hWindow := Edit1.Handle;
  WndInfo.Title   := '';
  EnumChildWindows(Handle, @EnumChildWndTestProc, LPARAM(@WndInfo));

  //戻ってきた値は10倍になっているので元に戻して比較
  if (WndInfo.hWindow div 10) = Edit1.Handle then begin
    StrText := '同じ値です' + sLineBreak + WndInfo.Title;
    MessageBox(Handle, PChar(StrText), 'テスト結果', MB_OK);
  end;
end;

end.


レコード型の変数をポインタ型で定義して使用する場合は、以下のように、メモリ領域の確保が必要です。確保しないと、下図のようなエラーが発生します。
確保したポインタは必要なくなったら解放します。解放しないとメモリリークが発生します。


図9
メモリ確保しないとエラー

リスト8
レコード型の変数をポインタ型で定義した場合は、メモリの確保が必要
//=============================================================================
//  コールバック関数の引数にレコード型を使用する例
//  レコード型の変数をポイント型で定義した場合
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  ppWndInfo : PWndInfo;
  StrText   : String;
begin
  //変数をポインタ型で定義した場合は、メモリを確保する
  New(ppWndInfo);

  //コールバックの関数に、Edit1のハンドル値を与えて子ウィンドウを検索
  ppWndInfo^.hWindow := Edit1.Handle;
  ppWndInfo^.Title   := '';
  EnumChildWindows(Handle, @EnumChildWndTestProc, LPARAM(ppWndInfo));

  //戻ってきた値は10倍になっているので元に戻して比較
  if (ppWndInfo^.hWindow div 10) = Edit1.Handle then begin
    StrText := '同じ値です' + sLineBreak + ppWndInfo^.Title;
    MessageBox(Handle, PChar(StrText), 'テスト結果', MB_OK);
  end;

  //確保したポインタのメモリは必ず破棄する
  Dispose(ppWndInfo);
end;


コールバック関数の引数を整数型にした場合は、レコード型のポインタでキャストしたアドレスを逆参照して、レコード型の各変数にアクセスすることになります。


リスト9
コールバック関数の引数に整数型を使用する例
//-----------------------------------------------------------------------------
//  EnumChildWindowsのコールバック関数
//  このフォームの子ウィンドウの列挙
//-----------------------------------------------------------------------------
function EnumChildWndTestProc(hWindow: hWnd; lPar: NativeInt):
  Boolean; Stdcall;
begin
  Result := True;

  //子ウィンドウのハンドル値が引数の値と同じだったら検索終了
  //ハンドル値は10倍にして返す。文字列は適当
  if hWindow = PWndInfo(lPar)^.hWindow then begin
    PWndInfo(lPar)^.hWindow := PWndInfo(lPar)^.hWindow * 10;
    PWndInfo(lPar)^.Title   := 'コールバック関数の戻り値';
    Result := False;
  end;
end;

//=============================================================================
//  コールバック関数の引数にレコード型を使用する例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  WndInfo : TWndInfo;
  StrText : String;
begin
  //コールバックの関数に、Edit1のハンドル値を与えて子ウィンドウを検索
  WndInfo.hWindow := Edit1.Handle;
  WndInfo.Title   := '';
  EnumChildWindows(Handle, @EnumChildWndTestProc, LPARAM(@WndInfo));

  //戻ってきた値は10倍になっているので元に戻して比較
  if (WndInfo.hWindow div 10) = Edit1.Handle then begin
    StrText := '同じ値です' + sLineBreak + WndInfo.Title;
    MessageBox(Handle, PChar(StrText), 'テスト結果', MB_OK);
  end;
end;


サンプルコードでは、レコード型のポインタを、レコード型の後で定義していますが、オンライン・ヘルプに書いてある通り、レコード型の定義の前でも構いません。


リスト10
ポインタの定義はレコード型の定義の前でもよい
// レコード型の定義
// PWndInfoの様にポインタ型を定義しておくと便利
// ポインタの定義はレコード型の定義の前に書いてもよい
type
  PWndInfo= ^TWndInfo;
  TWndInfo = record
    hWindow : HWND;
    Title   : String;
  end;




04_コールバック関数の引数 - クラス型


コールバック関数の引数に、Delphi のクラス型を使用する例です。ここでは、システムのトップレベルのウィンドウを列挙して、そのウィンドウタイトルをTMemoに格納して表示します。
Delphi のクラスのインスタンスはそのインスタンスのポインタです、このサンプルでは TMemo のインスタンス、Memo1 のことになります。通常の Delphi と同じようなコーディングで動作します。クラス型での扱いが一番面倒がありません。

このようなテストをする度に、Delphi のクラス型というのは本当にうまいことを考えたものだと思います。いまでは他の開発言語でも当たり前になっているのかも知れませんが、Delphi の誕生当時、このような仕組みを考えたというのは、実に驚きます。
Delphi の初期の段階のコンポーネントが、10 年以上経った現在のバージョンの Delphi でも動作するのというのにも驚かされます。もちろん、Unicode 化等により、多少の修正が必要な場合もあります。


図10
設計時画面
図11
実行結果

リスト11
コールバック関数の引数にクラス型を使用する例
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//-----------------------------------------------------------------------------
//  ウィンドウのタイトルを取得する関数
//-----------------------------------------------------------------------------
function GetWindowTitleStr(hWindow: HWND): String;
var
  Buffer : array[0..MAX_PATH-1] of Char;
  Len    : Integer;
begin
  FillChar(Buffer, SizeOf(Buffer), #0);
  Len := GetWindowText(hWindow, Buffer, Length(Buffer));
  if Len > 0 then Result := Buffer else Result := '';
end;

//-----------------------------------------------------------------------------
//  EnumWindowsのコールバック関数
//  ここでは、トップレベルのウィンドウのタイトルをTMemoに格納
//-----------------------------------------------------------------------------
function EnumWndTestProc(hWindow: hWnd; lPar: TMemo):
  Boolean; Stdcall;
var
  hWndTitle : String;
begin
  Result := True;

  //ウィンドウのタイトルを取得してMemo1に格納
  hWndTitle := GetWindowTitleStr(hWindow);
  if hWndTitle <> '' then lPar.Lines.Add(hWndTitle);
end;

//=============================================================================
//  コールバック関数の引数にクラス型を使用する例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Clear;
  Memo1.Lines.BeginUpdate;

  EnumWindows(@EnumWndTestProc, LPARAM(Memo1));

  Memo1.Lines.EndUpdate;
end;

end.




05_コールバック関数の引数 - TList


このページのサンプルはほとんどがウィンドウのハンドル値を列挙するサンプルです。
以下のコードでは、ウィンドウハンドル値を TList に格納しています。TList にはハンドルのポインタを格納し、値を取出す時にキャストしています。


リスト12
TList にウィンドウのハンドル値を格納する例
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//-----------------------------------------------------------------------------
//  ウィンドウのタイトルを取得する関数
//-----------------------------------------------------------------------------
function GetWindowTitleStr(hWindow: HWND): String;
var
  Buffer : array[0..MAX_PATH-1] of Char;
  Len    : Integer;
begin
  FillChar(Buffer, SizeOf(Buffer), #0);
  Len := GetWindowText(hWindow, Buffer, Length(Buffer));
  if Len > 0 then Result := Buffer else Result := '';
end;

//-----------------------------------------------------------------------------
//  EnumChildWindowsのコールバック関数
//-----------------------------------------------------------------------------
function EnumWndTestProc(hWindow: hWnd; lPar: TList):
  Boolean; Stdcall;
begin
  Result := True;
  //ポインタを代入して取出す時にキャストする
  lPar.Add(Pointer(hWindow));
end;

//=============================================================================
//  コールバック関数の引数にTListを与える例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  AList   : TList;
  i       : Integer;
  hWindow : HWND;
  ATitle  : String;
begin
  AList := TList.Create;
  try
    EnumWindows(@EnumWndTestProc, LPARAM(AList));

    Memo1.Clear;
    Memo1.Lines.BeginUpdate;
    for i := 0 to AList.Count - 1 do begin
      hWindow := HWND(AList[i]);
      ATitle  := GetWindowTitleStr(hWindow);
      if Trim(ATitle) <> '' then Memo1.Lines.Add(ATitle);
    end;
    Memo1.Lines.EndUpdate;
  finally
    FreeAndNil(AList);
  end;
end;

end.