Windowsドッキリプログラミング初級

池谷

はじめに

ここでは、C++を使ってWindows上で動くビックリなプログラム(初級)を厳選して作っていきます。なので、プログラミングの知識が必要となってきます。今回、ここでプログラミングの説明をしている余裕がないので、勝手に、あなたがC++を知っているということにしてしまいます(←超手抜き)。C++での作業にあたってはVC++を使ってください。そうすればここに書いてあるとおりにすれば同じ結果になるはず。あと、ここでは、Windowsの特殊な知識(API)を身につけなきゃならんので要注意。APIについては次の章を読んでね。

APIについて

WindowsにはAPI(Application Programming Interface)という、膨大な関数群がある。APIは使用用途によって分類されているのだ(Windowsで標準的に使われるWin32API、DirectX関連で使われるDirectX APIなど)。Windowsアプリケーションはこれらを使っていろんなことができるわけだ(例えばウィンドウの描画とか…etc)。C++では、APIを直接使うのが一般的(MFCなるものもありますがね)。 C++では “windows.h” をインクルードするだけで利用可能。具体的にもっと知りたいという読者は、本を立ち読みしたり(プログラミング関連の本って高すぎ)、MSDNライブラリを漁ってみたりしてください。

サンプルについて

我が部には、CD-ROM付録など、高等なことをするほどさいふが豊かではないので、ベーマガ(BASIC Magazineのつもり)同様、人間コピー機を作動させてください。スキャナーを持っているブルジョアな人は、それを使ってもかまいませんよ。あと、新型のヴィールスを作ろうと思っている方、ハッキングを試みている方、この記事はあまりその方面には縁がないと思います。どちらかというと憎いやつを叩きのめしてやりたいと思っている人向けかも?

注意!

ここでは、少々アブナイことをするので、要注意。慎重に作業を遂行せよ!あと、生成物は個人で楽しむことを前提としているので、**決して他人に迷惑をかけないように!また、この記事が原因で発生したいかなる損害もわれわれは責任をとらないぞ!**あと、WinNT系での動作は一切未確認。大半はできないでしょう。では、さっそく実践に入りましょう。

マウスが。。。

マウスカーソルが飛び回るものを作ってみましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <windows.h>

WINAPI WinMain(HINSTANCE hI nstance, HINSTANCE hPrevInstance, LPSTR Cmd, int iState)
{
    BOOL ret;
    MSG msg;
    // メッセージループ
    while (1)
    {
        // メッセージチェック
        ret = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
        if (ret)
        { // 終了判定
            f(msg.message == WM_QUIT) return msg.wParam;
            // その他の処理
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else
        {
            // Win が暇なとき
            if (rand() % 50000 == 0)
            {
                // マウスカーソルをランダムに動かす
                int x = rand() % GetSystemMetrics(SM_CXSCREEN);
                int y = rand() % GetSystemMetrics(SM_CYSCREEN);
                SetCursorPos(x, y);
            }
        }
    }
    return msg.wParam;
}

メッセージループのためにコードが冗長になっていますね。メッセージループとはアプリケーションが実行されている間に無限に行われるループのことだよん。ここで、イベント処理などをしたりするんです。PeekMessage()、TranslateMessage()、DispatchMessage()等がそれにあたります。重要なのは、**elseの中は、イベントが何もなかったときに実行されるということ。**つまり、ほぼ毎回実行されると考えて良いでしょう。細かい説明はぬきにして、次に進みましょう。このサンプルでは、先ほどあげた関数やGetSystemMetrics()、SetCursorPos()など多くのAPIを利用しています。GetSystemMetrics(SM_CXSCREEN)で、全画面の幅、GetSystemMetrics(SM_CXSCREEN)で高さを取得可能。それらを乱数にかけ、画面内の任意の座標を定め、SetCursorPos()関数でマウスカーソルの座標をピクセル単位で指定(X座標,Y座標)してやればマウスが画面上を動き回るわけ。ちなみに、rand()%50000とあるけれど、それは、マウスカーソルをゆっくり動かすためのものなのです。このサンプルで大事なのは、SetCursorPos()の使い方です。そこを理解すれば、自分のものにできるでしょう。最後に終了の仕方なのですが、VC++上でなら[Shift]+[F5]、それ以外のときは強制終了してください(他のサンプルのときも同様)。

壁紙が!

実行すると壁紙が変わってしまうプログラムを作りましょう。ビットマップファイルが必要になるので適当なもの(サイズ、色数、内容は何でもよし)を実行ファイルと同じフォルダに置いておきましょう。 名前は “wp.bmp” としてください。

1
2
3
4
5
6
7
8
9
#include <windows.h>

WINAPI WinMain(HINSTANCE hI nstance, HINSTANCE hPrevInstance, LPSTR Cmd, int iState)
{
    // メッセージループ不要
    char s[MA X_PATH] = "wp.bmp";
    SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, s, SPIF_SENDWININICHANGE ¦SPIF_UPDATEINIFILE);
    return -1;
}

