ふにゃるんv2

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

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
ctypes1 posted by (C) wackyさんの写真


とりあえず、これでDLLの単体テストに使えますね。
また、膨らましてやれば、.pyd でバイナリなモジュールも動かせるようになりますかね?

ところで

FePyプロジェクトに報告したいんですけど、どうやればいいのか&英語わかんないです。
どなたか、優しい方、代わりに報告してもらえると ありがたいです…。