コマンドプロンプトを子ウィンドウ化する

icon 項目のみ表示/展開表示の切り替え

概要

子プロセスとしてcmd.exeを起動しダイアログボックスの子ウィンドウにします。
子ウィンドウですのでダイアログボックスを移動させると同時に移動します。
コマンドプロンプトは起動してみるとわかりますが、自由にサイズを変更することができません。
本プログラムでは、ダイアログボックスの定義時はスタティックコントロールで位置を参照してコマンドプロンプトを起動しコマンドプロンプトの大きさに合わせてダイアログボックス自身の大きさ及びプッシュボタンの位置を変更しています。
子プロセスの終了を検出するためにWaitForSingleObject APIを使用しますが、これによりダイアログのメッセージ処理が止まり操作不能に陥りますので、子スレッドを起動し子スレッドから子プロセスを起動します。

テスト環境

コンパイラ

Visual C++ 2008 Standard 32/64bit
Visual C++ 2013 Express 32/64bit

プロジェクトの作成

Win32プロジェクト Windowsアプリケーション

実行環境

Windows 8.1 Enterprise 64bit
Windows 7 EnterPrise Service Pack 1 64bit
Windows Vista Ultimate Service Pack 2 32bit
Windows XP Professional Service Pack 3 32bit

プログラムソースの概要

getvbox.cpp

_tWinMain

ダイアログボックスを表示します。

DlgProc1

仮想マシーン名を取得するダイアログボックスプロシージャーです。
case WM_INITDIALOG:
コマンドプロンプトのウィンドウ上の位置を決めるためのスタティックコントロールの位置をGetWindowRect APIで取得しスタティック変数rect1に保存します。DestroyWindow APIによりスタティックコントロールを閉じます。
CMD_DATAクラスのrun_cmdメンバー関数により新規スレッドを作成し新規スレッドから子プロセスとしてコマンドプロンプトを起動します。引数にはコマンドプロンプトが終了したときにダイアログボックスへ送信するWM_CMD_EXITメッセージとコマンドプロンプト作成後に送信するWM_RESIZEサイズメッセージを指定しています。メンバー関数からはコマンドプロンプトのウィンドウハンドルが返されます。
case WM_CMD_EXIT:
コマンドプロンプト上でexitコマンドを入力するとコマンドプロンプトが終了します。
新規スレッドから起動されたコマンドプロンプトが終了するとこのメッセージが発生します。
EndDialog APIによりダイアログボックスを終了させます。
case WM_RESIZE:
コマンドプロンプトが起動した後に新規スレッドからこのメッセージが送信されます。
このタイミングでスタティックコントロールの左上位置とコマンドプロンプト左上位置を合わせます。
プログラムでは以下の様に記述しています。

WM_INITDIALOGメッセージで保存したスタティックコントロールの位置はスクリーン座標なのでScreenToClient APIによりダイアログボックスのクライアント座標に変換します。
SetWindowPos APIによりコマンドプロンプトの位置を変更します。

GetWindowRec APIによりコマンドプロンプトの大きさを取得します。
スタティックコントロールとコマンドプロンプトの大きさの差分を計算します。
GetWindowRec APIによりダイアログボックスのスクリーン座標上の位置を取得します。
MoveWindow APIによりダイアログボックスの大きさを先ほど計算した差分で調整します。
GetWindowRect APIによりOKボタンのスクリーン座標上の位置を取得します。
ScreenToClient APIによりスクリーン座標をダイアログボックス上のクライアント座標に変換します。
MoveWindow APIによりOKボタンの位置を先ほど計算した差分で調整します。
InvalidateRect APIによりダイアログボックスを再描画します。
SetFocus APIによりフォーカスをコマンドプロンプトに設定します。
case WM_COMMAND:
IDOK
EndDialog APIによりダイアログボックスを終了させます。
IDCANCEL
閉じるボタンをクリックしたときに呼び出されます。
EndDialog APIによりダイアログボックスを終了させます。

