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

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

Pythonから固定長文字列を含む構造体を要求するWin APIを呼び出す他

Pythonから固定長文字列を含む構造体を要求するWin APIを呼び出す方法のサンプルと途中で学んだことの覚書です。最初は実際のコードから。

サンプルコード

このコードではGetVersionEx関数とOSVERSIONINFO構造体(固定長文字列''TCHAR szCSDVersion[128]''を含む)を用いて、固定長文字列を含む構造体を要求するWin APIの呼び出しを確認します。

from ctypes import *

class OSVERSIONINFO(Structure):
    _fields_ = [
        ("os_version_info_size", c_uint),
        ("major_version", c_uint),
        ("minor_version", c_uint),
        ("build_number", c_uint),
        ("platform_id", c_uint),
        ("csd_version", c_wchar * 128)]
    def __init__(self):
        self.os_version_info_size = 20 + 128 * 2

osverinfo = OSVERSIONINFO()
ret = windll.Kernel32.GetVersionExW(pointer(osverinfo))
print(osverinfo.csd_version)

補足

ctypesパッケージ

Pythonで「動的リンク/共有ライブラリを純粋な Python でラップするために使う」機能を提供するパッケージです。詳細な説明は公式ドキュメントを参考にして下さい。ドキュメントを精読した訳ではないので以下の説明も一部間違っている可能性があります。

windll.Kernel32.GetVersionExW(pointer(...))

左から順番に意味を説明すると、 windll:C言語でいうstdcall呼び出し規約(WINAPI/APIENTRY)で Kernel32:Kernel32(.dll)に存在する GetVersionEx:GetVersionExW関数を呼び出しなさい。 pointer(...):引数はpointer(...)とする ということだそうです。

実際にはctypes内部でwindllという変数が定義されていて、「.Kernel32」はそれが実装する__getattr__を呼び出す仕組みだと思います。__getattr__はKernel32を読み込んだクラスのインスタンスを返しますが、そのクラスがまた__getattr__を実装しているので.GetVersionExWでその名前のクラスのインスタンスが返されます。

GetVersionEx(...)部分ではpointer(osverinfo)とすることで事前に作成したOSVERSIONINFOクラス(Structureクラスから派生)のポインタを渡すことを明示しています。数値(0, 1, 2,...)や文字列では直に指定することもできるかと思いますが、同じように明示したければc_int(0)といったc_*クラスを指定して型を指定します。

呼び出し規約をcdeclに変えたい場合はcdll.Kernel32のように呼び出します。

getattr

クラスのインスタンスに存在しない属性が参照された場合に呼び出されます。以下のコードが最も簡単な例です。

class class1:
    def __getattr__(self, name):
        return name

if __name__ == "__main__":
    c1 = class1()
    print(c1.abc)
    print(c1.defg) #defはキーワードなのでシンタックスエラーが発生します。

少し複雑にするとwindll.~のようにクラスを返すことも出来ます。

class class2:
    def __init__(self, name):
        self.name = name
    def __getattr__(self, name):
        return "{0}, {1}".format(self.name, name)

class class1:
    def __getattr__(self, name):
        return class2(name)

if __name__ == "__main__":
    print(class1().abc.defg)

なお、インスタンスに存在する属性が参照された場合も呼び出されるメソッドとして__getattribute__があります。

ctypesの構造体に固定長配列を持たせる

上のサンプルコードのcsd_versionのようにc_* * nあるいは<Structureの派生型> * nとすれば良いそうです。

class OSVERSIONINFO(Structure):
    _fields_ = [
        ("os_version_info_size", c_uint),
        ("major_version", c_uint),
        ("minor_version", c_uint),
        ("build_number", c_uint),
        ("platform_id", c_uint),
        ("csd_version", c_wchar * 128)]
    ...