Pythonでctypes
モジュールを用いて外部DLLを使用する方法を解説します。
Contents
外部のDLLを使用する
Pythonは非常に豊富なライブラリが用意されているプログラミング言語であるため、多くのことはPythonの標準ライブラリ、またはサードパーティ製ライブラリで解決策を見つけることができます。
しかし、ソフトウェア開発会社から提供されているソフトウェアやWindowsなどで提供されているライブラリなど、別途C/C++等の他の言語で開発されている動的リンクライブラリ(DLL: Dynamic Link Libraries)をPythonプログラムから読み込み使用したい場合があります。
Pythonプログラムから外部のDLLを使用するための標準ライブラリとしてctypes
モジュールがあります。この記事では、ctypes
モジュールを用いて外部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.WinDLL | Windows固有のstdcall呼び出し規約を使用してDLLをロードします。主にWindows API関数で使用します。 |
ctypes.OleDLL | OLE呼び出し規約を使用してDLLをロードし、WindowsのCOMコンポーネントとのやり取りに使用されます。エラーチェックが自動的に行われます。 |
ctypes.PyDLL | GIL (Global Interpreter Lock)を解放せずにライブラリをロードすることで、PythonのAPI関数やPython拡張モジュールを扱う際にPythonインタプリタの安全性を保ちながら操作を行えます。 一方で、外部関数実行中に他のPythonスレッドが停止するため、並行性に悪影響を及ぼす可能性があります。 |
代表的な関数
C言語のポインタ操作をPythonで適切に行うために、以下のような代表的なポインタを扱う関数が用意されています。
関数 | 概要 |
---|---|
ctypes.byref | 引数としてオブジェクトを取り、そのオブジェクトの参照ポインタを生成します。C言語のポインタのようにオブジェクトへ参照を渡すための関数です。 |
ctypes.POINTER | 指定した型のポインタ型を生成する関数です。これはC言語のポインタ型に相当して、より明示的なポインタ操作が必要な場合に使用します。 |
基本データ型のクラス
ctypes
では、ctypes.c_int
、ctypes.c_float
、ctypes.c_char
などのクラスが定義されています。これらはC言語の基本的なデータ型(int
、float
、char
等)に対応するクラスです。基本データ型クラスは多岐にわたりますので、公式ドキュメントの基本データ型のセクションを参照してください。
構造化データ型
ctypes
では、ctypes.Structure
やctypes.Union
といった構造化データ型のクラスも定義されています。これらはC言語での構造体や共用体をPythonで表現するためのクラスです。構造化データ型のクラスについても公式ドキュメントの構造化データ型のセクションを参照してください。
ctypesを使用した外部DLLの使用方法
ctypes
を使用した外部DLLの基本的な使い方の流れをイメージいただくための使用例を紹介します。上記で紹介したようにctypes
の動的ライブラリローダーは4つあり、実際には呼び出すDLLによって、どれを使うかなどについて十分に検討する必要があります。
例: user32.dllやkernel32.dllを用いたMessageBoxの表示
ここでは、ctypes.WinDLL
を用いて、Windowsのライブラリであるuser32.dll
とkernel32.dll
を使用してMessageBox
を表示する例について紹介します。
user32.dll
は、Windowsに標準で含まれているWindows APIの一部で、Windowsのユーザーインターフェースに関する関数を提供しています。kernel32.dll
は、WindowsOSの核心となるDLLの一つで、低レベルのメモリ管理、プロセスやスレッドの管理、ファイル操作、入出力等のシステムの基本的な機能を提供しています。
user32.dll
やkernel32.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}")
【実行結果】
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.MessageBoxW
とkernel32.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}")
実行する場合には、上記のように引数を渡して実行します。実行すると以下のような画面が表示できます。
戻り値に対してエラーチェックを行い、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.dll
やkernel32.dll
)を使用してメッセージボックスを表示する簡単な例を紹介しました。
ctypes
を用いることで、外部のDLLを使用することができますが、実際に使用する際には、どの動的ライブラリローダーのクラスを使うべきかや、関数の引数・戻り値の型定義をどのようにするかなど細かな点に気を使って使用する必要があります。
ctypes
で外部DLLを使用する際には、よくDLLのドキュメント等を確認したうえで使用するようにしましょう。