Python 3 から MessageBox (Windows API) を呼び出す

公開日:
最終更新日:

タイトルの通りのことを行いたい場合, Python ドキュメント によれば,下記のコードで実現できるとされている.

              from ctypes import c_int, WINFUNCTYPE, windll
              from ctypes.wintypes import HWND, LPCSTR, UINT
              prototype = WINFUNCTYPE(c_int, HWND, LPCSTR, LPCSTR, UINT)
              paramflags = (1, "hwnd", 0), (1, "text", "Hi"), (1, "caption", None), (1, "flags", 0)
              MessageBox = prototype(("MessageBoxA", windll.user32), paramflags)

              MessageBox()
              MessageBox(text="Spam, spam, spam")
              MessageBox(flags=2, text="foo bar")
            

しかし,予期に反して Python 3 ではこのコードは動作せず,以下のエラーが出力される.

              Traceback (most recent call last):
                File "<stdin>", line 1, in <?>
              ctypes.ArgumentError: argument 2: <class 'TypeError'>: wrong type
            

原因はエラーメッセージの通り,つまり,Unicode文字列を,バイト列を引数にとるMessageBoxAへ渡そうとしていることである.
この場合,MessageBoxAの代わりにMessageBoxWを呼び出すよう以下のように修正すれば,期待する動作となるはずだ.

              from ctypes import c_int, WINFUNCTYPE, windll
              from ctypes.wintypes import HWND, LPCWSTR, UINT
              prototype = WINFUNCTYPE(c_int, HWND, LPCWSTR, LPCWSTR, UINT)
              paramflags = (1, "hwnd", 0), (1, "text", "Hi"), (1, "caption", None), (1, "flags", 0)
              MessageBox = prototype(("MessageBoxW", windll.user32), paramflags)

              MessageBox()
              MessageBox(text="Spam, spam, spam")
              MessageBox(flags=2, text="foo bar")
            

あるいは,特別な事情(?)によりMessageBoxAを呼び出さなくてはならない場合は,以下のコードにより問題を部分的に解決できる.

              from ctypes import c_int, WINFUNCTYPE, windll
              from ctypes.wintypes import HWND, LPCSTR, UINT
              from locale import getpreferredencoding
              preferredEncoding = getpreferredencoding()
              prototype = WINFUNCTYPE(c_int, HWND, LPCSTR, LPCSTR, UINT)
              paramflags = (1, "hwnd", 0), (1, "text", "Hi".encode(preferredEncoding)), (1, "caption", None), (1, "flags", 0)
              MessageBox = prototype(("MessageBoxA", windll.user32), paramflags)

              MessageBox()
              MessageBox(text="Spam, spam, spam".encode(preferredEncoding))
              MessageBox(flags=2, text="foo bar".encode(preferredEncoding))
            

getpreferredencoding()によりシステムのデフォルトエンコーディングを取得し,Unicode文字列をそれにエンコードしたものをMessageBoxAへ渡せばよい.
ただし,エンコードできない文字を含んだUnicode文字列の場合この方法では当然失敗するし,オーバヘッドも生じるので,MessageBoxWを呼び出すことを推奨したい.