今回は、J#による.NET Framework用のプログラムの作成を行います。
J#に関するMicrosoftの文章を見てみると、これは「オリジナルの言語である」とのことが書いてあります。
確かにまぁ、オリジナルの言語であることは確かなんですけど、名前の付け方といい、文法的な部分といい、Javaを意識して作られていることは確かですね。ただし、JavaはJavaVM上で動きますが、J#は.NET Framework上で動きます。
さて、前回、前々回と同様に、keybk_event()(Windows API)を使うための特別なテクニックが必要になります。
ソースコードはこんな感じになります。
●ソースコードと実行プログラムのダウンロードはこちらから (5KB)
package keysimjs;
//
// 引数で指定された文字列に従い、キーボード入力をシミュレートします。
import System.*;
// Win32APIを呼び出すためのクラス
public class win32api
{
/** @dll.import("user32.dll") */
public static native UInt32 keybd_event(byte bVk, byte bScan, UInt32 dwFlags, UIntPtr dwExtraInfo);
}
public class keysimjs
{
public static void main(String[] args) throws java.lang.InterruptedException
{
// 入力対象のプログラムをアクティブにするまでの待ち時間(ミリ秒単位)
int iSleepTime = 5000;
// キーの取りこぼしを防ぐための、各入力間の待機時間(ミリ秒単位)
int iWaitTime = 10;
// ※1ミリ秒=0.001秒のこと
if (args.length == 0) {
// 引数が指定されていない場合、使用方法を表示
Console.WriteLine("キーボード入力をシミュレートしたい文字列を引数に指定してください。");
Console.WriteLine("");
Console.WriteLine("keysimjs <文字列> [<待機時間> [<文字間ウェイト>]]");
Console.WriteLine("");
Console.WriteLine("文字列に指定可能な文字は、英大文字・数字・スペース・タブです。");
Console.WriteLine("(英小文字は無視されますのでご注意下さい)");
Console.WriteLine("スペース・タブを指定する場合は、文字列全体をダブルコーテーションで囲います。");
Console.WriteLine("待機時間は、入力開始までの待ち時間をミリ秒単位で指定します(1以上)。");
Console.WriteLine("文字間ウェイトは、入力文字間のウェイト時間をミリ秒単位で指定します(1以上)。");
System.exit(1);
}
// 待機時間取得
if (args.length > 1) {
try {
iSleepTime = Convert.ToInt32(args[1]);
}
catch (FormatException exf) {
Console.WriteLine("待機時間の指定が不正です。ミリ秒単位で1以上の数を指定してください。");
System.exit(1);
}
}
// 文字間ウェイト取得
if (args.length > 2) {
try {
iWaitTime = Convert.ToInt32(args[2]);
}
catch(FormatException exf) {
Console.WriteLine("文字間ウェイトの指定が不正です。ミリ秒単位で1以上の数を指定してください。");
System.exit(1);
}
}
// 入力対象のプログラムをアクティブにするまでの待ち
Console.WriteLine("入力対象のプログラムをアクティブにしてください。");
Console.WriteLine(iSleepTime + "ミリ秒間待機します...");
java.lang.Thread.sleep(iSleepTime, 0);
Console.WriteLine("\n入力中...");
// コマンドラインの文字列を取得する
String p = args[0];
// 取得された文字列を1文字ずつ検査し、
// シミュレート可能文字列であればkeybd_event() APIに
// 渡す。
for (int i=0 ; i<p.get_Length() ; ++i) {
// 入力対象の文字列かどうかを調べる
if ( checkKey(p.get_Chars(i)) ) {
// キーの押し下げをシミュレートする。
win32api.keybd_event((byte)p.get_Chars(i), (byte)0, (UInt32)0, UIntPtr.Zero);
// キーの解放をシミュレートする。
win32api.keybd_event((byte)p.get_Chars(i), (byte)0, (UInt32)2/*KEYEVENTF_KEYUP*/, UIntPtr.Zero);
// dwWaitTimeミリ秒間待機する
// (キーの取りこぼしを防ぐため)
java.lang.Thread.sleep(iWaitTime, 0);
}
}
Console.WriteLine("キーボードシミュレートが終了しました。");
System.exit(0);
}
// ********************************************************************
// * checkKey *
// * キーボードシミュレート対象文字列かどうかを調べる。 *
// * 引数cが対象ならばtrueを返し、そうでなければfalseを返す。 *
// ********************************************************************
public static boolean checkKey(char c)
{
if ( '0' <= c && c <= '9' ) { // 数字ならOK
return true;
}
else if ( 'A' <= c && c <= 'Z' ) { // 英大文字ならOK
return true;
}
else if ( c == ' ' ) { // スペースならOK
return true;
}
else if ( c == '\t' ) { // タブならOK
return true;
}
// それら以外の場合はNG
return false;
}
}
実行画面は次のようになります。
C:\MyProject\keysimjs\release>keysimjs キーボード入力をシミュレートしたい文字列を引数に指定してください。 keysimjs <文字列> [<待機時間> [<文字間ウェイト>]] 文字列に指定可能な文字は、英大文字・数字・スペース・タブです。 (英小文字は無視されますのでご注意下さい) スペース・タブを指定する場合は、文字列全体をダブルコーテーションで囲います。 待機時間は、入力開始までの待ち時間をミリ秒単位で指定します(1以上)。 文字間ウェイトは、入力文字間のウェイト時間をミリ秒単位で指定します(1以上)。 C:\MyProject\keysimjs\release>
C:\MyProject\keysimjs\release>keysimjs ABCDEFG12345 入力対象のプログラムをアクティブにしてください。 5000ミリ秒間待機します... 入力中... キーボードシミュレートが終了しました。 C:\MyProject\keysimcjs\release>
「入力対象のプログラムをアクティブにしてください」という表示が出たところで、貼り付け先のウィンドウをアクティブにしてやる必要があります。引数は、英大文字・数字・スペース・タブを指定してください。英小文字は無視されます。
入力対象のプログラムとして、例えばメモ帳にしたい場合は、メモ帳をアクティブにしておくことで、次のような画面になります(CAPSロックがやカナロック等がかっていない場合)。

