ctypes for IronPythonのWinAPI用パッチを作ったです
ここ最近、何度もTry & Errorしていましたが、ようやく動くようになりました。
IronPythonは、.NETのアセンブリモジュールを気軽に呼び出し可能なのですが、普通のDLLは呼び出し出来ないんですね。
で、外部DLLの ちょっとした呼び出ししたい場合、普通だと わざわざアセンブリモジュールを作らないといけない→面倒だなぁ。という状態だったんです。
普通のPythonだと、DLLを そのまま呼び出すモジュールとして、ctypesモジュールがあるんですが、内部でバイナリ化(.pyd)されているので、IronPythonから そのまま呼び出せない状態でした。
そういった諸問題を解決する為に、FePyって、プロジェクトが立ち上がっていて、これはIronPythonで そのまま使えないモジュールを移植するプロジェクトみたいなんですが、この中に、ctypes.pyモジュールがあったんです。
ところが、このFePyのctypes.pyモジュールは、どうもWindows APIに対応してないっぽくて(C形式の呼び出しにのみ対応)、普通のDLLのAPIを呼び出すと、返値が常に「0」という、ちょっと悲しい状態だったんです。
でまぁ、ここ暫くヒマを見つけては、どうにかして動かす方法は無いか、何度もトライしていましたが、何とか動かせるようになりました!
という訳で、以下に修正版のctypes.pyを、以下に示します。
# Copyright (c) 2006 by Seo Sanghyeon # 2006-06-08 sanxiyn Created # 2006-06-11 sanxiyn Implemented .value on primitive types # 2006-12-03 add Stdcall patch __all__ = [ 'c_int', 'c_float', 'c_char_p', 'c_void_p', 'LibraryLoader', 'CDLL', 'WDLL', 'cdll', 'wdll', 'byref', 'sizeof' ] # -------------------------------------------------------------------- # Dynamic module definition from System import AppDomain from System.Reflection import AssemblyName from System.Reflection.Emit import AssemblyBuilderAccess def pinvoke_module(): domain = AppDomain.CurrentDomain name = AssemblyName('pinvoke') flag = AssemblyBuilderAccess.Run assembly = domain.DefineDynamicAssembly(name, flag) module = assembly.DefineDynamicModule('pinvoke') return module # -------------------------------------------------------------------- # General interface class pinvoke_value: type = None value = None def get_type(obj): if isinstance(obj, pinvoke_value): return obj.type else: return type(obj) def get_value(obj): if isinstance(obj, pinvoke_value): return obj.value else: return obj # -------------------------------------------------------------------- # Primitive types from System import IntPtr from System import UInt32 class pinvoke_primitive(pinvoke_value): def __init__(self, value=None): if value is None: value = self.type() if not isinstance(value, self.type): expected = self.type.__name__ given = value.__class__.__name__ msg = "%s expected instead of %s" % (expected, given) raise TypeError(msg) self.value = value def __repr__(self): clsname = self.__class__.__name__ return "%s(%r)" % (clsname, self.value) class c_int(pinvoke_primitive): type = int class c_float(pinvoke_primitive): type = float class c_char_p(pinvoke_primitive): type = str class c_void_p(pinvoke_primitive): type = IntPtr # -------------------------------------------------------------------- # Reference from System import Type class pinvoke_reference(pinvoke_value): def __init__(self, obj): self.obj = obj self.type = Type.MakeByRefType(obj.type) self.value = obj.value def __repr__(self): return "byref(%r)" % (self.obj,) def byref(obj): if not isinstance(obj, pinvoke_value): raise TypeError("byref() argument must be a ctypes instance") ref = pinvoke_reference(obj) return ref # -------------------------------------------------------------------- # Utility from System.Runtime.InteropServices import Marshal def sizeof(obj): return Marshal.SizeOf(obj.type) # -------------------------------------------------------------------- # Dynamic P/Invoke from System import Array, Object, Type from System.Reflection import CallingConventions, MethodAttributes, MethodImplAttributes from System.Runtime.InteropServices import CallingConvention, CharSet class pinvoke_method: pinvoke_attributes = ( MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.PinvokeImpl ) calling_convention = None return_type = None def __init__(self, dll, entry): self.dll = dll self.entry = entry self.restype = None self.argtypes = None self.method = None def create(self, restype, argtypes): dll = self.dll entry = self.entry attributes = self.pinvoke_attributes cc = self.calling_convention argtypes = Array[Type](argtypes) module = pinvoke_module() method = module.DefinePInvokeMethod( entry, dll, attributes, CallingConventions.Standard, restype, argtypes, cc, CharSet.Ansi ) method.SetImplementationFlags(MethodImplAttributes.PreserveSig) module.CreateGlobalFunctions() method = module.GetMethod(entry) self.method = method return method def __call__(self, *args): if self.restype: restype = self.restype.type else: restype = self.return_type.type if self.argtypes: argtypes = [argtype.type for argtype in self.argtypes] else: argtypes = [get_type(arg) for arg in args] method = self.method or self.create(restype, argtypes) args = Array[Object]([get_value(arg) for arg in args]) result = method.Invoke(None, args) return result # -------------------------------------------------------------------- # Function loader def is_special_name(name): return name.startswith('__') and name.endswith('__') class pinvoke_dll: method_class = None def __init__(self, name): self.name = name def __repr__(self): clsname = self.__class__.__name__ return "<%s '%s'>" % (clsname, self.name) def __getattr__(self, name): if is_special_name(name): raise AttributeError(name) method = self.method_class(self.name, name) setattr(self, name, method) return method class CDLL(pinvoke_dll): class method_class(pinvoke_method): calling_convention = CallingConvention.Cdecl return_type = c_int class WDLL(pinvoke_dll): class method_class(pinvoke_method): calling_convention = CallingConvention.Winapi return_type = c_int # -------------------------------------------------------------------- # Library loader class LibraryLoader(object): def __init__(self, dlltype): self.dlltype = dlltype def __getattr__(self, name): if is_special_name(name): raise AttributeError(name) dll = self.dlltype(name) setattr(self, name, dll) return dll def LoadLibrary(self, name): return self.dlltype(name) cdll = LibraryLoader(CDLL) wdll = LibraryLoader(WDLL)
例えば、以下のように呼び出します。
import clr from ctypes import * ko = WDLL(r"kernel32.dll") print ko.GetTickCount() print ko.GetTickCount() usr = WDLL(r"user32.dll") print usr.MessageBoxA(0, "Hello", "Hello world", 0)
実行例:
ctypes1 posted by (C) wackyさんの写真
とりあえず、これでDLLの単体テストに使えますね。
また、膨らましてやれば、.pyd でバイナリなモジュールも動かせるようになりますかね?
ところで
FePyプロジェクトに報告したいんですけど、どうやればいいのか&英語わかんないです。
どなたか、優しい方、代わりに報告してもらえると ありがたいです…。