ctypes チュートリアル

注:このドキュメントは Thomas Heller 氏の ctypes モジュールドキュメント を翻訳したものです.オリジナルのドキュメントは ctypes のページ にあります.

概要 :: チュートリアル :: リファレンス :: faq

( 作業中: COM :: COM サンプル )

このチュートリアルでは,バージョン 0.6.2 の ctypes について 記述します.バージョン 0.4.x からはかなりの変更が加えられており, その中でも最も重要なものを ここ に挙げておきました.

ダイナミックリンクライブラリをロードする

ダイナミックリンクライブラリをロードするため, ctypes では cdll , Windows ではさらに windll および oledll オブジェクトを公開しています.

これらのオブジェクトでは,オブジェクトの属性名としてライブラリに アクセスすることでライブラリをロードすることができます.'cdll' は標準の cdecl 呼び出し規約を使う関数を公開しているライブラリをロード しています.一方 windll のライブラリでは,'stdcall' 呼び出し規約を 使い,Windows 形式の HRESULT エラーコードを返すような関数を呼び出します. 関数呼び出しが失敗すると,エラーコードは Python の WindowsError 例外を 自動的に発行するために用いられます.

以下に Windows におけるいくつかの例を示します.ここで msvcrt は ほとんどの標準 C 関数が収められている MS の標準 C ライブラリで,cdecl 呼び出し規約を使います :

      >>> from ctypes import *
      >>> print windll.kernel32
      <WinDLL 'kernel32', handle 77e80000 at 7ecfe8>
      >>> print cdll.msvcrt
      <CDLL 'msvcrt', handle 78000000 at 80b010>

原理的には,同様のことが Linux でも動作するはずですが,ほとんどの場合 には以下のようにライブラリ検索パスを指定する必要があるようです.従って, この例では,ファイル名を指定してライブラリをロードするやりかたも示して おきます:

       >>> from ctypes import *
       >>> libc = cdll.LoadLibrary("/lib/libc.so.6")
       <CDLL '/lib/libc.so.6', handle 40018c28 at 4019978c>
       >>>

このチュートリアルでは Windows を例に使っていますが,strchr,printf, その他の標準 C ライブラリ由来の関数は Linux や他のシステムでも 動作します.

ロードされた dll から関数にアクセスする

関数には dll オブジェクトの属性としてアクセスします:

      >>> from ctypes import *
      >>> print cdll.msvcrt.printf
      <ctypes._CdeclFuncPtr object ar 0x00905F68>
      >>> print windll.kernel32.GetModuleHandleA
      <ctypes._StdcallFuncPtr object ar 0x008E6D28>
      >>> print windll.kernel32.MyOwnFunction
      Traceback (most recent call last):
        File "<stdin>", line 1, in ?
        File "ctypes.py", line 239, in __getattr__
          func = _StdcallFuncPtr(name, self)
      Attribute: function 'MyOwnFunction' not found

XXX ctypes バージョン 0.6.2 以降では,dll 内にシンボルが見つから なかった場合に,以前発行されていた ValueError ではなく AttributeErrors を発行します.

kernel32user32 ,その他の win32 システム dll は時に ANSI バージョンの関数と同時に UNICODE バージョンの関数を公開しています. UNICODE バージョンは関数名に W を付けた名前で公開されており, ANSI バージョンでは A が付けられています.指定したモジュール名に 対して モジュールハンドル を返す GetModuleHandle 関数は,これらの C プロトタイプを持っており,C マクロを使うことで,UNICODE が定義されて いるかどうかによって,どちらかを GetModuleHandle として公開するように なっています:

      /* ANSI version */
      HMODULE GetModuleHandleA(LPCSTR lpModuleName);
      /* UNICODE version */
      HMODULE GetModuleHandleW(LPCWSTR lpModuleName);

windll はこれらの関数を自動的に判別しようとはしないので, GetModuleHandleAGetModuleHandleW を明示的に指定することで 必要な関数をロードし,それぞれ通常の文字列か unicode 文字列を使って 呼び出さなければなりません.

時に,dll は Python の識別子として有効でない, "??2@YAPAXI@Z" のような関数名を公開しています.この場合には, getattr を使って 関数を取得する必要があります XXX Better example:

      >>> getattr(cdll.msvcrt, "??2@YAPAXI@Z")
      <ctypes._CdeclFuncPtr object at 0x00905EE0>
      >>>

関数を呼び出す

関数は他の Python の呼び出し可能オブジェクトと同様に呼び出すことが できます.以下の例では, UNIX エポックからのシステム時間を秒を単位と して返す time() 関数,そして win32 のモジュールハンドルを返す GetModuleHandleA() 関数を用います.

