1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【備忘録】PythonからDLL呼び出し + 二次元配列を渡す

Posted at

1. 背景

PythonからCで作られたライブラリにndarray(2次元)をpointer-to-pointerで渡す必要が出てきた.例えばこんな感じ.
(単ポインタで渡せよという意見はごもっともだが,使いたいライブラリがご丁寧にpointer-to-pointerで作られているのでしゃあなし.)

動作検証もかねて記事を作ることにした.

# python
from ctypes import *
lib = LoadLibrary()
lib.func.argtypes = [POINTER(POINTER(c_float)), c_int, c_int]
# how to pass?
lib.func(???, size_y, size_x)
// C, exported as dll.
void func(float **arrays, y, x){ // 省略 }

2. コントリビューション

本記事では,

  1. CMakeでDLLの作り方(Linux, Macのみ)
  2. PythonでのDLLの呼び方
  3. ndarray to pointer pointer
    にわけて解説する.なお,2次元配列を出力するprint2fを作成およびコールするものとする.

本記事は中上級者向けの記事です.
おそらく生ポを扱うことになるので試す場合は自己責任で.
あと結論だけ欲しい人は3.4だけ見れば良い.

3. 本編

3.1 DLL作成

まずはじめにCMakeであるが,SHAREDを忘れないように.

add_library(libPtP SHARED library.cpp library.h)

次にCコードlibrary.c.ダブルポインタでも配列として扱うことにする.
ほんとはポインタを進める書き方がいいのだろうけどここではこうしておく.

# include <stdio.h>
# include "library.h"

void print2f(float **arrays, int y, int x) {
    for(int i=0; i<y; i++) {
        for(int j=0; j<x; j++) {
            printf("%f ", arrays[i][j]);
        }
        printf("\n");
    }
}

ヘッダーlibrary.h. extern Cを忘れると詰む.

extern "C" {
    void print2f(float **, int, int);
}

あとはビルドしてやれば良い.ビルドの仕方については解説しない.
CLionとか使えばビルドボタン一撃である.

3.2 PythonでDLLを呼ぶ

ctypesというDLLを扱うためのライブラリがあるのでこいつを使ってライブラリのロードと関数の呼び出しを行う.
が,ここでPOINTER(POINTER(c_float))をどうするか問題が出てくるので,3.3でどうするか説明する.

import numpy as np
from ctypes import *
lib = cdll.LoadLibrary(path)

arrays = np.zeros((2, 2))
lib.print2f.argtypes = [POINTER(POINTER(c_float)), c_int, c_int]
lib.print2f(arrays, 2, 2) # error

3.3 ndarray to pointer pointer

シングルポインタの場合は特に問題がない.なぜならnp.ctypeslib.as_ctypeがArrayをPointerにしてくれるからだ.
(print1fは一次元配列を出力する関数で,ライブラリに定義されているものとする)

ちなみにここでprint(a)すると,c_float_Array_2と出るので,c_float_Array_[n]POINTER(c_float)には互換性があるのだろう.たぶん.

# print1f
import numpy as np
from ctypes import *
a = np.zeros((2, )).astype(np.float32)
a = np.ctypeslib.as_ctypes(a)
lib.print1f.argtypes = [POINTER(c_float), c_int]
lib.print1f(a, 2)

問題はpointer-to-pointerの場合だ.一捻り必要になる.
以下手順をコードで解説する.


# まず出力する配列を作成
arrays = np.array([[1, 2], [3, 4]]).astype(np.float32)
# >> arrays = [[1, 2]
#              [3, 4]]

# 次にポインタのリストにしてやる
ptrs = [array.ctypes.data_as(POINTER(c_float)) for array in arrays]
# >> ptrs = [ &arrays[0], &arrays[1] ]

# ポインタリストをctypesに変換する
pp = (POINTER(c_float) * y)(*ptrs)
# >> <__main__.LP_c_float_Array_2 object>

# 出力
lib.print2f.argtypes = [POINTER(POINTER(c_float)), c_int, c_int]
lib.print2f(pp, 2, 2)

3.4 まとめ

コードを乗せておく.

import numpy as np
from ctypes import *

arrays = np.array([[1, 2], [3, 4]]).astype(np.float32)
ptrs = [array.ctypes.data_as(POINTER(c_float)) for array in arrays]
pp = (POINTER(c_float) * y)(*ptrs)
lib.print2f.argtypes = [POINTER(POINTER(c_float)), c_int, c_int]
lib.print2f(pp, 2, 2)

多分もうちょっといい方法があると思う.

4. 環境

C++11
Python 3.5
clang-1000.10.44.4
Mac OS High Sierra

1
1
1

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?