まぁ、前回以前と同じ動きをしています。当たり前ですね。同じように動くことを目的にして作っていますから。
VBのときも書いた感想ですが、C#で書いたコードと似ています。実際のところは逆でしょうかね。Javaに似せてC#が作られたのです。だから、J#がC#に似てくるのは当然、と。
というか、使う人っているんですかね。僕は使いましたけど。明日からは使わないかもしれない。でもまぁ、Javaの過去資産を生かして.NET Framework対応アプリを作ろうという企業には、ちょうど良いかもしれませんね。なお、実のところ、J#でコーディングをしたのは、今回が初めてです。なので、色々と戸惑いました。
プログラムが戻り値を返せるようにするため、mainをintにしようとしたのですが、そうしたらなんだか、コンパイラに怒られました。仕方がないので、System.exit()を使っています。
あと、文字列を走査するためにforを使っています。ひょっとしたら、foreach相当の文法があるのかもしれませんが、よくわかりませんでした。
究極にわからなかったのは、Windows APIを呼ぶための手続きです。困ったことに、Web上の文献も、MSDNのヘルプも、J#に関する情報が少ないのです。あちこち探しまくって、nativeというキーワードを使うということはわかったのですが、これだけでは user32.dll と結びつけることはできませんし………ムリなのかなぁと諦めかけていたところ、MSDNのヘルプ内の記述があるのを発見しました。
…とまぁ、だいぶ苦労しましたが、具体的には次の部分です。
// Win32APIを呼び出すためのクラス
public class win32api
{
/** @dll.import("user32.dll") */
public static native UInt32 keybd_event(byte bVk, byte bScan, UInt32 dwFlags, UIntPtr dwExtraInfo);
}
一見するとコメントのように見える /** @dll.import("user32.dll") */ が、どうやら重要な役割を果たしているようです。user32.dllとの結び付けをやってくれている様子。次の行で、キーワードnativeを使って、Windows APIを使うことの宣言をしています。
で、keybd_event()の呼び出しそのものは、次のようになります。
// キーの押し下げをシミュレートする。
win32api.keybd_event((byte)p.get_Chars(i), (byte)0, (UInt32)0, UIntPtr.Zero);
// キーの解放をシミュレートする。
win32api.keybd_event((byte)p.get_Chars(i), (byte)0, (UInt32)2, UIntPtr.Zero);
// 上記の「2」は 「KEYEVENTF_KEYUP」を意味しています。
キャストが登場しまくっているのは、僕が未熟なせいかもしれません。また、UIntPtr.Zeroという記述は、今回初めて発見しました。当初は、(UIntPtr)0で片付けようとしたのですが、なぜかキャストができないと怒られました。そこで、冗談のつもりでCOBOLっぽくZeroって書いてみたら通ったので、これでいいかぁ〜ということにしてしまいました。
いろんな言語でキーボードシミュレータを記述してみようとかいう企画に変わり果てている今企画ですが、次回でいよいよ最終回。次回はC++.NETを用いて、キーボードシミュレータ(.NET Framework版)のコマンドライン版を作ってみます。
MFCよりはまともな変化を遂げているだろうとウワサされるC++.NETですが、さて、どうなっているでしょう。お楽しみに。