2011年10月27日木曜日

VC++でグローバルフック

後日、コードを書き直しました。

-----------------------------------

ここしばらくwpfを触ってましたが、今日はちょっと思い立ってVC++を使いました。 VC++にしたのはグローバルフックが使いたかったからです。 C#だと色々面倒くさそうだったので、ずっと前にやったことがあるVC++にしてみました。

作ったのは、Ctrl+Shift+Fキーを押すと10桁のランダムな文字列がキー入力される簡単なものです。 このプログラムを立ち上げておいて、別のアプリで作業中で適当な文字列が欲しくなったらCtrl+Shift+Fを押すと、勝手に10文字入力してくれます。 「ちょっとhtmlの書き方を参考にしたいページがいくつかあって一時的に保存したいだけなのに全部index.htmlでした」っていうようなことがあって作成しました。 ファイル名を考えるのも連番を付けるのも面倒くさかったもので...

何回使うのやら。

コードは「猫でもわかる」をコピー&ペーストしてチョコチョコ書き換えるだけ。 手抜きです。

「すぐに出来るかな?」と思ってたんですが、win32をすっかり忘れていて思ったより時間がかかってしまいました。 これならC#でやってもあまり変わらなかったかも。

手抜きって事と、自分用ツールなので親切に作りこむ必要がないって事でかなり怪しいコードになりました。 変数名の付け方とか、昔のコードの書き方と今のコードの書き方の違いがどうでもよくなってしまって適当に。 「上手いコードを書く人はこういうところで妥協しない」ってのは知っているんですが、つい...

パラメータとかも固定にしてて変更できないし、キーイベントの発行(SendInput)の仕方も不親切仕様です。 でもまぁ、色々作りこんでも自分では使わなそうなので割愛。 不便に思ったら、そのとき勉強しなおして作り直しましょう。

dllのコードはこんな感じ。 VC++の新規プロジェクトでdllを作るとテンプレートに色々書いてあるので古いコードと今のコードの違いとかでは苦しみませんでした。 ただ、C++自体忘れているので細かい部分で色々怪しくなっています。 cppファイルのみ投稿。 ヘッダファイルなど他のファイルは定石っぽいことしか書いてないので省略。

#include "stdafx.h"
#include "CreateRandomString_KeyHook.h"

#define HASH_COL 10

HINSTANCE hInst;
HWND hOwner;
int shortcutKey;

CREATERANDOMSTRING_KEYHOOK_API HHOOK hMyHook = 0;

CREATERANDOMSTRING_KEYHOOK_API int SetHook(HWND hOwnerWnd, int shortcutKeyCode)
{
    srand((unsigned int)time(NULL));

    hOwner = hOwnerWnd;
    shortcutKey = shortcutKeyCode;

    hMyHook = SetWindowsHookEx(WH_KEYBOARD, MyHookProc, hInst, 0);
    if (hMyHook == NULL)
    {
        MessageBox(hOwner, TEXT("フック失敗"), TEXT("dll"), MB_OK);
    }
    return 0;
}

CREATERANDOMSTRING_KEYHOOK_API int ResetHook()
{
    if (UnhookWindowsHookEx(hMyHook) == 0)
    {
        MessageBox(hOwner, TEXT("フック解除失敗"), TEXT("Error"), MB_OK);
    }
    return 0;
}

CREATERANDOMSTRING_KEYHOOK_API LRESULT CALLBACK MyHookProc(int nCode, WPARAM wp, LPARAM lp)
{
    if (nCode < 0)
    {
        return CallNextHookEx(hMyHook, nCode, wp, lp);
    }
    
    if (GetKeyState(VK_SHIFT) < 0 &&
        GetKeyState(VK_CONTROL) < 0 &&
        wp == shortcutKey &&
        lp & 0x80000000
        )
    {
        MessageBeep(0xffffffff);
        ReleaseModKey();

        int *hash = CreateHash();

        for(int i = 0; i < HASH_COL; i++)
        {
            PushKey(hash[i]);
        }

        delete hash;

        return TRUE;
    }
    return CallNextHookEx(hMyHook, nCode, wp, lp);
}

void PushKey(int key)
{
    INPUT input[4];
    memset(input, 0, sizeof(INPUT) * 4);
    if('A' <= key && key <= 'Z')
    {
        input[0].type = INPUT_KEYBOARD;
        input[0].ki.wVk = VK_SHIFT;
        input[1].type = INPUT_KEYBOARD;
        input[1].ki.wVk = key;
        input[2].type = INPUT_KEYBOARD;
        input[2].ki.wVk = key;
        input[2].ki.dwFlags = KEYEVENTF_KEYUP;
        input[3].type = INPUT_KEYBOARD;
        input[3].ki.wVk = VK_SHIFT;
        input[3].ki.dwFlags = KEYEVENTF_KEYUP;
        SendInput(4, input, sizeof(INPUT));
    }
    else
    {
        if('a' <= key && key <= 'z')
        {
            key = key - 'a' + 'A';
        }

        input[0].type = INPUT_KEYBOARD;
        input[0].ki.wVk = key;
        input[1].type = INPUT_KEYBOARD;
        input[1].ki.wVk = key;
        input[1].ki.dwFlags = KEYEVENTF_KEYUP;
        SendInput(2, input, sizeof(INPUT));
    }
}