GetWindowHandle

プロセスIDからウィンドウハンドルを取得します。
EnumWindows APIを使用して親ウィンドウが存在しないウィンドウを検索します。
ウィンドウが見つかるたびにコールバック関数が呼び出されます。
検索条件は構造体PROCESSID2HWNDを経由してコールバック関数に引き渡します。
構造体PROCESSID2HWNDは検索するプロセスIDとプロセスIDが一致したらウィンドウハンドルが保存されます。

EnumWindowsProcessCallBack

EnumWindows APIから検索されたウィンドウ毎に呼び出されます。
GetWindowThreadProcessId APIによりウィンドウハンドルからプロセスIDを取得し、検索するプロセスIDと一致すれば構造体PROCESSID2HWNDにウィンドウハンドルを保存し、検索を終了させます。

thread

構造体CMD_DATAのフレンド関数であり、run_cmdメンバー関数から起動されるスレッドです。
CreateProcess APIによりcmd.exeを子プロセスとして起動します。起動時はウィンドウを非表示にしておきます。
エラーが発生してた場合は、メンバ変数ErrorCodeに-1をセットし、イベントをシグナル状態にします。
WaitForInputIdle APIにより子プロセスcmd.exeがアイドル状態になるまで待機します。
Sleep APIにより0.1秒待機します。
GetWindowHandle APIによりcmd.exeのプロセスIDからウィンドウハンドルを取得します。
SetParent APIによりcmd.exeのウィンドウの親ウィンドウを本プログラムのダイアログボックスに変更します。
PostMessage APIによりダイアログボックスにコマンドプロンプトが起動したことを示すWM_RESIZEメッセージを送信します。
コマンドプロンプトのウィンドウスタイルをマウス等で大きさが変更できないように、変更用のフレームとメニューボックスを以下の手順で消去します。

GetWindowLongPtr APIによりウィンドウスタイルを取得します。
ウィンドウスタイルのうち変更用のフレームとメニューボックスフラグをクリアする。
SetWindowLongPtr APIによりウィンドウスタイルを変更します。

SetWindowPos APIによりコマンドプロンプトを最前面に変更します。
SetEvent APIによりコマンドプロンプトの初期化が終了したことを示すイベントをセットします。
WaitForSingleObject APIにより子プロセスとして起動したコマンドプロンプトが終了するまで待機します。
CloseHandle APIによりプロセスハンドルを解放します。
PostMessage APiによりコマンドプロンプトが終了したことを示すメッセージをダイアログに送信します。

CMD_DATA::run_cmd

子の関数は、子スレッドを立ち上げ子プロセスとしてcmd.exeを起動します。
CreateEvent APIでコマンドプロンプトを起動中であることを示すイベントを作成します。
CreateThread APIでスレッドとしてthread関数を起動します。
WaitForSingleObject APIでthread関数内でイベントがセットされるまで待機します。
CloseHandle APIでイベントハンドルを解放します。
CreateProcessでエラーが発生しない場合は、コマンドプロンプトをShowWindow APIで表示状態にし、ウィンドウの描画を更新しウィンドウハンドルを返します。

プログラムソース

createprocess.cpp

//       ダイアログにコマンドプロンプトを子ウィンドウとして貼り付ける
//      コマンドプロンプトが終了したら自動的にダイアログボックスが閉じる

#include <windows.h>
#include <tchar.h>
#include "resource.h"

//      ダイアログボックスプロシージャー
LRESULT CALLBACK DlgProc1(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);
HWND GetWindowHandle(DWORD processId);

struct PROCESSID2HWND{
        DWORD processId;
        HWND hWnd;
};

#define WM_CMD_EXIT (WM_USER+10)
#define WM_RESIZE (WM_USER+11)

