こんにちは、Yu-saです。
最近はPythonで記載することが多くなってきましたが、高速化する為に一部のコードをC++に変換することがありました。
Python側からは「ctypes」を使用してDLLを呼び出すことになり、その際の経験を基に「ctypes」の使い方を紹介させて頂きます。
「ctypes」とは?
「ctypes」は標準で使用できるライブラリです。
Cと互換性のあるデータ型を提供しており、動的リンク/共有ライブラリ内の関数の呼び出しが可能です。
基本のデータ型としては以下の表の型がサポートされています。
ctypesの型 | Cの型 | Pythonの型 |
---|---|---|
c_bool | _Bool | bool (1) |
c_char | char | 1文字のバイト列オブジェクト |
c_wchar | wchar_t | 1文字の文字列 |
c_byte | char | int |
c_ubyte | unsigned char | int |
c_short | short | int |
c_ushort | unsigned short | int |
c_int | int | int |
c_uint | unsigned int | int |
c_long | long | int |
c_ulong | unsigned long | int |
c_longlong | __int64 または long long | int |
c_ulonglong | unsigned __int64 または unsigned long long | int |
c_size_t | size_t | int |
c_ssize_t | ssize_t または Py_ssize_t | int |
c_float | float | 浮動小数点数 |
c_double | double | 浮動小数点数 |
c_longdouble | long double | 浮動小数点数 |
c_char_p | char* (NUL 終端) | バイト列オブジェクトまたは None |
c_wchar_p | wchar_t* (NUL 終端) | 文字列または None |
c_void_p | void* | 整数または None |
また、構造体やコールバック関数もサポートされています。
※引用元※
ctypes(Python公式サイト)
Pythonからの使用例
引数/戻り値
Python側ではctypesで提供されている型を使用することでC++側とデータのやり取りが可能です。
#define DLL_EXPORT extern "C" __declspec(dllexport)
DLL_EXPORT int __stdcall Func_001(int value1 , int value2)
{
int sum = value1 + value2;
return sum;
}
import ctypes
def test_001():
# DLLを指定
dll = ctypes.WinDLL(r"DllTest.dll")
# 関数を指定(DLLで公開している名称を使用する)
func = dll.Func_001
# 戻り値の型を指定
func.restype = ctypes.c_int32
# 引数の型を指定
func.argtypes = (ctypes.c_int32 , ctypes.c_int32)
# 関数を実行
num = func( 3 ,2 )
print(num)
5
文字列配列
文字列は「ctypes.c_char_p」を使用します。
#define DLL_EXPORT extern "C" __declspec(dllexport)
DLL_EXPORT void __stdcall Func_002(char* msg)
{
printf("%s\r\n", msg);
}
import ctypes
def test_002():
# DLLを指定
dll = ctypes.WinDLL(r"DllTest.dll")
# 関数を指定(DLLで公開している名称を使用する)
func = dll.Func_002
# 戻り値の型を指定
func.restype = None
# 引数の型を指定
func.argtypes = (ctypes.c_char_p , )
# 引数の準備
msg_base = "Hello World"
msg_tmp = ctypes.create_string_buffer(msg_base.encode('UTF-8')) # 新しく文字列を作成
msg_data = ctypes.cast(msg_tmp, ctypes.c_char_p) # ポインタにキャスト
# 関数を実行
func( msg_data )
Hello World
配列
文字列以外の配列は「ctypes.POINTER」と「ctypes.pointer」を使用します。この時に使用できるのもctypesで提供されている型となります。
「ctypes.POINTER」で型を取得し、「ctypes.pointer」ではアドレスを取得します。
また、動的配列を使用する場合はC++側では要素数を取得できないので、ポインタとは別に要素数を渡す必要があります。
#define DLL_EXPORT extern "C" __declspec(dllexport)
DLL_EXPORT void __stdcall Func_003(int* values, int length)
{
for (int idx = 0; idx < length; idx++)
{
printf("index[%d] = %d\r\n", idx , values[idx]);
}
}
import ctypes
def test_003():
# DLLを指定
dll = ctypes.WinDLL(r"DllTest.dll")
# 関数を指定(DLLで公開している名称を使用する)
func = dll.Func_003
# 戻り値の型を指定
func.restype = None
# 引数の型を指定
func.argtypes = ( ctypes.POINTER(ctypes.c_int32) , ctypes.c_int32 )
# 引数の準備
num_base = [ 11 , 22 , 33 , 44 , 55 ]
num_tmp = (ctypes.c_int32 * len(num_base))(*num_base) # c_int32の配列に変換
# 変換する際には「型 * 要素数」でキャストする
num_data = ctypes.cast(ctypes.pointer(num_tmp) , ctypes.POINTER(ctypes.c_int32))
# ポインタにキャスト
# 関数を実行
func(num_data , len(num_base)) # 第1引数で配列のポインタ、第2引数で配列の要素数を渡す
index[0] = 11
index[1] = 22
index[2] = 33
index[3] = 44
index[4] = 55
構造体
「ctypes.Structure」を継承したクラスを定義することで構造体を使用できます。
また、構造体のポインタを使用したい場合は「ctypes.Structure」を継承したクラスであれば、「ctypes.POINTER(Structure_Test_004)」のようにすることでポインタとして使用できます。
#define DLL_EXPORT extern "C" __declspec(dllexport)
typedef struct
{
char* value_1;
int value_2;
int* value_list;
size_t value_list_length;
} ST_TEST_004;
DLL_EXPORT void __stdcall Func_004(ST_TEST_004 data)
{
printf("value_1 = [%s]\r\n", data.value_1);
printf("value_2 = [%d]\r\n", data.value_2);
for (int idx = 0; idx < data.value_list_length; idx++)
{
printf("value_list[%d] = %d\r\n", idx, data.value_list[idx]);
}
}
import ctypes
class Structure_Test_004(ctypes.Structure):
# 構造体パッキングを指定。#pragma pack (push, 1)と同等
_pack_ = 1
# 構造体のメンバを指定。
_fields_ = [
('value_1',ctypes.c_char_p), # Cでの「char*」
('value_2',ctypes.c_int32), # Cでの「int」
('value_list',ctypes.POINTER(ctypes.c_int32)), # Cでの「int*」
('value_list_length',ctypes.c_size_t), # 配列の要素数
]
def test_004():
# DLLを指定
dll = ctypes.WinDLL(r"DllTest.dll")
# 関数を指定(DLLで公開している名称を使用する)
func = dll.Func_004
# 戻り値の型を指定
func.restype = None
# 引数の型を指定
func.argtypes = ( Structure_Test_004 , )
# 文字列を準備
msg_base = "Hello World"
msg_tmp = ctypes.create_string_buffer(msg_base.encode('UTF-8')) # 新しく文字列を作成
msg_data = ctypes.cast(msg_tmp, ctypes.c_char_p) # キャスト
# 配列を準備
num_base = [ 11 , 22 , 33 , 44 , 55 ]
num_tmp = (ctypes.c_int32 * len(num_base))(*num_base) # c_int32の配列に変換
# 変換する際には「型 * 要素数」でキャストする
num_data = ctypes.cast(ctypes.pointer(num_tmp) , ctypes.POINTER(ctypes.c_int32))
# ポインタにキャスト
# 構造体を準備
st_test_004 = Structure_Test_004()
st_test_004.value_1 = msg_data
st_test_004.value_2 = 12345678
st_test_004.value_list = num_data
st_test_004.value_list_length = len(num_base)
# 関数を実行
func(st_test_004)
value_1 = [Hello World]
value_2 = [12345678]
value_list[0] = 11
value_list[1] = 22
value_list[2] = 33
value_list[3] = 44
value_list[4] = 55
コールバック
関数ポインタを使用するには「ctypes.CFUNCTYPE」もしくは「ctypes.WINFUNCTYPE」を使用します。
なお、コールバック関数に指定する関数はctypesで提供されている型を使用している必要があります。
#define DLL_EXPORT extern "C" __declspec(dllexport)
typedef int (*CALLBACK_POINTER)(int*, size_t);
DLL_EXPORT void __stdcall Func_005(CALLBACK_POINTER func)
{
// データの準備
const size_t length = 10;
int data[length] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// コールバック関数の実行
int sum = func(data, length);
// 戻り値の表示
printf("sum=%d\r\n", sum);
}
import ctypes
# C++側から呼び出されるコールバック関数
def test_005_callback( data:ctypes.POINTER(ctypes.c_int32) , length:ctypes.c_size_t)->ctypes.c_int32:
sum = 0
for idx in range(length):
sum += data[idx]
return sum
def test_005():
# コールバック関数の型を定義
CallbackProc = ctypes.WINFUNCTYPE(
ctypes.c_int32 , # コールバック関数の戻り値の型
ctypes.POINTER(ctypes.c_int32) , # コールバック関数の第1引数の型
ctypes.c_size_t # コールバック関数の第2引数の型
)
# 関数ポインタへの変換
callback = CallbackProc(test_005_callback)
# DLLを指定
dll = ctypes.WinDLL(r"DllTest.dll")
# 関数を指定(DLLで公開している名称を使用する)
func = dll.Func_005
# 戻り値の型を指定
func.restype = None
# 引数の型を指定
func.argtypes = ( CallbackProc , )
# 関数を実行
func(callback)
sum=55
おわり
いかがでしたでしょうか。
ここで紹介させて頂いたのは基本的な使い方となり、全て紹介できておりません。
機会があれば、続きを紹介してみたいと思います。
関連記事
-
第1回 Visual C++で作成したDLL内のクラスをC#で利用する方法
こんにちは、ILCです。 Visual C++ (以下 VC++)で作成されたDynamic...
公開日:2024.01.19 更新日:2024.01.19
tag : Windows
-
-
-
第1回 ラズパイを使用したBLE通信 ~ ディスプレイ、キーボード、マウスを接続しないで設定 前編 ~
こんにちは、GTです。よろしくお願いします。 最近業務でラズパイのBluetooth機能を使...
公開日:2021.12.24 更新日:2021.12.24
tag : Bluetooth Raspberry Pi
-
【新機能探訪】Android 13から導入された『アプリごとの言語設定』
こんにちは、KNSKです。よろしくお願いします。 今回は Android13の新機能である『...
公開日:2022.12.09 更新日:2022.12.09
tag : スマートデバイス
-
第3回 ラズパイを使用したBLE通信 ~ A/D変換・D/A変換を用いた入出力編 ~
こんにちは、GTです。よろしくお願いします。 第3回の今回は、ラズパイの入出力についてご紹介...
公開日:2023.02.24 更新日:2023.02.24
tag : Bluetooth BLE Raspberry Pi