|
Delphi Programming / Object Pascal
[掲載 2005年04月01日] [更新 2015年03月14日] Delphi サンプルプログラム集 |
280_アプリの起動と終了を知る WH_CBT フック |
動作確認等 |
Windows 7 U64(SP1) + Delphi XE(UP1) Pro |
|
280_HookCBTActivate.zip [1,863 KB] 2015年03月14日版 (EXE 同梱) |
- 2005年12月08日
- 「アプリケーションの終了を知る」コードを追加
- [起動を知る・起動阻止] のコードに追加せず.各動作のコードを見やすくするため別にした
- クラス名とウィンドウタイトルの取得コードが適切でなかったのを修正しました
- 2007年09月22日
- DLL からの受信メッセージとしてユーザ定義 (WM_APP + XX) を使用するコードを追加
- 2007年10月29日
- コードをコピペして使用した場合に発生する[リストのインデックスが範囲を超えています)]に対する修正
- 2009年07月27日
- 他の CBT フックのサンプルと重複しないように MapFileName を変更
- コールバックの戻り値 (Result) を CallNextHookEx にした
- 2013年03月17日
- [DLL 関係] CreateFileMapping の第 1 引数を High(NativeUInt) に変更 (64 ビット対応)
- [DLL 関係] packed record を record に変更
- [DLL 関係] Delphi XE3 Pro VCL-32, VCL-64 でも動作確認
- Windows 7 U64(SP1) + Delphi XE Pro で動作確認
- 2015年03月14日
- UIPI (ユーザーインターフェイス特権の分離) 対応処理を追加
- コード整備等
|
WH_CBT フックを使用して,ウィンドウの生成やアプリの起動,終了を検出するサンプルです.
検出したウィンドウのタイトルとクラス名を TListView に表示するだけです.
WH_CBT フックは,ウィンドウの生成などのイベントに応じてシステムから呼出されるもので,メッセージの変更も可能です.下表のようなイベントの処理が可能となっています.このフック関数は DLL 内に置かなければ機能しません. |
HCBT_ACTIVATE |
ウィンドウがこれからアクティブになる |
HCBT_CLCKSKIPPED |
システムのメッセージキューからマウスメッセージが削除された |
HCBT_CREATEWND |
ウィンドウが作成された後,WM_CREATE,WM_NCCREATEを受取る前,およびウィンドウの最終的な位置とサイズが決定される前 |
HCBT_DESTROYWND |
ウィンドウがこれから破棄される |
HCBT_KEYSKIPPED |
キーボードのメッセージキューから削除された |
HCBT_MINMAX |
ウィンドウがこれから最大化または最小化される |
HCBT_MOVESIZE |
ウィンドウがこれから移動またはサイズ変更される |
HCBT_QS |
システムのメッセージキューからWS_QUEUESYNCメッセージが取出された |
HCBT_SETFOCUS |
ウィンドウはこれから入力フォーカスを受取る |
HCBT_SYSCOMMAND |
メッセージキューからシステムコマンドメッセージが取出された |
下のコードは,本ページで使用している DLL のプロジェクトのソースコードです.最後のサンプルではメッセージ文字列の登録をしないので, GetHookMsgString がありません. |
リスト1
サンプルプログラムで使用している DLL のプロジェクトのソース |
library plHookCBT;
uses
plHookCBTUnit in 'plHookCBTUnit.pas';
//-----------------------------------------------------------------------------
// 外部からDLL内のメソッドを利用可能にするためのオマジナイ
//-----------------------------------------------------------------------------
exports
StartCBTHook,
StopCBTHook,
GetHookMsgString;
end.
WH_CBT フックでウィンドウの生成等を検出します.それだけでは面白くないので,
- エクセルの起動を阻止.つまりエクセルは起動させない
- メモ帳は起動するが,位置とサイズは変更できない
変更しようとしても,元の位置とサイズに戻す
という仕様にしてみました.この動作を実現するコードは DLL に記述しています.
このサンプルでは,DLL 内で定義してある文字列を,DLL を使うプログラム側で取得し,メッセージの値を生成していますが,RegisterWindowMessage 関数で取得した値を DLL 側に渡す方法もあります. |
|
図1
設計時画面
|
|
図2
プロジェクトのファイル構成
- plHookCBT.dpr が DLL のプロジェクト
|
テストの実行例です.
このサンプルでは,64
ビットアプリケーションのメッセージは処理できません.つまり,64 ビットアプリケーションの起動や破棄等は検出できないことに注意してください. |
|
図3
ワードとエクセルを起動
- EXE を直接実行してテスト
- エクセルは実際には起動しない
|
|
図4
メモ帳を起動してリサイズ
- EXE を直接実行してテスト
- 32 ビット版のメモ帳を起動
- 位置とサイズは DLL 内の設定となる
- 位置もサイズも変更できない
- 変更しようとしても元に戻される
|
このサンプルではエクセルの起動を阻止しています.
このサンプルの終了後にエクセルを起動すると,下図のように,「Excel は前回正常に起動しませんでした」というダイアログが表示されます.
この場合,特にセーフモードで起動する必要はありません. |
|
図5
エクセルの再起動 |
リスト2
アプリケーションの起動を知る・起動阻止等 |
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
StdCtrls, ComCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
ListView1: TListView;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
protected
procedure WndProc(var msg: TMessage);override;
procedure DisplayWindowInfo(ClassName, TitleStr: String);
function GetWindowTitleStr(hWindow: HWND): String;
function GetWindowClassNameStr(hWindow: HWND):string;
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
function StartCBTHook(Wnd: HWND): Boolean; stdcall; external 'PLHOOKCBT.DLL';
procedure StopCBTHook; stdcall; external 'PLHOOKCBT.DLL';
function GetHookMsgString: PChar; stdcall; external 'PLHOOKCBT.DLL';
var
hHookLib : THANDLE;
HookMessage : String;
MsgHooking : WORD;
//=============================================================================
// フォーム生成時の処理
// クラス名とタイトルを一覧表示するためのListViewの設定
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
LStyle : Integer;
begin
ListView1.ViewStyle := vsReport;
ListView1.Columns.Add;
ListView1.Columns[0].Caption := ' CLASS';
ListView1.Columns[0].Width := 130;
ListView1.Columns.Add;
ListView1.Columns[1].Caption := ' TITLE';
ListView1.Columns[1].Width := 151;
//垂直スクロールバーを最初から表示
LStyle := GetWindowLong(ListView1.Handle, GWL_STYLE);
ShowScrollBar(ListView1.Handle, SB_VERT, True);
SetWindowLong(ListView1.Handle, GWL_STYLE, LStyle);
end;
//=============================================================================
// フォーム破棄時の処理
// フックを解除
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
if hHookLib <> 0 then begin
StopCBTHook;
FreeLibrary(hHookLib);
end;
end;
//=============================================================================
// [StartHook]ボタン
// フックを有効にする
// 予めDLL内で定義してある文字列を使用してフックメッセージを登録
// UIPI(ユーザーインターフェイス特権の分離)対応処理
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
const
MSGFLT_ADD = 1;
MSGFLT_REMOVE = 2;
var
DLLHandle : THandle;
FuncFilter : function (msg: Cardinal; dwFlag: DWORD): BOOL; stdcall;
begin
if hHookLib = 0 then begin
hHookLib := LoadLibrary('PLHOOKCBT.DLL');
StartCBTHook(Handle);
HookMessage := GetHookMsgString;
MsgHooking := RegisterWindowMessage(PChar(HookMessage));
//UIPI(ユーザーインターフェイス特権の分離)対応処理
//このプログラムを管理者権限で実行してもメッセージの受信を可能にする
if CheckWin32Version(6, 0) then begin
DLLHandle := loadLibrary('user32.dll');
try
if DLLHandle > 0 then begin
@FuncFilter := GetProcAddress(DLLHandle, 'ChangeWindowMessageFilter');
if @FuncFilter <> nil then begin
FuncFilter(MsgHooking, MSGFLT_ADD);
end;
end;
finally
FreeLibrary(DLLHandle);
end;
end;
end;
end;
//=============================================================================
// [StopHook]ボタン
// フックを無効にする
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
FormDestroy(nil);
end;
//=============================================================================
// DLLからのメッセージを受取る
// ここではフォームのWndProcメソッドを使用
//=============================================================================
procedure TForm1.WndProc(var msg: TMessage);
var
TargetWnd : HWND;
begin
inherited WndProc(msg);
if msg.Msg = MsgHooking then begin
TargetWnd := msg.WParam;
//クラス名とウィンドウタイトルをListViewに表示
DisplayWindowInfo(GetWindowClassNameStr(TargetWnd),
GetWindowTitleStr(TargetWnd));
end;
end;
//=============================================================================
// ListViewに起動したアプリのクラス名とタイトルを表示
//=============================================================================
procedure TForm1.DisplayWindowInfo(ClassName, TitleStr: String);
var
LItem : TListItem;
begin
if Trim(ClassName) = '' then exit;
LItem := ListView1.Items.Add;
LItem.Caption := ClassName;
LItem.SubItems.Add(TitleStr);
//ListViewの一番最後を表示
//ここでは実際にはMakeVisibleだけでOK
ListView1.Items[ListView1.Items.Count - 1].Selected := True;
ListView1.Items[ListView1.Items.Count - 1].Focused := True;
ListView1.Items[ListView1.Items.Count - 1].MakeVisible(True);
end;
//-----------------------------------------------------------------------------
// ウィンドウハンドルからウィンドウのクラス名を取得
//-----------------------------------------------------------------------------
function TForm1.GetWindowClassNameStr(hWindow: HWND): string;
var
Buffer : array[0..MAX_PATH-1] of Char;
Len : Integer;
begin
FillChar(Buffer, SizeOf(Buffer), #0);
Len := GetClassName(hWindow, Buffer, Length(Buffer));
if Len > 0 then Result := Buffer else Result := '';
end;
//-----------------------------------------------------------------------------
// ウィンドウハンドルからウィンドウのタイトルを取得
//-----------------------------------------------------------------------------
function TForm1.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;
end.
DLL のコードです.
Delphi XE2, Delphi XE3 でターゲットプラットフォームを Win64 にすれば,64 ビット版のアプリのウィンドウの生成の検出が可能となります.当然,DLL を利用するプログラムも 64 ビットにする必要があります.
32 ビットプロセスでは 64 ビット DLL を読み込んで実行することができません.また、64 ビットプロセスでは 32 ビット DLL を読み込んで実行することができません.
DLL Injection という機能を使用する方法があるようですが,筆者はテストしていません. |
リスト3
サンプルプログラムで使用している DLL のコード |
unit plHookCBTUnit;
interface
uses
Windows, Messages, SysUtils;
//外部のアプリケーションから使用する関数類
function StartCBTHook(Wnd: HWND): Boolean; stdcall;
procedure StopCBTHook; stdcall;
function GetHookMsgString: PChar; stdcall;
implementation
//共有メモリの内容の構造体
type
PHookInfo = ^THookInfo;
THookInfo = record
HookHandle : HHOOK;
HostWnd : HWND;
MsgID : WORD;
end;
// hMapFile : メモリマップドファイルのハンドル
// HookStr : Hostへの通知メッセージ定義に使用
// MapFileName : メモリマップドファイル名
var
hMapFile : THandle;
const
HookStr = 'plHookCBT hook';
MapFileName = 'HookCBTActivate';
//-----------------------------------------------------------------------------
// ウィンドウハンドルからウィンドウクラス名を取得
//-----------------------------------------------------------------------------
function GetClassNameStr(hWindow: HWND): string;
var
Buffer : array[0..MAX_PATH-1] of Char;
Len : Integer;
begin
FillChar(Buffer, SizeOf(Buffer), #0);
Len := GetClassName(hWindow, Buffer, Length(Buffer));
if Len > 0 then Result := Buffer else Result := '';
end;
//-----------------------------------------------------------------------------
// ウィンドウハンドルからウィンドウのタイトルを取得
//-----------------------------------------------------------------------------
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;
//-----------------------------------------------------------------------------
// メッセージ作成用文字列の取得関数
//-----------------------------------------------------------------------------
function GetHookMsgString: PChar; stdcall;
begin
Result := HookStr;
end;
//-----------------------------------------------------------------------------
// 共有メモリアクセスのための準備
//
// 成功すると0を返す.失敗すると負数を返す.
// 負の値で処理を分岐できるようになっているが,このコードでは未使用.
//-----------------------------------------------------------------------------
function MapFileMemory(var AMapWnd: THandle; var ApMap: Pointer): Integer;
begin
Result := 0;
//メモリマップドファイルを開く
AMapWnd := OpenFileMapping(FILE_MAP_ALL_ACCESS, False, PChar(MapFileName));
if AMapWnd = 0 then begin
Result := -1;
end else begin
//メモリマップドファイルの割り当て
ApMap := MapViewOfFile(AMapWnd, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if ApMap = nil then begin
Result := -2;
CloseHandle(AMapWnd);
end;
end;
end;
//-----------------------------------------------------------------------------
// 共有メモリアクセスの後始末
// 割当てたメモリがあればビューを解除
// ハンドルがあればクローズ
//-----------------------------------------------------------------------------
procedure UnMapFileMemory(AMapWnd: THandle; ApMap: Pointer);
begin
if ApMap <> nil then UnmapViewOfFile(ApMap);
if AMapWnd <> 0 then CloseHandle(AMapWnd);
end;
//-----------------------------------------------------------------------------
// フックのコールバック関数
// メッセージIDはこのDLL内で設定した文字列から生成したもの
//-----------------------------------------------------------------------------
function MyHookProc(nCode: Integer; wPar: WPARAM; lPar: LPARAM): LRESULT; stdcall;
var
LpMap : Pointer;
LMapWnd : THandle;
LTargetWnd : HWND;
LClass : String;
LFlag : UINT;
begin
Result := 0;
if MapFileMemory(LMapWnd, LpMap) <> 0 then exit;
if nCode < 0 then begin
Result := CallNextHookEx(pHookInfo(LpMap)^.HookHandle, nCode, wPar, lPar);
end else begin
//操作対象のウィンドウハンドルとクラス名取得
LTargetWnd := wPar;
LClass := GetClassNameStr(LTargetWnd);
case nCode of
HCBT_ACTIVATE:
begin
//StartCBTHookの引数で指定したウィンドウにメッセージを送信
PostMessage(pHookInfo(LpMap)^.HostWnd, pHookInfo(LpMap)^.MsgID, wPar, lPar);
//エクセルの場合
if Pos('Excel', GetWindowTitleStr(LTargetWnd) ) > 0 then begin
Result := 1;
SendMessage(LTargetWnd, WM_CLOSE, 0, 0);
end;
//メモ帳の表示位置とサイズを強制設定
if LClass = 'Notepad' then begin
LFlag := SWP_NOZORDER or SWP_SHOWWINDOW;
SetWindowPos(LTargetWnd, 0, 0, 0, 500, 600, LFlag);
end;
end;
HCBT_CREATEWND:
begin
//クラス名がXLMAINのウィンドウは生成させない
if LClass = 'XLMAIN' then Result := 1;
end;
HCBT_MOVESIZE:
begin
//メモ帳は移動リサイズさせない
if LClass = 'Notepad' then Result := 1;
end;
HCBT_MINMAX:
begin
//メモ帳は最大化も最小化もさせない
if LClass = 'Notepad' then Result := 1;
end;
end;
if Result <> 1 then begin
Result := CallNextHookEx(pHookInfo(LpMap)^.HookHandle, nCode, wPar, lPar);
end;
end;
UnMapFileMemory(LMapWnd, LpMap);
end;
//-----------------------------------------------------------------------------
// フック関数の登録
// 登録するフック関数はMyHookProc
//-----------------------------------------------------------------------------
function StartCBTHook(Wnd: HWND): Boolean; stdcall;
var
LMapWnd : THandle;
LpMap : Pointer;
begin
Result := False;
//メモリマップドファイル使用準備
MapFileMemory(LMapWnd, LpMap);
if LpMap <> nil then begin
//DLL内部で使用する変数類を共有メモリにセット
//ここでは引数のホスト側のウィンドウハンドルのみ
pHookInfo(LpMap)^.HostWnd := Wnd;
//フックをインストール}
pHookInfo(LpMap)^.HookHandle := SetWindowsHookEx(WH_CBT,
@MyHookProc,
hInstance,
0);
//アプリケーションへ通知するメッセージを登録
pHookInfo(LpMap)^.MsgID := RegisterWindowMessage(HookStr);
if pHookInfo(LpMap)^.MsgID = 0 then exit;
//フック成功
Result := pHookInfo(LpMap)^.HookHandle > 0;
//メモリマップドファイル使用終了処理
UnMapFileMemory(LMapWnd, LpMap);
end;
end;
//-----------------------------------------------------------------------------
// フックの解除
//-----------------------------------------------------------------------------
procedure StopCBTHook; stdcall;
var
LMapWnd : THandle;
LpMap : Pointer;
begin
//メモリマップドファイル使用準備
MapFileMemory(LMapWnd, LpMap);
if LpMap <> nil then begin
//フック解除
if pHookInfo(LpMap)^.HookHandle > 0 then begin
UnhookWindowsHookEx(pHookInfo(LpMap)^.HookHandle);
end;
//メモリマップドファイル使用終了処理
UnMapFileMemory(LMapWnd, LpMap);
end;
end;
//-----------------------------------------------------------------------------
// ユニット初期化部
// メモリマップドファイルの作成
//
// High(NativeUInt)の値は
// 32ビットアプリでは$FFFFFFFF
// 64ヒットアプリでは$FFFFFFFFFFFFFFFF
//-----------------------------------------------------------------------------
initialization
begin
hMapFile := CreateFileMapping(High(NativeUInt),
nil,
PAGE_READWRITE,
0,
SizeOf(THookInfo),
PChar(MapFileName));
end;
//-----------------------------------------------------------------------------
// ユニット終了処理部
// メモリマップドファイルのクローズ
//-----------------------------------------------------------------------------
finalization
begin
if hMapFile <> 0 then CloseHandle(hMapFile);
end;
end.
WH_CBT フックの HCBT_DESTROYWND を検出します.つまり,アプリケーションの終了の検出です.
このサンプルのコードは,上のサンプルとほとんど同じです.違うのは DLL の名前だけです.
自分自身のアプリで,CreateProcess や ShellExecuteEx で他のアプリを起動した場合は,グローバルフックを使用しなくても,その終了を知ることができます.
このサンプルでは,DLL 内で定義してある文字列を,DLL を使うプログラム側で取得し,メッセージの値を生成していますが,RegisterWindowMessage 関数で取得した値を DLL 側に渡す方法もあります. |
|
図6
設計時画面 |
|
図7
エクセルを終了した結果
- エクセルを起動
- [Start Hook] をクリック
- エクセルを閉じる
- エクセルも GDI+ を使用している
|
リスト4
アプリケーション終了メッセージ取得用サンプルプログラムのコード |
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
StdCtrls, ComCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
ListView1: TListView;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
protected
procedure WndProc(var msg: TMessage);override;
procedure DisplayWindowInfo(ClassName, TitleStr: String);
function GetWindowTitleStr(hWindow: HWND): String;
function GetWindowClassNameStr(hWindow: HWND):string;
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
function StartCBTHook(Wnd: HWND): Boolean; stdcall; external 'plHookCBTDestroy.dll';
procedure StopCBTHook; stdcall; external 'plHookCBTDestroy.dll';
function GetHookMsgString: PChar; stdcall; external 'plHookCBTDestroy.dll';
var
hHookLib : THANDLE;
HookMessage : String;
MsgHooking : WORD;
//=============================================================================
// フォーム生成時の処理
// クラス名とタイトルを一覧表示するためのListViewの設定
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
LStyle : Integer;
begin
ListView1.ViewStyle := vsReport;
ListView1.Columns.Add;
ListView1.Columns[0].Caption := ' CLASS';
ListView1.Columns[0].Width := 130;
ListView1.Columns.Add;
ListView1.Columns[1].Caption := ' TITLE';
ListView1.Columns[1].Width := 151;
//垂直スクロールバーを最初から表示
LStyle := GetWindowLong(ListView1.Handle, GWL_STYLE);
ShowScrollBar(ListView1.Handle, SB_VERT, True);
SetWindowLong(ListView1.Handle, GWL_STYLE, LStyle);
end;
//=============================================================================
// フォーム破棄時の処理
// フックを解除
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
if hHookLib <> 0 then begin
StopCBTHook;
FreeLibrary(hHookLib);
end;
end;
//=============================================================================
// [StartHook]ボタン
// フックを有効にする
// 予めDLL内で定義してある文字列を使用してフックメッセージを登録
// UIPI(ユーザーインターフェイス特権の分離)対応処理
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
const
MSGFLT_ADD = 1;
MSGFLT_REMOVE = 2;
var
DLLHandle : THandle;
FuncFilter : function (msg: Cardinal; dwFlag: DWORD): BOOL; stdcall;
begin
if hHookLib = 0 then begin
hHookLib := LoadLibrary('PLHOOKCBT.DLL');
StartCBTHook(Handle);
HookMessage := GetHookMsgString;
MsgHooking := RegisterWindowMessage(PChar(HookMessage));
//UIPI(ユーザーインターフェイス特権の分離)対応処理
//このプログラムを管理者権限で実行してもメッセージの受信を可能にする
if CheckWin32Version(6, 0) then begin
DLLHandle := loadLibrary('user32.dll');
try
if DLLHandle > 0 then begin
@FuncFilter := GetProcAddress(DLLHandle, 'ChangeWindowMessageFilter');
if @FuncFilter <> nil then begin
FuncFilter(MsgHooking, MSGFLT_ADD);
end;
end;
finally
FreeLibrary(DLLHandle);
end;
end;
end;
end;
//=============================================================================
// フックを無効にする
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
FormDestroy(nil);
end;
//=============================================================================
// Application側でメッセージ通知を受取る
// ここではフォームのWndProcメソッドを使用
//=============================================================================
procedure TForm1.WndProc(var msg: TMessage);
var
TargetWnd : HWND;
begin
inherited WndProc(msg);
//plHookCBTDestroy.dllから来たメッセージの場合
if msg.Msg = MsgHooking then begin
TargetWnd := msg.WParam;
//クラス名とウィンドウタイトルをListViewに表示
DisplayWindowInfo(GetWindowClassNameStr(TargetWnd),
GetWindowTitleStr(TargetWnd));
end;
end;
//-----------------------------------------------------------------------------
// ListViewにウィンドウのクラス名とタイトルを表示
//-----------------------------------------------------------------------------
procedure TForm1.DisplayWindowInfo(ClassName, TitleStr: String);
var
LItem : TListItem;
begin
if Trim(ClassName) = '' then exit;
LItem := ListView1.Items.Add;
LItem.Caption := ClassName;
LItem.SubItems.Add(TitleStr);
//ListViewの一番最後を表示
//ここでは実際にはMakeVisibleだけでOK
ListView1.Items[ListView1.Items.Count - 1].Selected := True;
ListView1.Items[ListView1.Items.Count - 1].Focused := True;
ListView1.Items[ListView1.Items.Count - 1].MakeVisible(True);
end;
//-----------------------------------------------------------------------------
// ウィンドウハンドルからウィンドウのクラス名を取得
//-----------------------------------------------------------------------------
function TForm1.GetWindowClassNameStr(hWindow: HWND): string;
var
Buffer : array[0..MAX_PATH-1] of Char;
Len : Integer;
begin
FillChar(Buffer, SizeOf(Buffer), #0);
Len := GetClassName(hWindow, Buffer, Length(Buffer));
if Len > 0 then Result := Buffer else Result := '';
end;
//-----------------------------------------------------------------------------
// ウィンドウハンドルからウィンドウのタイトルを取得
//-----------------------------------------------------------------------------
function TForm1.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;
end.
DLL のコードも上のサンプルとほとんど同じです.
違うのは DLL の名前と,以下のフックのコールバック関数の処理部分です. |
リスト5
DLL 内のフックのコールバック関数のコード |
//-----------------------------------------------------------------------------
// フックのコールバック関数
//-----------------------------------------------------------------------------
function MyHookProc(nCode: Integer; wPar: WPARAM; lPar: LPARAM): LRESULT; stdcall;
var
LMapWnd : THandle;
LpMap : Pointer;
begin
Result := 0;
if MapFileMemory(LMapWnd, LpMap) <> 0 then exit;
if nCode < 0 then begin
Result := CallNextHookEx(pHookInfo(LpMap)^.HookHandle, nCode, wPar, lPar);
end else begin
case nCode of
HCBT_DESTROYWND:
begin
PostMessage(pHookInfo(LpMap)^.HostWnd, pHookInfo(LpMap)^.MsgID, wPar, lPar);
end;
end;
Result := CallNextHookEx(pHookInfo(LpMap)^.HookHandle, nCode, wPar, lPar);
end;
UnMapFileMemory(LMapWnd, LpMap);
end;
03_ユーザ定義メッセージ WM_APP を使用する |
ここまでのサンプルでは,メッセージの設定に RegisterWindowMessage 関数を使用しています.
この関数を使用すると,システムで一意のメッセージの数値を動的に決定してくれます.したがって本来はこの RegisterWindowMessage を使用すべきですが,WM_APP + XXX という静的な数値を直接使用することもできます.そのサンプルです.
WH_CBT フックの HCBT_ACTIVATE を検出し,アプリケーションの起動時にメッセージを送信します.
このサンプルのコードは,上のサンプルとほとんど同じです.違うのは DLL の名前と,メッセージ登録用の RegisterWindowMessage 関係を使用していないことだけです. |
|
図8
設計時画面 |
|
図9
アプリを起動
- 32 ビット版のメモ帳を起動
- 32 ビット版のエクセルを起動
- 32 ビット版のワードを起動
- サンプルのフォームをクリック
|
リスト6
静的なメッセージの値を使用して,アクティブになったウィンドウを検出 |
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
ListView1: TListView;
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
protected
procedure DisplayWindowInfo(ClassName, TitleStr: String);
function GetWindowTitleStr(hWindow: HWND): String;
function GetWindowClassNameStr(hWindow: HWND):string;
private
{ Private declarations }
hHookLib : THANDLE;
procedure WMAppMessage(var Msg: TMessage); message WM_APP + 1;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
// DLL内の呼出関数類
function StartCBTHook(Wnd: HWND): Boolean; stdcall; external 'PLHOOKCBTMSG.DLL';
procedure StopCBTHook; stdcall; external 'PLHOOKCBTMSG.DLL';
//=============================================================================
// フォーム生成時の処理
// クラス名とタイトルを一覧表示するためのListViewの設定
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
LStyle : Integer;
begin
ListView1.ViewStyle := vsReport;
ListView1.Columns.Add;
ListView1.Columns[0].Caption := ' CLASS';
ListView1.Columns[0].Width := 130;
ListView1.Columns.Add;
ListView1.Columns[1].Caption := ' TITLE';
ListView1.Columns[1].Width := 151;
//垂直スクロールバーを最初から表示
LStyle := GetWindowLong(ListView1.Handle, GWL_STYLE);
ShowScrollBar(ListView1.Handle, SB_VERT, True);
SetWindowLong(ListView1.Handle, GWL_STYLE, LStyle);
end;
//=============================================================================
// フォーム破棄時の処理
// フックを解除
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
if hHookLib <> 0 then begin
StopCBTHook;
FreeLibrary(hHookLib);
end;
end;
//=============================================================================
// [StartHook]ボタン
// フックを有効にする
// UIPI(ユーザーインターフェイス特権の分離)対応処理
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
const
MSGFLT_ADD = 1;
MSGFLT_REMOVE = 2;
var
DLLHandle : THandle;
FuncFilter : function (msg: Cardinal; dwFlag: DWORD): BOOL; stdcall;
begin
if hHookLib = 0 then begin
hHookLib := LoadLibrary('PLKHOOKCBTMSG.DLL');
StartCBTHook(Handle);
//UIPI(ユーザーインターフェイス特権の分離)対応処理
//このプログラムを管理者権限で実行してもメッセージの受信を可能にする
if CheckWin32Version(6, 0) then begin
DLLHandle := loadLibrary('user32.dll');
try
if DLLHandle > 0 then begin
@FuncFilter := GetProcAddress(DLLHandle, 'ChangeWindowMessageFilter');
if @FuncFilter <> nil then begin
FuncFilter(WM_APP + 1, MSGFLT_ADD);
end;
end;
finally
FreeLibrary(DLLHandle);
end;
end;
end;
end;
//=============================================================================
// [StopHook]ボタン
// フックを無効にする
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
FormDestroy(nil);
end;
//=============================================================================
// Application側でメッセージ通知を受取る
// ここでは定義したWM_APP+1メッセージ受信専用のプロシージャーを作成
//=============================================================================
procedure TForm1.WMAppMessage(var Msg: TMessage);
var
TargetWnd : HWND;
begin
TargetWnd := msg.WParam;
//クラス名とウィンドウタイトルをListViewに表示
DisplayWindowInfo(GetWindowClassNameStr(TargetWnd),
GetWindowTitleStr(TargetWnd));
end;
//=============================================================================
// ListViewにウィンドウのクラス名とタイトルを表示
//=============================================================================
procedure TForm1.DisplayWindowInfo(ClassName, TitleStr: String);
var
LItem : TListItem;
begin
if Trim(ClassName) = '' then exit;
LItem := ListView1.Items.Add;
LItem.Caption := ClassName;
LItem.SubItems.Add(TitleStr);
//ListViewの一番最後を表示
//ここでは実際にはMakeVisibleだけでOK
ListView1.Items[ListView1.Items.Count - 1].Selected := True;
ListView1.Items[ListView1.Items.Count - 1].Focused := True;
ListView1.Items[ListView1.Items.Count - 1].MakeVisible(True);
end;
//-----------------------------------------------------------------------------
// ウィンドウハンドルからウィンドウのクラス名を取得
//-----------------------------------------------------------------------------
function TForm1.GetWindowClassNameStr(hWindow: HWND): string;
var
Buffer : array[0..MAX_PATH-1] of Char;
Len : Integer;
begin
FillChar(Buffer, SizeOf(Buffer), #0);
Len := GetClassName(hWindow, Buffer, Length(Buffer));
if Len > 0 then Result := Buffer else Result := '';
end;
//-----------------------------------------------------------------------------
// ウィンドウハンドルからウィンドウのタイトルを取得
//-----------------------------------------------------------------------------
function TForm1.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;
end.
DLL のコードも上のサンプルとほとんど同じです.
違いは DLL の名前,メッセージ関係のコードと,以下のコールバック関数の処理部分です. |
リスト7
DLL 内のフックのコールバック関数部分 |
//-----------------------------------------------------------------------------
// フックのコールバック関数
// HCBT_ACTIVATEの時にWM_APP + 1を送信する
//-----------------------------------------------------------------------------
function MyHookProc(nCode: Integer; wPar: WPARAM; lPar: LPARAM): LRESULT; stdcall;
var
LMapWnd : THandle;
LpMap : Pointer;
begin
Result := 0;
if MapFileMemory(LMapWnd, LpMap) <> 0 then exit;
if nCode < 0 then begin
Result := CallNextHookEx(pHookInfo(LpMap)^.HookHandle, nCode, wPar, lPar);
end else begin
if nCode = HCBT_ACTIVATE then begin
PostMessage(pHookInfo(LpMap)^.HostWnd, WM_APP + 1, wPar, lPar);
end;
Result := CallNextHookEx(pHookInfo(LpMap)^.HookHandle, nCode, wPar, lPar);
end;
UnMapFileMemory(LMapWnd, LpMap);
end;
|