Pythonでウィンドウメッセージを受け取る(Windows API)

最近、Windows APIのウィンドウメッセージを取得しないといけないことがありました。
Windows APIなんか触ったことないし、Pythonでのウィンドウメッセージの取得の方法の記事なんか全然なくて、もうC++とか.NETなどを使うしかないかなと思ったのですが、どうにかPythonでできたので今回記事を書きました。(需要はほぼないと思いますが)

windowを作成し、ウィンドメッセージを処理する

コードは以下の通りです。コメントでコードの説明を入れましたのでご覧ください。
特別なことは何もやっておらず、ただウィンドウを作成してウィンドウメッセージを処理する関数を定義してあげただけです。
自分はここで作成したウィンドウハンドルを使って、APIのようなものに渡してあげて、そのAPIからメッセージを投げてもらって、メッセージを処理しました。

from ctypes import *
from ctypes.wintypes import *

# 関数プロトタイプ(CやC++の関数の宣言で使われるやつでインターフェイスを示すためのもの)
# 第一引数が戻り値の型、それ以降はPyWndProcedureの引数の型
WNDPROCTYPE = WINFUNCTYPE(c_int, HWND, c_uint, WPARAM, LPARAM)

# ウィンドウクラスの各項目を設定する際に使われる構造体
class WNDCLASSEX(Structure):
    _fields_ = [("cbSize", c_uint),
                ("style", c_uint),
                ("lpfnWndProc", WNDPROCTYPE),
                ("cbClsExtra", c_int),
                ("cbWndExtra", c_int),
                ("hInstance", HANDLE),
                ("hIcon", HANDLE),
                ("hCursor", HANDLE),
                ("hBrush", HANDLE),
                ("lpszMenuName", LPCWSTR),
                ("lpszClassName", LPCWSTR),
                ("hIconSm", HANDLE)]

class Window:

    WS_OVERLAPPEDWINDOW = 0xcf0000
    WS_CAPTION = 0xc00000

    SW_SHOW = 5

    CS_HREDRAW = 2
    CS_VREDRAW = 1

    CW_USEDEFAULT = 0x80000000

    WM_DESTROY = 2

    WHITE_BRUSH = 0

    def __init__(self, handle_data_callback):

        def PyWndProcedure(hWnd, Msg, wParam, lParam):
            # ここに受け取ったメッセージごとの処理を書く
            if Msg == self.WM_DESTROY:
                windll.user32.PostQuitMessage(0)
            # 特定のメッセージに対してやりたい処理をかませる
            elif Msg == 10000:
                handle_data_callback()
            # メッセージのデフォルトの処理は元々の処理系に任せる。
            return windll.user32.DefWindowProcW(hWnd, Msg, wParam, lParam)

        WndProc = WNDPROCTYPE(PyWndProcedure)
        # GetModuleHandleWは現在呼び出し元プロセスにロードされているexeやdllのメモリ上の位置を示すアドレスを返す
        hInst = windll.kernel32.GetModuleHandleW(0)
        wclassName = 'My Python Win32 Class'
        wname = 'My window'
        
        wndClass = WNDCLASSEX()
        wndClass.cbSize = sizeof(WNDCLASSEX)
        wndClass.style = self.CS_HREDRAW | self.CS_VREDRAW
        wndClass.lpfnWndProc = WndProc
        wndClass.cbClsExtra = 0
        wndClass.cbWndExtra = 0
        wndClass.hInstance = hInst
        wndClass.hIcon = 0
        wndClass.hCursor = 0
        wndClass.hBrush = windll.gdi32.GetStockObject(self.WHITE_BRUSH)
        wndClass.lpszMenuName = 0
        wndClass.lpszClassName = wclassName
        wndClass.hIconSm = 0
        
        # byrefは参照によってパラメータを渡すために使うためのもの(pointerと同じような働きだがこちらの方が高速)
        regRes = windll.user32.RegisterClassExW(byref(wndClass))
        
        # ウィンドウハンドルが返り値。ウィンドウハンドルとはコンピュータが各ウィンドウに割り振る管理番号のこと
        # ANSI文字列、Unicodeのどちらを使用するかという区別で関数名の最後にAかWがついている。(C言語はオーバーロードがないため別の関数になってるらしい) 今回はWで統一
        hWnd = windll.user32.CreateWindowExW(
        0,wclassName,wname,
        self.WS_OVERLAPPEDWINDOW | self.WS_CAPTION,
        self.CW_USEDEFAULT, self.CW_USEDEFAULT,
        300,300,0,0,hInst,0)
        
        
        if not hWnd:
            print('Failed to create window')
            exit(0)

        # ウィンドウの表示(表示せずただメッセージを受け取りたいだけの場合はコメントアウト)
        print('ShowWindow', windll.user32.ShowWindow(hWnd, SW_SHOW))
        print('UpdateWindow', windll.user32.UpdateWindow(hWnd))

        msg = MSG()
        lpmsg = pointer(msg)

        # GetMessageは呼び出し側スレッドのメッセージキューからメッセージを取得し、指定された構造体にそのメッセージを格納する。
        while windll.user32.GetMessageW(lpmsg, 0, 0, 0) != 0:
            windll.user32.TranslateMessage(lpmsg)
            windll.user32.DispatchMessageW(lpmsg)

def handle_message():
    print("メッセージ受け取ったよ!")

if __name__ == '__main__':
    win = Window(handle_message)

参照

https://stackoverflow.com/questions/5353883/python3-ctype-createwindowex-simple-example https://gist.github.com/mouseroot/6128651