概要

今回はC++とWinAPI (Windows API) を使い、デスクトップの右クリックメニュー(コンテキストメニュー)に、好きなプログラムを起動するための項目を簡単に追加・削除できるGUIツールを作成しました。

目的は、WinAPIによる基本的なGUIアプリケーションの作成(ウィンドウ、ボタン、エディットボックスの配置)と、OSのカスタマイズの核となる「Windowsレジストリ」の操作方法を学ぶことです。

レジストリ操作を理解することは、Windowsがどのように設定を管理しているかを知る上で重要であり、システムの深いカスタマイズやトラブルシューティングに応用できます。

thumbnail

Windowsレジストリとコンテキストメニュー

Windowsレジストリは、Windowsオペレーティングシステムやインストールされたアプリケーションの設定情報を一元管理する、階層型のデータベースです。OSの動作、ハードウェア設定、ユーザープロファイルなど、あらゆる情報が「キー」と「値」の形式で格納されています。

デスクトップの何もないところを右クリックした際に出る「コンテキストメニュー」の内容も、このレジストリによって定義されています。具体的には、HKEY_CLASSES_ROOT\DesktopBackground\Shell というキー配下に、表示したい項目を定義することで、メニューに機能を追加できます。

img0

実装の仕組み

このツールは、ユーザーがGUIで入力した「表示名」と「プログラムのパス」を受け取り、WinAPIが提供するレジストリ操作関数(RegCreateKey, RegSetValueEx, RegDeleteKey)を直接呼び出すことで機能します。

GUI部分は、WinAPIの伝統的なウィンドウプログラミング(ウィンドウクラスの登録、ウィンドウ作成、メッセージループ処理)によって構築されています。

コンテキストメニューへの追加処理

「追加」ボタンを押すと、AddRegistry 関数が以下の2段階の処理を行います。

  1. 項目キーの作成: HKEY_CLASSES_ROOT\DesktopBackground\Shell\[入力された表示名] というキーを作成します。
    • このキーの「(既定値)」に表示名を設定します。これがメニューに表示されるテキストとなります。
    • さらに Icon という値にプログラムのパスを設定することで、メニュー項目に実行ファイルのアイコンを表示させることができます。
  2. コマンドキーの作成: ...\[入力された表示名]\Command というサブキーを作成します。
    • このキーの「(既定値)」に、実行したいプログラムのフルパスを設定します。

Windowsはコンテキストメニューでこの項目がクリックされると、Command キーに登録されたパスを実行します。

コンテキストメニューからの削除処理

「削除」ボタンを押すと、RemoveRegistry 関数が追加時とは逆の手順で、作成したキーを削除します。レジストリキーはサブキーを持つと削除できないため、必ず階層の深い方(Command キー)から先に削除する必要があります。

C++によるコード解説

WinMain と ウィンドウ初期化 (InitApp, InitInstance)

WinMain は、WinAPIアプリケーションのエントリーポイント(開始地点)です。InitApp でウィンドウの基本的な外観や動作(ウィンドウプロシージャ `WndProc` の指定など)を「ウィンドウクラス」としてOSに登録します。InitInstance で、そのクラス設計図を基に実際のウィンドウを CreateWindow で生成・表示します。その後、GetMessage ループ(メッセージループ)に入り、ユーザー操作などのイベントを待ち受けます。

// InitApp: ウィンドウクラスをOSに登録
ATOM InitApp(HINSTANCE hInst) {
	WNDCLASSEX wc;
    // (ウィンドウのスタイル、プロシージャ、アイコンなどを設定)
	return (RegisterClassEx(&wc));
}

// InitInstance: 実際にウィンドウを作成
BOOL InitInstance(HINSTANCE hInst, int nCmdShow) {
	HWND hWnd;
	hWnd = CreateWindow(
		szClassName, // クラス名
		TEXT("Registry Controller"), // タイトル
		WS_OVERLAPPE | WS_SYSMENU, // スタイル
		// (座標、サイズなどの設定)
	);
    // (ウィンドウの表示・更新)
	return TRUE;
}

ウィンドウプロシージャ (WndProc) - WM_CREATE