struct CMD_DATA{
        HWND hParent;   //      コマンドプロンプトの親ウィンドウハンドル
        HWND hChild;    //      コマンドプロンプトのウィンドウハンドル
        UINT ExitMessage;       //      コマンドプロンプト終了時のメッセージ
        UINT ReSizeMessage;     //      リサイズメッセージ
        HANDLE initEvent;       //      コマンドプロンプト作成終了のイベント
        TCHAR* processName;
        int ErrorCode;
        friend DWORD WINAPI thread(LPVOID lParam);
        HWND run_cmd(TCHAR* cmd, HWND hWnd, UINT exit_msg,UINT resize_msg);
};

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPreInst, TCHAR* lpszCmdLine, int nCmdShow){
        DialogBox(hInstance, TEXT("CMD_DLG"), 0, (DLGPROC)DlgProc1);

        return (int)0;
}

//      ダイアログボックスプロシージャー

LRESULT CALLBACK DlgProc1(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam){
        static HWND hChild;
        static CMD_DATA cmd;
        static RECT rect1;
        switch (msg) {
        case WM_INITDIALOG:{
                GetWindowRect(GetDlgItem(hDlg, IDC_LABEL100), &rect1);
                DestroyWindow(GetDlgItem(hDlg, IDC_LABEL100));
                TCHAR path[MAX_PATH];
                _tcscpy_s(path, sizeof(path) / sizeof(TCHAR), _TEXT("cmd.exe /K dir"));
                hChild = cmd.run_cmd(path, hDlg, WM_CMD_EXIT, WM_RESIZE);
                if (hChild == 0){
                        MessageBox(hDlg, _TEXT("コマンドプロンプトの作成に失敗しました"), _TEXT("エラー"), MB_OK);
                        EndDialog(hDlg, FALSE);
                }
                return TRUE;
        }
        case WM_CMD_EXIT:       //      コマンドプロンプトが終了
                EndDialog(hDlg, TRUE);
                break;
        case WM_RESIZE:{        //      ウィンドウのリサイズ
                RECT rect2;
                POINT p;
                p.x=rect1.left; //      staticコントロールの左上スクリーン座標
                p.y=rect1.top;
                ScreenToClient(hDlg,&p);
                SetWindowPos(cmd.hChild, hDlg, p.x, p.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOZORDER);
                GetWindowRect(cmd.hChild, &rect2);
                int dx = (rect2.right - rect2.left) - (rect1.right - rect1.left);
                int dy = (rect2.bottom - rect2.top) - (rect1.bottom - rect1.top);

                GetWindowRect(hDlg, &rect1);

                MoveWindow(hDlg, rect1.left, rect1.top, (rect1.right - rect1.left) + dx, (rect1.bottom - rect1.top) + dy, TRUE);

                GetWindowRect(GetDlgItem(hDlg, IDOK), &rect2);
                p.x = rect2.left;
                p.y = rect2.top;
                ScreenToClient(hDlg, &p);

                MoveWindow(GetDlgItem(hDlg,IDOK), p.x+dx, p.y+dy, (rect2.right - rect2.left) , (rect2.bottom - rect2.top), TRUE);

                InvalidateRect(hDlg, NULL, TRUE);

                ShowWindow(hDlg, SW_SHOW);
                InvalidateRect(hDlg, NULL, FALSE);
                UpdateWindow(hDlg);
                SetFocus(cmd.hChild);

                break;
        }
        case WM_COMMAND:
                switch (LOWORD(wParam)) {
                case IDOK:
                        EndDialog(hDlg, TRUE);
                        return TRUE;
                case IDCANCEL:
                        EndDialog(hDlg, FALSE);
                        return FALSE;
                default:
                        return FALSE;
                }
        default:
                return FALSE;
        }
        return TRUE;
}

BOOL CALLBACK EnumWindowsProcessCallBack(HWND hWnd, LPARAM  lParam){
        PROCESSID2HWND* p = (PROCESSID2HWND*)lParam;
        DWORD processId = 0;
        GetWindowThreadProcessId(hWnd, &processId);
        if (p->processId == processId){
                p->hWnd = hWnd;
                return FALSE;
        }
        return TRUE;

}