壁紙の変更はひとつのAPIで可能。それはSystemParametersInfo()関数。この関数は、システム情報を取得/設定できます。第1パラメータに取得/設定する項目、第2パラメータに取得したものを格納するポインタ、第3パラメータに設定する値、第4パラメータに更新の方法を入れてやる。今回、壁紙の変更をするので、第1パラメータはSPI_SETDESKWALLPAPERにして、第2パラメータは不要なので0にし、第3パラメータは壁紙にするビットマップファイルのパスの文字列へのポインタ、第4パラメータには設定ファイルを更新し、全てのアプリケーションにその旨を伝えることを意味するSPIF_SENDWININICHANGE定数SPIF_UPDATEINIFILE定数OR結合しておきます(APIなどでは、複数の項目をセットする際、OR結合を良く使います)。注意してほしいのは、ビットマップファイルのパスの文字列を直接、パラメータに入れてはいけないということ。必ず、**変数に書き込んでからそのポインタをパラメータに入れよう。**なお、カレントディレクトリが実行ファイルのあるフォルダと異なっていると変更に失敗します。それを解消したいなら、SearchPath()やGetModuleFileName()などのAPI関数を使うと良いでしょう(各自で調べてくれ)。SystemParametersInfo()は他にいろんな設定を取得/変更することが可能。ぜひ、MSDNライブラリを漁ってみるといいよ。ちなみに、壁紙の表示方法には中央/並べる/拡大の3つがある。これらを変更するにはSystemParametersInfo()を呼び出す前にレジストリをいじくらなけりゃならない。場所はHKEY_CURRENT_USER¥ControlPanelセクションにあるDesktopTileWallpaperキーと、WallpaperStyleキーだ。前者(堅苦っしいしい表現だなぁ)を0にすると中央に表示、1にすると並べて表示になる。後者を2にすると拡大表示になるので試してみては。regeditを起動して先ほどの場所に行ってみましょう。面倒だったら検索にかけてもかまいません(私はWallpaperという単語でこれらの項目を探し出した)。レジストリをいじったり探したりするのにはregeditが便利。プログラム上でのレジストリの変更方法は最後のサンプルを参考にしてみてね。

うんざりアイコン

デスクトップに嫌なほどアイコンを作ってみましょう(実行ファイル自身をデスクトップフォルダに大量に複製)。余談ですが、我が部ではこのようなことをネットワークでやった愚者がいて、戦争になった事もあったなぁ(そいつ遊んでばっかいる馬鹿中の馬鹿であるのだが、なぁK藤君)。それを自分のパソコンに自動的にやるわけですね(自爆?意味無いじゃん)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <windows.h>
#include <shlobj.h>

#define ICON_NUM 100

WINAPI WinMain(HINSTANCE hI nstance, HINSTANCE hPrevInstance, LPSTR Cmd, int iState)
{
    char path[MAX_PATH];
    char src[MAX_PATH];
    char dest[MAX_PATH];
    // デスクトップに実行ファイルを多数置く
    SHGetSpecialFolderPath(NULL, path, CSIDL_DESKTOPDIRECTORY, FALSE);
    wsprintf(path, "%s¥¥game", path);
    GetModuleFileName(hInstance, src, MAX_PATH);
    for (int i = 0; i < ICON_NUM; i++)
    {
        wsprintf(dest, "%s(%d).exe", path, i);
        CopyFile(src, dest, FALSE);
    }
    return -1;
}
消そうとして、誤って全選択中にダブルクリックしてしまったら...100x100!

