[BlueLeaf1336]> PROBLEMS> RecentApps>

RecentApps > アプリケーションの起動終了を監視

historyTOP

2005/02/20:作成

2005/02/20TOP

アプリケーションが起動された時・終了された時を監視することができるかどうかをテストします。テストといっても、実は心当たりがあります。

以前に「ファイルを開く/名前を付けて保存する」ダイアログの生成と破棄を監視することができるかどうかを調べたことがあり、「CBTフック」をしかけると「HCBT_CREATEWND(ウィンドウの生成)」「HCBT_DESTROYWND(ウィンドウの破棄)」に対処することで、あらゆるウィンドウの生成と破棄の瞬間に立ち会えることができる、とういことがわかっています。

ただし、注意するべきなのは、この方法でチェックできるのが「ウィンドウの生成と破棄」であって「アプリケーションの起動と終了ではない」とう点です。ただ、全てかどうかわからないけども、基本的に普通のアプリケーションはメインウィンドウを持っているだろう、と仮定しても問題なさそうとすれば、ウィンドウ生成時に、初めての誰にも所有されていないウィンドウかどうかを判定すればよさそうな感じがします。

一瞬ソースコードないのか? と思いましたが

にちゃんとありました。コレを元に、とりあえず、アプリケーションが起動してから終了するまでに処理された、ウィンドウの生成と破棄の状況だけを表示するプログラムを作ってみます。また、単純にウィンドウハンドルだけを表示してもわけがわからないので、ウィンドウのクラス名とテキストもついでに表示することにします。

以下にソースを掲載します。構成としては、DLLでフックのインストールやアンインストールを行うのですが、ファイルマッピングオブジェクトを使用する必要があるため(細かい話は理解していません)、まずファイルマッピングオブジェクトを処理するユニットを作成しています。それをusesに加えて、CBTフックを行うDLLを作成します。

次に、フォームにかいてもかまわないのですが、ここでは、DLLからのCBTフックによる通知を受け取るクラスを作成し、そのクラスの生成と破棄をフォームで行うようにしています。なぜそうしたかというと、参考にしたソースがそうなっていたからです。実際そうなんですが、処理が分かれているほうがよいような気もします。

およそ参考にしたサイトに関しては、ソースコード内にコメントとして記述しています。が、パクリミス等動作不具合に関して、元のサイトは無関係であり、また、このサイトにおいても責任をとりません。早い話、指摘していただくと嬉しいですが、どんな問題が起こったとしても「耳日曜」です。

(CBTフックのための)ファイルマッピングオブジェクトの処理TOP

(*
    ===========================================================================
    共有メモリアクセス処理
    ---------------------------------------------------------------------------
    Gen's LowTech : メッセージフックを使う
    http://www2.biglobe.ne.jp/~sakai/usehook.htm
    http://www2.biglobe.ne.jp/~sakai/bin/list2.txt
    ---------------------------------------------------------------------------
    MMF = Memory Mapped File
    ===========================================================================
*)

unit iFMO;

interface

uses
    Windows, SysUtils;

function CreateFileMappingObject(const ADataSize: integer; const AName: string): Cardinal;
procedure DestroyFileMappingObject(var AFileMappingObject: THandle);
function GetFileMappingObject(var h: THandle; var p: Pointer; const AName: string): Boolean;
procedure ReleaseFileMappingObject(var h: THandle; var p: Pointer);

implementation

//-----------------------------------------------------------------------------
//  ファイルマッピングオブジェクト作成
function    CreateFileMappingObject(const ADataSize: integer; const AName: string): Cardinal;
begin
    //  MMF作成
    Result := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, ADataSize, PChar(AName));
    //  エラーでも何もしない
    if (Result = 0) then ;
end;

//-----------------------------------------------------------------------------
//  ファイルマッピングオブジェクト作成
procedure DestroyFileMappingObject(var AFileMappingObject: THandle);
begin
    //MMF開放
    if (AFileMappingObject <> 0) then
    begin
        CloseHandle(AFileMappingObject);
    end;
end;

//-----------------------------------------------------------------------------
//  共有メモリアクセスのための準備
function GetFileMappingObject(var h: THandle; var p: Pointer; const AName: string): Boolean;
begin
    Result := true;
    //MMFを開く
    h := OpenFileMapping(FILE_MAP_ALL_ACCESS, False, PChar(AName));
    //  開けなくても何もしない
    if (h = 0) then
    begin
        Result := false;
    end
    else
    //  開けた
    begin
        //MMFの割り当て
        p := MapViewOfFile(h, FILE_MAP_ALL_ACCESS, 0, 0, 0);
        //  割り当てられなくても何もしない
        if (p = nil) then Result := false;
    end;
end;