//      プロセスIDからウィンドウを検索

HWND GetWindowHandle(DWORD processId){
        PROCESSID2HWND p2h;
        p2h.processId = processId;
        p2h.hWnd = 0;
        EnumWindows(EnumWindowsProcessCallBack, (LPARAM)&p2h);
        return p2h.hWnd;
}

DWORD WINAPI thread(LPVOID lParam){
        STARTUPINFO si;
        PROCESS_INFORMATION pi;
        CMD_DATA& c = *(CMD_DATA*)lParam;
        ZeroMemory(&si, sizeof(si));
        ZeroMemory(&pi, sizeof(pi));
        si.cb = sizeof(si);
        si.dwFlags = STARTF_USESHOWWINDOW;
        si.wShowWindow = SW_HIDE;
        if (CreateProcess(NULL, c.processName, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi) == 0){ // エラーが発生
                c.ErrorCode = -1;
                SetEvent(c.initEvent);  //      初期化処理終了シグナルをセット
                return 0;
        }
        CloseHandle(pi.hThread);
        WaitForInputIdle(pi.hProcess, INFINITE);        //      アイドル状態まで待機
        Sleep(100);
        c.hChild = GetWindowHandle(pi.dwProcessId);
        SetParent(c.hChild, c.hParent);
        PostMessage(c.hParent, WM_RESIZE, 0, 0);
        LONG_PTR lp = GetWindowLongPtr(c.hChild, GWL_STYLE);
        lp &= ~WS_THICKFRAME;       //      ウィンドウのサイズ変更用のフレームを消去
        lp &= ~WS_SYSMENU;          //      タイトルバー上にウィンドウメニューボックスを消去
        SetWindowLongPtr(c.hChild, GWL_STYLE, lp);
        SetWindowPos(c.hChild, HWND_TOPMOST, 0, 0, 0, 0, SWP_SHOWWINDOW);

        SetEvent(c.initEvent);  //      初期化処理終了シグナルをセット
        WaitForSingleObject(pi.hProcess, INFINITE);     //      コマンドプロンプトが終了するまで待機
        CloseHandle(pi.hProcess);
        PostMessage(c.hParent, c.ExitMessage, 0, 0);    //      ウィンドウに終了を通知
        return 0;
}

HWND CMD_DATA::run_cmd(TCHAR* cmd, HWND hWnd, UINT exit_msg,UINT resize_msg){
        processName = cmd;
        hParent = hWnd;
        ExitMessage = exit_msg;
        ReSizeMessage = resize_msg;
        initEvent = CreateEvent(NULL, TRUE, FALSE, _TEXT("RUN_CMD_INIT"));
        CreateThread(NULL, 0, thread, (LPVOID)this, 0, NULL);   //      終了通知するスレッドを起動
        WaitForSingleObject(initEvent, INFINITE);       //      ウィンドウの初期化処理が終了するまで待機
        CloseHandle(initEvent);
        if (ErrorCode == 0){
                ShowWindow(hChild, SW_SHOW);
                InvalidateRect(hChild, NULL, FALSE);
                UpdateWindow(hChild);
                SetFocus(hChild);
                return hChild;
        }
        else
                return 0;
}

resource.h

#define IDC_LABEL100 100

resource.rc

#include <windows.h>
#include "resource.h"

CMD_DLG DIALOG DISCARDABLE 0, 0, 526, 416
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_THICKFRAME | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | DS_SETFONT
CAPTION "コマンドプロンプトを子ウィンドウ化"
FONT 9, "MS 明朝"
{
 CONTROL "", IDC_LABEL100, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 7, 7, 512, 384

 CONTROL "OK", IDOK, "BUTTON", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 236, 395, 54, 14
}

ソースファイルと実行ファイルのダウンロード

ダウンロード createprocess.zip(39.4k)
createprocess.cpp
resource.h
resource.rc