実行ファイルのアイコンを設定するには、プロジェクトに自分の好きなアイコンを追加してください(上図はWingdingsフォントの大文字のN=どくろマークをアイコンにした例)。やることは単純、まず、デスクトップフォルダのパスを取得、そこに実行ファイル自身をコピー。これを一定の数だけ繰り返す。同じファイル名でコピーすると上書きされてしまうので、ファイル名をgame(n).exeのように、数字が含まれるようにします(なぜ、game?)。SHGetSpecialFolderPath()関数でデスクトップフォルダを取得します。このAPIは"windows.h"では定義されていないので注意が必要。代わりに “shlobj.h” で定義されている。ファイルシステムに関するAPIはWin32と隔離されているのです。それはさておき、SHGetSpecialFolderPath()関数は他に、いろいろな特殊フォルダを取得できます(マイドキュとかWindowsとかSystem、のちのち出てきますがスタートアップとか…ほかいろいろ)。3番目のパラメータに定数を代入すれば取得するフォルダの種類が選択可。デスクトップフォルダを取得するには、CSIDL_DESKTOPDIRECTORYを使います。このフォルダはご存じの通り、たいていの場合は"ルートドライブ:¥Windows¥デスクトップ¥"でしょう。一見、これをそのまま使えばよいと思うかもしれませんが、あくまでこれはデフォルト値なのです。凝ったユーザーは故意に別にしているかもしれない。アプリケーションを設計する際にはそういうまれなケースも重要視しなければならないのだ(念には念を押すのだぁ!)。SHGetSpecialFolderPath()を使えばほぼ確実に目的のフォルダのパスを入手できる。パスを格納するためのバッファのポインタを2番目のパラメータに入れましょう。ファイルのコピーにはCopyFile()関数を使うのですが、その前に、実行ファイル自身のパスを準備しなければなりませんね。仮に実行ファイル名を、“game.exe"とするならば、コピー元を"game.exe"にするだけで一応OKです(壁紙変更のサンプルと同様)。しかし、このように書くと、カレントディレクトリ内にある"game.exe"という名のファイルを示します(カレントディレクトリがわからんという人はDOSプロンプトを起動してそこに表示されているパスを見てくれればわかるでしょう)。そのためカレントディレクトリとアプリケーションのディレクトリが異なる場合、問題発生!そこで、GetModuleFilePath()関数を使う。このAPI関数は、実行ファイル自身のパスとファイル名を取得できるのでとても便利。後はコピー先として、デスクトップフォルダに数字を含めたファイル名を結合してやるだけ。わりと簡単でしょ。ちなみに、ICON_NUM定数は(見てのとおり)複製するファイルの数だよ(サンプルでは100になっているが、この数字をどう変えるかはあなた次第だ)。

強制終了っと…あれっ!

Windowsで不可欠な強制終了。今それを剥ぎ取ってみせましょう。

1
2
3
4
5
6
7
#include <windows.h>
WINAPI WinMain(HINSTANCE hI nstance, HINSTANCE hPrevInstance, LPSTR Cmd, int iState)
{
    int sw = 0;
    SystemParametersInfo(SPI_SCREENSAVERRUNNING, sw, 0, SPIF_SENDWININICHANGE ¦ SPIF_UPDATEINIFILE);
    return -1;
}

