[C/C++][Win32] プロセスID、実行ファイル名からウインドウハンドルを取得



「プロセスIDなら分かるけど、ウィンドウハンドルが分からない」「実行ファイル名なら分かる」「実行ファイルのタイトル名なら分かる」など色々な状況があると思います。

スポンサードリンク

前提条件

Windows パソコン上で gnupack_devel(cygwin) を利用して動作確認しています。

VC++等を使わず、windowsアプリケーションを開発する事を前提としています。

mingwのコンパイラをインストール

mingwはgccコンパイラです。gnupack_devel(cygwin) 利用者はmingw64-i686-gcc-coreを次のようにインストールします。

apt-cyg install mingw64-i686-gcc-core

もしくはインストーラーをダウンロードしてきて設定を行います。

ダウロードサイト

ダウロードしてインストールを開始します。

「MinGW Installation Manager」を開き、次の項目にチェック(Mark for Installation)しましょう。

インストール後には、環境変数にパス設定が必要です。

Windowsの環境変数に設定する場合には、変数名に「PATH」、変数値に「C:\MinGW\bin;C:\MinGW\msys\1.0\bin」のように記述します。

gnupack_devel(cygwin)で行う場合は次のようになります。

export PATH="/c/MinGW/bin:/c/MinGW/msys/1.0/bin":$PATH

ソースコード

説明するために各部分を切り分けています。

ヘッダのインクルード

#include 
#include 
#include 
#include 

PSAPIは、ウィンドウズでプロセス情報の取得を行うプログラムを書くためのAPIです。

まず、コードの中でpsapi.hをincludeしなくてはなりません。

プロセスIDからウィンドウハンドルを取得

HWND GetWindowHandleByPID(const DWORD targetPID)
{
    HWND hWnd = GetTopWindow(NULL);
    do {
        if (GetWindowLong( hWnd, GWL_HWNDPARENT) != 0 || !IsWindowVisible( hWnd)) {
            continue;
        }
        DWORD getPID;
        GetWindowThreadProcessId( hWnd, &getPID);
        if (targetPID == getPID) {
            return hWnd;
        }
    } while((hWnd = GetNextWindow( hWnd, GW_HWNDNEXT)) != NULL);
    
    return NULL;
}

次のような実装になっています。

このため、トップレベルウィンドウ(親ウィンドウがない普通のウィンドウ)が、複数あるようなアプリケーションでは、Zオーダーが上のウィンドウ(より前面にあるウィンドウ)が返されます。

要するに、1つのプロセスで複数のウィンドウが開くアプリケーションに対して実行する場合は注意してください。

実行ファイル名からウィンドウハンドルを取得

HWND GetWindowHandleByName(const char exeName[]) {
    DWORD allProc[1024];
    DWORD cbNeeded;
    int nProc;
    int i;
    // PID一覧を取得
    if (!EnumProcesses(allProc, sizeof(allProc), &cbNeeded)) {
        return NULL;
    }
    
    nProc = cbNeeded / sizeof(DWORD);
    for (i = 0; i < nProc; i++) {
        char procName[MAX_PATH] = TEXT("");
        HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
                                      PROCESS_VM_READ,
                                      FALSE, allProc[i]);
        
        // プロセス名を取得
        if (NULL != hProcess) {
            HMODULE hMod;
            DWORD cbNeeded;
            if (EnumProcessModules(hProcess, &hMod, sizeof(hMod), 
                                   &cbNeeded)) {
                GetModuleBaseName(hProcess, hMod, procName, 
                                  sizeof(procName)/sizeof(TCHAR));
            }
        }
        if (!lstrcmpi(procName, exeName)) {
            return GetWindowHandleByPID(allProc[i]);
        }
        CloseHandle(hProcess);
    }
    return NULL;
}

32 ビットのアプリケーションで GetModuleFileNameEx 関数を使用した場合、64 ビットのアプリケーションが生成したプロセスを起動した実行パス名は検出できません。

メイン関数

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR pCmdLine, int showCmd)
{
    RECT lprc;
    HWND hWnd = GetWindowHandleByName("WinSCP.exe");
    if (hWnd == NULL) {
        MessageBox(NULL, "実行ファイルは起動していません", "メッセージ", MB_OK);
        return -1;
    }
    GetWindowRect( hWnd, &lprc );
    printf("left = %ld\n", lprc.left);
    printf("top = %ld\n", lprc.top);

    return 0;
}

起動していることを確認するために参考として、GetWindowRect() を利用して、指定したウインドウの左上端の座標を出力しています。

なお、ヘッダファイル「windows.h」をインクルードするエントリポイントは、main関数ではなく「WinMain関数」となります。

ビルド方法

g++ -Wall -O3 -o getwindowhandle.o -c getwindowhandle.c
g++ getwindowhandle.o -lpsapi -static-libgcc -static-libstdc++ -o getwindowhandle.exe

リンカ対応として、psapi.lib(またはpsapi.dll)のライブラリファイルがリンクできるようにする必要があります。

その他の関係するGCCオプションは次のとおりです。

オプション 説明
-mno-cygwin cygwin.dll がなくても起動できるようにする。gccが4.6以降はこのオプションはありません
-mwindows ウィンドウを伴うプログラム(無いとexe実行時にコマンドプロンプト表示)
-static-libgcc libgcc_s_dw2-1.dll を静的リンクする
-static-libstdc++ libstdc++-6.dll を静的リンクする

cygwinのdllと一緒にコピーして使えばいいですが、GNUとかライセンスなどの配布条件があります。

libgcc_s_dw2-1.dll、libstdc++-6.dll のライブラリを静的リンク指定しています。指定しないと次のようなエラーが出力されます。

コンピュータに libgcc_s_dw2-1.dll がないため、プログラムを開始できません。この問題を解決するには、プログラムを再インストールしてみてください。

なお、Makefile を作成する場合は次のような記述になります。

SRC=getwindowhandle.c
OBJS=$(SRC:.c=.o)
PROG=getwindowhandle.exe
CC=g++
CFLAGS=-Wall -O3
LIBS=-lpsapi -static-libgcc -static-libstdc++
RM=rm

%.o: %.c
	$(CC) $(CFLAGS) -o $@ -c $<

.PHONY : all
all: $(PROG)

$(PROG): $(OBJS)
	$(CC) $(OBJS) $(LIBS) $(LDFLAGS) -o $@

.PHONY : clean
clean:
	$(RM) $(OBJS)

スポンサードリンク

実行結果

コンソール上から実行、もしくは実行ファイルのクリックで起動します。

$ getwindowhandle.exe
....
  (PID: 24312)
mshta.exe  (PID: 17540)
chrome.exe  (PID: 18876)
chrome.exe  (PID: 25484)
WinSCP.exe  (PID: 27516)
left = 527
top = 135

スポンサードリンク