Cのヘッダファイルから、ctypes の呼び出しコードを自動生成させる
突然ですが、いつものように ぼ〜っと ネットワークを ふらふら さ迷っていると、面白い物を見つけました。
- README: ctypes code generation
http://starship.python.net/crew/theller/ctypes/codegen.html
ctypes は、.DLLや.soなどの共有ライブラリを、pythonから動的に呼び出すライブラリなんですが、その呼び出しコードを生成するコードのようです。
という按配らしいです。
興味が湧いたので、早速使ってみました。
インストール
英文なので、適当に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 posted from フォト蔵
今度は、うまく行きました。
結論
最後に結論。
- h2xml.pyとxml2py.pyを使うと、ctypesを使った呼び出しコードの作成が、非常に簡単になります。
- 但し、decorators.py の修正を行う必要があります。
(2.4.3では必要ないかも知れないけど、試してません)