ctypes

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

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

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

外部のDLLを使用する

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

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

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

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

同様に外部DLLを使用するためのサードパーティ製ライブラリとしてはcffiがあります。cffiの公式ドキュメントはこちらを参照してください。

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.dllやkernel32.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公式ドキュメントの基本データ型のセクションを参照して、適切に検討する必要があります。

Note

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のドキュメント等を確認したうえで使用するようにしましょう。