Written in Japanese(UTF-8)
2006. 3.13
INASOFT
/トップ/ユーティリティとゲーム/キーボードシミュレータ/作ってみよう

キーボードシミュレータ / keybd_event APIについて / C++ / C# / VB.NET / J# / C++.NET

Icon キーボードシミュレータを作ってみよう!!


キーボードシミュレータの簡単なコマンドライン版を作ってみようという、プログラミング解説講座です。
最終回の第6回では、C++.NETによる.NET Frameworkなアプリケーションとして作ってみます。

第6回(最終回)

第5回へ


■まずはソースコード

今回は、C++.NETによる.NET Framework用のプログラムの作成を行います。

Microsoftは以前に、C++を使いやすくするためのライブラリとして「MFC」というクラスライブラリを作成しました。…が、なんというか、どこかムリヤリ感が漂っていたような気がします。そういえばBorlandも、似たようなライブラリを作っていましたけど、あれはファイルサイズデカすぎだ。

で、今回C++.NETが登場したことで、MFCのときに感じたような違和感がなくなっているかどうかが気になりました。

また、C++直接記述ということで、keybd_event()(Windows API)を使うための特別なテクニックが不必要になるはず…という期待感もありました。

で、ソースコードはこんな感じになります。

  ●ソースコードと実行プログラムのダウンロードはこちらから (13KB)



//
// 引数で指定された文字列に従い、キーボード入力をシミュレートします。

#define WIN32_LEAN_AND_MEAN  // Windows ヘッダーから使用されていない部分を除外します。
#include "windows.h"
#include "stdio.h"
#include "stdlib.h"

using namespace System;

// keybd_event() のためのライブラリ取り込みをコンパイラに指示
#pragma comment(lib, "user32.lib")

// 入力対象のプログラムをアクティブにするまでの待ち時間(ミリ秒単位)
DWORD dwSleepTime = 5000;
// キーの取りこぼしを防ぐための、各入力間の待機時間(ミリ秒単位)
DWORD dwWaitTime = 10;
// ※1ミリ秒=0.001秒のこと

// ********************************************************************
// * checkKey                                                         *
// *   キーボードシミュレート対象文字列かどうかを調べる。             *
// *   引数cが対象ならばtrueを返し、そうでなければfalseを返す。       *
// ********************************************************************
bool checkKey(wchar_t c)
{
    if ( L'0' <= c && c <= L'9' ) { // 数字ならOK
        return true;
    }
    else if ( L'A' <= c && c <= L'Z' ) { // 英大文字ならOK
        // 余談:処理系によっては、A〜Zは連続していないかもしれないが、
        //    少なくとも動作対象の日本語Winodwsで用いられているUTF-8
        //    は連続しているので、この記述を使う。
        return true;
    }
    else if ( c == L' ' ) { // スペースならOK
        return true;
    }
    else if ( c == L'\t' ) { // タブならOK
        return true;
    }

    // それら以外の場合はNG
    return false;
}
int main(array<System::String ^> ^args)
{
    int argc = args->Length;

    if (argc == 0) {
        // 引数が指定されていない場合、使用方法を表示
        Console::WriteLine(L"キーボード入力をシミュレートしたい文字列を引数に指定してください。");
        Console::WriteLine(L"");
        Console::WriteLine(L"keysimnet <文字列> [<待機時間> [<文字間ウェイト>]]");
        Console::WriteLine(L"");
        Console::WriteLine(L"文字列に指定可能な文字は、英大文字・数字・スペース・タブです。");
        Console::WriteLine(L"(英小文字は無視されますのでご注意下さい)");
        Console::WriteLine(L"スペース・タブを指定する場合は、文字列全体をダブルコーテーションで囲います。");
        Console::WriteLine(L"待機時間は、入力開始までの待ち時間をミリ秒単位で指定します(1以上)。");
        Console::WriteLine(L"文字間ウェイトは、入力文字間のウェイト時間をミリ秒単位で指定します(1以上)。");
        return 1;
    }

    // 待機時間取得
    if ( argc > 1 ) {
        try {
            dwSleepTime = Convert::ToUInt32(args[1]);
        }
        catch( Exception^ ) {
            Console::WriteLine(L"待機時間の指定が不正です。ミリ秒単位で1以上の数を指定してください。");
            return 1;
        }
    }

    // 文字間ウェイト取得
    if ( argc > 2 ) {
        try {
            dwWaitTime = Convert::ToUInt32(args[2]);
        }
        catch( Exception^ ) {
            Console::WriteLine(L"文字間ウェイトの指定が不正です。ミリ秒単位で1以上の数を指定してください。");
            return 1;
        }
    }

    // 入力対象のプログラムをアクティブにするまでの待ち
    Console::WriteLine(L"入力対象のプログラムをアクティブにしてください。");
    Console::WriteLine(dwSleepTime.ToString() + L"ミリ秒間待機します...");
    Sleep(dwSleepTime);
    Console::WriteLine(L"\n入力中...");

    // コマンドラインの文字列を取得する
    String^ p = args[0];

    // 取得された文字列を1文字ずつ検査し、
    // シミュレート可能文字列であればkeybd_event() APIに
    // 渡す。
    for(int i=0; i < p->Length ; ++i) {
        // 入力対象の文字列かどうかを調べる
        if ( checkKey(p[i]) ) {
            // キーの押し下げをシミュレートする。
            keybd_event( (BYTE)p[i], (BYTE)0, (DWORD)0, (ULONG_PTR)0);
            // キーの解放をシミュレートする。
            keybd_event( (BYTE)p[i], (BYTE)0, (DWORD)KEYEVENTF_KEYUP, (ULONG_PTR)0);

            // dwWaitTimeミリ秒間待機する
            // (キーの取りこぼしを防ぐため)
            Sleep(dwWaitTime);
        }
    }

    Console::WriteLine(L"キーボードシミュレートが終了しました。");
    return 0;
}