以下の例では,どちらの関数も NULL ポインタを引数として呼び出します (None は NULLL ポインタとして使われます):

      >>> from ctypes import *
      >>> print cdll.msvcrt.time(None)
      1048777320
      >>> print hex(windll.kernel32.GetModuleHandleA(None))
      0x1d000000

ctypes は引数の数を間違えて関数を呼び出さないようにベストをつくし ます.残念ながら,この機能は Windows でしか動作しません.ctypes はこの チェックを,関数が戻ったときのスタックを調べることによって行います:

      >>> windll.kernel32.GetModuleHandleA()
      Traceback (most recent call last):
        File "<stdin>", line 1, in ?
      ValueError: Procedure probably called with not enough arguments
      >>> windll.kernel32.GetModuleHandleA(0, 0)
      Traceback (most recent call last):
        File "<stdin>", line 1, in ?
      ValueError: Procedure probably called with too many arguments
      >>>

Windows では, ctypes は関数が無効な引数値で呼び出された際の 一般保護違反によってクラッシュするのを防ぐため, win32 の構造化例外処理 を行います:

      >>> windll.kernel32.GetModuleHandleA(32)
      Traceback (most recent call last):
        File "<stdin>", line 1, in ?
      WindowsError: exception: access violation
      >>>

しかしながら, ctypes を使っていて Python をクラッシュさせてしまう 場合は多々あるので,よく注意してください.

Python の整数型,文字列型および unicode 文字列型だけが,関数 呼び出しの際にパラメタとして直接利用することができます.

他の引数型を使った関数呼び出しの説明に移る前に,まず ctypes データ型 についてもっと理解を深める必要があります.

単純なデータ型

ctypes では,多くのプリミティブな C 互換データ型を定義しています :

ctypes' type

C type

Python type

c_char

char

character

c_byte

char

integer

c_ubyte

unsigned char

integer

c_short

short

integer

c_ushort

unsigned short

integer

c_int

int

integer

c_uint

unsigned int

integer

c_long

long

integer

c_ulong

unsigned long

long

c_longlong

__int64 or long long

long

c_ulonglong

unsigned __int64 or unsigned long long

long

c_char_p

char * (NUL terminated)

string

c_wchar_p

wchar_t * (NUL terminated)

unicode

c_void_p

void *

integer

これらの型は全て,オプションの初期化指定子として正しい型と値を使って 作成することができます:

      >>> c_int()
      c_int(0)
      >>> c_char_p("Hello World")
      c_char_p('Hello, World')
      >>> c_uint(-3)
      Traceback (most recent call last):
        File "<stdin>", line 1, in ?
      ValueError: Value out of range

これらの型は可変 (mutable) なので,その値はあとで変更することが できます:

      >>> i = c_int(42)
      >>> print i
      c_int(42)
      >>> print i.value
      42
      >>> i.value = -99
      >>> print i.value
      -99

c_char_pc_wchar_p ,および c_void_p といったポインタ型の インスタンスに値を代入すると,ポインタが指し示す メモリ上の位置が 変更され, ポインタの指し示していた メモリブロックの内容が変更される わけではありません (Python の文字列型は不変 (immutable) なので,もちろん そんなことはできるわけがありません):

      >>> s = "Hello, World"
      >>> c_s = c_char_p(s)
      >>> print c_s
      c_char_p('Hello, World')
      >>> c_s.value = "Hi, there"
      >>> print c_s
      c_char_p('Hi, there')
      >>> print s                 # first string is unchanged
      Hello, World      

しかし,上記のポインタを可変のメモリ領域が渡されると期待している関数に 渡さないように注意してください.可変のメモリブロックが必要なら,様々な 方法でそうしたメモリブロックを生成する c_buffer 関数が ctypes には あります.現在のメモリブロックの内容に対するアクセス (と変更) は raw プロパティで行うことができます.メモリブロックを NUL で終端された文字列と してアクセスしたいなら string プロパティを使います:

      >>> from ctypes import *
      >>> p = c_buffer(3)      # create a 3 byte buffer, initialized to NUL bytes
      >>> print sizeof(p), repr(p.raw)
      3 '\x00\x00\x00'
      >>> p = c_buffer("Hello")      # create a buffer containing a NUL terminated string
      >>> print sizeof(p), repr(p.raw)
      6 'Hello\x00'
      >>> print repr(p.value)
      'Hello'
      >>> p = c_buffer("Hello", 10)  # create a 10 byte buffer
      >>> print sizeof(p), repr(p.raw)
      10 'Hello\x00\x00\x00\x00\x00'
      >>> p.value = "Hi"      
      >>> print sizeof(p), repr(p.raw)
      10 'Hi\x00lo\x00\x00\x00\x00\x00'
      >>>

続・関数の呼び出し

