ctypes

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

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

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

外部の DLL を使用する

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

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

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

同様に外部 DLL を使用するためのサードパーティ製ライブラリとして CFFI があります。CFFI の使用方法については「CFFIで外部DLLを使用する方法」を参考にしてください。

ctypes とは

Python の ctypes モジュールは、Python プログラムから DLL を呼び出すための機能を提供してくれる Python 標準のライブラリです。標準ライブラリのため、サードパーティのライブラリに依存しないでいつでも使用可能です。

ctypes を用いることで、C 言語の呼び出し規約に従って公開されている関数を持つ DLL にアクセスすることができます。なお、DLL は Windows では .dll ファイル、Linux や Unix では .so ファイル、macOS では .dylib といったファイルといったものがあり、ctypes はこれらに対応しているためクロスプラットフォームで使用することが可能です。

これらのライブラリは外部からアクセス可能な形で関数をエクスポートしており、C 互換のインターフェース(データ型など)を使用している必要があります。DLL が何の言語で書かれているかは問いません。

ctypes は非常に強力なモジュールですが、C/C++ 言語の詳細な知識がなくても使用ができます。ただし、使用するには C 言語の知識がある程度必要になってきます。特に、C 言語のライブラリを使用する際のメモリ管理や型の安全性には注意が必要です。Python から言語を学んだ人はあまりメモリや型についてそれほど意識しなくても開発ができてしまうため注意が必要です。

ctypes が対応する動的ライブラリローダーと規約

ctypes は、いくつかの代表的な動的ライブラリローダーのクラスや関数、データ型のクラスを提供しています。

動的ライブラリローダーのクラス

DLL についてはどのような呼び出し規約に従うかによって 4 つの動的ライブラリローダークラスが用意されています。どのクラスを使用するかは呼び出す DLL がどのような規約に従って用意されたライブラリかどうかによります。

クラス概要
ctypes.CDLL標準の C 言語呼び出し規約を使用して動的ライブラリをロードします。Windows、Linux、macOS の共有ライブラリや DLL に使用します。
ctypes.WinDLLWindows 固有の stdcall 呼び出し規約を使用して DLL をロードします。主に Windows API 関数で使用します。
ctypes.OleDLLOLE 呼び出し規約を使用して DLL をロードし、Windows の COM コンポーネントとのやり取りに使用されます。エラーチェックが自動的に行われます。
ctypes.PyDLLGIL (Global Interpreter Lock) を解放せずにライブラリをロードすることで、Python の API 関数や Python 拡張モジュールを扱う際 Python インタプリタの安全性を保ちながら操作できます。
一方で、外部関数実行中に他の Python スレッドが停止するため、並行性に悪影響を及ぼす可能性があります。

代表的な関数

C 言語のポインタ操作を Python で適切に行うために、以下のような代表的なポインタを扱う関数が用意されています。

関数概要
ctypes.byref引数としてオブジェクトを取り、そのオブジェクトの参照ポインタを生成します。C 言語のポインタのようにオブジェクトへ参照を渡すための関数です。
ctypes.POINTER指定した型のポインタ型を生成する関数です。これは C 言語のポインタ型に相当して、より明示的なポインタ操作が必要な場合に使用します。

基本データ型のクラス

ctypes では、ctypes.c_intctypes.c_floatctypes.c_char などのクラスが定義されています。これらは C 言語の基本的なデータ型(intfloatchar等)に対応するクラスです。基本データ型クラスは多岐にわたりますので、公式ドキュメントの基本データ型のセクションを参照してください。

構造化データ型

ctypes では、ctypes.Structurectypes.Union といった構造化データ型のクラスも定義されています。これらは C 言語での構造体や共用体を Python で表現するためのクラスです。構造化データ型のクラスについては公式ドキュメントの構造化データ型のセクションを参照してください。

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

ctypes を使用した外部 DLL の基本的な使用例を紹介します。上記で紹介したように ctypes の動的ライブラリローダーは 4 つあり、実際には呼び出す DLL によって、どれを使うかなどについて十分に検討する必要があります。

例: user32.dllkernel32.dll を用いた MessageBox の表示

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

user32.dll は、Windows に標準で含まれている Windows API の一部で、Windows のユーザーインターフェースに関する関数を提供しています。kernel32.dll は、WindowsOS の核心となる DLL の一つで、低レベルのメモリ管理、プロセスやスレッドの管理、ファイル操作、入出力等のシステムの基本的な機能を提供しています。

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

import ctypes

# user32.dllを読み込む
user32 = ctypes.WinDLL("user32.dll", use_last_error=True)
# kernel32.dllを読み込む
kernel32 = ctypes.WinDLL("kernel32.dll", use_last_error=True)


# MessageBoxを定義
MessageBox = user32.MessageBoxW
MessageBox.argtypes = [
    ctypes.c_void_p,  # HWND (ウィンドウハンドルを示す識別子)
    ctypes.c_wchar_p,  # LPCTSTR (メッセージのテキスト)
    ctypes.c_wchar_p,  # LPCTSTR (メッセージボックスのタイトル)
    ctypes.c_uint,  # UINT (メッセージボックスのスタイル)
]
MessageBox.restype = ctypes.c_int  # INT (返却値)