■実行画面

実行画面は次のようになります。

  1. 引数を付けなかった場合

    
    C:\MyProject\keysimnet\release>keysimnet
    キーボード入力をシミュレートしたい文字列を引数に指定してください。
    
    keysimjs <文字列> [<待機時間> [<文字間ウェイト>]]
    
    文字列に指定可能な文字は、英大文字・数字・スペース・タブです。
    (英小文字は無視されますのでご注意下さい)
    スペース・タブを指定する場合は、文字列全体をダブルコーテーションで囲います。
    待機時間は、入力開始までの待ち時間をミリ秒単位で指定します(1以上)。
    文字間ウェイトは、入力文字間のウェイト時間をミリ秒単位で指定します(1以上)。
    
    C:\MyProject\keysimjs\release>
    

  2. 引数を付けた場合

    
    C:\MyProject\keysimnet\release>keysimnet ABCDEFG12345
    入力対象のプログラムをアクティブにしてください。
    5000ミリ秒間待機します...
    入力中...
    キーボードシミュレートが終了しました。
    
    C:\MyProject\keysimcnet\release>
    

「入力対象のプログラムをアクティブにしてください」という表示が出たところで、貼り付け先のウィンドウをアクティブにしてやる必要があります。引数は、英大文字・数字・スペース・タブを指定してください。英小文字は無視されます。

入力対象のプログラムとして、例えばメモ帳にしたい場合は、メモ帳をアクティブにしておくことで、次のような画面になります(CAPSロックがやカナロック等がかっていない場合)。



■解説

.NETのクセに、クラスの構造を取らなくていいんですね。

実は、C++.NETでコーディングを行うのは、今回が初めてだったりします。このコードについても、意味のわからない箇所が何カ所かあったりします。「^」とか。おそらく、演算子やメソッドのオーバーロードを解除する意味合いがあるんじゃないかと見ているんですが、合ってますかね。

.NET Frameworkでは、規定で使われる文字コード体系がUnicodeであるため、C++での記述はwide char(ワイド文字)にしてあります。このあたり、wide charでのコーディングが不慣れだったので、けっこう大変でした。

さて、keybd_event()を使うための手続きが一切必要ないだろうと踏んでいたわけですが、下記の1行が必要となりました。


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

#pragma は、コンパイラやリンカに対して指示を出すための指示子です。ここではリンカに対して、user32.lib を取り込むよう、指示をしています。

この指示はリンカに対する指示ですので、リンカへのオプションとして加えるのでもOKです。…が、ここではあえて、コード中に書くようにしました。

keybd_event()の呼び出しそのものは、C++の呼び出し方と同じです。



    // キーの押し下げをシミュレートする。
    keybd_event( (BYTE)p[i], (BYTE)0, (DWORD)0, (ULONG_PTR)0);
    // キーの解放をシミュレートする。
    keybd_event( (BYTE)p[i], (BYTE)0, (DWORD)KEYEVENTF_KEYUP, (ULONG_PTR)0);


■おわりに

いつのまにやら「いろんな言語でキーボードシミュレータを記述してみよう」という企画にすり替わってしまっていたわけですが、いかがだったでしょうか。

2月の初めにVisual Studio 2005 を16万円で購入しまして、「C++, C#, VB, J# といろんな言語が入っているけど、この中で実際に使うのはC++くらいだろうなぁ」とか思っていました。が、それだと4万円分しか楽しめていないわけで、あとの12万円は捨てていると思うとどうもいたたまれなくて、いろんな言語で書いてみるに至りました。

特に、J#は、あまり日の目を見ることのない言語でしょうから、こういう企画で登場させてやらないと、表に出る機会がなかったでしょう。まぁ、今回が最初で最後です。きっと。

C#は、Microsoftイチオシの言語であることもあってか、言語としてだいぶ洗練されて使いやすい感があったと思います。C++のように、Cを基本とした無理な拡張で生まれた言語というわけではないため、様々な点で優位に立っているとも言えましょう。

C系列の言語の中ではかなり最近になって登場した言語であるのだから、C++やJavaの失敗を踏まえて作成された言語であるのは当然で、優位に立っているのは当たり前というか、むしろそうでなきゃ困るわけですが。

ただ、Windows APIの範囲を完全にカバーできていないというのが、大きな欠点だと思います。これはVBでも言えることであり、C++に対して大きく劣っている点であると言えましょう。まぁ、Windows APIが、C言語を基本に作られているというのが原因なんでしょうけど。でも、Microsoftの力を持ってすれば、難なく克服できる点であるはずなんですけどね。

ところで今後は、WinFXという新しいAPI体系が誕生することになっています。これは、増え続けてもはや管理不能になったWindows APIを整理し拡張しやすくするためのもので、また .NET Frameworkのクラス体型と結びつきやすいものと考えられます。

もしかすると、.NET Frameworkは、WinFX APIへの移行をスムーズなものとするために、あえてWin32API への簡単なインタフェースを準備せず、使いにくくしているのかもしれません。人々がWin32APIに慣れてしまったら、WinFX APIへの移行は大変になってしまうかもしれませんからね。

まぁ、そんなことを思いつつ、今回の企画を終了したいと思います。今度はGUIのプログラムを、いろんな言語で作ってみたいですね。何年後になるかはわかりませんが………。

第5回へ


キーボードシミュレータ / keybd_event APIについて / C++ / C# / VB.NET / J# / C++.NET
/トップ/ユーティリティとゲーム/キーボードシミュレータ/作ってみよう