cffi

【Python】CFFIで外部DLLを使用する方法

【Python】CFFIで外部DLLを使用する方法
naoki-hn

Pythonで CFFI モジュールを用いて外部 DLL を使用する方法を解説します。

外部の DLL を使用する

Python は非常に豊富なライブラリが用意されているプログラミング言語であるため、多くのことは Python の標準ライブラリ、またはサードパーティ製ライブラリで解決策を見つけることができます。

しかし、ソフトウェア開発会社から提供されているソフトウェアや Windows などで提供されているライブラリなど、別途 C/C++ 等の他の言語で開発されている動的リンクライブラリ(DLL: Dynamic Link Libraries)をPythonプログラムから読み込み使用したい場合があります。

Python プログラムから外部の DLL を使用するためのサードパーティ製ライブラリとして CFFI モジュールがあります。この記事では、CFFIモジュールを用いて外部DLLを使用する方法を紹介します。

外部 DLL を使用する方法として、標準モジュールの ctypes を使用する方法があります。ctypes の使用方法は「ctypesで外部DLLを使用する方法」を参考にしてください。

CFFIとは

CFFI (Foreign Function Interface for Python) は、C 言語の規約に従って開発された DLL の関数を呼び出すための Python ライブラリです。

CFFI を用いることで、C 言語の関数、変数、データ構造に Python からアクセスすることが可能になります。CFFI は API モードと ABI モードの 2 つのモードで動作することができますが、DLL を使用する場合は主に ABI モードが利用されます。

ctypes との主な違い

外部 DLL を使用する方法としては、標準モジュールの ctypes を使用する方法もあります。ここでは、ctypes と CFFI の主な違いを紹介します。

API モードと ABI モード

CFFI では、API (Application Programming Interface) モードと ABI (Application Binary Interface) モードの両方を提供します。

  • API モード:C 言語のソースコードから Python 拡張モジュールをコンパイルして使用します。
  • ABI モード:コンパイルされたバイナリ(例えば、.dll.so.dylib等)に直接アクセスして使用します。

ctypes は、ABI モードのような方式のみを提供するため、コンパイルされたバイナリに直接アクセスすることのみ可能です。CFFI は、2 つのモードを使用できますが、この記事では外部の DLL を呼び出すための ABI モードの利用方法を中心に紹介します。

クラスに関する考慮

ctypes を使用する際には、リンクする DLL がどのような規約に従っているかによって、動的リンクライブラリローダーのクラス (ctypes.CDLLctypes.WinDLLctypes.OleDLLctypes.PyDLL) のうち、どれを使用するかを考量する必要がありました。

CFFI では、このような区別を気にすることなく DLL へアクセスすることができます。しかし、関数のプロトタイプ(関数定義や引数の型など)を正確に指定する必要があるなど CFFI 独自の注意点があることにも理解が必要です。

データ型の宣言

CFFI では、C 言語のデータ型を文字列で直接宣言します。これにより、C 言語のヘッダファイルから型宣言をコピーできるため、直感的でエラーが発生しにくくなります。

エラーハンドリング

CFFI は、Python の例外を使用してエラーを処理します。ctypes に比べてよりPythonic なアプローチをとります。

ctypes と CFFIの使い分けの基準

ctypes と CFFI のどちらを使用するかは、プロジェクトの要件や開発者の好みに依存します。しかし、一般的に考えることができる使い分けの基準について紹介します。

項目概要
プロジェクト規模と複雑さctypes は、学習コストが低く小規模で簡単な開発には最適です。一方で、CFFI はより複雑なインタフェースやパフォーマンスが重要な大規模開発に適しています。
型安全性とエラー処理CFFI は型チェックが行われるので型安全性を保ちやすくなります。また、例外でのエラーハンドリングで適切な例外処理が可能です。
依存関係と配布ctypes は標準ライブラリのため、外部依存を避けたい場合に適しています。一方で、CFFI はサードパーティ製ライブラリのため別途インストールが必要です。
開発速度と柔軟性小規模な開発や試作には ctypes が便利ですが、CFFI の方がより柔軟な開発や深い統合を求める場合に強力です。

上記のように簡単なプロジェクトの開発やスピードが求められる試作では ctypes が適しているかもしれませんが、大規模開発や複雑な C 言語ライブラリとの統合は CFFI の方がより適していると考えることができます。

CFFI を使用した外部 DLL の使用方法

ctypes の紹介記事である「ctypesで外部DLLを使用する方法」でも紹介した DLL 呼び出し例を使用して CFFI を使用した外部 DLL の使用方法を紹介します。

user32.dll を用いた MessageBox の表示

ここでは ctypes.WinDLL を用いて Windows のライブラリである user32.dll を使用して MessageBox を表示する例を紹介します。

user32.dll は Windows に標準で含まれている Windows API の一部で、Windows のユーザーインターフェースに関する関数を提供しています。

user32.dll を用いて MessageBox を表示するには以下のようにします。

from cffi import FFI

# FFIインスタンスの生成
ffi = FFI()

# 関数の宣言
ffi.cdef(
    """
    // メッセージボックス
    int MessageBoxW(void *hwnd, const wchar_t *text, const wchar_t *caption, unsigned int type);
"""
)

# DLLのロード
user32 = ffi.dlopen("user32.dll")

