4. Windows 上での C および C++ 拡張モジュールのビルド

この章では Windows 向けの Python 拡張モジュールを Microsoft Visual C++ を使って作成する方法について簡単に述べ、その後に拡張モジュールのビルドがどのように動作するのかについて詳しい背景を述べます。この説明は、Python 拡張モジュールを作成する Windows プログラマと、Unix と Windows の双方でうまくビルドできるようなソフトウェアの作成に興味がある Unix プログラマの双方にとって有用です。

モジュールの作者には、この節で説明している方法よりも、distutils によるアプローチで拡張モジュールをビルドするよう勧めます。また、Python をビルドした際に使われた C コンパイラが必要です; 通常は Microsoft Visual C++です。

注釈

この章では、Python のバージョン番号が符号化されて入っているたくさんのファイル名について触れます。これらのファイル名は XY で表されるバージョン名付きで表現されます; 'X' は使っている Python リリースのメジャーバージョン番号、'Y' はマイナーバージョン番号です。例えば、Python 2.2.1 を使っているなら、XY は実際には 22 になります。

4.1. 型どおりのアプローチ

Windows での拡張モジュールのビルドには、Unix と同じように、 distutils パッケージを使ったビルド作業の制御と手動の二通りのアプローチがあります。 distutils によるアプローチはほとんどの拡張モジュールでうまくいきます; distutils を使った拡張モジュールのビルドとパッケージ化については、 Python モジュールの配布 (レガシーバージョン) にあります。これらを本当に手動で行わなければならないとわかった場合、標準ライブラリモジュールの winsound のプロジェクトファイルが学習に有益かもしれません。

4.2. Unix と Windows の相違点

Unix と Windows では、コードの実行時読み込みに全く異なるパラダイムを用いています。動的ロードされるようなモジュールをビルドしようとする前に、自分のシステムがどのように動作するか知っておいてください。

Unix では、共有オブジェクト (.so) ファイルにプログラムが使うコード、そしてプログラム内で使う関数名やデータが入っています。ファイルがプログラムに結合されると、これらの関数やデータに対するファイルのコード内の全ての参照は、メモリ内で関数やデータが配置されている、プログラム中の実際の場所を指すように変更されます。これは基本的にはリンク操作にあたります。

Windows では、動的リンクライブラリ (.dll) ファイルにはぶら下がり参照 (dangling reference) はありません。その代わり、関数やデータへのアクセスはルックアップテーブルを介します。従って DLL コードの場合、実行時にポインタがプログラムメモリ上の正しい場所を指すように修正する必要はありません; その代わり、コードは常に DLL のルックアップテーブルを使い、ルックアップテーブル自体は実行時に実際の関数やデータを指すように修正されます。

Unix には、唯一のライブラリファイル形式 (.a) しかありません。 .a ファイルには複数のオブジェクトファイル (.o) 由来のコードが入っています。共有オブジェクトファイル (.so) を作成するリンク処理の段階中に、リンカは定義場所の不明な識別子に遭遇することがあります。このときリンカはライブラリ内のオブジェクトファイルを検索します; もし識別子が見つかると、リンカはそのオブジェクトファイルから全てのコードを取り込みます。

Windows では、二つの形式のライブラリ、静的ライブラリとインポートライブラリがあります (どちらも .lib と呼ばれています)。静的ライブラリは Unix における .a ファイルに似ています; このファイルには、必要に応じて取り込まれるようなコードが入っています。インポートライブラリは、基本的には特定の識別子が不正ではなく、 DLL がロードされた時点で存在することを保証するためにだけ使われます。リンカはインポートライブラリからの情報を使ってルックアップテーブルを作成し、DLL に入っていない識別子を使えるようにします。アプリケーションや DLL がリンクされるさい、インポートライブラリが生成されることがあります。このライブラリは、アプリケーションや DLL 内のシンボルに依存するような、将来作成される全ての DLL で使うために必要になります。

二つの動的ロードモジュール、B と C を作成し、別のコードブロック A を共有するとします。Unix では、 A.aB.soC.so をビルドするときのリンカに渡したりは しません ; そんなことをすれば、コードは二度取り込まれ、B と C のそれぞれが自分用のコピーを持ってしまいます。 Windows では、 A.dll をビルドすると A.lib もビルドされます。 B や C のリンクには A.lib を渡します。 A.lib にはコードは入っていません; 単に A のコードにアクセスするするために実行時に用いられる情報が入っているだけです。

Windows ではインポートライブラリの使用は import spam とするようなものです; この操作によって spam の名前にアクセスできますが、コードのコピーを個別に作成したりはしません。Unix では、ライブラリとのリンクはむしろ from spam import * に似ています; この操作では個別にコードのコピーを生成します。

4.3. DLL 使用の実際

Windows 版の Python は Microsoft Visual C++でビルドされています; 他のコンパイラを使うと、うまく動作したり、しなかったりします (Borland も一見うまく動作しません)。この節の残りの部分は MSVC++ 向けの説明です。

Windows で DLL を作成する際は、 pythonXY.lib をリンカに渡さねばなりません。例えば二つの DLL 、spam と ni (spam の中には C 関数が入っているとします) をビルドするには、以下のコマンドを実行します:

cl /LD /I/python/include spam.c ../libs/pythonXY.lib
cl /LD /I/python/include ni.c spam.lib ../libs/pythonXY.lib

最初のコマンドで、三つのファイル: spam.objspam.dll および spam.lib ができます。 Spam.dll には (PyArg_ParseTuple() のような) Python 関数は全く入っていませんが、 pythonXY.lib のおかげで Python コードを見つけることはできます。

二つ目のコマンドでは、 ni.dll (および .obj.lib) ができ、このライブラリは spam と Python 実行形式中の必要な関数をどうやって見つければよいか知っています。

全ての識別子がルックアップテーブル上に公開されるわけではありません。他のモジュール (Python 自体を含みます) から、自作の識別子が見えるようにするには、void _declspec(dllexport) initspam(void)PyObject _declspec(dllexport) *NiGetSpamData(void) のように、_declspec(dllexport) で宣言せねばなりません。

Developer Studio は必要もなく大量のインポートライブラリを DLL に突っ込んで、実行形式のサイズを 100K も大きくしてしまいます。不用なライブラリを追い出したければ、「プロジェクトのプロパティ」ダイアログを選び、「リンカ」タブに移動して、 インポートライブラリの無視 を指定します。その後、適切な msvcrtxx.lib をライブラリのリストに追加してください。