[Python]pythonからcの関数を呼び出す方法(ctypes編)

# pythonからcの関数を呼び出す方法(ctypes編)

pythonからcの関数を呼び出す方法はいくつかありますが、今回はctypesを使った方法を説明します。
実行環境はwindowsを使用しました。

全体の手順は、以下のようになります。
1. Visual Studio でdllを作成する。
2. pythonから呼び出す

# 1. Dllの作成
DLLはVisual Studioを使って作成します。

1. Visual Studioを起動し、「Create a new project」を選択します。
2. Dynamic-Link Library (DLL)を選択
3. Project nameを入力(ctypes_sample1)
4. Createボタンを押す
5. Solution ExplorerのSource Filesで右クリックしAdd->New ItemからC++ File (.cpp)を選び、ファイル名を入力する。(ctypes_sample1.cpp)
6. Solution ExplorerのHeader Filesで右クリックしAdd->New ItemからHeader File (.h)を選び、ファイル名を入力する。(ctypes_sample1.h)
7. 関数を記述する
8. Build->Build SolutionでDLLを作成する。

今回はVisual Studio 2019を使用しました。

参考URL
https://docs.microsoft.com/en-us/cpp/build/walkthrough-creating-and-using-a-dynamic-link-library-cpp?view=vs-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の直下に置きます。

関数の呼び出しは、以下の手順になります。

1. ctypes.cdll.LoadLibraryでdllを読み込む
2. argtypesで引数の型を設定する
3. 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]で１行移動したときのバイト数を取得します。

```
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