[Python][FFI] ctypes で文字列を C++ メソッドに渡す

  • 2022年8月11日
  • 2022年9月17日
  • Python
  • 31View
  • 0件

はじめに

Python 標準ライブラリ ctypes を利用して、C++ メソッドに文字列を渡します。

動作環境は以下です。

  • Windows 11
  • WSL Ubuntu (20.04)
  • g++ 9.4.0
  • Python 3.8.10

サンプルコード

C++ ライブラリ

C++ 側の print メソッドでは、文字列を受け取りこれを標準出力します。

#include <iostream>

extern "C" void print(const char *c_str)
{
    std::cout << c_str << std::endl;
}
$ g++ --shared -fPIC myprint.cc -o libmyprint.so

Python スクリプト

Python 側では、Hello World! の文字列を生成し、これを libmyprint.soprint メソッドに渡します。

import ctypes
import pathlib

dirname = pathlib.Path(__file__).parent.resolve()
libpath = dirname.joinpath("libmyprint.so")
lib = ctypes.cdll.LoadLibrary(libpath)

lib.print.argtypes = [ctypes.c_char_p]
lib.print("Hello World!".encode())
lib.print("こんにちは、世界!".encode())
$ python3 main.py
Hello World!
こんにちは、世界!

ポイント解説

argtypes の指定

print メソッドを実行する前に、lib.printargtypes を指示してあります。
今回のケースに限れば、指定せずとも動作上問題ありませんでした。
型によっては C++ 側のメソッドに引数が正しく渡されなくなってしまいますので、argtypes は常に明示するのが良いです。

バイト文字列への変換(encode)

lib.print に文字列を渡す際に、.encode() メソッドでバイト列への変換を行っています。
C++ の側のメソッドでは引数に const char *(バイト列)を期待しているために必要な変換であります。

バイト列に変換しないと

バイト列に変換しない場合は、型不正のエラーが発生してしまいます。

import ctypes
import pathlib

dirname = pathlib.Path(__file__).parent.resolve()
libpath = dirname.joinpath("libmyprint.so")
lib = ctypes.cdll.LoadLibrary(libpath)

lib.print.argtypes = [ctypes.c_char_p]
lib.print("Hello World!")
lib.print("こんにちは、世界!")
$ python3 main.py
Traceback (most recent call last):
  File "main.py", line 9, in <module>
    lib.print("Hello World!")
ctypes.ArgumentError: argument 1: <class 'TypeError'>: wrong type

さらに argtypes も指定しないと

さらに argtypes の指定をサボっていると、正常終了はするものの文字列が正しく渡されない結果が出力されます。

import ctypes
import pathlib

dirname = pathlib.Path(__file__).parent.resolve()
libpath = dirname.joinpath("libmyprint.so")
lib = ctypes.cdll.LoadLibrary(libpath)

# lib.print.argtypes = [ctypes.c_char_p]
lib.print("Hello World!")
lib.print("こんにちは、世界!")
$ python3 main.py
H
S0

バイト列を直接記述

なお、ASCII 文字列のみであれば、b"..." の形式でバイト列を直接定義することも可能です。

import ctypes
import pathlib

dirname = pathlib.Path(__file__).parent.resolve()
libpath = dirname.joinpath("libmyprint.so")
lib = ctypes.cdll.LoadLibrary(libpath)

lib.print.argtypes = [ctypes.c_char_p]
lib.print(b"Hello World!")
$ python3 main.py 
Hello World!

wchar_t を利用する場合

C++ の文字列は、wchar_t によってワイド型で表現することも可能です。
サンプルとして、引数の型を const char * から const wchar_t * に変更します。
coutwcout に変更します。

#include <iostream>

extern "C" void print(const wchar_t *c_str)
{
    std::wcout << c_str << std::endl;
}
$ g++ --shared -fPIC myprint.cc -o libmyprint.so

Python 側では、argtypesc_wchar_p を指定するように変更します。
また、文字列はバイト列にエンコードせずそのまま渡すようにします。

import ctypes
import pathlib

dirname = pathlib.Path(__file__).parent.resolve()
libpath = dirname.joinpath("libmyprint.so")
lib = ctypes.cdll.LoadLibrary(libpath)

lib.print.argtypes = [ctypes.c_wchar_p]
lib.print("Hello World!")
lib.print("こんにちは、世界!")
$ python3 main.py
Hello World!
こんにちは、世界!

無事実行できました。