printf は真の標準出力チャネルを指しており, sys.stdout を指しては いない ので注意してください.このため,以下の例はコンソールプロンプト 上でのみ動作し, IDLEPythonWin では動作しません:

      >>> from ctypes import *; printf = cdll.msvcrt.printf
      >>> printf("Hello, %s\n", "World!")
      Hello, World!
      14
      >>> printf("Hello, %S", u"World!") # Note the upper case S!
      Hello, World!
      14
      >>> printf("%d bottles of beer\n", 42)
      42 bottles of beer
      19
      >>> printf("%f bottles of beer\n", 42.5)
      Traceback (most recent call last):
        File "<stdin>", line 1, in ?
      TypeError: Don't know how to convert parameter 2
      >>>

前に述べたように,Python 整数型,文字列型,および unicode 文字列型を 除く全ての型は,必要とする C データ型に変換できるようにするために, 対応する ctypes 型でラップされていなければなりません:

      >>> from ctypes import *
      >>> printf = cdll.msvcrt.printf
      >>> printf("An int %d, a double %f\n", 1234, c_double(3.14))
      Integer 1234, double 3.1400001049
      34
      >>>

自作のデータ型を使って関数を呼び出す

ctypes の引数変換をカスタマイズして,自作のクラスのインスタンスを 関数の引数として使うことができます. ctypes_as_parameter_ 属性を探して,関数の引数として用います.もちろん,この値は整数, 文字列,unicode 文字列のいずれかでなければなりません:

      >>> class Bottles(object):
      ...     def __init__(self, number):
      ...         self._as_parameter_ = number
      ...
      >>> bottles = Bottles(42)
      >>> from ctypes import *
      >>> printf = cdll.msvcrt.printf
      >>> printf("%d bottles of beer\n", bottles)
      42 bottles of beer
      19
      >>>

インスタンスのデータを _as_parameter_ インスタンス変数に保持 したくない場合, property を定義して,データを取得できるように します.

要求される引数型を指定する (関数プロトタイプ)

argtypes 属性を設定して,DLL が公開している関数で必要とされる 引数型を指定することができます.

argtypes は C データ型からなる配列 ( printf は引数の数が可変で, フォーマット文字列に応じて異なる型の引数をとるため,よい例ではない のですが,その一方でこの機能を実験してみるにはとても便利です):

      >>> from ctypes import *
      >>> printf = cdll.msvcrt.printf
      >>> printf.argtypes = [c_char_p, c_char_p, c_int, c_double]
      >>> printf("String '%s', Int %d, Double %f\n", "Hi", 10, 2.2)
      String 'Hi', Int 10, Double 2.200000

書式を指定すると (C 関数のプロトタイプ宣言のように) 不完全な引数型 になるのを防ぎ,有効な型に変換しようと試みます:

      >>> printf("%d %d %d", 1, 2, 3)
      Traceback (most recent call last):
        File "<stdin>", line 1, in ?
      TypeError: string expected instead of int instance
      >>> printf("%s %d %f", "X", 2, 3)
      X 2 3.00000012
      >>>

自作のクラスを定義して,関数呼び出しの引数として渡すなら, from_param クラスメソッドを定義して argtypes 配列で使えるようにしなければ なりません. from_param クラスメソッドは関数呼び出しの際に 与えられた Python オブジェクトを受理し,型チェックか,このオブジェクト が受理され得るような何らかの処理を行い,オブジェクト自体, _as_parameter_ 属性,あるいは C 関数の引数として渡したい何らかの 値を返します.繰り返しになりますが,返される値は整数,文字列,unicode 文字列, ctypes インスタンス,あるいは _as_parameter_ 属性を持つ 何らかのオブジェクトでなければなりません.

戻り値の型

デフォルトでは,関数は整数を返すと仮定されています.関数オブジェクト の restype 属性を設定することで,他の戻り値型を指定することが できます.

restype として許されている値は, c_intc_longc_char ,などといった単純なデータ型と,その他のデータ型への ポインタです.構造体を返す関数はまだサポートされていません.

以下により高度な例を示します.この例では strchr 関数を用いており, 文字列へのポインタと char を引数としてとり,文字列へのポインタを返し ます:

      >>> from ctypes import *
      >>> strchr = cdll.msvcrt.strchr
      >>> strchr("abcdef", ord("d"))
      8059983
      >>> strchr.restype = c_char_p # c_char_p is a pointer to a string
      >>> strchr("abcdef", ord("d"))
      'def'
      >>> print strchr("abcdef", ord("x"))
      None
      >>>

