チケット #47 (new blog)
pythonからctypesで任意のDLLの関数を使う。
| 報告者: | mtamaki | 担当者: | mtamaki |
|---|---|---|---|
| 優先度: | major | マイルストーン: | |
| コンポーネント: | blog | バージョン: | |
| キーワード: | 関係者: |
説明 (最終更新者: mtamaki) (diff)
ctypesを使って躓いたところメモ&チュートリアル。
まずHelloWorldがてらMessageBox。
from ctypes import windll windll.user32.MessageBoxW( 0, u"テスト", 0, 0 )
こんだけ。超楽。
次に、自前のDLLを使う。 たとえばc:\test\test.dllで
extern "C"
{
const char* test()
{
return "test";
}
}
という関数がエクスポートされていて、これを使うとき、
from ctypes import windll, c_char_p print c_char_p( windll.LoadLibrary(r"c:\test\test.dll").test() ).value
とする。 LoadLibraryで読み込んで、test関数を呼ぶ。 ctypesはデフォルトでは整数を返してしまうので、文字列型にキャストして、それのvalueを見ることで戻り値の文字列を取得できる。ちなみにargtypesを使うもっとエレガントなやり方もある。 たくさん関数を呼ぶときはデコレータとかうまく使うときれいにかけそうな予感がする。
んで、今回のメイン。GetOpenFileNameを読んでみる。 これはいわゆる「ファイルを開くダイアログ」で、GetOpenFileNameを呼ぶだけで表示できるが、GetOpenFileNameにOPENFILENAMEという結構複雑な構造体を渡さないといけない。 しかも、複数選択を有効にしたときは、OPENFILENAMEのlpstrFile変数に、「ディレクトリパス\0ファイル名1\0ファイル名2\0...ファイル名n\0\0」というフォーマットで結果が返ってくるのでそれをパースする必要がある。
def ctypes_get_open_file_name( is_multi_select = False ):
from ctypes import Structure, c_ulong, c_wchar_p, c_buffer, c_ushort, sizeof, windll, byref, c_void_p, create_unicode_buffer, cast
class OPENFILENAME(Structure):
_fields_ = [
("lStructSize", c_ulong),
("hwndOwner", c_ulong),
("hInstance", c_ulong),
("lpstrFilter", c_wchar_p),
("lpstrCustomFilter", c_wchar_p),
("nMaxCustFilter", c_ulong),
("nFilterIndex", c_ulong),
("lpstrFile", c_wchar_p),
("nMaxFile", c_ulong),
("lpstrFileTitle", c_wchar_p),
("nMaxFileTitle", c_ulong),
("lpstrInitialDir", c_wchar_p),
("lpstrTitle", c_wchar_p),
("Flags", c_ulong),
("nFileOffset", c_ushort),
("nFileExtension", c_ushort),
("lpstrDefExt", c_wchar_p),
("lCustData", c_ulong),
("lpfnHook", c_void_p),
("lpTemplateName", c_wchar_p),
("pvReserved", c_void_p),
("dwReserved", c_ulong),
("FlagsEx", c_ulong),
]
def __init__(self):
Structure.__init__(self)
self.lStructSize = sizeof( OPENFILENAME )
self.set_file_path_size(512*5)
def set_allow_multi_select(self):
self.Flags |= 0x200 | 0x00080000
min_size = 1024 * 5
if self.nMaxFile < min_size:
self.set_file_path_size( min_size )
def set_file_path_size(self, size):
self.lpstrFileBuffer = create_unicode_buffer(size)
self.lpstrFile = cast( self.lpstrFileBuffer, c_wchar_p )
self.nMaxFile = size
def get_file_paths(self):
results = []
result = ""
for ch in self.lpstrFileBuffer:
if u'\x00' == ch:
if "" == result:
break
else:
results.append( result )
result = ""
else:
result += ch
if 1 < len( results ):
import os
dirname = results[0]
results = [os.path.join( dirname, result ) for result in results[1:]]
return results
def set_file_title_size(self, size):
self.lpstrFileTitleBuffer = create_unicode_buffer(size)
self.lpstrFileTitle = cast( self.lpstrFileTitleBuffer, c_wchar_p )
self.nMaxFileTitle = size
def set_create_prompt(self):
self.Flags |= 0x2000
def set_over_write_prompt(self):
self.Flags |= 0x2
def set_path_must_exist(self):
self.Flags |= 0x800
def set_file_must_exist(self):
self.Flags |= 0x1000
ofn = OPENFILENAME()
if is_multi_select:
ofn.set_allow_multi_select()
if windll.LoadLibrary("comdlg32").GetOpenFileNameW(byref( ofn )):
if is_multi_select:
return ofn.get_file_paths()
else:
return ofn.lpstrFile
return None
if __name__ == '__main__':
print ctypes_get_open_file_name( True )
こんな感じ。ポイントはlpstrFileにcreate_unicode_bufferをキャストしてつめているところ。 たとえばGetModuleFileNameとかにはcreate_unicode_bufferの戻り値をそのまま与えることができるが、構造体のメンバには_fields_で宣言した型に合わせたインスタンスを代入する必要がある。
が、create_unicode_bufferで返される配列には型を合わせてくれる機能がないのでキャストするとうまくいく。