try:
    # MessageBoxの表示
    result = user32.MessageBoxW(ffi.NULL, "Hello, World!", "From Python", 0)
    print(f"MessageBox return: {result}")

except TypeError as ex:
    print(f"An error occurred: {ex}")

【実行結果】

CFFI 使用例

OK を押すと標準出力には以下のように表示されます。

MessageBox return: 1

以降で部分ごとにポイントを解説します。

【インポートとFFIインスタンスの準備】

from cffi import FFI

# FFIインスタンスの生成
ffi = FFI()

CFFI を使用するには、まずインポートする必要があります。cffi モジュールから FFI クラスをインポートして、インスタンスを生成します。

【関数のプロトタイプ定義】

# 関数の宣言
ffi.cdef(
    """
    // メッセージボックス
    int MessageBoxW(void *hwnd, const wchar_t *text, const wchar_t *caption, unsigned int type);
"""
)

CFFI を使用するうえで非常に重要になってくるのが、この関数のプロトタイプ定義です。プロトタイプ定義と言っているのは関数定義や引数の型指定のことをです。

関数の宣言では、FFI クラスの cdef メソッドを使用します。cdef に関数の宣言となる文字列を指定します。これにより Python の型と適切に変換を行うことができます。ctypes を使った外部 DLL 利用の際でも同様の型宣言 (argtypesrestype) がありますが CFFI では、文字列を使用して宣言ができることが特徴的です。

cdef メソッドに指定する文字列内では、C 言語の文法の通りコメントアウト (//) 等も有効です。こういった宣言は、C 言語のヘッダファイルから型宣言を簡単にコピーできるため、不必要なミスを減らすことができる可能性があります。また、読み込み先のDLL が複数であっても、1 つの文字列でまとめて宣言することが可能です。

【DLLのロード】

# DLLのロード
user32 = ffi.dlopen("user32.dll")

DLL のロードでは FFL クラスの dlopen メソッドを使用して、DLL のファイル名を指定します。今回は user32.dllkernel32.dll を読み込んでいます。

【実行とエラーハンドリング】

try:
    # MessageBoxの表示
    result = user32.MessageBoxW(ffi.NULL, "Hello, World!", "From Python", 0)
    print(f"MessageBox return: {result}")
except TypeError as ex:
    print(f"An error occurred: {ex}")

MessageBox の表示では、ロードした DLL に対して関数を呼び出して使用します。

CFFI では、例外でエラーチェックを行うことができますので try ... except ...で例外処理をしています。例えば、以下のように HWND(ウィンドウハンドルを示す識別子)に該当する第 1 引数に -1 を指定して実行すると例外が発生します。

result = user32.MessageBoxW(-1, "Hello, World!", "From Python", 0)
【実行結果】
An error occurred: initializer for ctype 'void *' must be a cdata pointer, not int

結果を見ると引数の型に対するチェックをして例外を出していることが分かります。

なお、ctypes の紹介記事である「ctypesで外部DLLを使用する方法」でも、同じように -1 を設定するような例を紹介しましたが、ctypes の例では戻り値でエラーをチェックし、GetLastError を使ってエラー識別しました。

もちろん CFFI でも、戻り値をベースに GetLastError を使用することもできますし、戻り値でのエラー処理が問題があるということではありません。ここでお伝えしたいのは、ctypes と CFFI で例外に対する処理が少し異なるという点です。

上記で紹介したように CFFI を使用することで簡単に外部の Windows API を Python から使用することができました。他にも様々な DLL を扱う際に CFFI は使用できます。使用の際には関数のプロトタイプ定義が重要になってきますので注意して使用するようにしましょう。

まとめ

Python で CFFI モジュールを用いて外部 DLL を使用する方法を解説しました。

この記事では CFFI の概要を紹介するとともに Windows API の DLL (user32.dll)を使用してメッセージボックスを表示する簡単な例を紹介しました。また、同様に外部 DLL を使用することができる Python 標準ライブラリの ctypes との違いについても触れながら紹介をしています。

CFFI は、C 言語の関数宣言をそのままうまく使うことができ、非常に使いやすいライブラリです。小規模な開発や簡単な試作では ctypes の方が簡単に使用できるかもしれませんが、大規模開発や複雑な C 言語ライブラリとの統合は ctypes よりも CFFI の方がより適している可能性があります。

CFFI の使用方法を理解していただき、必要に応じて調べながら使用してみていただければと思います。

CFFI の公式ドキュメントはこちらを参照してください。

あわせて読みたい
【Python Tech】プログラミングガイド
【Python Tech】プログラミングガイド
ABOUT ME
ホッシー
ホッシー
システムエンジニア
はじめまして。当サイトをご覧いただきありがとうございます。 私は製造業のメーカーで、DX推進や業務システムの設計・開発・導入を担当しているシステムエンジニアです。これまでに転職も経験しており、以前は大手電機メーカーでシステム開発に携わっていました。

プログラミング言語はこれまでC、C++、JAVA等を扱ってきましたが、最近では特に機械学習等の分析でも注目されているPythonについてとても興味をもって取り組んでいます。これまでの経験をもとに、Pythonに興味を持つ方のお役に立てるような情報を発信していきたいと思います。どうぞよろしくお願いいたします。

※キャラクターデザイン:ゼイルン様
記事URLをコピーしました