【C++】32bit C++からPythonを利用する（VisualStudio）

## はじめに
- 本記事では、Boostライブラリを利用し、C++からPythonを呼び出す方法をまとめる
- x86 ビルド(32bit)での環境構築に限定し、x64 ビルド(64bit)の解説は行わない
  - 64bit版は、[こちらの記事](https://qiita.com/yusa0827/items/ebc0e606b31604d45712)へ。
  - 32bit版での環境構築の際は、64bit版Python, Anacondaを全て削除することをおすすめします。（ハマるポイントが少なくなります）

## 何を実現したいのか？
- VisualStudioのC++プロジェクトから、Pythonクラスを利用したい
- 速度の求められる処理をC++で実行し、一部の複雑な処理はPythonを利用したい
- 32bitマシンを制御する必要がある（シーケンサ、マイコン、ロボットなど）
  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349189/c7c7aa3a-54f4-c897-18c2-a45ddb865679.png" width="480">


## 32bit 環境のセットアップ
- 環境構築の流れは64bitと同じだが、少々クセがあるため以下で説明する。

### 1. Pythonインストール
- Python本家から、<u>[Python 3.8` (32bit)`](https://www.python.org/downloads/release/python-385/) </u>をインストールする
    - Anaconda は使用しない方が良い。
        - <u>Anacondaではうまくいかないとのコメントがありました。</u>( [こちらのリンクを押し、一番下までスクロール](https://qiita.com/yusa0827/items/ebc0e606b31604d45712)）
        - <u>複数バージョンのPythonを入れると設定がめんどくさくなります。PC内に、32bit版Pythonのみが存在する状態にしておいた方が良いです。</u>

### 2. Boostセットアップ
- Boostをダウンロードする（筆者は[ Boost 1.76.0](https://www.boost.org/users/history/version_1_76_0.html)）
  - 本記事ではCドライブ直下にダウンロード 
- 解凍したBoostのファイル中で、`bootstrap.bat`を実行する

    ```cmd
    cd C:\boost_1_76_0 #解凍したフォルダパスへGO
    C:\boost_1_76_0>bootstrap.bat
    ```

- 実行後、`project-config.jam`というフォルダが出来上がっているので、このファイルを変更する
    - これが、Boostのビルドオプションの設定ファイル

    ```project-config.jam
    # Boost.Build Configuration 
    # Automatically generated by bootstrap.bat 
     
    import option ; 
     
    using msvc ; 
     
    option.set keep-going : false ; 
     
    using python : 3.8 :  C:/Users/Tanaka.Taro/AppData/Local/Programs/Python/Python38-32 : C:/Users/Tanaka.Taro/AppData/Local/Programs/Python/Python38-32/include :  C:/Users/Tanaka.Taro/AppData/Local/Programs/Python/Python38-32/libs : ;
    ```
- __ポイント__：自分の環境に合わせ、下記のように設定する
    - `using python : [Pythonのバージョン] :  [Pythonのルートディレクトリ] : [Pythonのルートディレクトリ]/include :  [Pythonのルートディレクトリ]/libs : ;`
    - <u>↑上記のコードの空白文字(スペース)は省略しないこと！(`libs : ;` などの箇所です)</u>
    
- project-config.jam の設定後、**b2.exe**を実行する
  - コマンドプロントで下記のコマンドを実行
    ```cmd
    b2.exe toolset=msvc-14.2 link=static,shared runtime-link=static,shared address-model=32 -j12 install --build-dir=C:\boost_1_76_0\build\x86 --includedir=C:\boost_1_76_0\include --libdir=C:\boost_1_76_0\stage\lib\x86
    ```
- **b2.exe コンパイルオプションの解説**
    - toolset= "コンパイラを指定"
    - runtime-link="Visual Studioで使用する際のBoostライブラリのリンク形式"
        - Boostライブラリ使用時に下記の違いがある
            - runtime-link=__static__ : Visual Studioで(MT or MTd)を指定する
            - runtime-link=__shared__ : Visual Studioで(MD or MDd)を指定する
                - **注意: sharedの場合、実行時に.libと同名のdllを置かなければならない**
    - address-model="ビット数: 32 or 64 を指定" 
    - -j12: "ビルド時の並列スレッド数。（数値を大きくするほどコンパイルが早くなるが、いずれ頭打ちになる）
    - --build-dir="ビルド後のBoost実行ファイルが生成される場所"
    - --includedir="ビルド後のBoostライブラリのヘッダファイルが生成される場所"
    -  --libdir="ビルド後のBoostライブラリのライブラリファイルが生成される場所"
    - コマンド中に `--with-python` オプションはつけない（すべてのパッケージをインストールする）
      - --with-python: "Boost提供ライブラリのうち、Pythonのみ利用しますよ。という意味"
- __ポイント__
   - `msvc-14.2` の数値は、自身のVisualStudioのバージョンによって変えなければならない
     - 確認方法
       - VisualStudioを開く > プロジェクト > プロパティ > プラットフォームツールセット
   - b2.exeの実行オプションは、<u>[こちらの記事](https://boostjp.github.io/howtobuild.html)</u> にまとまっているので、一読すること

### 3. Visual Studio のセットアップ
- Boostのビルドが完了したら、次はVisualStudioでBoostPythonを利用するための設定を行う
- 今回は32bitビルドなので、 ビルドプラットフォームを `x86` にすること。
    - プロジェクトのプロパティを設定する
        - C/C++ --> 全般 --> 追加のインクルードディレクトリ
            - C:\boost_1_76_0\include\boost-1_76
            - [Pythonのルートディレクトリ]/include
        - リンカー --> 全般 --> 追加のライブラリディレクトリ
            - C:\boost_1_76_0\stage\lib\x86
            - [Pythonのルートディレクトリ]/libs

#### PCの環境変数の設定
- 筆者の環境では特にいじる必要がなかったが、もしうまくいかない場合は環境変数を確認するとよいかも
  - PCに複数のPythonバージョンがインストールされていたり、Anacondaを利用している場合、環境変数あたりで苦戦するはず。

## C++からPythonを利用する
### Boost Python 使用時の作法
- プログラムを作成する際に気を付ける点をまとめる
    - プログラムの初めに `Py_Initialize();` を、 ~~プログラムの終わりに`Py_Finalize();`~~ (必須ではない）をつけること
    - Boost::numpy を使用する際は、np::initialize(); もつけること

   - プログラム全体を try-catchで囲み、キャッチした例外をPrint()できるようにすること

        ```c++:main.py
        int main()
        {
            //名前空間
            namespace py = boost::python;
            try {
                //初期化
                Py_Initialize();
                
                // Do something with Python ! //
            }
            catch (py::error_already_set& e)
            {
                // Pythonの例外を標準出力にPrintします
                PyErr_Print();
            }
            Py_Finalize();
        }
        ```

    - ※ 補足
        - Python から 返り値で渡されたnumpy配列の値をC++で取得しようとすると変な値になった。要確認。
        - BoostPythonで実行中、Pythonのカレントディレクトリは、C++ソースファイルの場所になることに注意。（ファイルパスが通らない場合確認のこと）

## 参考記事
- [C++ で Python を実行 on Visual Studio 2017](https://qiita.com/yusa0827/items/ebc0e606b31604d45712)
- [Anaconda仮想環境について](https://qiita.com/yakisobamilk/items/867dce8e53824146ce05)
- [BootStrapインストール方法](https://gist.github.com/UnaNancyOwen/d879a41710e9c05025f8)
- [Python 32bit と64bit の話](https://gammasoft.jp/support/python-32bit-64bit/)