14.14.1.17 コールバック関数

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

最初に、コールバック関数のためのクラスを作る必要があります。そのクラスには 呼び出し規約、戻り値の型およびこの関数が受け取る引数の数と型についての情報があります。

CFUNCTYPEファクトリ関数は通常のcdecl呼び出し規約を用いて コールバック関数のための型を作成します。 Windowsでは、WINFUNCTYPEファクトリ関数がstdcall呼び出し規約を用いて コールバック関数の型を作成します。

これらのファクトリ関数はともに最初の引数に戻り値の型、 残りの引数としてコールバック関数が想定する引数の型を渡して 呼び出されます。

標準Cライブラリのqsort関数を使う例を示します。 これはコールバック関数の助けをかりて要素をソートするために使われます。 qsortは整数の配列をソートするために使われます:

>>> IntArray5 = c_int * 5
>>> ia = IntArray5(5, 1, 7, 33, 99)
>>> qsort = libc.qsort
>>> qsort.restype = None
>>>

qsortはソートするデータを指すポインタ、データ配列の要素の数、 要素の一つの大きさ、およびコールバック関数である比較関数へのポインタを引数に渡して 呼び出さなければなりません。そして、コールバック関数は要素を指す二つのポインタを渡されて 呼び出され、一番目が二番目より小さいなら負の数を、等しいならゼロを、 それ以外なら正の数を返さなければなりません。

コールバック関数は整数へのポインタを受け取り、整数を 返す必要があります。まず、コールバック関数のためのtypeを 作成します:

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

コールバック関数のはじめての実装なので、受け取った引数を単純に表示して、 0を返します(漸進型開発(incremental development)です ;-):

>>> 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) # doctest: +WINDOWS
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
py_cmp_func <ctypes.LP_c_long object at 0x00...> <ctypes.LP_c_long object at 0x00...>
>>>

ポインタの中身にアクセスする方法がわかっているので、コールバック関数を再定義しましょう:

>>> def py_cmp_func(a, b):
...     print "py_cmp_func", a[0], b[0]
...     return 0
...
>>> cmp_func = CMPFUNC(py_cmp_func)
>>>

Windowsでの実行結果です:

>>> qsort(ia, len(ia), sizeof(c_int), cmp_func) # doctest: +WINDOWS
py_cmp_func 7 1
py_cmp_func 33 1
py_cmp_func 99 1
py_cmp_func 5 1
py_cmp_func 7 5
py_cmp_func 33 5
py_cmp_func 99 5
py_cmp_func 7 99
py_cmp_func 33 99
py_cmp_func 7 33
>>>

linuxではソート関数がはるかに効率的に動作しており、 実施する比較の数が少ないように見えるのが不思議です:

>>> qsort(ia, len(ia), sizeof(c_int), cmp_func) # doctest: +LINUX
py_cmp_func 5 1
py_cmp_func 33 99
py_cmp_func 7 33
py_cmp_func 5 7
py_cmp_func 1 7
>>>

ええ、ほぼ完成です!最終段階は、実際に二つの要素を比較して 実用的な結果を返すことです:

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

Windowsでの最終的な実行結果です:

>>> qsort(ia, len(ia), sizeof(c_int), CMPFUNC(py_cmp_func)) # doctest: +WINDOWS
py_cmp_func 33 7
py_cmp_func 99 33
py_cmp_func 5 99
py_cmp_func 1 99
py_cmp_func 33 7
py_cmp_func 1 33
py_cmp_func 5 33
py_cmp_func 5 7
py_cmp_func 1 7
py_cmp_func 5 1
>>>

Linuxでは:

>>> qsort(ia, len(ia), sizeof(c_int), CMPFUNC(py_cmp_func)) # doctest: +LINUX
py_cmp_func 5 1
py_cmp_func 33 99
py_cmp_func 7 33
py_cmp_func 1 7
py_cmp_func 5 7
>>>

Windowsのqsort関数はlinuxバージョンより多く比較する必要があることがわかり、 非常におもしろいですね!

簡単に確認できるように、今では配列はソートされています:

>>> for i in ia: print i,
...
1 5 7 33 99
>>>

コールバック関数についての重要な注意事項:

Cコードから使われる限り、CFUNCTYPEオブジェクトへの参照を確実に保持してください。 ctypesは保持しません。もしあなたがやらなければ、オブジェクトはゴミ集めされてしまい、 コールバックしたときにあなたのプログラムをクラッシュさせるかもしれません。

ご意見やご指摘をお寄せになりたい方は、 このドキュメントについて... をご覧ください。