ニコニコ動画マイリスト

2020年4月25日土曜日

インストーラを使わずにC言語からPythonを呼び出す

以前、エンドユーザーにPythonのインストール作業をさせずに
PythonのDLL群を使ってPythonコードを実行するような
ペイントソフト用のプラグインを作っていて
嵌まったので、ノート替わりにやったことを公開します。

■背景
 私が公開しているフリーソフトに
 Pythonを使っているソースコードをパクって 参考にして
 プラグインを作成しようとしたのですが
 本体がインストーラ不要であることがひとつの長所なのに
 「プラグインのためにPythonのインストーラを使わせるなんて
   言語道断」…という、わがままによって
 エンドユーザーにPythonのインストーラを使わせずに
 Pythonのコードを自前のプログラムから実行する方法を模索しました。

■結論
 Windows環境であれば、以下、5点を実施すれば可能です。
  1. Portable Pythonをダウンロードする
  2. ダウンロードしたPortalbe PythonのConsole-Launcher
    を起動して「python -m pip install --upgrade pip」しておく
  3. 必要なPythonのライブラリを「python -m pip install ○○」
    で使えるようにしておく
  4. 配布物にPortable PythonのAppフォルダの中身を追加する
  5. C言語のソースコードでPy_Initialize()する前に
    setenv関数等で以下の環境変数を設定する
    PATH、PYTHONPATH、PYTHONSTARTUP
    (この環境変数は呼び出し元のプロセスの中でのみ有効ですので
     ユーザーの環境変数を汚しません)
あとはPython のC APIリファレンス通りにやれば
 ちゃんと動く …ハズ。

■手順詳細
 1-1. Portable Pythonのサイト(Sourceforge)から
    使いたいバージョンのPythonをダウンロードします。
    (Portable PythonはUSBメモリに入れて持ち歩くことを
     想定したものなのでexe形式の解凍実行ファイルを
     ダウンロードすることになります)
 1-2. ダウンロードしたexeファイルを実行して
    任意のフォルダに解凍します。
 2-1. 解凍されたフォルダのConsole-Launcher○○を起動する。
    (○○は64bit用なら64、32bit用なら86です)
 2-2. コマンド「python -m pip install --upgrade pip」
    でpipを最新の状態にしておく。
 3. コマンド「python -m pip install ○○」で
    必要なライブラリをインストールしておく。
    (例えばnumpyなら
     「python -m pip install numpy」になります)
   これで、配布に必要なファイルが揃います。
 4. 配布時にはPortable Pythonを解凍したフォルダにある
  「App\Python」フォルダも一緒に配布する必要があります。
 5. Python のC API経由でPythonのスクリプトを実行する為には
   Pythonの初期化時に必要なライブラリをPYTHONPATHに
   設定されたフォルダが参照されますので
   Py_Initialize (またはPy_InitializeEx)を呼び出す前に
   PYTHONPATHの値を設定する必要があります。
 5-1. PYTHONPATHには次の値を設定します。
   私は設定にsetenv()関数を使いました。
   (1) PATHに Portable Pythonのフォルダ内の
    「Python」フォルダのパスを追加
   (2) PYTHONPATHにPortable Pythonのフォルダ内の
    「Python」フォルダのパスを追加
   (3) PYTHONPATHにPortable Pythonのフォルダ内の
    「Python\DLLs」フォルダのパスを追加
   (4) PYTHONPATHにPortable Pythonのフォルダ内の
    「Python\Lib」フォルダのパスを追加
   (5) PYTHONPATHにPortale Pythonのフォルダ内の
    「Python\Lib\site-packages」フォルダのパスを追加
   (6) PYTHONSTARTUPにPortable Pythonのフォルダ内の
    「Python\Lib\ppp.py」ファイルのパスを追加
  …6つ中、4つがPYTHONPATHですね…
 5-2. C言語で書いた関数を呼び出したい場合は
   PyMODINIT_FUNC 関数名(void)の関数を作成し、
   その中で
   (1) static struct PyMethodDef 配列変数名[]で
     {"Pythonで呼び出すときの関数名", C言語の関数ポインタ,
                    METH_VARARGS, "関数のヘルプ文字列"}
     の構造体配列を作成
   (2) static struct PyModuleDef 変数名で
     {PyModuleDef_HEAD_INIT, "適当な文字列",
                    NULL, -1, (1)の変数}の構造体変数を作成
   (3) import_array() 関数を呼び出す。
     (NumPyを使わない場合は不要。
      これをしないとC言語で書いたPython用関数の中で
      PyArg_ParseTuple関数にPyArray_TYPEを指定した際に
      クラッシュする)
   (4) return PyModule_Create(引数は(2)の変数のアドレス)
  の処理を行います。 (1)、(2)の変数はstaticなローカル変数の代わりに
  グローバル変数でもO.K.
 5-3. Py_Initialize()関数を呼び出す。

 …あとはPyObject_CallObject関数やPyObject_CallMethod関数などを使えば
 Pythonの中のインスタンス作成やメソッド呼び出しを行えます。
 5. 以降の内容は「C言語からPythonの呼び出し」等で検索して出てきた
 結果を参考にした方が良いかもしれませんね。

説明は以上です。何かご質問などありましたら、
お気軽にコメントまでどうぞ。