ctypesとは
ctypesは、PythonからC言語のライブラリ(DLL/so)を直接呼び出すための標準ライブラリ。外部ライブラリのインストールなしで使える。
低レベルなことやりたいときにモのすごく便利。
import ctypes
基本的な型
| ctypes型 | C型 | サイズ |
|---|---|---|
c_int |
int |
4 bytes |
c_long |
long |
4/8 bytes |
c_float |
float |
4 bytes |
c_double |
double |
8 bytes |
c_char |
char |
1 byte |
c_char_p |
char* |
ポインタ |
c_void_p |
void* |
ポインタ |
c_bool |
bool |
1 byte |
ライブラリの読み込み
import ctypes
import ctypes.util
# Windows
libc = ctypes.CDLL('msvcrt')
# Linux/macOS
libc = ctypes.CDLL(ctypes.util.find_library('c'))
# 直接パス指定
lib = ctypes.CDLL('/path/to/library.so')
lib = ctypes.CDLL('C:/path/to/library.dll')
関数の呼び出し
基本的な呼び出し
# 引数と戻り値の型を設定
libc.strlen.argtypes = [ctypes.c_char_p]
libc.strlen.restype = ctypes.c_size_t
# 呼び出し(バイト文字列で渡す)
length = libc.strlen(b"Hello World")
print(length) # 11
数学関数
# sqrt
libm.sqrt.argtypes = [ctypes.c_double]
libm.sqrt.restype = ctypes.c_double
print(libm.sqrt(2.0)) # 1.4142135623730951
# pow
libm.pow.argtypes = [ctypes.c_double, ctypes.c_double]
libm.pow.restype = ctypes.c_double
print(libm.pow(2.0, 10.0)) # 1024.0
構造体
class Point(ctypes.Structure):
_fields_ = [
("x", ctypes.c_int),
("y", ctypes.c_int),
]
# 使用
p = Point(10, 20)
print(f"({p.x}, {p.y})") # (10, 20)
print(ctypes.sizeof(Point)) # 8
ネストした構造体
class Rectangle(ctypes.Structure):
_fields_ = [
("top_left", Point),
("bottom_right", Point),
]
rect = Rectangle(Point(0, 0), Point(100, 50))
配列
# 固定サイズ配列の型を作成
IntArray5 = ctypes.c_int * 5
# 配列を作成
arr = IntArray5(1, 2, 3, 4, 5)
print(list(arr)) # [1, 2, 3, 4, 5]
print(sum(arr)) # 15
配列を含む構造体
class Vector3D(ctypes.Structure):
_fields_ = [
("coords", ctypes.c_double * 3),
]
vec = Vector3D((1.0, 2.0, 3.0))
ポインタ
# 値を作成
x = ctypes.c_int(42)
# ポインタを取得
ptr = ctypes.pointer(x)
# ポインタ経由でアクセス
print(ptr.contents.value) # 42
# 値を変更
ptr.contents.value = 100
print(x.value) # 100
byref(参照渡し)
# 関数に参照を渡す(高速)
y = ctypes.c_int(0)
some_c_func(ctypes.byref(y))
コールバック関数
Pythonの関数をCに渡す:
# 関数型を定義
COMPARE_FUNC = ctypes.CFUNCTYPE(
ctypes.c_int, # 戻り値
ctypes.c_void_p, # 引数1
ctypes.c_void_p # 引数2
)
# Python関数
def py_compare(a, b):
a_val = ctypes.cast(a, ctypes.POINTER(ctypes.c_int)).contents.value
b_val = ctypes.cast(b, ctypes.POINTER(ctypes.c_int)).contents.value
return a_val - b_val
# コールバックを作成
callback = COMPARE_FUNC(py_compare)
# qsortで使用
arr = (ctypes.c_int * 5)(5, 2, 8, 1, 9)
libc.qsort(arr, 5, ctypes.sizeof(ctypes.c_int), callback)
print(list(arr)) # [1, 2, 5, 8, 9]
Union(共用体)
同じメモリを別の型として解釈:
class IntOrFloat(ctypes.Union):
_fields_ = [
("i", ctypes.c_int),
("f", ctypes.c_float),
]
u = IntOrFloat()
u.i = 1234567890
print(u.f) # 同じビットをfloatとして解釈
実践例:自作Cライブラリ
1. Cコード
// mylib.c
#include <math.h>
double circle_area(double radius) {
return M_PI * radius * radius;
}
typedef struct {
double x, y;
} Point;
double distance(Point* p1, Point* p2) {
double dx = p2->x - p1->x;
double dy = p2->y - p1->y;
return sqrt(dx*dx + dy*dy);
}
2. コンパイル
# Linux
gcc -shared -fPIC -o mylib.so mylib.c -lm
# Windows
cl /LD mylib.c
# macOS
gcc -shared -o mylib.dylib mylib.c
3. Python から呼び出し
import ctypes
# ライブラリ読み込み
lib = ctypes.CDLL('./mylib.so')
# circle_area
lib.circle_area.argtypes = [ctypes.c_double]
lib.circle_area.restype = ctypes.c_double
print(lib.circle_area(5.0)) # 78.53981633974483
# 構造体定義
class Point(ctypes.Structure):
_fields_ = [("x", ctypes.c_double), ("y", ctypes.c_double)]
# distance
lib.distance.argtypes = [ctypes.POINTER(Point), ctypes.POINTER(Point)]
lib.distance.restype = ctypes.c_double
p1 = Point(0.0, 0.0)
p2 = Point(3.0, 4.0)
print(lib.distance(ctypes.byref(p1), ctypes.byref(p2))) # 5.0
エラーハンドリング
# Cのerrnoを取得
libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
# Windowsの場合
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
error = ctypes.get_last_error()
注意点
- 型の一致 - 引数と戻り値の型を正確に設定
- メモリ管理 - Pythonのオブジェクトがガベージコレクションされないよう注意
-
文字列 - Cにはバイト文字列(
b"...")で渡す - 32bit/64bit - ポインタサイズが異なる
まとめ
| 用途 | 方法 |
|---|---|
| ライブラリ読み込み | ctypes.CDLL() |
| 型設定 |
argtypes, restype
|
| 構造体 | ctypes.Structure |
| 配列 | c_type * n |
| ポインタ |
pointer(), byref()
|
| コールバック | CFUNCTYPE() |
ctypesを使えば、高速なCライブラリをPythonから簡単に呼び出せます!