void ReleaseModKey()
{
    INPUT input[2];
    memset(input, 0, sizeof(INPUT) * 2);
    input[0].type = INPUT_KEYBOARD;
    input[0].ki.wVk = VK_SHIFT;
    input[0].ki.dwFlags = KEYEVENTF_KEYUP;
    input[1].type = INPUT_KEYBOARD;
    input[1].ki.wVk = VK_CONTROL;
    input[1].ki.dwFlags = KEYEVENTF_KEYUP;
    SendInput(2, input, sizeof(INPUT));
}

int* CreateHash()
{
    WCHAR *charTable = TEXT("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");

    int* res = new int[HASH_COL];
    memset(res, 0, sizeof(int) * HASH_COL);

    unsigned long long t = 0;

    for(int i = 0; i < HASH_COL; i++)
    {
        if(t == 0)
        {
            t = (((unsigned long long)rand() ) << 32) | ((unsigned long long)rand());
        }

        int c = t % 62;
        t /= 62;
        res[i] = (int)charTable[c];
    }

    return res;
}

本体側のコードはこんな感じ。 一応動いているけど、「こんなのブログに投稿していいのか?」ってできですな。

#include <windows.h>
#include "..\CreateRandomString_KeyHook\CreateRandomString_KeyHook.h"

#pragma comment(lib, "CreateRandomString_KeyHook.lib")

#define WINDOW_TITLE TEXT("ランダム文字列(0-9a-zA-Z)を挿入")

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
ATOM InitApp(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);

WCHAR szClassName[] = TEXT("hookmain");        //ウィンドウクラス

BOOL bHook = FALSE;

int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,
                   LPSTR lpsCmdLine, int nCmdShow)
{
    MSG msg;
    
    if (!InitApp(hCurInst))
        return FALSE;
    if (!InitInstance(hCurInst, nCmdShow)) 
        return FALSE;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return (int)msg.wParam;
}

//ウィンドウ・クラスの登録

ATOM InitApp(HINSTANCE hInst)
{
    WNDCLASSEX wc;
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WndProc;    //プロシージャ名
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInst;        //インスタンス
    wc.hIcon = LoadIcon(hInst, TEXT("CreateRandomString"));
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName = NULL;    //メニュー名
    wc.lpszClassName = (LPCWSTR)szClassName;
    wc.hIconSm = LoadIcon(hInst, TEXT("CreateRandomString"));
    return (RegisterClassEx(&wc));
}

//ウィンドウの生成

BOOL InitInstance(HINSTANCE hInst, int nCmdShow)
{
    HWND hWnd;

    hWnd = CreateWindow(szClassName,
            WINDOW_TITLE,    //タイトルバーにこの名前が表示されます
            WS_CAPTION | WS_SYSMENU,    //ウィンドウの種類
            CW_USEDEFAULT,    //X座標
            CW_USEDEFAULT,    //Y座標
            500,    //幅
            70,    //高さ
            NULL,            //親ウィンドウのハンドル、親を作るときはNULL
            NULL,            //メニューハンドル、クラスメニューを使うときはNULL
            hInst,            //インスタンスハンドル
            NULL);
    if (!hWnd)
        return FALSE;
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    return TRUE;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
    static HINSTANCE hInst;
    WCHAR* cmdLine = NULL;
    int lc = 0;
    size_t tlen = 0;

    switch (msg) {
        case WM_CREATE:
            cmdLine = GetCommandLine();
            lc = cmdLine[wcslen(cmdLine) - 1];
            if('A' <= lc && lc <= 'Z')
            {
                SetHook(hWnd, lc);

                tlen = wcslen(WINDOW_TITLE);
                WCHAR* title = new WCHAR[tlen + 3];
                wcscpy_s(title, tlen + 3, WINDOW_TITLE);
                title[tlen] = ' ';
                title[tlen + 1] = lc;
                title[tlen + 2] = '\0';
                SetWindowText(hWnd, title);

                delete title;
            }
            else
            {
                SetHook(hWnd, 'F');
            }
            break;
        case WM_CLOSE:
            ResetHook();
            DestroyWindow(hWnd);
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return (DefWindowProc(hWnd, msg, wp, lp));
    }
    return 0;
}

無駄に64bitでコンパイルしてみました。 なんだかんだ言って、私が最初に作った64bit専用プログラムはこれなのか? いやいや、他に何か作ったような...