# GetLastErrorを定義
GetLastError = kernel32.GetLastError
GetLastError.argtypes = []
GetLastError.restype = ctypes.c_ulong

# メッセージボックスを表示
result = MessageBox(0, "Hello, World!", "From Python", 0)

# 戻り値をチェックしてエラー処理
if result == 0:
    error_code = GetLastError()
    print(f"MessageBox failed with error code: {error_code}")
else:
    print(f"MessageBox return: {result}")

【実行結果】

user32.dllのMessageBox

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

MessageBox return: 1

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

【インポートと必要なDLLの読み込み】

import ctypes

# user32.dllを読み込む
user32 = ctypes.WinDLL("user32.dll", use_last_error=True)
# kernel32.dllを読み込む
kernel32 = ctypes.WinDLL("kernel32.dll", use_last_error=True)

ctypes を使用するには、まずインポートする必要があります。その後、対象の DLL を読み込みます。今回は Windows の DLL であるため ctypes.WinDLL を使います。use_last_error はエラーを取得するために True を設定しておきます。

【型の定義】

# MessageBoxを定義
MessageBox = user32.MessageBoxW
MessageBox.argtypes = [
    ctypes.c_void_p,  # HWND (ウィンドウハンドルを示す識別子)
    ctypes.c_wchar_p,  # LPCTSTR (メッセージのテキスト)
    ctypes.c_wchar_p,  # LPCTSTR (メッセージボックスのタイトル)
    ctypes.c_uint,  # UINT (メッセージボックスのスタイル)
]
MessageBox.restype = ctypes.c_int  # INT (返却値)

# GetLastErrorを定義
GetLastError = kernel32.GetLastError
GetLastError.argtypes = []
GetLastError.restype = ctypes.c_ulong

今回は user32.MessageBoxWkernel32.GetLastError を使用します。これらを使用するには、関数の引数の型 (argtypes) と戻り値の型 (restype) を定義する必要があります。これにより、Python と DLL 間でのデータ型の変換を適切に行えます。

特に、DLL 側の関数が受け取ることができる引数の型や戻り値の型が Python の型と直接対応しない場合に、この型の定義は非常に重要なポイントとなります。

今回の例でいうと Windows API の MessageBox は引数として「HWND (ウィンドウハンドルを示す識別子)」「LPCTSTR (メッセージのテキスト)」「LPCTSTR (メッセージボックスのタイトル)」「UINT (メッセージボックスのスタイル)」の 4 つを取ります。

これらに対応する型を ctypes.xxx という形式で指定します。どの型を指定するのが適切かは、DLL 側の関数の定義と ctypes 公式ドキュメントの基本データ型のセクションを参照して、適切に検討する必要があります。

MessageBoxW の「W」はワイド文字のことを表します。MessageBox 関数を呼び出す場合に、MessageBoxW を使うと Unicode 文字列を正しく扱うことができます。

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

# メッセージボックスを表示
result = MessageBox(0, "Hello, World!", "From Python", 0)

# 戻り値をチェックしてエラー処理
if result == 0:
    error_code = GetLastError()
    print(f"MessageBox faild with error code: {error_code}")
else:
    print(f"MessageBox return: {result}")

実行する場合には、上記のように引数を渡して実行します。実行すると以下のような画面が表示できます。

user32.dllのMessageBox

戻り値に対してエラーチェックを行い GetLastError からエラー情報を取得することも可能です。今回のプログラムであえてエラーを発生させようとする場合、HWND (ウィンドウハンドルを示す識別子)に不適切な値を設定する方法が考えられます。

以下のようにHWNDに該当する引数に -1 を指定して実行するとエラーとなります。

# メッセージボックスを表示
result = MessageBox(-1, "Hello, World!", "From Python", 0)
【実行結果】
MessageBox failed with error code: 1400

エラーコード 1400 は、Windows API 関数の呼び出しで発生する可能性があるエラーコードの一つで ERROR_INVALID_WINDOW_HANDLE を表します。このエラーは「無効なウィンドウ ハンドルが指定されました」という意味です。

つまり、関数に渡されたウィンドウハンドル(HWND)が無効である、または既に閉じられているなど、オペレーティングシステムが認識できる有効なウィンドウハンドルではない場合にこのエラーが発生します。

上記のように ctypes を使用することで Windows API の関数を Python から使用することができました。Windows API 以外にも様々な DLL を ctypes で扱うことが可能ですが、実際に DLL を使う場合には、よく使用する関数等について調べて使用するようにしてください。

まとめ

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

この記事では ctypes における動的ライブラリローダークラス (ctypes.CDLL, ctypes.WinDLL, ctypes.OleDLL, ctypes.PyDLL)、データ型等の概要を説明するとともに Windows API の DLL (user32.dllkernel32.dll) を使用してメッセージボックスを表示する簡単な例を紹介しました。

ctypes を用いることで、外部の DLL を使用することができますが、実際に使用する際には、どの動的ライブラリローダーのクラスを使うべきかや、関数の引数・戻り値の型定義をどのようにするかなど細かな点に気を使って使用する必要があります。

ctypes で外部 DLL を使用する際には、よく DLL のドキュメント等を確認したうえで使用するようにしましょう。

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

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

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

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