ホーム | プログラミングTClock |


2003/11/20更新。031114以降のソースに基づいています。

TClock Lightの終了処理

ここでは、TClock Lightでの終了処理の手順について紹介します。TClock Lightの終了処理には、次の3+1パターンがあります。

右クリックメニューから「終了」を選んだとき

  1. 右クリックメニューから「TClockの終了」を選ぶと、tclock.exeのメインウィンドウにメッセージWM_COMMAND(wParam=IDC_EXIT)が送られます。すると、メッセージCLOCKM_EXITを時計ウィンドウにポストします。(exe/command.c)。
    void OnTClockCommand(HWND hwnd, WORD wID, WORD wCode)
    {
        switch(wID)
        {
            (中略)
            case IDC_EXIT: // Exit
                PostMessage(g_hwndClock, CLOCKM_EXIT, 0, 0);
                break;
    
  2. tcdll.tclockでは、CLOCKM_EXITを受け取ったら関数EndClockを呼び出します(dll/wndproc.c)。
            case CLOCKM_EXIT:   // clean up all
                EndClock(hwnd);
                return 0;
    
    関数EndClockでは、サブクラス化の解除など各種終了処理を行ったあとで、メッセージWM_CLOSEをtclock.exeのメインウィンドウにポストします(dll/main2.c)。
    void EndClock(HWND hwnd)
    {
        (中略)
        
        // restore window procedure
        SetWindowLong(hwnd, GWL_WNDPROC, (LONG)g_oldWndProc);
        g_oldWndProc = NULL;
        
        RefreshTaskbar(hwnd);  // taskbar.c
        
        // close window of tclock.exe
        PostMessage(g_hwndTClockMain, WM_CLOSE, 0, 0);
    }
    
  3. メッセージWM_CLOSEがポストされると、Windowsのデフォルトの処理によってウィンドウが破棄され、メッセージWM_DESTROYが送られてきます。WM_DESTROYを処理する関数OnDestroyでは、関数ClearTClockMainを呼び出します。ClearTClockMainでは、tcdll.tclockのAPI関数HookEndを呼んで、フックを解除します(exe/wndproc.c)。
    void OnDestroy(HWND hwnd)
    {
        ClearTClockMain(hwnd);
        
        PostQuitMessage(0);
    }
    
    void ClearTClockMain(HWND hwnd)
    {
        (中略)
        if(m_bHook) HookEnd();  // dll/main.c - uninstall the hook
        m_bHook = FALSE;
    }
    
  4. 関数HookEndでは、時計ウィンドウにメッセージCLOCKM_EXITを送ります。が、すでにサブクラス化は解除されているので効果はありません。その後、API UnhookWindowsHookExでフックをアンインストールします(dll/main.c)。
    void WINAPI HookEnd(void)
    {
        g_bHookEnable = FALSE;
        
        // EndClock() will be called
        if(g_hwndHook && IsWindow(g_hwndHook))
            SendMessage(g_hwndHook, CLOCKM_EXIT, 0, 0);
        
        // uninstall my hook
        if(g_hhook != NULL)
            UnhookWindowsHookEx(g_hhook);
        g_hhook = NULL;
    }
    

メッセージWM_COMMAND(wParam=IDC_EXIT)が送られた段階でtclock.exeにWM_CLOSEを投げずに、tcdll.tclock→tclock.exeの順で終了処理をさせています。そのほうが、なぜだかWindows 95/98/Meでエラーが出なくなるからです。

tclock.exeのメインウィンドウにWM_CLOSEが送られたとき

他のアプリケーションからtclock.exeのメインウィンドウにWM_CLOSEが直接送られる場合もあります。そのときは、tclock.exe→tcdll.tclockの順で終了処理が行われます。

  1. メッセージWM_DESTROYが送られてくるので、tclock.exeの関数OnDestory→関数ClearTClockMainで、tcdll.tclockのAPI関数HookEndを呼び出す(exe/wndproc.c)。
  2. tcdll.tclockでは、関数HookEndで時計ウィンドウにSendMessageでメッセージCLOCKM_EXITを送る(dll/main.c)。
  3. メッセージCLOCKM_EXITが送られたら、関数EndClockを呼び出してサブクラス化を解除する(dll/wndproc.c、dll/main2.c)。
  4. 関数HookEndでフックがアンインストールされる(dll/main.c)。

Windowsがシャットダウン(ログオフ)されるとき

Windowsで「電源を切る」「再起動」「ログオフ」を選ぶと、tclock.exeのメインウィンドウにメッセージWM_ENDSESSIONが送られてきます。WM_CLOSEやWM_DESTROYは送られてきません。

WM_ENDSESSIONが送られると関数ClearTClockMainを呼び出し、ClearTClockMainではtcdll.tclockのAPI関数HookEndを呼び出します。メッセージWM_CLOSEが送られたときと同じ手順になります(exe/wndproc.c)。

        case WM_ENDSESSION:
            if(wParam) ClearTClockMain(hwnd);
            break;

Win95+IE4/98/Meでのシャットダウン

上記の「Windowsがシャットダウン(ログオフ)されるとき」は、Windows NT4/2000/XPおよびIE4なしのWindows 95の話です。IE4以上がインストールされたWindows 95、98、Meでは、tclock.exeのメインウィンドウにWM_ENDSESSIONが送られる前に、時計ウィンドウにメッセージWM_DESTROYが送られてきます。終了処理がなされる前に、時計ウィンドウが破棄されてしまいます。

tcdll.tclockでは、WM_DESTROYが送られたら関数OnDestroyを呼び出します(dll/wndproc.c)。

        case WM_DESTROY:
            OnDestroy(hwnd); // main2.c
            break;

関数OnDestoryでは、サブクラス化の解除などの終了処理を行わず、リソースを解放する関数だけを呼び出します(dll/main2.c)。

void OnDestroy(HWND hwnd)
{
    ClearStartButtonResource(); // startbtn.c
    ClearDrawing();             // drawing.c
}

その後、tclock.exeにWM_ENDSESSIONが送られ、関数ClearTClockMain→HookEndが呼ばれますが、時計ウィンドウが破棄されたあとなので、関数EndClockは実行されません。

昔のバグの話

1997年始めのTClockのプログラムでは、時計のサブクラス化プロシージャで、WM_DESTROYが送られてきたときも、終了処理のために関数EndClockを呼び出してました。

        case WM_DESTROY:
            EndClock(hwnd);
            break;

関数EndClockには、次のようなコードがありました。

    SetWindowLong(hwnd, GWL_WNDPROC, (LONG)g_oldWndProc);
    
    g_oldWndProc = NULL;

するとどうなるかというと、WM_DESTROYのcase文にはbreakがあるので……

    return CallWindowProc(g_oldWndProc, hwnd, message, wParam, lParam);

CallWindowProcに値がNULLのg_oldWndProcが渡されて、Explorerがクラッシュするということになります。おそらくこれが「IE 4を入れたらTClockのせいでExplorerが落ちるようになった」の原因ではなかったかと思われます。