突然ですが、いつものように ぼ〜っと ネットワークを ふらふら さ迷っていると、面白い物を見つけました。
- 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では必要ないかも知れないけど、試してません)