MFC
WinXP の簡易ユーザー切り替えを検知する


HomeProgramming TipsMFC Tips[MFC-028]

 ダウンロード : WinXP簡易ユーザー切り替え検知プログラムサンプル(2007/11/17) 

Windows XP から、ユーザー切り替えを瞬時に行える機能として簡易ユーザー切り替えが実装されました。
これはとても便利で、マルチユーザー機能を使う事で複数のユーザーが快適に PC を使う事が出来ます。

このユーザー切り替えのタイミングを捕捉してみましょう。
このタイミングは WindowsXP では WTS 関連 API として追加されており、
これらを使用する事で切り替えを検知する事が出来ます。

最初にすべきことは WTSUnRegisterSessionNotification() を使用して、自分のウィンドウハンドルを登録します。
すると、以降に発生した切り替えなどの情報がメッセージとして指定のウィンドウに通知されるようになります。

通常、 WinXP 専用のプログラムを作成する場合は stdafx.h の _WIN32_WINNT、WINVER を両方とも 0x0501 と書き換えます。
しかし、私の環境ではなぜかうまくいかなかった事に加えて、この方法では WinXP 以外で起動すら不可能になります。

そこで私の推奨としては関数の動的リンクを使います。
WTS 関連は WTSAPI32.DLL に含まれています。ここから目的の関数ポインタを探し出して使えばよいのです。
この方法であれば、WinXP 以外の OS でも起動は出来ます。こちらの方が異常と思われないので安心です(笑)。

これらの通知を受け取る方法は二種類あります。
特に必要がなければ自分の情報だけで充分です。


// ---------------------------------------------------------------------
// セッションの切り替え通知の受け取りを登録する
// dwFlag = NOTIFY_FOR_THIS_SESSION : 自分の情報のみ受け取る
//          NOTIFY_FOR_ALL_SESSIONS : 全ての情報を受け取る
// ---------------------------------------------------------------------
BOOL CTestUserStatusApp::WTSRegisterSessionNotification(
    HWND hWnd,    // ウィンドウハンドル
    DWORD dwFlag  // 通知受け取りフラグ
)
{
    BOOL bSuccess = FALSE;

    // 通知ウィンドウを登録する(動的リンク呼び出し)
    HINSTANCE hDLL = ::LoadLibrary(_T("WTSAPI32.DLL"));
    if (hDLL) {
        typedef BOOL (__stdcall *WTSFUNC)(HWND, DWORD);
        WTSFUNC pWTSRegSession = (WTSFUNC)::GetProcAddress(
            hDLL,
            _T("WTSRegisterSessionNotification")
        );
        if (pWTSRegSession) {
            bSuccess = pWTSRegSession(hWnd, dwFlag);
        }
        ::FreeLibrary(hDLL);
    }
    return bSuccess;
}


これで hWnd のプロシージャに通知メッセージが飛んでくるようになりました。
Windows2000 等の OS では、結果が FALSE となります。アドレスがないのですから当たり前です(笑)。
どのみち通知されないのですから処理に関して特に OS チェックなど気を遣う必要はありません。

飛んでくるメッセージは WM_WTSSESSION_CHANGE です。
このメッセージが来たあとで wParam を更にチェックすれば切り替えなどに関する情報を取得できます。

メッセージは自分で定義します。
これは WINVER がデフォルトなので未定義となってしまうためです。


#include <Wtsapi32.h>
#include <Lmcons.h>

// ---------------------------------------------------------------------
// セッション変更通知メッセージの定義
// ---------------------------------------------------------------------

#define WM_WTSSESSION_CHANGE            0x02B1
#define WTS_CONSOLE_CONNECT             0x1
#define WTS_CONSOLE_DISCONNECT          0x2
#define WTS_REMOTE_CONNECT              0x3
#define WTS_REMOTE_DISCONNECT           0x4
#define WTS_SESSION_LOGON               0x5
#define WTS_SESSION_LOGOFF              0x6
#define WTS_SESSION_LOCK                0x7
#define WTS_SESSION_UNLOCK              0x8
#define WTS_SESSION_REMOTE_CONTROL      0x9


さて、wParam のパラメータがどのような意味を持つのか説明します。 コンソールセッションとは文字通りコンソールつまり操作権という事になります。
そのため WTS_CONSOLE_CONNECT の状態で、かつ、その対象が自分自身であれば、
簡易ユーザー切り替えにより自分に戻ってきたという事になります。
メッセージの通知登録を NOTIFY_FOR_THIS_SESSION としておけば、ユーザー名の確認は不要です。

セッションのロックとアンロックは動作のために一時的に制御を停止させる事であり、
このメッセージでユーザー制御が戻ってきたと考えるのは拙いです。
論より RUN でサンプルプログラムを実行させたまま、
あれこれ簡易ユーザー切り替えを試していただければ、一発で動作が理解できると思います。