かっ、簡単!これだけでそんなことができるのか!このサンプルは岡本武史さんの「VisualBasicAPIサンプル集」で発見。このサンプル集は他にもいろいろ役立てられるものがたくさん。Webで探してみてください(後で知ったことなのだが、壁紙の変更方法がまるっきりこのサンプル集に載っていました。もっと早く見つけてれば、レジストリの検索しないで済んだのにぃT_T)。しかし、なぜこのようなAPIがあるのだろうか。SystemParametersInfo()関数は、壁紙の変更サンプルでも登場しましたね。SPI_SCREENSAVERRUNNING定数を使うとこういう使い道もできるんですねぇ。2番目のパラメータを0にすると強制終了OFF、1にするとON。sw変数の値を変更してください(元に戻すのを忘れると大変なことに…)。3番目は0にしてください。でもなんでSCREENSAVERなんだ?そうか、スクリーンセーバー中に強制終了されないようにするためのものなのか!だから、**ウィンドウズキーや[Alt]+[Tab]**も効かないんだ。納得納得(もし、パスワード付のスクリーンセーバー中に強制終了されてしまったらしゃれになんないなぁ)。そこで、MSDNライブラリを調べてみると…ぎゃっはっはぁ、「内部的に使用されています。使わないでください。」だって。笑えるね。さすがにおおっぴらにするわけにはいかなかったんだろうね。ただし、WinNT系では動かないそうです(そりゃそうじゃなきゃ困る)。

起動時に発症!

では最後に、Windows起動時に起動するようなアプリケーションを作りましょうか。これはかなり役に立つし使える。一般に2とおりの方法がありますがひとつずつ見てきましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// スタートアップによる手法
#include <windows.h>

WINAPI WinMain(HINSTANCE hI nstance, HINSTANCE hPrevInstance, LPSTR Cmd, int iState)
{
    char path[MAX_PATH];
    char file[MAX_PATH];
    // スタートアップフォルダを探す(カレントユーザー)
    SHGetSpecialFolderPath(NULL, path, CSIDL_STARTUP, FALSE);
    if (SearchPath(path, "Worm.exe", NULL, MAX_PATH, file, NULL) == 0)
    {
        // インストールされておらず
        wsprintf(path, "%s¥¥%s", path,"Worm.exe");
        GetModuleFileName(hInstance, file, MAX_PATH);
        CopyFile(file, path, FALSE);
    }
    else
        MessageBox(NULL, "がはは", "", 0); // インストール済み
    return -1;
}

まず、これはスタートメニューのスタートアップを利用したやつ。一回起動するとスタートアップに自分自身を複製し、次回からのWindows起動時に毎回メッセージボックスを表示させます。デスクトップフォルダを入手するときに使った**SHGetSpecialFolderPath()**が再登場!CSIDL_STARTUP定数を使えばカレントユーザーのスタートアップフォルダを取得可能。

全てのユーザーに有効なわけではない(マルチユーザーの場合)。その後、SearchPath()関数を使って目的のファイルがすでに存在しているかを確認。SearchPath()関数は、ファイルの検索に使用するAPI関数ですが、このようにファイルの存在を確認するのにも利用でる。ややこしいが、パラメータは順に、検索パス、ファイル名、拡張子、バッファのサイズ、バッファへのポインタ、ファイル名の先頭へのポインタを格納する変数のポインタとなっている。ファイル検索をするなら、検索したいところをNULLにしてやれば良い。今回のように存在の有無の確認ならば、最初の3つは指定してやる。検索したわけではないので、検索結果は必要ないんだが、バッファをNULLにすると、エラーが出たのでダミーを入れときました。SearchPath()関数は成功すると0以外の値を返します。つまり、ここでは、ファイルがすでに存在しているなら0以外が返りますね。じゃあ、逆に存在していない(エラーがあった可能性もあるが)場合は0を返すわけです。つまり始めての起動ってこと。それなら、スタートアップに登録しなければ。複製の仕方は前のサンプル同様。よって省略。もし存在する(=Windows起動時に起動)ならメッセージボックス表示。MessageBox()関数は簡単なので省略。早く次へ進みましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// レジストリを使う手法
#include <windows.h>

WINAPI WinMain(HINSTANCE hI nstance, HINSTANCE hPrevInstance, LPSTR Cmd, int iState)
{
    char file[MAX_PATH];
    DWORD result;
    HKEY hkey;
    RegCreateKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE¥¥Microsoft¥¥Windows¥¥CurrentVersion¥¥run", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkey, &result);
    if (RegQueryValueEx(hkey, "Worm", 0, NULL, NULL, NULL) != ERROR_SUCCESS)
    {
        // インストールされていない
        GetModuleFileName(hInstance, file, MAX_PATH);
        RegSetValueEx(hkey, "Worm", 0, REG_SZ,
                      (const BYTE *)file, lstrlen(file) + 1);
    }
    else
        MessageBox(NULL, "がはは", "", 0); // インストール済み
    RegCloseKey(hkey);
    return -1;
}

