ふにゃるんv2

もとは、http://d.hatena.ne.jp/Wacky/

Cのヘッダファイルから、ctypes の呼び出しコードを自動生成させる

突然ですが、いつものように ぼ〜っと ネットワークを ふらふら さ迷っていると、面白い物を見つけました。


ctypes は、.DLLや.soなどの共有ライブラリを、pythonから動的に呼び出すライブラリなんですが、その呼び出しコードを生成するコードのようです。

  • h2xml.py で、ヘッダファイルをXMLに変換し
  • xml2py.py で、XMLから ctypes用の呼び出しコードを生成する

という按配らしいです。


興味が湧いたので、早速使ってみました。

インストール

英文なので、適当にWeb翻訳にかけると、GCC-XMLを突っ込めとか書いてあるようです。
なので、以下のファイルをダウンロードして、入れました。

  • ctypes-0.9.9.6.win32-py2.4.exe
  • gccxml-20050318-setup.exe

余談ですが、テストした環境は、Python 2.4.2 for Windowsです。はい。
後、利用にあたって、Visual C++が入っている必要があるっぽいです。…本当かな? Core SDK を突っ込めばOKのような気もしないではないですが。誰か試して下さいまし。

h2xml.py で、XMLファイルを作る

h2xml.pyは、Pythonのインストールフォルダから、'Lib\site-packages\ctypes\wrap' の場所にあります。
サンプルに従って、まずは windows.h を XML化してみましょう。

F:\Wacky\Test\python\test>python C:\tool2\Python24\Lib\site-packages\ctypes\wrap
\h2xml.py "C:\Program Files\Microsoft Visual Studio\VC98\Include\WINDOWS.H" -o w
indows.xml -q -c
Warning:
Compiler "cl" specified, but more than one of MSVC 6, 7, and 7.1 are installed.
Please specify "msvc6", "msvc7", or "msvc71" for the GCCXML_COMPILER setting.
Using MSVC 6 because it was used to build GCC-XML.

Warning:
Compiler "cl" specified, but more than one of MSVC 6, 7, and 7.1 are installed.
Please specify "msvc6", "msvc7", or "msvc71" for the GCCXML_COMPILER setting.
Using MSVC 6 because it was used to build GCC-XML.

skipped #define SNDMSG ::SendMessage FunctionType

あっけなく、windows.xmlができあがります。
ちなみに、上で warning message が出ていますが、'set GCCXML_COMPILER=msvc6'とか、そういったコマンドを叩けば、何の文句も言われなくなります。

xml2py.py で、ctypesの呼び出しコードを作成させる

XMLファイルが出来たので、早速 ctypesコードを作成させてみましょう。

まずは、サンプルに従って。

F:\Wacky\Test\python\test>python C:\tool2\Python24\Lib\site-packages\ctypes\wrap
\xml2py.py windows.xml -s RECT
# generated by 'xml2py'
# flags 'windows.xml -s RECT'
from ctypes import *
# C:/PROGRA~1/MICROS~3/VC98/Include/windef.h 287
class tagRECT(Structure):
    pass
RECT = tagRECT
LONG = c_long
tagRECT._fields_ = [
    # C:/PROGRA~1/MICROS~3/VC98/Include/windef.h 287
    ('left', LONG),
    ('top', LONG),
    ('right', LONG),
    ('bottom', LONG),
]
assert sizeof(tagRECT) == 16, sizeof(tagRECT)
assert alignment(tagRECT) == 4, alignment(tagRECT)

サンプルと同じ雰囲気のコードが出来上がりました。


次、何か適当に呼び出すコードを作って、動かしてみましょう。
んと、MessageBox 関数を、まず試します。

F:\Wacky\Test\python\test>python C:\tool2\Python24\Lib\site-packages\ctypes\wrap
\xml2py.py windows.xml -w -s MessageBox -d
# generated by 'xml2py'
# flags 'windows.xml -w -s MessageBox -d'
from ctypes import *
# C:/PROGRA~1/MICROS~3/VC98/Include/windef.h 195
class HWND__(Structure):
    pass
CHAR = c_char

@ stdcall(c_int, 'user32', [POINTER(HWND__), POINTER(CHAR), POINTER(CHAR), c_uin
t])
def MessageBoxA(p1, p2, p3, p4):
    # C:/PROGRA~1/MICROS~3/VC98/Include/winuser.h 6140
    return MessageBoxA._api_(p1, p2, p3, p4)

MessageBox = MessageBoxA # alias
HWND__._fields_ = [
    # C:/PROGRA~1/MICROS~3/VC98/Include/windef.h 195
    ('unused', c_int),
]
assert sizeof(HWND__) == 4, sizeof(HWND__)
assert alignment(HWND__) == 4, alignment(HWND__)

吐き出したコードを、test.pyにしてしまいます。

test.py:

# generated by 'xml2py'
# flags 'windows.xml -w -s MessageBox -d'
from ctypes import *
# C:/PROGRA~1/MICROS~3/VC98/Include/windef.h 195
class HWND__(Structure):
    pass
CHAR = c_char

@ stdcall(c_int, 'user32', [POINTER(HWND__), POINTER(CHAR), POINTER(CHAR), c_uint])
def MessageBoxA(p1, p2, p3, p4):
    # C:/PROGRA~1/MICROS~3/VC98/Include/winuser.h 6140
    return MessageBoxA._api_(p1, p2, p3, p4)

