目次

前のトピックへ

3. distutils による C および C++ 拡張モジュールのビルド

次のトピックへ

5. 他のアプリケーションへの Python の埋め込み

このページ

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 モジュールの配布 にあります。この節では、C や C++で書かれた Python 拡張モジュールを手動でビルドするアプローチについて述べます。

以下の説明に従って拡張モジュールをビルドするには、インストールされている Python と同じバージョンの Python のソースコードを持っていなければなりません。また、 Microsoft Visual C++ “Developer Studio” が必要になります; プロジェクトファイルは VC++ バージョン 7.1 向けのものが提供されていますが、以前のバージョンの VC++も使えます。Python自体をビルドしたものと同じバージョンの VC++を使わなければならないことに注意しましょう。ここで述べる例題のファイルは、Python ソースコードと共に配布されており、 PC\example_nt\ ディレクトリにあります。

  1. 例題ファイルをコピーするexample_nt ディレクトリは PC ディレクトリのサブディレクトリになっています。これは PC 関連の全てのファイルをソースコード配布物内の同じディレクトリに置くための措置です。とはいえ実際には、 example_nt ディレクトリは PC の下では利用できません。そこで、まずこのディレクトリを一階層上にコピーして、 example_ntPC および Include と同じ階層のディレクトリになるようにします。以降の作業は、移動先の新しいディレクトリ内で行ってください。

  2. プロジェクトを開く — VC++から、 ファイル ‣ ソリューションを開く ダイアログメニューを選択します (ファイル ‣ 開く ではありません!)。ディレクトリ階層を辿って、 example_nt をコピーしたディレクトリ 内の example.sln を選択し、「開く」をクリックします。

  3. 例題の DLL をビルドする — 設定が全て正しく行われているか調べるために、ビルドしてみます:

  4. ビルド構成を選びます。このステップは省略できます。 ビルド ‣ 設定マネージャー ‣ アクティブなソリューションの設定 を選び、 リリース または デバッグ を選びます。このステップを飛ばすと、VC++ はデフォルトでデバッグ構成を使います。

  5. DLL をビルドします。 ビルド ‣ ソリューションのビルド を選びます。全ての中間ファイルおよび最終ファイルが、上のビルド構成で選んだ構成に従って Debug または Release という名前のディレクトリに生成されます。

  6. デバッグモードの DLL をテストする — デバッグビルドが成功したら、コマンドプロンプトを起動し、 example_nt\Debug ディレクトリに移動してください。以下のセッション通りにコマンドを実行できるはずです (C> は DOS コマンドのプロンプト、 >>> は Python のプロンプトです; ビルド情報や様々なデバッグ出力は、ここに示したスクリーン出力と一致しないこともあるので注意して下さい):

    C>..\..\PCbuild\python_d
    Adding parser accelerators ...
    Done.
    Python 2.2 (#28, Dec 19 2001, 23:26:37) [MSC 32 bit (Intel)] on win32
    Type "copyright", "credits" or "license" for more information.
    >>> import example
    [4897 refs]
    >>> example.foo()
    Hello, world
    [4903 refs]
    >>>

    おめでとうございます! とうとう初めての Python 拡張モジュールのビルドに成功しましたね。

  7. 自分用にプロジェクトを作成する — プロジェクト用のディレクトリを適当な名前で作成してください。自作の C ソースコードをディレクトリ内にコピーします。モジュールのソースコードファイル名は必ずしもモジュール名と一致している必要はありませんが、初期化関数の名前はモジュール名と一致していなければなりません — 初期化関数の名前が initspam() なら、モジュールは spam という名前でしか import できません。 initspam() は第一引数を "spam" にして、 Py_InitModule() を呼び出します (このディレクトリにある、最小限の内容が書かれている example.c を手がかりにするとよいでしょう)。ならわしとして、ファイルは spam.c または spammodule.c という名前にしておきます。出力ファイル名はリリースモードでは spam.pyd 、デバッグモードでは spam_d.pyd 、になるはずです。 .pyd という拡張子は、システムライブラリの spam.dll と作成した拡張モジュールの間での混乱を避けるために選ばれました。

    バージョン 2.5 で変更.

    さて、やり方は二通りあります:

  8. example.dswexample.vcproj をコピーし、 spam.* に名前を変えて、手作業で編集する

  9. 新しくプロジェクトを作成する; 説明は下にあります。

    どちらの場合も、 example_nt\example.defspam\spam.def にコピーして、新たにできた spam.def を編集し、二行目に ‘initspam‘ が入るようにします。自分で新たなプロジェクトを作成したのなら、ここで spam.def をプロジェクトに追加しておいてください (このファイルはたった二行しかない目障りなファイルです。 .def ファイルを全く無視するという方法もあり、それには /exprt:initspam を「プロジェクトのプロパティ」ダイアログにあるリンク設定のどこかに手動で追加します)。

  10. 新しくプロジェクトを作成するファイル ‣ 新規作成 ‣ プロジェクト ダイアログを使って、新たなプロジェクト用ワークスペースを作成します。 Visual C++ プロジェクト/Win32/Win32 プロジェクト を選択し、名前(spam) を入れ、「場所」が先ほど作成した spam ディレクトリの親ディレクトリに (Python ビルドツリーの直下のサブディレクトリで、 Include および PC と同じディレクトリになるはずです) あることを確かめます。「作成」をクリックします。

    前の節で述べた spam.def をここで作成しておかねばなりません。その後、 追加 ‣ ファイルをプロジェクトに追加 ダイアログを選びます。「ファイルの種類」を *.* にして、 spam.cspam.def を選び、 OK をクリックします (一つ一つファイルを追加してもかまいません)。

    プロジェクト ‣ spam のプロパティ ダイアログを開きます。ほんのいくつかですが、設定の変更が必要です。 構成 ドロップダウンリストに すべての構成 が設定されているか確かめてください。 C/C++ タブを選び、ポップアップメニューから「一般」カテゴリを選びます。以下のテキスト:

    ..\Include,..\PC

    を、 追加のインクルードディレクトリ とラベルされたエントリボックスに入力します

    次に、「リンカ」タブの「一般」カテゴリを選び、

    ..\PCbuild

    追加のライブラリディレクトリ と書かれたテキストボックスに入力します。

    さて、構成ごとに特有の設定をいくつか行う必要があります:

    「構成」ドロップダウンリストから、 リリース を選んでください。「リンク」タブをクリックし、「入力」カテゴリを選んで、「追加の依存ファイル」ボックス内のリストに pythonXY.lib を追加します。

    「構成」ドロップダウンリストから、 デバッグ に切り替え、「追加の依存ファイル」ボックス内のリストに pythonXY_d.lib を追加します。次に C/C++ タブをクリックして、 コード生成 をカテゴリから選び、 ラインタイムライブラリ に対して マルチスレッドデバッグ DLL を選びます。

    「構成」ドロップダウンリストから リリース に切り替えなおします。 ラインタイムライブラリ に対して マルチスレッド DLL を選びます。

作っているモジュールが新たな型を作成するのなら、以下の行:

PyObject_HEAD_INIT(&PyType_Type)

がうまくいかないはずです。そこで:

PyObject_HEAD_INIT(NULL)

に変更してください。また、以下の行をモジュール初期化関数に加えます:

MyObject_Type.ob_type = &PyType_Type;

この操作を行う詳しい理由は、 Python FAQ の第 3 節を参照してください。

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 をライブラリのリストに追加してください。