ウィンドウが作成された直後に一度だけ WM_CREATE メッセージが送信されます。このタイミングで、ウィンドウ内に配置するGUI部品(コントロール)を作成します。CreateWindowCreateWindowEx を使い、「STATIC」(静的テキスト)、「EDIT」(テキスト入力ボックス)、「BUTTON」(押しボタン)を適切な座標に配置します。

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
    // (static 変数の定義)
	switch (msg) {
		case WM_CREATE: {
            // "RegsName" の静的テキスト
			HWND hRegsNameText = CreateWindowEx( ... TEXT("STATIC"), TEXT(" RegsName "), ... );
            
            // "ProgPath" の静的テキスト
			HWND hProgPathText = CreateWindowEx( ... TEXT("STATIC"), TEXT(" ProgPath "), ... );

            // レジストリ名入力用エディットボックス
			hRegsNameEdit = CreateWindowEx( ... TEXT("EDIT"), ... );

            // プログラムパス入力用エディットボックス
			hProgPathEdit = CreateWindowEx( ... TEXT("EDIT"), ... );

            // 「追加」ボタン
			CreateWindow( TEXT("button"), TEXT("追加"), ... (HMENU)add_BUTTON, ... );
            
            // 「削除」ボタン
			CreateWindow( TEXT("button"), TEXT("削除"), ... (HMENU)rm_BUTTON, ... );
			break;
		}
// ( ... 以下略 ... )

ウィンドウプロシージャ (WndProc) - WM_COMMAND

WM_COMMAND メッセージは、ボタンがクリックされた時などに送信されます。LOWORD(wp) でどの子ウィンドウ(ボタン)が操作されたかを識別します。

add_BUTTON または rm_BUTTON の場合、GetWindowTextA を使ってエディットボックス(hRegsNameEdit, hProgPathEdit)に入力されている現在のテキストを取得し、それぞれ `AddRegistry` または `RemoveRegistry` 関数に渡して実行します。

		case WM_COMMAND: {
			switch (LOWORD(wp)) {
				case add_BUTTON: {
                    // エディットボックスからテキストを取得
					GetWindowTextA(hRegsNameEdit, regsName, sizeof(regsName));
					GetWindowTextA(hProgPathEdit, progPath, sizeof(progPath));
					
					AddRegistry(regsName, progPath); // 追加関数を呼び出し
					MessageBox(hWnd, TEXT("...追加されました。"), ...);
					break;
				}
				case rm_BUTTON: {
                    // エディットボックスからテキストを取得
					GetWindowTextA(hRegsNameEdit, regsName, sizeof(regsName));
					RemoveRegistry(regsName); // 削除関数を呼び出し
					MessageBox(hWnd, TEXT("...削除されました。"), ...);
					break;
				}
			}
			break;
		}
// ( ... 以下略 ... )

レジストリ追加関数 (AddRegistry)

レジストリ操作の核心部です。_stprintf_s で、ベースとなるキーパス (DesktopBackground\Shell\) とユーザーが入力した名前 (regsName) を連結し、フルパスを作成します。

  • RegCreateKey: 指定したパスのキーを作成します。既に存在する場合はそのキーを開きます。
  • RegSetValueEx: キーに対して「値」を設定します。(LPBYTE)regsName(LPBYTE)progPath で、実際のデータを書き込んでいます。
  • RegCloseKey: 操作が完了したら、キーのハンドルを必ず閉じます。

これを、...\[regsName] キーと ...\[regsName]\Command キーの2回に分けて実行しています。

void AddRegistry(TCHAR* regsName, TCHAR* progPath) {
	HKEY hKey;

    // パス文字列を構築 (Shellキー)
	TCHAR szKey[] = TEXT("DesktopBackground\\Shell\\");
	TCHAR fullszKey[256];
	_stprintf_s(fullszKey, ...); 

    // パス文字列を構築 (Commandキー)
	TCHAR szKey2[] = TEXT("\\Command");
	TCHAR fullszKey2[256];
	_stprintf_s(fullszKey2, ...);

	// 1. Shellキーの作成と値の設定
	if (RegCreateKey(HKEY_CLASSES_ROOT, fullszKey, &hKey) == ERROR_SUCCESS) {
		RegSetValueEx(hKey, NULL, 0, REG_SZ, (LPBYTE)regsName, ...); // (既定値) = 表示名
		RegSetValueEx(hKey, TEXT("Icon"), 0, REG_SZ, (LPBYTE)progPath, ...); // Icon = プログラムパス
		RegCloseKey(hKey);
	}

	// 2. Commandキーの作成と値の設定
	if (RegCreateKey(HKEY_CLASSES_ROOT, fullszKey2, &hKey) == ERROR_SUCCESS) {
		RegSetValueEx(hKey, NULL, 0, REG_SZ, (LPBYTE)progPath, ...); // (既定値) = プログラムパス
		RegCloseKey(hKey);
	}
}

レジストリ削除関数 (RemoveRegistry)

削除は RegDeleteKey 関数を使います。この関数は、サブキーが存在するキーを削除できないため、必ず階層の深いキー(fullszKey2 が指す Command キー)から先に削除し、その次に親キー(fullszKey が指す Shell キー)を削除します。

void RemoveRegistry(TCHAR* regsName) {
	// (パス文字列の構築 ... AddRegistry と同様)
	TCHAR fullszKey[256];
    // ...
	TCHAR fullszKey2[256];
    // ...

	// 下の階層から順にキーを削除
	LONG result2 = RegDeleteKey(HKEY_CLASSES_ROOT, fullszKey2); // 1. Command キーを削除
	LONG result1 = RegDeleteKey(HKEY_CLASSES_ROOT, fullszKey);  // 2. Shell キーを削除
}

実際の動作

操作に関する重要な注意

このコードは、WinAPIとレジストリ操作の学習目的で作成したものです。Windowsレジストリの操作には細心の注意が必要です。

レジストリ破損のリスク

レジストリはWindowsの動作設定を司る中核的なデータベースです。誤ったキーや値を編集・削除すると、システムが不安定になったり、最悪の場合Windowsが起動しなくなったりする危険性があります。 このツールを使用する前に、必ずレジストリのバックアップを取るか、システムの復元ポイントを作成することを強く推奨します。

管理者権限

HKEY_CLASSES_ROOT (HKCR) キーはシステム全体の設定であり、この場所への書き込みには管理者権限が必要です。このプログラム(.exe)を実行する際は、実行ファイルを右クリックして「管理者として実行」を選択する必要があります。

起動

それでは実際に動作させてみたいと思います。ビルドが完了したら、実行ファイルを管理者として実行で起動します。

起動させると、プログラム名やパスを入力する欄やボタンが配置されたウィンドウが開きます。

img1

追加

今回は筆者がよくお世話になっているBandicamをコンテキストメニューに追加してみたいと思います。

RegsNameにプログラム(任意)の名前を入力し、ProgPathに起動させたいプログラムのパスを入力します。

追加ボタンをクリックすると、ウィンドウが表示されレジストリに入力した情報が追加されます。

img2

レジストリエディタで確認してみると、入力した情報がちゃんと追加できてることが確認できます。

img3

コンテキストメニューを確認してみると、Bandicamという項目が追加されており、クリックするとBandicamが起動しました。

img4

削除

これらの追加した項目が必要なくなったら、削除したいプログラム名を入力し、削除ボタンをクリックします。すると、ウィンドウが表示されレジストリから削除されます。

img5

再びレジストリエディタとコンテキストメニューを確認してみると、追加した項目が消えていることが分かります。

img6
img7

感想と考察

今回はレジストリに変更を加え、コンテキストメニューに起動させたいプログラムを追加できるソフトを作ってみました。 WinAPIを使い、ウィンドウを用いることによって直感的に操作できるプログラムの開発は、CUIとは異なる「イベント駆動型」のコーディング(ボタンが押されたら処理する、など)の難しさと面白さを実感する良い経験となりました。

特に、レジストリは悪意を持った攻撃者が攻撃に利用することも多いので、プログラムからレジストリを操作する手法を学ぶことは、セキュリティの分野においても有意義な学習でした。例えば、マルウェアが自身の起動設定をレジストリに書き込んで「永続化」する手口は有名ですが、今回RegCreateKeyやRegSetValueExといったAPIを直接触ったことで、その仕組みをコードレベルで具体的に理解できました。

システムの根幹に触れるレジストリ操作は危険も伴いますが、それ故に防御側としても知っておくべき必須知識だと改めて感じました。