LoginSignup
11
5

More than 3 years have passed since last update.

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

Posted at

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]で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

11
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
5