MessageBox = MessageBoxA # alias
HWND__._fields_ = [
    # C:/PROGRA~1/MICROS~3/VC98/Include/windef.h 195
    ('unused', c_int),
]
assert sizeof(HWND__) == 4, sizeof(HWND__)
assert alignment(HWND__) == 4, alignment(HWND__)

早速動かしてみましょう。

F:\Wacky\Test\python\test>ipython

F:\Wacky\Test\python\test>python C:\tool2\Python24\Scripts\ipython
WARNING: Could not import user config!
 ('C:\Documents and Settings\user\_ipython/ipy_user_conf.py' does not exist?
Please run '%upgrade')

Python 2.4.2 (#67, Sep 28 2005, 12:41:11) [MSC v.1310 32 bit (Intel)]
Type "copyright", "credits" or "license" for more information.

IPython 0.7.2 -- An enhanced Interactive Python.
?       -> Introduction to IPython's features.
%magic  -> Information about IPython's 'magic' % functions.
help    -> Python's own help system.
object? -> Details about 'object'. ?object also works, ?? prints more.

In [1]: import test
---------------------------------------------------------------------------
exceptions.NameError                                 Traceback (most recent call
 last)

F:\Wacky\Test\python\test\<ipython console>

F:\Wacky\Test\python\test\test.py
      7 CHAR = c_char
      8
----> 9 @ stdcall(c_int, 'user32', [POINTER(HWND__), POINTER(CHAR), POINTER(CHAR
), c_uint])
     10 def MessageBoxA(p1, p2, p3, p4):
     11     # C:/PROGRA~1/MICROS~3/VC98/Include/winuser.h 6140

NameError: name 'stdcall' is not defined

おー、まい、がっ!

コードが、呼び出せるように修正する

何で動かねぇんだろ〜と思って、サンプルを眺めていると、エラー箇所の所が、生成したコードとサンプルでは違うんですね。

  • エラーが起きる場合:@ stdcall(...)
  • サンプルの場合:@ decorator.stdcall(...)
    後、先頭に'from ctypes import decorators'が付加

なるほどぉ。と思って、コードに修正を加えます。

test.py:

# generated by 'xml2py'
# flags 'windows.xml -w -s MessageBox -d'
from ctypes import *
from ctypes import decorators
# C:/PROGRA~1/MICROS~3/VC98/Include/windef.h 195
class HWND__(Structure):
    pass
CHAR = c_char

@ decorators.stdcall(c_int, 'user32', [POINTER(HWND__), POINTER(CHAR), POINTER(CHAR), c_uint])
def MessageBoxA(p1, p2, p3, p4):
    # C:/PROGRA~1/MICROS~3/VC98/Include/winuser.h 6140
    return MessageBoxA._api_(p1, p2, p3, p4)

MessageBox = MessageBoxA # alias
HWND__._fields_ = [
    # C:/PROGRA~1/MICROS~3/VC98/Include/windef.h 195
    ('unused', c_int),
]
assert sizeof(HWND__) == 4, sizeof(HWND__)
assert alignment(HWND__) == 4, alignment(HWND__)

早速実行。

F:\Wacky\Test\python\test>python C:\tool2\Python24\Scripts\ipython
...
In [1]: import test
---------------------------------------------------------------------------
exceptions.TypeError                                 Traceback (most recent call
 last)

F:\Wacky\Test\python\test\<ipython console>

F:\Wacky\Test\python\test\test.py
      8 CHAR = c_char
      9
---> 10 @ decorators.stdcall(c_int, 'user32', [POINTER(HWND__), POINTER(CHAR), POINTER(CHAR), c_uint])
     11 def MessageBoxA(p1, p2, p3, p4):
     12     # C:/PROGRA~1/MICROS~3/VC98/Include/winuser.h 6140

C:\tool2\Python24\lib\site-packages\ctypes\decorators.py in decorate(func)
     96         else:
     97             this_dll = dll
---> 98         api = ctypes.WINFUNCTYPE(restype, *argtypes)(func.func_name, this_dll)
     99         # This simple way to find out an empty function body doesn't work.
    100 ##        if len(func.func_code.co_code) == 4:

TypeError: function takes exactly 1 argument (2 given)

だめジャン。


何が悪いんだろ〜と思って、手当たり次第、ぐーぐるして、上の decorators.py を以下のように修正すれば良い事に気付きました。

 96         else:
 97             this_dll = dll
 98         api = ctypes.WINFUNCTYPE(restype, *argtypes)(func.func_name, this_dll)
                  ↓
 98         api = ctypes.WINFUNCTYPE(restype, *argtypes)((func.func_name, this_dll))
 99         # This simple way to find out an empty function body doesn't work.
100 ##        if len(func.func_code.co_code) == 4:

要するに、タプルで渡せばいいようです。

注意事項
136行にも、同様に同じ修正を加えてください。


では、気を取り直して、再度 呼び出し。
ctypes
ctypes posted from フォト蔵

今度は、うまく行きました。

結論

最後に結論。

  • h2xml.pyとxml2py.pyを使うと、ctypesを使った呼び出しコードの作成が、非常に簡単になります。
  • 但し、decorators.py の修正を行う必要があります。
    (2.4.3では必要ないかも知れないけど、試してません)