上の例のような ord("x") の呼び出しを避けたいなら, argtypes 属性を設定します.こうすると,二つ目の引数が長さ 1 文字の Python 文字列から C の char 型に変換されます:

      >>> from ctypes import *
      >>> msvcrt = cdll.msvcrt
      >>> msvcrt.strchr.restype = "s"
      >>> msvcrt.strchr.argtypes = [c_char_p, c_char]
      >>> msvcrt.strchr("abcdef", "d")
      'def'
      >>> msvcrt.strchr("abcdef", "def")
      Traceback (most recent call last):
        File "<stdin>", line 1, in ?
      TypeError: one character string expected
      >>> print msvcrt.strchr("abcdef", "x")
      None
      >>> print msvcrt.strchr("abcdef", "d")
      "def"
      >>>

呼び出し可能な Python オブジェクト (例えば関数やクラス) を restype 属性として使うこともできます. 呼び出し可能オブジェクトは C 関数が返るときの 整数 とともに呼び出され,このオブジェクトを呼び 出した結果が関数の結果として使われます.この機能はエラーの戻り値 を調べ,例外を自動的に発行するのに便利です:

      >>> from ctypes import *
      >>> GetModuleHandle = windll.kernel32.GetModuleHandleA
      >>> def ValidHandle(value):
      ...     if value == 0:
      ...         raise WinError()
      ...     return value
      ...
      >>>
      >>> GetModuleHandle.restype = ValidHandle
      >>> GetModuleHandle(None)
      486539264
      >>> GetModuleHandle("something silly")
      Traceback (most recent call last):
        File "<stdin>", line 1, in ?
        File "<stdin>", line 3, in ValidHandle
      WindowsError: [Errno 126] The specified module could not be found.
      >>>

WinError は関数で, Windows の FormatMessage() API を呼び出してエラーコードの文字列表現をを取得し,例外を 返し ます. WinError はオプションのエラーコードをパラメタに とります.この値が使われない場合, GetLastError() を 呼び出して値を取得します.

ポインタを渡す (別名: パラメタの参照渡し)

時に C API 関数はパラメタとして ポインタ をとるようにしていて, これはおそらくポインタに対応する場所に書き込みを行なえるようにして いたり,データが値渡しするには大きすぎる場合です.これは パラメタの参照渡し としても知られています.

ctypes では,'byref' 関数を公開していて,パラメタを参照渡しする 際に使います.同じ効果は pointer 関数でも達成できますが, pointer は実際のポインタオブジェクトを生成するため,かなり余分な 処理を行います.このため,Python コードの中でポインタオブジェクト 自体が必要ではない場合, byref を使ったほうが高速になります:

      >>> from ctypes import *
      >>> msvcrt = cdll.msvcrt
      >>> i = c_int()
      >>> f = c_float()
      >>> s = c_string('\000' * 32)
      >>> print i.value, f.value, repr(s.value)
      0 0.0 ''      
      >>> msvcrt.sscanf("1 3.14 Hello", "%d %f %s",
      ...               byref(i), byref(f), s)
      3
      >>> print i.value, f.value, repr(s.value)
      1 3.1400001049 'Hello'

ポインタ渡しは難しい問題のようであり,メーリングリストでは ポインタを要求する関数の呼び出し方についての質問がたまに寄せられ ます.前節の内容を改善するための提案があれば, ctypes-users に ポストしてください.

構造体と共用体

構造体と共用体は ctypes モジュールで定義されている Structure および Union 基底クラスから導出しなくては なりません.各サブクラスでは, _fields_ 属性を定義しなければ なりません. _fields_フィールド名フィールド型 からなる 2 要素のタプル の入ったリストでなければなりません.

フィールド型は, c_int のような ctypes の型か, ctypes の型: 構造体,共用体,配列,ポインタ,から導出された別の型でなければ なりません.

以下は二つの整数 x および y を含む POINT 構造体の簡単な例です. 構造体をコンストラクタで初期化する方法も示します:

      >>> from ctypes import *
      >>> class POINT(Structure):
      ...     _fields_ = [("x", c_int),
      ...                 ("y", c_int)]
      ...
      >>> point = POINT(10, 20)
      >>> print point.x, point.y
      10 20
      >>> point = POINT(y=5)
      >>> print point.x, point.y
      0 5
      >>> POINT(1, 2, 3)
      Traceback (most recent call last):
        File "<stdin>", line 1, in ?
      ValueError: too many initializers
      >>>

上の例は簡単なものですが,もっと複雑な構造体を構築することも できます.構造体はそれ自体別の構造体を含めることができ,これは フィールド型に structure 型を含めることで行います.

以下は二つの POINT 構造体 upperleft および lowerright を含む RECT 構造体です:

      >>> class RECT(Structure):
      ...     _fields_ = [("upperleft", POINT),
      ...                 ("lowerright", POINT)]
      ...
      >>> rc = RECT(point)
      >>> print rc.upperleft.x, rc.upperleft.y
      10 20
      >>> print rc.lowerright.x, rc.lowerright.y
      0 0
      >>>