こちらは、レジストリを使ったやつ。Windowsの起動時に起動させるプログラムを、なんとレジストリに登録することができるのです。レジストリに登録すれば、ユーザーに気づかれずに、また、レジストリに詳しくなければ削除することもできない(これは無敵では、と思うかもしれないが、実行ファイルを削除されればお釈迦)。登録する場所は、HKEY_LOCAL_MACHINE内の"SOFTWARE¥¥Microsoft¥¥Windows¥¥CurrentVersion¥¥run"セクションの中です。この中に好きな名前のキーを作り実行ファイルのパスを格納する。そうすれば次回起動時から発症。レジストリにアクセスするのは少々厄介である。まず最初にセクションを開く。RegCreateKeyEx()関数は、指定したセクションが存在するならそれを開き、しないなら新規に作成するという優れものである。セクションの指定だが、最初のHKEY_LOCAL_MACHINEは定数で、あとの"Software…“は文字列で指定する。細かい設定については省略。サンプルのようにすれば標準的な、読み書き可能なセクションが作成または開かれることになる。そのとき重要なのはhkey変数だ。hkeyは、先ほど開いたセクションのハンドルで、このハンドルでどのセクション内のキーを編集するかを識別する。RegCreateKeyEx()関数は、作成/開いたセクションのハンドルをhkeyに格納してくれる。result変数には、作成されたか、開かれたかが格納される。今回アクセスするセクションは、ほぼ100%存在しているのでこの情報は無視。次に、RegQueryValueEx()関数でキーの有無を確認する。このサンプルの名前を、“Worm"とすると、“Worm"というキーがセクション内に存在していなければ、始めての起動ということで登録を開始する。存在するならば、Windowsの起動時の起動なのでメッセージボックスを表示します。RegQueryValueEx()関数は本来、キーの値を取得するために使いますが、ここではキーの存在の有無を確認するために使うので、パラメータにNULLが連発しています。1番目のパラメータに先ほどのハンドルを指定。キーが存在するならERROR_SUCCESS、しないならそれ以外の値が返るので有無が簡単に確認できます。そしたら、実行ファイルのパスを取得し、RegSetValueEx()関数でそのパスをキーに書き込みましょう。同じように1番目のパラメータに先ほどのハンドルを指定。次に、キー名。ここでは、“Worm"にしておく。3番目は必ず0、4番目はデータが文字列であることを示すREG_SZを指定。5番目のパラメータにはデータへのポインタを指定。型変換が必要なのでキャストを使用。最後のパラメータはデータのサイズ。文字列の場合、文字列の長さ+1なので注意。これで登録完了。最後にセクションを閉じるのを忘れずに。RegCloseKey()関数で閉じる。パラメータにハンドルを指定。実行してみたら、regeditで確認してみよう。

再起動して実際に体験してみよう。ちゃんと起動時に反応するでしょ?あと、調査したところ、レジストリに登録したほうが先に起動します。実験が終わったら、スタートアップ、レジストリ双方元に戻しておきましょう。以上にてサンプル終わり。

あとがき

なぜ、こんなくだらない記事を書くために、大事な、とても大事な睡眠時間を割いたんだろうか?あ~眠い。でもこんな記事、うちの部では誰も書かないだろうな。書くとしたら、いたずらに興味を持っているK藤か?いや、あいつは人の作ったブツをフロッピーに盗んで、私が阻止しようとしたら強引にフロッピーを抜きやがって、ドライブを壊した(後に復活)ような野郎だから無理であろう。1番の候補は魔のF井だろう。奴の作ったプログラムを見れば想像できる。内輪ねたですんません。余白を埋めるためです。ここまで我慢して読んでくれた人、本当にありがとう。(-_-)/̃

次へ面白フォントを使おう>
前へフォークリフト製作記>