処理の分岐ですが下記のように実装するのが MFC 的にはお勧めです。


// ---------------------------------------------------------------------
// CTestUserStatusView メッセージマップ
// ---------------------------------------------------------------------

BEGIN_MESSAGE_MAP(CTestUserStatusView, CScrollView)
    ON_WM_CREATE()
    ON_WM_DESTROY()
    ON_MESSAGE(WM_WTSSESSION_CHANGE, OnWtsSessionChange) // 追加
END_MESSAGE_MAP()

// ---------------------------------------------------------------------
// セッションの状態が変更された
// ---------------------------------------------------------------------
LRESULT CTestUserStatusView::OnWtsSessionChange(WPARAM wParam, LPARAM lParam)
{
    DWORD dwId = (DWORD)lParam;
    switch (wParam) {
        case WTS_CONSOLE_CONNECT:        OnWtsConsoleConnect(dwId);         break;
        case WTS_CONSOLE_DISCONNECT:     OnWtsConsoleDisconnect(dwId);      break;
        case WTS_REMOTE_CONNECT:         OnWtsRemoteConnect(dwId);          break;
        case WTS_REMOTE_DISCONNECT:      OnWtsRemoteDisconnect(dwId);       break;
        case WTS_SESSION_LOGON:          OnWtsSessionLogOn(dwId);           break
        case WTS_SESSION_LOGOFF:         OnWtsSessionLogOff(dwId);          break
        case WTS_SESSION_LOCK:           OnWtsSessionLock(dwId);            break
        case WTS_SESSION_UNLOCK:         OnWtsSessionUnlock(dwId);          break
        case WTS_SESSION_REMOTE_CONTROL: OnWtsSessionRemoteControl(dwId);   break;
    }
    return 0;
}


なお、lParam の値を使えば、ユーザー名が取得できます。


typedef BOOL (__stdcall *WTSQUERYFUNC)(HANDLE, DWORD, WTS_INFO_CLASS, LPTSTR*, DWORD*);
typedef VOID (__stdcall *WTSFREEFUNC)(PVOID);

// ---------------------------------------------------------------------
// WTSユーザー名を取得する
// ---------------------------------------------------------------------
CString CTestUserStatusApp::GetWTSUserName(DWORD dwID)
{
    CString strUser;
    HINSTANCE hDLL = ::LoadLibrary(_T("WTSAPI32.DLL"));
    if (hDLL) {

        // WTSQuerySessionInformationA 関数ポインタ動的取得
        WTSQUERYFUNC pWTSQuerySessionInformation = (WTSQUERYFUNC)::GetProcAddress(
            hDLL,
            _T("WTSQuerySessionInformationA")
        );

        // WTSFreeMemory 関数ポインタ動的取得
        WTSFREEFUNC pWTSFreeMemory = (WTSFREEFUNC)::GetProcAddress(
            hDLL,
            _T("WTSFreeMemory")
        );

        // ユーザー名を取得する
        if (pWTSQuerySessionInformation && pWTSFreeMemory) {
            LPTSTR lpUser =NULL;
            DWORD  dwSize = 0;
            if (pWTSQuerySessionInformation(
                WTS_CURRENT_SERVER_HANDLE,
                dwID,
                WTSUserName,
                &lpUser,
                &dwSize
            )){
                strUser = lpUser;
                pWTSFreeMemory(lpUser);
            }
        }
        ::FreeLibrary(hDLL);
    }
    return strUser;
}


さて、然るべき処理をしたら最後に登録したウィンドウハンドルを抹消します。
これは登録した数だけ解除が必要です。足りなくても余分にやっても拙いようです。
MSDN では WM_DESTROY で処理せよとありますので、そちらで行うのが王道でしょう。

解除処理も WinXP 以降専用関数ですから、動的リンクにより関数呼び出ししてやります。


// ---------------------------------------------------------------------
// 登録したセッションの切り替え通知登録を解除する
// ---------------------------------------------------------------------
BOOL CTestUserStatusApp::WTSUnRegisterSessionNotification(HWND hWnd)
{
    BOOL bSuccess = FALSE;

    // 通知ウィンドウの登録を解除する(動的リンク呼び出し)
    HINSTANCE hDLL = ::LoadLibrary(_T("WTSAPI32.DLL"));
    if (hDLL) {
        typedef BOOL (__stdcall *WTSFUNC)(HWND);
        WTSFUNC pWTSUnRegSession = (WTSFUNC)::GetProcAddress(
            hDLL,
            _T("WTSUnRegisterSessionNotification")
        );
        if (pWTSUnRegSession) {
            bSuccess = pWTSUnRegSession(hWnd);
        }
        ::FreeLibrary(hDLL);
    }
    return bSuccess;
}




 Copyright 2008 VALGUS. All Rights Reserved.