入れ子の構造体はいくつかの方法を使ってコンストラクタ内で初期化 することができます:

      >>> r = RECT(POINT(1, 2), POINT(3, 4))
      >>> r = RECT((1, 2), (3, 4))

フィールド記述子は クラス から引き出すことができ,読み出し専用 の size および offset 属性があります.これらはそれぞれフィールドの バイトサイズおよび内部的なメモリバッファの先頭部分からのオフセット. を記しています:

      >>> print POINT.x.size, POINT.x.offset
      0 4
      >>> print POINT.y.size, POINT.y.offset
      4 4
      >>>

構造体および共用体のフィールドは,デフォルトでは C コンパイラが 行うのと同様の方法でバイト整列 (byte alignment) されます.この挙動は サブクラスで packed クラス属性を指定することでオーバライドすることができ, この値は正の整数でなければならず,フィールドの最大整列長を指定します. MSVC で #pragma pack(n) がやっていることと同じだと私は考えています.

配列

配列 (array) は同じ型を持つ一定数のインスタンスの入った配列です.

配列型を生成する方法として推奨するのは,データ型を正の整数で乗算 するやり方です:

      TenPointsArray = POINT * 10

以下に,他の要素に混じって 4 つの POINT 構造体を含むような仮想的な データ型の例を示します:

      >>> from ctypes import *
      >>> class POINT(Structure):
      ...    _fields_ = ("x", c_int), ("y", c_int)
      ...
      >>> class MyStruct(Structure):
      ...    _fields_ = [("a", c_int),
      ...                ("b", float),
      ...                ("point_array", POINT * 4)]
      >>>
      >>> print len(MyStruct().point_array)
      4

インスタンスは通常の方法で,クラスを呼び出して生成します:

      arr = TenPointsArray()
      for pt in arr:
          print pt.x, pt.y

上のコードは 0 0 と表示された行が並んだものを印字します.これは 配列の内容がゼロで初期化されるためです.

