目的
Python3.0に限らず、新しいバージョンのPythonがリリースされるたびに悩ましい問題があります。バイナリ製の拡張モジュールが豊富である所がPythonのひとつのウリなんですが、Pythonのメジャーバージョンをまたいでは動作しないのがツライところ。特にWindowsではコンパイラが標準でインストールされていないこともあり、他のOSほどソースからのビルドが容易ではありません。バイナリがリリースするまで待つことになると、拡張モジュールの新しいバージョンが出揃うのにどうしても時間がかかってしまうのです。これが、Pythonのニューバージョンの普及が出遅れる要因のひとつになっています。
この問題の主な要因に、ライブラリとPythonの「橋渡し部分」を「C/C++の記述力」を利用して構築している事が挙げられます。ここの「橋渡し部分」のコンパイル作業によりPythonランタイムとの依存関係が発生しますので、「Pythonランタイムのバージョンアップ=リビルドが必要」になります。この「橋渡し部分」をPythonコードだけで作る方法を用いると、その手法で作ったモジュールはPythonのメジャーバージョンの違いに関係なく動作可能になります。最近のPythonはその橋渡しをPythonで記述できる「ctypes」というモジュールを標準で添付しています。また、ctypesベースの拡張モジュールもどんどん作られて増えてきています。
しかしこの「ctypes」は、C言語の型情報を考慮してctypes流儀の記述を自前で用意しなくてはいけないのが面倒なんです。Pythonの基本型とCの基本型の共通のものだけは自動的に変換してくれますが、その他の定数、列挙型、構造体、別名型定義などは無理です。開発中のctypeslibというツールセットを使うと、ctypes流儀のPythonコードを自動生成できます。
本記事では、C言語で作られたダイナミックリンクライブラリとヘッダから橋渡し部分のPythonモジュールを自動生成し、そのモジュールがPython3.0で動作することを示します。
仕組み
この手法では、まずC言語でダイナミックリンクライブラリを作成しておき、ctypeslibパッケージのh2xmlツールでgccxmlツールを経由して、Cヘッダを解析したXMLファイルを作成します。gccxmlの実装はgccにパッチを当てて、C/C++解析結果をXMLに出力できるようにしたものなので、そのXMLファイルには構文解析によって得られる詳細な情報が詰まっています。
次にctypeslibパッケージのxml2pyツールを利用してそのXMLファイルからPythonのコードだけでできた「橋渡し部分」つまり「ラッパーモジュール」を生成します。「ラッパーモジュール」はヘッダにある定義群(定数、列挙、関数、クラス、構造体、型宣言)をPythonのctypesに従った記述に置き換えたもので、普通ならctypes用のコードを書いて準備を手作業でやらねばならなかったところを、上記手法なら自動的に記述を生成できます。そのモジュールはPython2.5や2.6で動作するモジュールになっていますが、これを「Python3.0」付属の「2to3.py」ツールにてPython3.0用に変換することで「Python3.0」で動作可能なモジュールを構築できます。
利用するにはダイナミックリンクライブラリを環境変数PATHの通る場所に置いて、Pythonのモジュールをインポートするだけです。あとは、ヘッダで定義した定数や構造体を使って関数呼び出しも自在にできるようになっています。ヘッダに変更が無ければ、ダイナミックリンクライブラリのバージョン更新にも影響なく動作可能でなおかつctypesモジュールを持つPythonならどのバージョンでも動きます。
ctypeslibツールセットの準備
あらかじめ、gccxml-0.9.0をインストールしておきます。
上記サイトから gccxml-0.9.0-win32-x86.exe をダウンロード。バイナリインストーラを起動すれば簡単にインストールできます。次にctypeslibツールセットをインストールします。ソースからインストールしたい方以外は、以下のセクションはスキップしてください。
ctypeslibパッケージのインストール
以下のサイトからctypeslibパッケージを入手します。
svnか、TortoiseSVNでチェックアウトしましょう。ODEライブラリのために少しだけ修正を加えます(ODEを利用しない方はこの修正は無用です)。
svnの場合
その後以下の修正を行います。
その上で
とすると、「Pythonホーム/Scripts」フォルダにh2xml.pyとxml2py.pyがインストールされます。これらを利用することで、C言語ヘッダとダイナミックリンクライブラリからPythonモジュールを生成できるようになります。 しかし、このツール私の力不足でPython3ではうまく動きませんでした。
バイナリアーカイブの公開
上記セクションの処理を済ませたPython2.5バージョンのバイナリを配布します。
このアーカイブを適当なところに解凍して、そのフォルダパスを環境変数のPathに加えておいてください。
実際の手順
「-w」は実体をWindowsの標準DLLから検索するモードになります。標準でないDLLを利用する場合は「-w」の代わりに「-l hoge.dll」と名指しします。「-r」や「-s」を省略すると、可能な限りのシンボルをコンバートします。「-s」は単独のシンボルを指定できます。「-r」は取り込むシンボルを正規表現で指定できます(「-r」や「-s」は複数指定できます)。
このようにWindowsAPIを呼び出すモジュールが簡単に作れます。Python3では文字列はUnicodeなので、末尾がAかWのAPIを利用する場合、WつきのAPIを利用することになります。
リスト1のバッチファイルを実行すると、WindowsのAPIがほとんど利用可能なモジュール「windows.py」ができます。
windows.pyのテスト
SDLのモジュールを作る!
あらかじめSDLをビルドするか、バイナリをダウンロードしましょう(とにかくヘッダ一式とダイナミックリンクライブラリさえあればいい)。
上記に置いてあるSDL-devel-1.2.13-mingw32.tar.gzとSDL_ttf-devel-2.0.9-VC8.zipのそれぞれから、includeフォルダ以下と*.dllだけを展開しておきます。
リスト2にあるコマンドでビルドできます。
コツはxml2pyのとき、作業中のフォルダにダイナミックリンクライブラリを置くことです。h2xmlの「-I」や「-D」オプションはgccのものと同じ意味です。「-I」がインクルードファイル検索パスで、「-D」がマクロ定数定義です
sdl.pyのテスト
利用時は、ダイナミックリンクライブラリをctypesによって発見できるフォルダに置く必要があります。カレントフォルダまたは環境変数Pathに示した場所に置いておけば、ctypesのローダーが発見してくれます。
ODEのモジュールを作る!
あらかじめODEをビルドするか、バイナリをダウンロードしましょう。(とにかくヘッダ一式とダイナミックリンクライブラリさえあればいい)
リスト4のコマンドでビルドできます。
ode.pyのテスト
10m上空にあった箱を自由落下させると1.4秒後には地面に落下するという結果になりました。理論値は1.43秒なので微妙に誤差がありそうですが……。
OpenCVのモジュールを作る!
ctyes-opencvというモジュールがすでに公開されています。それはctypeslibを使っていなさそう? なんですが、結果としてはctypesによるPurePythonモジュールになっていますので「2to3.py」ツールでコンバートすれば動くようになりました。ですが、ここではあえてもう一度今回の手法で構築してみましょう。あらかじめ OpenCV1.0バイナリ版 をインストールしてください。
リスト7のコマンドでビルドできます。
cv、highguiを使ったテスト
OpenGLのモジュールを作る!
glutのヘッダとライブラリを別途入手しておきます。以下のリンクを参考に入手しました。
リスト9にあるコマンドでビルドできます。
OpenGLの描画テスト
まとめ
ctypeslibを利用することで、多くのC言語による資産を簡単に取り込めることがわかりました。あと、Linuxなどでも「.dll」ファイルの代わりに「.so」ファイルを利用して同様な手順で拡張モジュールを作ることができるようです。ctypesベースの場合、ダイナミックリンクライブラリのバージョンアップでもヘッダに変更がなければそのまま動作することや、 Pythonのバージョンアップにも影響なく動作することは非常に助かります。あと試してませんがPygletやPyOpenGLもctypesベースなので、「2to3」ツールで移行できるかも。
欠点としてはC++のライブラリが取り込めないのと、Unicodeをshortアレイ扱いしている定義では本当にshortアレイに変換しなくてはならないところや、ネイティブコードが行っていた橋渡しをPythonコードで行う以上オーバーヘッドが増えていることなどが挙げられます。
しかし、今回の内容だけでもWindowsAPI、SDL、ODE、OpenCV、OpenGLなどがPython3で利用可能になりました。Python3への全面移行は当分先だと思っていましたが、その時期は意外ともうすぐなのかもしれませんね。