カタバミさんのプログラミングノート

日曜プログラマーがプログラミング関係のメモを記録するブログです。

Pythonでウィンドウハンドルを列挙する

はじめに

PythonからctypesパッケージでWin32 APIを呼び出してトップレベルウィンドウのハンドルとタイトルを列挙するサンプルです。素人なので不束かな点も多いと思います。また、使用していない関数は実装のみでテストを行っていません。

ソースコード

windowtext.py

import ctypes


def get_window_text_w(handle):
    length = get_window_text_length_w(handle)
    if length == 0:
        return ""
    buffer = ctypes.create_unicode_buffer(length+1)
    if ctypes.windll.user32.GetWindowTextW(
            ctypes.c_int(handle), buffer, ctypes.sizeof(buffer)) == 0:
        raise WindowsError()
    return buffer.value


def set_window_text_w(handle, text):
    if not ctypes.windll.user32.SetWindowTextW(
            ctypes.c_int(handle), ctypes.c_wchar_p(text)):
        raise WindowsError()


def get_window_text_length_w(handle):
    l = ctypes.windll.user32.GetWindowTextLengthW(ctypes.c_int(handle))
    if l == 0:
        raise WindowsError()
    return l

windowhandleenumerator.py

import array
import ctypes


def enum_top_level_windows():
    ENUM_WINDOWS_FUNC = ctypes.WINFUNCTYPE(
        ctypes.c_int, ctypes.c_int, ctypes.py_object)
    handles = array.array("i")
    if not ctypes.windll.user32.EnumWindows(
            ENUM_WINDOWS_FUNC(_enumtopwindows_enumproc),
            ctypes.py_object(handles)):
        raise WindowsError()
    return handles


def enum_child_windows(handle):
    ENUM_WINDOWS_FUNC = ctypes.WINFUNCTYPE(
        ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.py_object)
    handles = array.array("i")
    if not ctypes.windll.user32.EnumChildWindows(
            ctypes.c_int(handle),
            ENUM_WINDOWS_FUNC(_enumtopwindows_enumproc),
            ctypes.py_object(handles)):
        raise WindowsError()
    return handles


def enum_thread_windows(thread_id):
    ENUM_WINDOWS_FUNC = ctypes.WINFUNCTYPE(
        ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.py_object)
    handles = array.array("i")
    if not ctypes.windll.user32.EnumThreadWindows(
            ctypes.c_uint32(thread_id),
            ENUM_WINDOWS_FUNC(_enumtopwindows_enumproc),
            ctypes.py_object(handles)):
        raise WindowsError()
    return handles


def _enumtopwindows_enumproc(handle, list):
    list.append(handle)
    return 1

test.py

import windowhandleenumerator
import windowtext

if __name__ == "__main__":
    handles = windowhandleenumerator.enum_top_level_windows()
    for handle in handles:
        print("\"{text}\" ({handle:0>8X})".format(
            handle=handle,
            text=windowtext.get_window_text_w(handle)))

覚書

文字列を扱うWinAPIの扱い

文字列を扱うWinAPI(上のコードではGetWindowText、GetWindowTextLength)にはAnsi/Unicode版(A/W版)が存在するものがあります。これらの関数、実際にはDLLからFunctionA/W(GetWindowTextA/W、GetWindowTextLengthA/W)と言う名前で公開されています。

.Net系列(VB.NetC#)では文字列型は統一して末尾のA/Wを自動的に解決したり、C++ではTCHAR型やら関数の別名やらのマクロを使ってコンパイル時の文字コードで切り替えていたりします。Pythonでもそれに該当する機能は存在するのかもしれませんが、ctypesのリファレンスをざっと見た限り、A/Wに対応するc_char/c_wcharの存在しか見付けられませんでした。

従って、このコードでは実行環境がW版に対応していると仮定してW版(GetWindowTextW、ctypes.create_unicode_buffer等)で決め打ちしています。

文字列バッファーを引数に取る関数の呼び出し

ctypes.create_string_bufferやctypes.create_unicode_bufferで最大長を容易して通常通り呼び出せばよい。

コールバック関数の呼び出し

Python3からコールバック関数を呼び出すには、ctypes.CFUNCTYPEやctypes.WINFUNCTYPEから作成した関数オブジェクトに呼び出したい関数を引数として呼び出し、対象の関数にそのまま与えてやればよい。

調べた範囲ではコールバック関数としてクラスのメソッドを与えるのはself引数の関係で困難。素直にctypes.py_objectでポインタを与えてやり、コールバック関数で素直にオブジェクトとして扱えばよい。