正しい型の初期化値を指定することもできます:

      >>> from ctypes import *
      >>> TenIntegers = c_int * 10
      >>> ii = TenIntegers(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
      <__main__.c_int_Array_10 object at 0x009119F0>
      >>> for i in ii: print i,
      ...
      1 2 3 4 5 6 7 8 9 10
      >>>

ポインタ

ポインタ (pointer) 型のインスタンスは ctypes の型に対して pointer 関数を呼び出して生成します:

       >>> from ctypes import *
       >>> i = c_int(42)
       >>> pi = pointer(i)
       >>>

ポインタ型インスタンスは contents 属性を持っており,ポインタが 指している ctypes の型を返します.上の例では c_int(42) となります:

       >>> pi.contents
       c_int(42)
       >>>

他の c_int インスタンスをポインタ型インスタンスの contents 属性 に代入すると,ポインタは代入した値のメモリ上の位置を指すようになります:

       >>> pi.contents = c_int(99)
       >>> pi.contents
       c_int(99)
       >>>

ポインタ型のインスタンスは整数でインデクス指定することもできます:

       >>> pi[0]
       99
       >>>

整数でインデクス指定して代入すると,ポインタが指している値が変更 されます:

       >>> i2 = pi[0]
       >>> i2
       c_int(99)
       >>> pi[0] = 22
       >>> i2
       c_int(22)
       >>>

ゼロでないインデクスを使うことも可能です.しかし,この操作を行う 際には自分が何をしようとしているかを理解しておかなければ なりません: 一般的には,この機能を使うのは,ポインタを C 関数から 受け取るときだけで,ポインタが実際に指しているのが単一の要素でなく 配列であると 知っている 場合だけです.

ポインタのクラス/型

舞台裏では, pointer 関数は単にポインタ型インスタンスを生成 する以外の処理も行っており,必ず最初にポインタ を作成します. この処理を行うのは POINTER 関数で,全ての ctypes 型を受け取って 新たな型を返します:

       >>> from ctypes import *
       >>> PI = POINTER(c_int)
       >>> PI
       <class 'ctypes.LP_c_int>
       >>> PI(42)
       Traceback (most recent call last):
         File "<stdin>", line 1, in ?
       TypeError: expected c_int instead of int
       >>> PI(c_int(42))
       <ctypes.LP_c_int object at 0x008ECCE8>
       >>>

不完全型

XXX ここに書かれた昨日は完全にはうまく動作しないようです XXX

不完全型 は,データメンバが指定されていない構造体,共用体または 配列です. ctypes のコンテキストでは,こうした不完全型のポインタを 表現する型は,それらの名前を POINTER 関数に渡しておき,その結果得られた サブクラスを後で補完することができます.

以下の例 (C のコード) を考えます:

      struct cell;

      struct {
          char *name;
          struct cell *next;
      } cell;

ctypes のコードに素直に変換すると以下のようになりますが,これは うまく動作しません:

       >>> class cell(Structure):
       ...     _fields_ = [("name", c_char_p),
       ...                 ("next", POINTER(cell))]
       ...
       Traceback (most recent call last):
         File "<stdin>", line 1, in ?
         File "<stdin>", line 2, in cell
       NameError: name 'cell' is not defined
       >>>

なぜなら,新たな class cell は,そのクラスの宣言文自体では 利用することができないからです.

このような実装は,POINTER を クラス名 を使って呼び出し, 定義後の完全型を設定することで行います:

       >>> from ctypes import *
       >>> lpcell = POINTER("cell")
       >>> class cell(Structure):
       ...     _fields_ = [("name", c_char_p),
       ...                 ("next", lpcell)]
       ...
       >>> SetPointerType(lpcell, cell)
       >>>

試してみましょう. cell の二つのインスタンスを生成し, それらが互いを指し示すようにして,最後にポインタの連鎖を数回 たどってみます:

       >>> c1 = cell()
       >>> c1.name = "foo"
       >>> c2 = cell()
       >>> c2.name = "bar"
       >>> c1.next = pointer(c2)
       >>> c2.next = pointer(c2)
       >>> p = c1
       >>> for i in range(8):
       ...     print p.name,
       ...     p = p.next[0]
       ...
       foo bar foo bar foo bar foo bar
       >>>    

コールバック関数

(この節の例は長すぎるので,もっと短い配列を使うべきでした)

ctypes では,C から呼び出し可能な関数ポインタを Python の呼び出し 可能オブジェクトから生成することができます.こうした関数はしばしば コールバック関数 と呼ばれます.

最初に,コールバック関数のためのクラスとして,呼び出し規約 関数が返さなければならない戻り値の方,および関数が受け取ることになる 引数の数と型について知っているクラスを作成しなければなりません.

ctypes では, コールバック関数を通常の cdecl 呼び出し規約を 用いて作成する CFUNCTYPE ファクトリ関数を提供しており, Windows では stdcall 呼び出し規約を使った関数を作成するための WINFUNCTYPE ファクトリ関数を提供しています.

これらのファクトリ関数は両方とも,最初の引数に戻り値の型をとり, 残りの引数にコールバック関数に必要な引数をとって呼び出します.

ここでは,コールバック関数の助けを借りて要素をソートする, 標準 C ライブラリの qsort 関数の例を示します. 整数の配列をソート するための qsort を使います:

       >>> from ctypes import *
       >>> IntArray10 = c_int * 10
       >>> ia = IntArray10(5, 4, 3, 1, 7, 9, 33, 2, 99, 0)
       >>> qsort = cdll.msvcrt.qsort
       >>>

qsort は並べ替えたいデータへのポインタと,配列中の要素の数, 単一の要素のサイズ,そしてコールバック関数である並べ替え処理の関数 を伴って呼び出さなくてはなりません.コールバック関数は 二つの要素へのポインタを伴って呼び出され,最初の要素が二つ目の要素 よりも小さい場合には負を,等しければ 0 を,それ以外の場合には正の 値を返さなければなりません.

従って,我々のコールバック関数は整数へのポインタを受け取り, 整数を返さなければならないことになります.最初にコールバック関数 の を作成します:

       >>> CMPFUNC = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
       >>>

最初のバージョンのコールバック関数実装では,単に受け取った引数を 出力して,0 を返すことにします (段階的な開発です):

       >>> def py_cmp_func(a, b):
       ...     print "py_cmp_func", a, b
       ...     return 0
       ...
       >>>

C から呼び出し可能な関数を作成します:

       >>> cmp_func = CMPFUNC(py_cmp_func)
       >>>

これで準備が整いました:

       >>> qsort(ia, len(ia), sizeof(c_int), cmp_func)
       py_cmp_func <ctypes.LP_c_int object at 0x009127E8> <ctypes.LP_c_int object at 0x00927DF8>
       py_cmp_func <ctypes.LP_c_int object at 0x009127E8> <ctypes.LP_c_int object at 0x00927DF8>
       py_cmp_func <ctypes.LP_c_int object at 0x009127E8> <ctypes.LP_c_int object at 0x00927DF8>
       py_cmp_func <ctypes.LP_c_int object at 0x009127E8> <ctypes.LP_c_int object at 0x00927DF8>
       py_cmp_func <ctypes.LP_c_int object at 0x009127E8> <ctypes.LP_c_int object at 0x00927DF8>
       py_cmp_func <ctypes.LP_c_int object at 0x009127E8> <ctypes.LP_c_int object at 0x00927DF8>
       py_cmp_func <ctypes.LP_c_int object at 0x009127E8> <ctypes.LP_c_int object at 0x00927DF8>
       py_cmp_func <ctypes.LP_c_int object at 0x009127E8> <ctypes.LP_c_int object at 0x00927DF8>
       py_cmp_func <ctypes.LP_c_int object at 0x009127E8> <ctypes.LP_c_int object at 0x00927DF8>
       py_cmp_func <ctypes.LP_c_int object at 0x009127E8> <ctypes.LP_c_int object at 0x00927DF8>
       py_cmp_func <ctypes.LP_c_int object at 0x009127E8> <ctypes.LP_c_int object at 0x00927DF8>
       py_cmp_func <ctypes.LP_c_int object at 0x009127E8> <ctypes.LP_c_int object at 0x00927DF8>
       py_cmp_func <ctypes.LP_c_int object at 0x009127E8> <ctypes.LP_c_int object at 0x00927DF8>
       py_cmp_func <ctypes.LP_c_int object at 0x009127E8> <ctypes.LP_c_int object at 0x00927DF8>
       py_cmp_func <ctypes.LP_c_int object at 0x009127E8> <ctypes.LP_c_int object at 0x00927DF8>
       py_cmp_func <ctypes.LP_c_int object at 0x009127E8> <ctypes.LP_c_int object at 0x00927DF8>
       py_cmp_func <ctypes.LP_c_int object at 0x009127E8> <ctypes.LP_c_int object at 0x00927DF8>
       py_cmp_func <ctypes.LP_c_int object at 0x009127E8> <ctypes.LP_c_int object at 0x00927DF8>
       py_cmp_func <ctypes.LP_c_int object at 0x009127E8> <ctypes.LP_c_int object at 0x00927DF8>
       -1
       >>>

これでポインタの内容にアクセスする方法を理解したので,コールバック 関数を再定義しましょう:

       >>> def py_cmp_func(a, b):
       ...     print "py_cmp_func", a[0], b[0]
       ...     return 0
       ...
       >>> cmp_func = CMPFUNC(py_cmp_func)
       >>> qsort(ia, len(ia), sizeof(c_int), cmp_func)
       py_cmp_func 5 9
       py_cmp_func 5 0
       py_cmp_func 9 0
       py_cmp_func 4 9
       py_cmp_func 3 9
       py_cmp_func 1 9
       py_cmp_func 7 9
       py_cmp_func 33 9
       py_cmp_func 2 9
       py_cmp_func 99 9
       py_cmp_func 0 9
       py_cmp_func 99 9
       py_cmp_func 99 9
       py_cmp_func 2 9
       py_cmp_func 33 9
       py_cmp_func 7 9
       py_cmp_func 1 9
       py_cmp_func 3 9
       py_cmp_func 4 9
       -1
       >>>

おっ,ほとんどできましたね!最後の仕上げです:

       >>> def py_cmp_func(a, b):
       ...     print "py_cmp_func", a[0], b[0]
       ...     return a[0] - b[0]
       ...
       >>> qsort(ia, len(ia), sizeof(c_int), CMPFUNC(py_cmp_func))
       py_cmp_func 5 9
       py_cmp_func 5 0
       py_cmp_func 9 5
       py_cmp_func 4 5
       py_cmp_func 3 5
       py_cmp_func 1 5
       py_cmp_func 7 5
       py_cmp_func 99 5
       py_cmp_func 2 5
       py_cmp_func 33 5
       py_cmp_func 33 5
       py_cmp_func 2 5
       py_cmp_func 7 33
       py_cmp_func 99 33
       py_cmp_func 9 99
       py_cmp_func 7 33
       py_cmp_func 9 33
       py_cmp_func 7 9
       py_cmp_func 4 0
       py_cmp_func 3 4
       py_cmp_func 1 4
       py_cmp_func 2 4
       py_cmp_func 2 0
       py_cmp_func 3 2
       py_cmp_func 1 3
       py_cmp_func 2 0
       py_cmp_func 1 2
       py_cmp_func 1 0
       -1
       >>>

ところで配列は正しくソートされているのでしょうか:

       >>> for i in ia: print i,
       ...
       0 1 2 3 4 5 7 9 33 99
       >>>

うむ,うまくいっていますね!

DLL で公開されている値にアクセスする

時に,dll は関数を公開するだけでなく,値を公開していることが あります.Python の dll 自体を例は,起動時に与えた -O-OO といったフラグに応じて整数 0,1,または 2 をとる Py_OptimizeFlag です.

バージョン 0.6.1 からは, ctypes はこのような値をデータ型の クラスメソッド in_dll でアクセスできるようになりました. 以下の例は Windows でのみ動作します:

      >>> from ctypes import *
      >>> pydll = cdll.python22
      >>> opt_flag = c_int.in_dll(pydll, "Py_OptimizeFlag")
      >>> print opt_flag
      c_int(0)
      >>>

インタプリタが -O 付きで起動されたなら,このサンプルは c_int(1) を出力し, -OO なら c_int(2) となるはずです.

もっと進んだ例として,Python が公開している PyImport_FrozenModules にアクセスするポインタの使用を例示します.

Python のドキュメントを引用すると: *このポインタは,終端が NULL またはゼロのメンバを持つ要素になっている struct _frozen レコードの配列を指すように初期化されます.フリーズされたモジュール (frozen module) が import される際,このテーブルからモジュールの 検索が行われます.サードパーティ製のコードでは,フリーズされた モジュールからなる動的に生成された集合を提供することで, この機能にいたずらを働くことができます.*

そこで,このポインタの操作を行って有効性を確かめます.サンプルコード の大きさを制限するために,このテーブルをどうやって ctypes で読むかだけを 示します:

      >>> from ctypes import *
      >>> pydll = cdll.python22
      >>>
      >>> class struct_frozen(Structure):
      ...     _fields_ = [("name", c_char_p),
      ...                 ("code", POINTER(c_ubyte)),
      ...                 ("size", c_int)]
      ...
      >>>

Python dll を ロード し, struct _frozen データ型を定義 したので,テーブルからポインタを取得することができます:

      >>> FrozenTable = POINTER(struct_frozen)
      >>> table = FrozenTable.in_dll(pdll, "PyImport_FrozenModules")
      >>>

tablestruct_frozen のレコードへの ポインタ なので, table に対して反復処理を行うことができます.ポインタにはデータ ブロックの大きさの情報がないので,ループがきちんと終了するように しなければなりません.遅かれ早かれアクセス違反か何かでクラッシュする ことになってしまうので,NULL のエントリに到達した際にループを抜ける ようにすべきです.

>>> for item in table: ... print item.name, item.size ... if item.name is None: ... break ... __hello__ 100 __phello__ -100 __phello__.spam 100 None 0 >>>

標準の Python がフリーズされたモジュールとパッケージ (負のサイズの メンバであらわされます) を持っていることはよく分かっていませんが, 私の知る限り,この値はテストに使われます.例えば, import __hello__ を試してみてください.

XXX モジュールのバイトコードが収められた code メンバフィールドにアクセスする方法を記述する予定.

ctypes の不思議

ctypes にはいくつかへんなところがあり,実際の動作が期待を裏切る ことになるかもしれません.

以下の例を考えてみます:

      >>> from ctypes import *
      >>> class POINT(Structure):
      ...     _fields_ = ("x", "i"), ("y", "i")
      ...
      >>> class RECT(Structure):
      ...     _fields_ = ("a", POINT), ("b", POINT)
      ...
      >>> p1 = POINT(1, 2)
      >>> p2 = POINT(3, 4)
      >>> rc = RECT(p1, p2)
      >>> print rc.a.x, rc.a.y, rc.b.x, rc.b.y
      1 2 3 4
      >>> # now swap the two points
      >>> rc.a, rc.b = rc.b, rc.a
      >>> print rc.a.x, rc.a.y, rc.b.x, rc.b.y
      3 4 3 4

うむむ,最後の文は 3 4 1 2 を出力すると予想していたはずです. 何が起こったのでしょう? 上の例には rc.a, rc.b = rc.b, rc.a という行があります:

      >>> temp0, temp1 = rc.b, rc.a
      >>> rc.a = temp0
      >>> rc.b = temp1

temp0temp1 は,まだ上の rc オブジェクトのバッファを 利用していることに注意してください.従って,'rc.a = temp0' を実行 すると, temp0 のバッファの内容を rc のバッファにコピーします. この処理は,今度は temp1 の内容を変更してしまいます.結果として, 最後の代入 rc.b = temp1 では,期待どおりの結果にはならないのです.

Structure,Union,および Array 型からの子オブジェクト取り出しは, 子オブジェクトを コピーせず ,その上元オブジェクトの根底にある メモリバッファにアクセスするためのラッパを取り出す,ということを心 にとどめて置いてください.

バグ,ToDo およびまだ実装されていないもの

ビットフィールドはまだ実装されていません.

列挙型 (enumeration) は実装されていません. c_int 型を基底クラスに すれば自分で容易に実装することができます.

long double は実装されていません.

構造体を関数の引数として渡すことはできず,戻り値の型として設定する こともできません (ポインタ型のみ可能です).

Python で実装されたコールバックは整数しか返すことができません.



Page updated: Fri Nov 28 15:50:54 2003