//-----------------------------------------------------------------------------
//  共有メモリアクセスのための後始末
procedure ReleaseFileMappingObject(var h: THandle; var p: Pointer);
begin
    //  MMFのビューを解除
    if (p <> nil) then
    begin
        UnmapViewOfFile(p);
        p := nil;
    end;
    //  MMFのハンドルを閉じる
    if (h <> 0) then
    begin
        CloseHandle(h);
        h := 0;
    end;
end;

end.

(CBTフックのための)DLLの処理TOP

(*
    ===========================================================================
    Gen's LowTech : メッセージフックを使う
    http://www2.biglobe.ne.jp/~sakai/usehook.htm
    http://www2.biglobe.ne.jp/~sakai/bin/list2.txt
    ---------------------------------------------------------------------------
    Using Hooks
    ms-help://MS.PSDK.1033/winui/winui/windowsuserinterface/windowing/hooks/usinghooks.htm
    ---------------------------------------------------------------------------
    2003/11/25  作り始め
    2004/02/05  修正(例外とか除去/整理)
    2005/02/20  ウィンドウの生成と破棄だけに限定した
    ===========================================================================
*)

unit HookMain;

interface

uses
    Windows, Messages, iFMO;

//-----------------------------------------------------------------------------
//  エクスポート関数
//-----------------------------------------------------------------------------

//  フックをインストール
function HookInstall(Wnd: HWND): Boolean; export; stdcall;

//  フックをアンインストール
procedure HookUnInstall(); export; stdcall;

//  準備
function HookInitialize(): Boolean; export; stdcall;

//  後始末
procedure HookFinalize(); export; stdcall;

const
    //  通知用ウィンドウメッセージ(DLL to ClickHereItIs)
    UM_CREATEWND   = WM_APP + 1000; //  CBTProc(in DLL)
    UM_DESTROYWND  = WM_APP + 1001; //  CBTProc(in DLL)

const
    //  MMFのファイル名
    FMO_NAME = '{C67868C5-21C1-4BEC-84B3-DB5ACDFA2427}';

implementation

//-----------------------------------------------------------------------------
//  共有メモリの内容にする構造体
type
    PShareHookInfo = ^TShareHookInfo;
    TShareHookInfo = packed record
        HostWindow : HWND;     //  通知するウィンドウ
        HCbtHook   : HHOOK;    //  フックした時のハンドル
    end;

