pythonからcの関数を呼び出す方法(ctypes編)
pythonからcの関数を呼び出す方法はいくつかありますが、今回はctypesを使った方法を説明します。
実行環境はwindowsを使用しました。
全体の手順は、以下のようになります。
- Visual Studio でdllを作成する。
- pythonから呼び出す
1. Dllの作成
DLLはVisual Studioを使って作成します。
- Visual Studioを起動し、「Create a new project」を選択します。
- Dynamic-Link Library (DLL)を選択
- Project nameを入力(ctypes_sample1)
- Createボタンを押す
- Solution ExplorerのSource Filesで右クリックしAdd->New ItemからC++ File (.cpp)を選び、ファイル名を入力する。(ctypes_sample1.cpp)
- Solution ExplorerのHeader Filesで右クリックしAdd->New ItemからHeader File (.h)を選び、ファイル名を入力する。(ctypes_sample1.h)
- 関数を記述する
- Build->Build SolutionでDLLを作成する。
今回はVisual Studio 2019を使用しました。
ctypes_sample1.h
#pragma once
#ifdef CTYPES_SAMPLE1_EXPORTS
#define CTYPES_SAMPLE1_API __declspec(dllexport)
#else
#define CTYPES_SAMPLE1_API __declspec(dllimport)
#endif
struct Vector3D
{
int x;
int y;
int z;
};
extern "C" CTYPES_SAMPLE1_API double add_double(double a, double b);
extern "C" CTYPES_SAMPLE1_API int add_int(int a, int b);
extern "C" CTYPES_SAMPLE1_API double accumulate(const double *x, int len);
extern "C" CTYPES_SAMPLE1_API bool copy(const double *from, double *to, int n);
extern "C" CTYPES_SAMPLE1_API bool copy2d(const double **from, double ** to, int m, int n);
extern "C" CTYPES_SAMPLE1_API Vector3D AddVector(Vector3D a, Vector3D b);
ctypes_sample1.cpp
#include"pch.h"
#include "ctypes_sample1.h"
double add_double(double a, double b)
{
return a + b;
}
int add_int(int a, int b)
{
return a + b;
}
double accumulate(const double* x, int len)
{
double sum = 0;
for (int i = 0; i < len; i++)
{
sum += x[i];
}
return sum;
}
bool copy(const double* from, double* to, int n)
{
for (int i = 0; i < n; i++) {
to[i] = from[i];
}
return true;
}
bool copy2d(const double** from, double** to, int m, int n)
{
for (int j = 0; j < n; j++) {
for (int i = 0; i < m; i++) {
to[j][i] = from[j][i];
}
}
return true;
}
Vector3D AddVector(Vector3D a, Vector3D b)
{
Vector3D v;
v.x = a.x + b.x;
v.y = a.y + b.y;
v.z = a.z + b.z;
return v;
}
2. pythonから呼び出す
作成したdllを.pyの直下に置きます。
関数の呼び出しは、以下の手順になります。
- ctypes.cdll.LoadLibraryでdllを読み込む
- argtypesで引数の型を設定する
- restypeで戻り値の型を設定する
参考URL:https://docs.python.org/3/library/ctypes.html
変数の場合
add_int
import ctypes
mydll=ctypes.cdll.LoadLibrary('ctypes_sample1')
addi=mydll.add_int
addi.argtypes=(ctypes.c_int,ctypes.c_int)
addi.restype=ctypes.c_int
addi(1,2)
3
add_doulbe
import ctypes
mydll=ctypes.cdll.LoadLibrary('ctypes_sample1')
addd=mydll.add_double
addd.restype=ctypes.c_double
addd.argtypes=(ctypes.c_double,ctypes.c_double)
addd(1.1,2.1)
3.2
ポインタの場合
引数に配列を渡す方法は、ctypesのarrayを使用するか、numpyを使用する方法があります。
一次元配列
ctypes array
ctypesで配列を作成する場合は*で配列の大きさを指定します。
c_int * 5
さらに配列のポインタがほしい場合は、ctypes.pointer()を使用します。
引数がポインタであることを示す場合は、ctypes.POINTER(ctypes.c_doulbe)のように大文字のPOINTERを使います。
import ctypes
mydll=ctypes.cdll.LoadLibrary('ctypes_sample1')
accumulate = mydll.accumulate
accumulate.restype=ctypes.c_double
accumulate.argtypes=(ctypes.POINTER(ctypes.c_double),ctypes.c_int)
_arr1=[1.0,2.0,3.0,4.0]
arr1 = (ctypes.c_double*len(_arr1))(*_arr1)
accumulate(cast(pointer(arr1),ctypes.POINTER(ctypes.c_double)),len(arr1))
10.0
import ctypes
mydll=ctypes.cdll.LoadLibrary('ctypes_sample1')
copy1d=mydll.copy
copy1d.argtypes=(ctypes.POINTER(ctypes.c_double),ctypes.POINTER(ctypes.c_double),ctypes.c_int)
copy1d.restype=ctypes.c_bool
arr1 = (ctypes.c_double*10)(*range(10))
arr2 = (ctypes.c_double*10)(*([0]*10))
copy1d(cast(pointer(arr1),POINTER(ctypes.c_double)),cast(pointer(arr2),POINTER(ctypes.c_double)),len(arr1))
for i in range(len(arr1)):
print('arr1[{0:d}]={1:.1f} arr2[{0:d}]={2:.1f}'.format(i,arr1[i],arr2[i]))
arr1[0]=0.0 arr2[0]=0.0
arr1[1]=1.0 arr2[1]=1.0
arr1[2]=2.0 arr2[2]=2.0
arr1[3]=3.0 arr2[3]=3.0
arr1[4]=4.0 arr2[4]=4.0
arr1[5]=5.0 arr2[5]=5.0
arr1[6]=6.0 arr2[6]=6.0
arr1[7]=7.0 arr2[7]=7.0
arr1[8]=8.0 arr2[8]=8.0
arr1[9]=9.0 arr2[9]=9.0
numpy array
numpyのデータを引数に渡す場合は、ctypes.data_as()を使って、ctypes objectのポインタを取得し引数に渡します。
参考URL: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.ctypes.html
import ctypes
import numpy as np
mydll=ctypes.cdll.LoadLibrary('ctypes_sample1')
accumulate = mydll.accumulate
accumulate.restype=ctypes.c_double
accumulate.argtypes=(ctypes.POINTER(ctypes.c_double),ctypes.c_int)
arr1=np.array([1.0,2.0,3.0,4.0],dtype=np.float)
accumulate(arr1.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),len(arr1))
10.0
import ctypes
import numpy as np
mydll=ctypes.cdll.LoadLibrary('ctypes_sample1')
copy1d=mydll.copy
copy1d.argtypes=(ctypes.POINTER(ctypes.c_double),ctypes.POINTER(ctypes.c_double),ctypes.c_int)
copy1d.restype=ctypes.c_bool
arr1=np.random.rand(10)
arr2=np.zeros_like(arr1)
copy1d(arr1.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),arr2.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),len(arr1))
for i in range(len(arr1)):
print('arr1[{0:d}]={1:f} arr2[{0:d}]={2:f}'.format(i,arr1[i],arr2[i]))
arr1[0]=0.062427 arr2[0]=0.062427
arr1[1]=0.957770 arr2[1]=0.957770
arr1[2]=0.450949 arr2[2]=0.450949
arr1[3]=0.609982 arr2[3]=0.609982
arr1[4]=0.351959 arr2[4]=0.351959
arr1[5]=0.300281 arr2[5]=0.300281
arr1[6]=0.148543 arr2[6]=0.148543
arr1[7]=0.094616 arr2[7]=0.094616
arr1[8]=0.379529 arr2[8]=0.379529
arr1[9]=0.810064 arr2[9]=0.810064
二次元配列
ctypes array
二次元配列を渡す場合は、ポインタの配列を渡します。
import ctypes
mydll=ctypes.cdll.LoadLibrary('ctypes_sample1')
copy2d = mydll.copy2d
copy2d.argtypes=((ctypes.POINTER(ctypes.c_double*5)*2),(ctypes.POINTER(ctypes.c_double*5)*2),ctypes.c_int,ctypes.c_int)
copy2d.restype=ctypes.c_bool
_arr1 = [ [1,2,3,4,5], [6,7,8,9,10] ]
arr1=((ctypes.c_double*5)*2)()
for j in range(2):
for i in range(5):
arr1[j][i] = _arr1[j][i]
arr2=((ctypes.c_double*5)*2)()
app1p=(ctypes.POINTER(ctypes.c_double*5)*2)()
for j in range(2):
app1p[j] = ctypes.pointer(arr1[j])
app2p=(ctypes.POINTER(ctypes.c_double*5)*2)()
for j in range(2):
app2p[j] = ctypes.pointer(arr2[j])
copy2d(app1p,app2p,5,2)
for j in range(2):
for i in range(5):
print('arr1[{0:d},{1:d}]={2:f} arr2[{0:d},{1:d}]={3:f}'.format(j,i,arr1[j][i],arr2[j][i]))
arr1[0,0]=1.000000 arr2[0,0]=1.000000
arr1[0,1]=2.000000 arr2[0,1]=2.000000
arr1[0,2]=3.000000 arr2[0,2]=3.000000
arr1[0,3]=4.000000 arr2[0,3]=4.000000
arr1[0,4]=5.000000 arr2[0,4]=5.000000
arr1[1,0]=6.000000 arr2[1,0]=6.000000
arr1[1,1]=7.000000 arr2[1,1]=7.000000
arr1[1,2]=8.000000 arr2[1,2]=8.000000
arr1[1,3]=9.000000 arr2[1,3]=9.000000
arr1[1,4]=10.000000 arr2[1,4]=10.000000
numpy array
numpyの場合も、ポインタの配列を渡します。
ポインタの配列の型は、numpy.ctypeslib.ndpointerを使って作ります。dtypeはポインタなのでuintpを指定します。
arr1._array_interface_['data'][0]'で先頭のポインタを取得します。
arr1.strides[0]で1行移動したときのバイト数を取得します。
import ctypes
import numpy as np
mydll=ctypes.cdll.LoadLibrary('ctypes_sample1')
copy2d = mydll.copy2d
_doublepp = np.ctypeslib.ndpointer(dtype=np.uintp, ndim=1, flags='C')
copy2d.argtypes=(_doublepp,_doublepp,ctypes.c_int,ctypes.c_int)
copy2d.restype=ctypes.c_bool
arr1=np.random.rand(4*7).reshape(4,7)
arr2=np.zeros_like(arr1)
arr1pp = (arr1.__array_interface__['data'][0]
+ np.arange(arr1.shape[0])*arr1.strides[0]).astype(np.uintp)
arr2pp = (arr2.__array_interface__['data'][0]
+ np.arange(arr2.shape[0])*arr2.strides[0]).astype(np.uintp)
copy2d(arr1pp,arr2pp,arr1.shape[1],arr2.shape[0])
for j in range(arr2.shape[0]):
for i in range(arr2.shape[1]):
print('arr1[{0:d},{1:d}]={2:f} arr2[{0:d},{1:d}]={3:f}'.format(j,i,arr1[j,i],arr2[j,i]))
arr1[0,0]=0.301684 arr2[0,0]=0.301684
arr1[0,1]=0.423735 arr2[0,1]=0.423735
arr1[0,2]=0.761370 arr2[0,2]=0.761370
arr1[0,3]=0.990014 arr2[0,3]=0.990014
arr1[0,4]=0.547852 arr2[0,4]=0.547852
arr1[0,5]=0.773549 arr2[0,5]=0.773549
arr1[0,6]=0.695525 arr2[0,6]=0.695525
arr1[1,0]=0.156089 arr2[1,0]=0.156089
arr1[1,1]=0.619667 arr2[1,1]=0.619667
arr1[1,2]=0.602623 arr2[1,2]=0.602623
arr1[1,3]=0.555263 arr2[1,3]=0.555263
arr1[1,4]=0.670706 arr2[1,4]=0.670706
arr1[1,5]=0.483012 arr2[1,5]=0.483012
arr1[1,6]=0.318976 arr2[1,6]=0.318976
arr1[2,0]=0.336153 arr2[2,0]=0.336153
arr1[2,1]=0.518378 arr2[2,1]=0.518378
arr1[2,2]=0.440815 arr2[2,2]=0.440815
arr1[2,3]=0.165265 arr2[2,3]=0.165265
arr1[2,4]=0.370611 arr2[2,4]=0.370611
arr1[2,5]=0.249356 arr2[2,5]=0.249356
arr1[2,6]=0.798799 arr2[2,6]=0.798799
arr1[3,0]=0.216579 arr2[3,0]=0.216579
arr1[3,1]=0.028188 arr2[3,1]=0.028188
arr1[3,2]=0.529525 arr2[3,2]=0.529525
arr1[3,3]=0.381811 arr2[3,3]=0.381811
arr1[3,4]=0.495189 arr2[3,4]=0.495189
arr1[3,5]=0.339180 arr2[3,5]=0.339180
arr1[3,6]=0.131087 arr2[3,6]=0.131087
構造体を渡す場合
構造体を渡す場合はctypes.Structureを使用します。_fields_にメンバー変数の型を指定します。
import ctypes
mydll=ctypes.cdll.LoadLibrary('ctypes_sample1')
class VECTOR3D(ctypes.Structure):
_fields_ = [("x", ctypes.c_int), ("y", ctypes.c_int), ("z", ctypes.c_int)]
vector_a = VECTOR3D(1, 2, 3)
vector_b = VECTOR3D(2, 3, 4)
AddVector = mydll.AddVector
AddVector.argtypes=(VECTOR3D,VECTOR3D)
AddVector.restype = VECTOR3D
vector_c = AddVector(vector_a, vector_b)
print('x={},y={},z={}'.format(vector_c.x,vector_c.y,vector_c.z))
x=3,y=5,z=7