ctypes

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

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

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

ctypes で外部の DLL を使用する

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

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

Python プログラムから外部の DLL を使用するための標準ライブラリとして ctypes モジュールが用意されています。ctypes は標準ライブラリとして広く利用されていますが、より大規模な C ライブラリとの連携などでは CFFI や専用ツールが使用される場合もあります。

この記事では、ctypes モジュールを用いて外部 DLL を使用する方法を紹介します。

CFFI の使用方法については「CFFIで外部DLLを使用する方法」を参考にしてください。

ctypes とは

ctypes は、Python から C 言語互換の共有ライブラリを呼び出すための標準ライブラリです。標準ライブラリであるため、追加のサードパーティ製パッケージを導入せずに利用することができます。

ctypes を用いることで、C の呼び出し規約に従って公開された関数を Python から直接呼び出すことが可能になります。対象となるライブラリは、Windows では .dll、Linux や Unix では .so、macOS では .dylib などの形式で提供されており、ctypes はこれらに対応しています。

利用するライブラリは、外部からアクセス可能な関数をエクスポートし、C 互換のインターフェース(データ型や呼び出し規約)を備えている必要があります。ライブラリがどの言語で実装されているかは問いません。

ctypes は強力な仕組みですが、安全に利用するためには C 言語のデータ型やポインタ、メモリ管理に関する基本的な理解が必要です。特に、型の不一致や不適切なメモリアクセスは実行時エラーの原因となるため注意が必要です。

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 で表現するためのクラスです。構造化データ型のクラスについては公式ドキュメントの構造化データ型のセクションを参照してください。

Windows API 向けの型 (ctypes.wintypes)

Windows API を扱う場合、ctypes には ctypes.wintypes というモジュールが用意されています。wintypes には、DWORDHANDLE などの基本型のエイリアスだけでなく、POINTRECT のような構造化データ型も定義されています。

これらは内部的には ctypes の基本データ型や構造化データ型を使用して実装されていますが、Windows API の型名にあわせて利用できるため、コードの可読性が向上し、API ドキュメントとの対応が明確になります。

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

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

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

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

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

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

import ctypes
from ctypes import wintypes

# user32.dllを読み込む (.dll拡張子は省略可能)
user32 = ctypes.WinDLL("user32", use_last_error=True)

# MessageBoxを定義
MessageBoxW = user32.MessageBoxW
MessageBoxW.argtypes = [
    wintypes.HWND,  # HWND (ウィンドウハンドルを示す識別子)
    wintypes.LPCWSTR,  # LPCWSTR (メッセージのテキスト)
    wintypes.LPCWSTR,  # LPCWSTR (メッセージボックスのタイトル)
    wintypes.UINT,  # UINT (メッセージボックスのスタイル)
]
MessageBoxW.restype = wintypes.INT  # 戻り値の型

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

# # 以下のように無効なウィンドウハンドルではエラーとなる。(エラー発生の確認例)
# result = MessageBoxW(
#     wintypes.HWND(12345678), "Hello, World!", "From Python", 0
# )

# 戻り値をチェックしてエラー処理
if result == 0:
    err = ctypes.get_last_error()
    raise ctypes.WinError(err)
else:
    print(f"MessageBox return: {result}")

【実行結果】

user32.dllのMessageBox

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

MessageBox return: 1

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

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

import ctypes
from ctypes import wintypes

# user32.dllを読み込む (.dll拡張子は省略可能)
user32 = ctypes.WinDLL("user32", use_last_error=True)

必要となる ctypesctypes.wintypes をインポートします。今回は Windows の DLL のため ctypes.WinDLL を使用します。"user32.dll" と指定もできますが .dll 拡張子は省略することが可能です。use_last_error はエラーを取得するために True を設定しておきます。

【型の定義】

# MessageBoxを定義
MessageBoxW = user32.MessageBoxW
MessageBoxW.argtypes = [
    wintypes.HWND,  # HWND (ウィンドウハンドルを示す識別子)
    wintypes.LPCWSTR,  # LPCWSTR (メッセージのテキスト)
    wintypes.LPCWSTR,  # LPCWSTR (メッセージボックスのタイトル)
    wintypes.UINT,  # UINT (メッセージボックスのスタイル)
]
MessageBoxW.restype = wintypes.INT  # 戻り値の型

今回は user32.MessageBoxW を使用します。MessageBoxW を正しく呼び出すには、関数の引数型 (argtypes) と戻り値型 (restype) を定義します。これにより、Python と DLL 間でのデータ型の変換を適切に行えます。

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

今回の例でいうと Windows API の MessageBoxW は引数 (argtypes) として以下を取ります。

  • wintypes.HWND:ウィンドウハンドルを示す識別子
  • wintypes.LPCWSTR:メッセージのテキスト
  • wintypes.LPCWSTR:メッセージボックスのタイトル
  • wintypes.UINT:メッセージボックスのスタイル

また、restype には戻り値の型として wintypes.INT を設定しておきます。

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

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

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

# 戻り値をチェックしてエラー処理
if result == 0:
    err = ctypes.get_last_error()
    raise ctypes.WinError(err)
else:
    print(f"MessageBox return: {result}")

実行する場合には、MessageBoxW に引数を渡して実行します。第 1 引数に None または 0 を渡すと親ウィンドウを持たないことを意味します。また、第 4 引数の 0 は、OK ボタンのみの標準メッセージボックスを指定しています。実行すると以下のような画面が表示できます。

user32.dllのMessageBox

MessageBoxW の使用上で、戻り値が 0 の場合はエラーとなります。その場合、ctypes.get_last_error() によりエラー情報を取得することが可能です。ctypes.WinError を使用することで、Windows のエラーコードを Python の OSError として生成できます。

例えば、プログラム例でコメントアウトしてある以下の部分のように、ウィンドウハンドルに想定しない値を設定するとエラーとなることが確認できます。

# 以下のように無効なウィンドウハンドルではエラーとなる。(エラー発生の確認例)
result = MessageBoxW(
    wintypes.HWND(12345678), "Hello, World!", "From Python", 0
)
【実行結果】
OSError: [WinError 1400] ウィンドウ ハンドルが無効です。

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

上記のように 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.dll) を使用してメッセージボックスを表示する簡単な例を紹介しました。

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

ctypes で外部 DLL を使用する際には、よく DLL のドキュメント等を確認したうえで使用するようにしましょう。特に Windows API を扱う場合は、公式ドキュメントの関数やデータ型を十分に確認することが重要です。

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

ソースコード

上記で紹介しているソースコードについては GitHub にて公開しています。参考にしていただければと思います。

あわせて読みたい
【Python Tech】プログラミングガイド
【Python Tech】プログラミングガイド

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

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

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