//-----------------------------------------------------------------------------
//  フック関数型
type
    THookProc = function(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;

//-----------------------------------------------------------------------------
//  共有メモリでない内容にする構造体
type
    PLocalHookInfo = ^TLocalHookInfo;
    TLocalHookInfo = record
        CbtHooked  : Boolean;   //  フック中フラグ
        FmoHandle: THandle;     //  FMOハンドル
    end;

var
    //  フック関数のインストールに関する情報を保持する
    LHI: TLocalHookInfo;

//-----------------------------------------------------------------------------
//  CBTProc
function CBTProc(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
var
    hFMO: THandle;
    p   : Pointer;
    Shi : PShareHookInfo;

    CreateWnd  : integer;
    DestroyWnd : integer;
begin
    //  MMF使用準備処理
    GetFileMappingObject(hFMO, p, FMO_NAME);
    //  フック情報構造体初期化
    Shi := PShareHookInfo(p);
    //  どんなメッセージきたかなー
    if not (nCode < 0) then
    begin
        case nCode of

        //  生まれたてウィンドウ
        HCBT_CREATEWND:
            begin
                //  作成されつつあるウィンドウのハンドル
                CreateWnd := wParam;
                PostMessage(Shi^.HostWindow, UM_CREATEWND, CreateWnd, 0);
            end;

        //  死にかけウィンドウ
        HCBT_DESTROYWND:
            begin
                //  破棄されつつあるウィンドウのハンドル
                DestroyWnd := wParam;
                PostMessage(Shi^.HostWindow, UM_DESTROYWND, DestroyWnd, 0);
            end;

        else
            ;
        end;
    end;

    //  次の人ー
    Result := CallNextHookEx(Shi^.HCbtHook, nCode, wParam, lParam);

    //MMFマッピング解除、MMFハンドルクローズ
    ReleaseFileMappingObject(hFMO, p);
end;

//-----------------------------------------------------------------------------
//  フックをインストール
function HookInstall(Wnd: HWND): Boolean;
var
    hFMO: THandle;
    p: Pointer;
    Shi: PShareHookInfo;
begin
    //  MMF使用準備処理
    GetFileMappingObject(hFMO, p, FMO_NAME);
    //  フック情報構造体初期化(あるいは取得)
    Shi := PShareHookInfo(p);
    try
        //  通知先記録
        Shi^.HostWindow := Wnd;
        //  フック関数の登録
        Shi^.HCbtHook := SetWindowsHookEx(WH_CBT, CBTProc, HInstance, 0);
        //  フック成功判定
        LHI.CbtHooked := (Shi^.HCbtHook > 0);
    finally
        //  MMF使用終了処理
        ReleaseFileMappingObject(hFMO, p);
    end;
    //  成功した?
    Result := LHI.CbtHooked;
end;

//-----------------------------------------------------------------------------
//  フックをアンインストール
procedure HookUnInstall();
var
    hFMO: THandle;
    p: Pointer;
    Shi: PShareHookInfo;
begin
    //  MMF使用準備処理
    GetFileMappingObject(hFMO, p, FMO_NAME);
    //  フック情報構造体初期化
    Shi := PShareHookInfo(p);
    //  フック解除
    if LHI.CbtHooked then UnhookWindowsHookEx(Shi^.HCbtHook);
    //  MMF使用終了処理
    ReleaseFileMappingObject(hFMO, p);
end;

//-----------------------------------------------------------------------------
//  準備
function HookInitialize(): Boolean;
begin
    //  MMF 作成
    FillChar(LHI, SizeOf(LHI), #0);
    LHI.FmoHandle := CreateFileMappingObject(SizeOf(TShareHookInfo), FMO_NAME);
    Result := (LHI.FmoHandle <> 0);
end;

//-----------------------------------------------------------------------------
//  後始末
procedure HookFinalize();
begin
    //  MMF 解放
    DestroyFileMappingObject(LHI.FmoHandle);
end;

end.

(EXE側)DLLによるCBTフックの結果を通知してもらうクラスTOP

//-----------------------------------------------------------------------------
//  CBT HOOK を行う DLL から通知してもらうクラス
//-----------------------------------------------------------------------------

unit iRecentApps;

interface

uses
    Windows, Messages, Classes, SysUtils;

type
    TMessageOut = procedure(const Msg: string) of object;

    //  CBTフック仲介クラスのベースクラス(ほとんど何もしない)
    TCbtHookReciever = class
    private
        FWndHandle: HWND;
        FEnabled: Boolean;
        FInstalled: Boolean;
        FMessageOut: TMessageOut;
        procedure WndProc(var Msg: TMessage); virtual;
    protected
        procedure DoTargetCreate(WH: HWND); virtual;
        procedure DoTargetDestroy(WH: HWND); virtual;
    public
        constructor Create();
        destructor Destroy; override;
        procedure Start();
        procedure Stop();
        property MessageOut: TMessageOut read FMessageOut write FMessageOut;
    end;

var
    CbtHookReciever: TCbtHookReciever = nil;

implementation

const
    //  通知用ウィンドウメッセージ(DLL)
    UM_CREATEWND   = WM_APP + 1000; //  CBTProc(in DLL)
    UM_DESTROYWND  = WM_APP + 1001; //  CBTProc(in DLL)

    //  DLL名称
    HOOK_DLL_NAME  = 'hook.dll';

    //  DLLからインポート・横着してスタティックリンクで
    function HookInstall(Wnd: HWND): Boolean; stdcall;external HOOK_DLL_NAME;
    procedure HookUnInstall(); stdcall;external HOOK_DLL_NAME;
    procedure HookInitialize(); stdcall;external HOOK_DLL_NAME;
    procedure HookFinalize(); stdcall;external HOOK_DLL_NAME;

//------------------------------------------------------------------------------
//  http://homepage2.nifty.com/Mr_XRAY/Halbow/VCL06.html
function GetClassNameStr(hWindow:HWND):string;
var
  p:PChar;
  ret:integer;
begin
  GetMem(p,100);
  ret := GetClassName(hWindow,p,100);
  SetString(result,p,ret);
  FreeMem(p);
end;

//------------------------------------------------------------------------------
//  http://homepage2.nifty.com/Mr_XRAY/Halbow/VCL06.html
function GetWindowTextStr(hWindow:HWND):string;
var
  p:PChar;
  ret:integer;
begin
  ret := GetWindowTextLength(hWindow);
  GetMem(p,ret+1);
  ret := GetWindowText(hWindow,p,ret+1);
  SetString(result,p,ret);
  FreeMem(p);
end;

//------------------------------------------------------------------------------
//  コンストラクタ
constructor TCbtHookReciever.Create();
begin
    inherited Create;
    // ウィンドウコントロールのふりをする
    FWndHandle := Classes.AllocateHWnd(WndProc);
    // とりあえず無効に
    FEnabled := False;
    //  フック準備
    HookInitialize();
    //  フックインストール
    FInstalled := HookInstall(FWndHandle);
    //  メッセージ表示イベント
    FMessageOut := nil;
end;

//------------------------------------------------------------------------------
//  デストラクタ
destructor TCbtHookReciever.Destroy;
begin
    FMessageOut := nil;
    //  フックアンインストール
    if (FInstalled) then HookUnInstall();
    //  フック後始末
    HookFinalize();
    // ウィンドウのふり中止
    Classes.DeallocateHWnd(FWndHandle);
    inherited;
end;

//------------------------------------------------------------------------------
//  ウィンドウが作られたぞ
//  実際には派生したクラスで、それなりの処理をする予定
procedure TCbtHookReciever.DoTargetCreate(WH: HWND);
var
    Msg: string;
begin
    Msg := Format('%s WINDOW CREATED   %10d [%s] - (%s)', [
        FormatDateTime('hh:nn:ss.zzz', Now()), WH,
        GetClassNameStr(WH),
        GetWindowTextStr(WH)
        ]);
    if Assigned(FMessageOut) then FMessageOut(Msg);
end;

//------------------------------------------------------------------------------
//  ウィンドウが破棄されたぞ
//  実際には派生したクラスで、それなりの処理をする予定
procedure TCbtHookReciever.DoTargetDestroy(WH: HWND);
var
    Msg: string;
begin
    Msg := Format('%s WINDOW DESTROYED %10d', [FormatDateTime('hh:nn:ss.zzz', Now()), WH]);
    if Assigned(FMessageOut) then FMessageOut(Msg);
end;

//------------------------------------------------------------------------------
//  ウィンドウプロシージャ
//------------------------------------------------------------------------------
//  ms-help://MS.PSDK.1033/winui/winui/windowsuserinterface/windowing/hooks/
//  hookreference/hookfunctions/cbtproc.htm
//------------------------------------------------------------------------------
procedure TCbtHookReciever.WndProc(var Msg: TMessage);
begin
    //  フックに成功していて中止中でない場合
    if (FInstalled) and (FEnabled) then
    begin
        case Msg.Msg of
            //  ウィンドウ作成
            UM_CREATEWND: DoTargetCreate(Msg.WParam);
            //  ウィンドウ破棄
            UM_DESTROYWND: DoTargetDestroy(Msg.WParam);
        end;
    end;
    // 他のメッセージは知らんよ。どうにかして。
    Msg.Result := DefWindowProc(FWndHandle, Msg.Msg, Msg.wParam, Msg.lParam);
end;

//------------------------------------------------------------------------------
//  受け付けるよ
procedure TCbtHookReciever.Start();
begin
    FEnabled := True;
end;

//------------------------------------------------------------------------------
//  受け付けません
procedure TCbtHookReciever.Stop();
begin
    FEnabled := False;
end;

end.

(EXE側)本体の処理TOP

type
  TDummyMainForm = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  private
    procedure MessageOut(const Msg: string);
  public
  end;

implementation

{$R *.dfm}

uses
    iRecentApps;

//-----------------------------------------------------------------------------
//  フォーム作成時
procedure TDummyMainForm.FormCreate(Sender: TObject);
begin
    //  CBTフック開始
    CbtHookReciever := TCbtHookReciever.Create();
    //  メッセージを表示するような関数を設定
    CbtHookReciever.MessageOut := MessageOut;
    CbtHookReciever.Start();
end;

//-----------------------------------------------------------------------------
//  終了時
procedure TDummyMainForm.FormCloseQuery(Sender: TObject;
  var CanClose: Boolean);
begin
    //  CBTフック終了
    CbtHookReciever.Stop();
    CbtHookReciever.Free();
end;

//-----------------------------------------------------------------------------
//  CBTフック中継クラスから呼ばれるメッセージ出力関数(暫定)
procedure TDummyMainForm.MessageOut(const Msg: string);
begin
    try
        DebugWindow.PutMsg(Msg);
    except
    end;
end;

注意事項TOP

大体こんな感じですが、最後の本体の処理を大幅に省略しているので(フック仲介クラスの使用箇所はコレが全てですが)、このままフォームに貼り付けても動作しません。もし、参考にするようなことがある場合は、プロジェクト全体を参照してください。動作します。

ただし、あくまでもここまでの流れの中で、タスクトレイプログラムということになってしまっているので、起動時にタスクトレイに表示され、アプリケーションの終了はタスクトレイ右クリックの「Exit」メニューからしかできません。表示されるテスト用のウィンドウを閉じたところでアプリケーションが終了するわけではないので、ご注意ください。

20050220RecentApps.zip(222,328bytes)

こんな感じの画面が表示されます(くり返しますが、メインウィンドウではないので、閉じたところでアプリケーションは終了しません。タスクトレイにあるDelphi6のデフォルトアイコンを右クリックして終了する必要があります)。

実際には、無条件にウィンドウの生成と破棄を監視しても仕方がないので、アプリケーションごとにひとつずつ、アプリケーションを代表するウィンドウの生成に対して処理するようにしなければなりません。

このページのタイトルを「アプリケーションの起動終了を監視」としてしまったのを後悔しているわけですが、行数もかなり増えてきたので、このページはここまでとします。

EOFTOP