Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2007年04月01日] [更新 2014年07月15日] Delphi サンプルプログラム集
830_フック等のコールバック関数の記述位置
動作確認等 Windows 7 U64(SP1) + Delphi XE Pro
Windows 7 U64(SP1) + Delphi XE2 Pro VCL-32, VCL-64
830_CallbackCode.zip [1,796 KB] 2014年07月15日版 (EXE 同梱)







コールバック関数やクラスのメンバではない関数等を書く場所


コールバック関数とは,ある関数が実行中に呼び出す,あるいは呼出される関数のことです.イベントのような動作をしますが,ある条件が満たされるまで何回でも呼び出されて実行される場合と,ある条件が発生した時に自動的に呼び出されるものとがあります.
キーフックやマウスイベントのフックは後者に相当します.

フックのコールバック関数やその他のコールバック関数は,システムから呼ばれる関数です.したがってクラスのメソッドにはできません.このサンプルプログラム集,Halbow 資料館にも同様にクラスメソッドでない関数を使用しています.それらも同じ理由でそうなっています.
したがって,本ページの [02], [03], [04] の内容は,クラスのメンバではない関数を使用する場合にも言える内容となっています.

これらの関数は原則として,その関数を使用するより前に記述しておく必要があります.以下のサンプルコードは,このことを実際に確認するためのものです.

なお,このページで使用しているウィンドウのタイトル文字列取得のコードは,取得する文字列に意味不明な文字,いわゆる「ゴミ」が含まれることがあります.これはメモリ上に残された情報です.これを避けるには,取得文字列列のバッファをクリアしてから GetWindowText 関数を実行します.参考コードが [832_コールバック関数の引数] 等にあります.
このページ程度の簡単なプログラムだけを実行するという状況であれば,ほんんど「ゴミ」は表示されません.



[備考]
関数の定義の末尾の Stdcall は,コールバック関数や Windows API の関数等,DLL 内の関数を使用する際の「呼出規約」と言われるものです.一般の関数の場合は必要ありません.




01_コールバック関数をクラスメソッドとした場合 ( コンパイル不可 )


このコードでは,TForm1のクラスメソッドとして関数を書いています.このコードをコンパイルすると「変数が必要です」のエラーが発生してコンパイル出来ません.
コールバック関数は,クラスのメンバ (クラスメソッド) にはできません.


図1
設計時画面
  • 全部のテストプログラムで同じ


Public\Documents\RAD Studio\8.0\hpp"   Callback01.dpr   
[DCC エラー] Callback01Unit.pas(90): E2036 変数が必要です
[DCC 致命的エラー] Callback01.dpr(5): F2063 'Callback01Unit.pas' ユニットはコンパイルでき
ませんでした
失敗
経過時間: 00:00:00.9


リスト1
クラスメソッドとした場合
unit Callback01Unit;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    function EnumWindowProc(Handle: hWnd; lPalam: NativeInt): Boolean; Stdcall;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//-----------------------------------------------------------------------------
//  タイトルがあって表示されているウインドウを表示
//  ただしこの動作内容がこのプログラムの主題ではない
//
//  このコードでウィンドウのタイトルを取得すると「ゴミ」が入ることがある
//-----------------------------------------------------------------------------
function TForm1.EnumWindowProc(Handle: hWnd; lPalam: NativeInt): Boolean;
var
  Title : array[0..MAX_PATH - 1] of Char;
begin
  if (GetWindowText(Handle, Title, MAX_PATH) <> 0) and
     IsWindowVisible(Handle) then begin
    Form1.Memo1.Lines.Add(Title);
  end;

  Result := True;
end;

//=============================================================================
//  コールバック関数を呼出す
//  EnumWondowsは,第一引数の(ウィンドウのリストを列挙する)コールバック関数を
//  実行(呼出)する関数
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Clear ;
  EnumWindows(@EnumWindowProc, 0);
end;

end.




02_implematation 部の先頭に書いた場合 ( 正常動作 )


以下のコードでは,コールバック関数をimplematationの先頭に書いています.先頭であれば,それを使うコードは必ずその後にあることになります.


図2
実行結果
  • ウィンドウのリストが表示される

リスト2
正常に動作する例
unit Callback02Unit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, 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 EnumWindowProc(Handle: hWnd; lPalam: NativeInt): Boolean; Stdcall;
var
  Title : array[0..MAX_PATH - 1] of Char;
begin
  if (GetWindowText(Handle, Title, MAX_PATH) <> 0) and
      IsWindowVisible(Handle) then begin
    Form1.Memo1.Lines.Add(Title);
  end;

  Result := True;
end;

//=============================================================================
//  コールバック関数を呼出す
//  EnumWondowsは,第一引数の(ウィンドウのリストを列挙する)コールバック関数を
//  実行(呼出)する関数
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Clear ;
  EnumWindows(@EnumWindowProc, 0);
end;

end.




03_関数を使用するより後に書いた場合 ( コンパイル不可 )


次のコードは,コールバック関数を使うコードよりも後にコールバック関数のコードを書いています.このコードをコンパイルすると「未定義の識別子」のエラーが発生して実行ファイルを作成することはできません.


リスト3 
Public\Documents\RAD Studio\8.0\hpp"   Callback03.dpr   
[DCC エラー] Callback03Unit.pas(78): E2003 未定義の識別子 : 'EnumWindowProc'
[DCC 致命的エラー] Callback03.dpr(5): F2063 'Callback03Unit.pas' ユニットはコンパイルでき
ませんでした
失敗
経過時間: 00:00:00.8


リスト4
コールバック関数のコードが後ろにある
unit Callback03Unit;

interface

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

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

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  コールバック関数を呼出す
//  EnumWondowsは,第一引数の(ウィンドウのリストを列挙する)コールバック関数を
//  実行(呼出)する関数
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Clear ;
  EnumWindows(@EnumWindowProc, 0);
end;

//-----------------------------------------------------------------------------
//  タイトルがあって表示されているウインドウを表示
//  ただしこの動作内容がこのプログラムの主題ではない
//
//  このコードでウィンドウのタイトルを取得すると「ゴミ」が入ることがある
//-----------------------------------------------------------------------------
function EnumWindowProc(Handle: hWnd; lPalam: NativeInt): Boolean; Stdcall;
var
  Title : array[0..MAX_PATH - 1] of Char;
begin
  if (GetWindowText(Handle, Title, MAX_PATH) <> 0) and
      IsWindowVisible(Handle) then begin
    Form1.Memo1.Lines.Add(Title);
  end;

  Result := True;
end;

end.




04_interface 部で関数の宣言をした場合 ( 正常動作 )


コールバック関数のコードを,ユニットの任意の場所に書きたい場合は,以下のコードのように,あらかじめinterface部で定義しておきます.これで,この関数がimpletation部のどこかにあるという宣言となります.この方法は,自作の関数類を別のユニットにまとめる時にも使用される手法です.


図3
実行結果

リスト5
正常に動作する例
unit Callback04Unit;

interface

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

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

  function EnumWindowProc(Handle: hWnd; lPalam: NativeInt): Boolean; Stdcall;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  コールバック関数を呼出す
//  EnumWondowsは,第一引数の(ウィンドウのリストを列挙する)コールバック関数を
//  実行(呼出)する関数
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Clear ;
  EnumWindows(@EnumWindowProc,0);
end;

//-----------------------------------------------------------------------------
//  タイトルがあって表示されているウインドウを表示
//  ただしこの動作内容がこのプログラムの主題ではない
//
//  このコードでウィンドウのタイトルを取得すると「ゴミ」が入ることがある
//-----------------------------------------------------------------------------
function EnumWindowProc(Handle: hWnd; lPalam: NativeInt):Boolean;Stdcall;
var
  Title : array[0..MAX_PATH - 1] of Char;
begin
  if (GetWindowText(Handle, Title, MAX_PATH) <> 0) and
      IsWindowVisible(Handle) then begin
    Form1.Memo1.Lines.Add(Title);
  end;

  Result := True;
end;

end.




05_TTimer の機能をコールバックで実装


ここまでのサンプルのコールバック関数は,一定の条件が満たされるまで実行するものでした.
コールバック関数には,ある条件の時に実行されるものがあります.以下はその例です.
次のコードは,SetTimer 関数でコールバック関数を指定しています.この時,コールバック関数の呼び出し間隔を 1,000 ミリセコンドとしています.このコードは,Delphi の TTimer コンポーネントと同じような動作を実装したものとなっています.


図4
実行結果
  • 1 秒ごとに Edit1 の表示が変わる

リスト6
TTimer の機能をコールバックで実装
unit Unit1;

interface

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

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

var
  Form1: TForm1;

implementation

{$R *.dfm}

var
  Count : Integer;

//-----------------------------------------------------------------------------
//  SetTimer関数のコールバック関数
//  SetTimer関数で指定したミリ秒ごとに呼び出されて実行される関数
//-----------------------------------------------------------------------------
procedure TimerCallBackProc(hWindow: HWND; uMsg: UINT; idEvent: UINT;
  dwTime: DWORD); stdcall;
begin
  Form1.Edit1.Text := IntToStr(Count);
  Inc(Count);
end;

//=============================================================================
//  タイマーを起動
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Count := 0;
  //タイマーを起動
  //第3引数の値(ミリ秒)経過する度にTimerCallbackProcを実行する
  SetTimer(Handle, 1, 1000, @TimerCallBackProc);
end;

//=============================================================================
//  タイマーを止める
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  KillTimer(Handle, 1);
end;

end.