WindowsでもCythonを使いたい！

## はじめに

普段はLinuxのjupyterで開発をしています。
excelファイルを作成したくなりまして、その際に生成したファイルをjupyter上でダウンロードして確認するという手順がどうにも効率悪いため、windowsのjupyterで開発したくなりました。

しかし、開発中のpythonプログラムはCythonを使っていたのです。

まあ、きっと何とかなるだろうと思いそのままの手順でやってみたのですが、躓くところが多々ありましたので、メモ代わりに残しておきます。

## 「Unable to find vcvarsall.bat」

まず最初に出てくるのがこちらのエラー。
Windows上のコンパイル環境を自動的に探して設定しようとしていますが、検索に失敗すると発生します。
vcvarsall.batに記述してある以下のパスの内容を取得しています。

|環境変数名|意味|
|:-----------|:------------|
|path|コンパイラのパス(cl.exe,link.exe,lib.exe,rc.exe,mc.exe,mt.exe)|
|include|インクルードパス|
|lib|ライブラリパス|
|py_vcruntime_redist|vcruntime140.dllをフルパスで指定。指定なしの場合はLIBCMT.libを使用する。|

Windowsに開発環境（VSとか）をインストールしていないと確実に出るかと思います。

「Unable to find vcvarsall.bat」の対処法
https://www.regentechlog.com/2014/04/13/build-python-package-on-windows/

今回は、Visual Studio 2017をインストールすることで回避できました。

標準インストールするとこちらに格納されます。

"C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat"

他のバージョンでも回避できるかもしれません。
どうしても回避できない場合は、DISTUTILS_USE_SDKを定義して、前出の環境変数を設定してから実行すればよいはずです。

## 「fatal error C1083: include ファイルを開けません。'io.h': No such file or directory」

自分の環境では発生しませんでした。
C:\Program Files (x86)\Windows Kits\10\Include\10.0.16299.0\ucrt
にありました。
多分、Windows10用のモジュール作成用をインストールするとできるみたいです。

## 「failed with exit status 2」

cythonの自動ビルド用の以下を利用している場合に発生します。

```py
import pyximport
pyximport.install()
from ほげほげ import ほげほげ
```

いったい何が原因かさっぱりわからないかと思いますが、あわてず騒がず、Jupyter Notebookの黒い画面を確認してみてください。
コンパイル時のエラーが出力されているはずです。

## 「マクロ 'min' に指定された実引数の数が多すぎます。」と構文エラー

windows.hにmin,maxがマクロとして定義されているため、エラーになるそうです。

windows.hのmin/maxマクロ回避策４パターンAdd StarUSAGI-WRP
http://d.hatena.ne.jp/yohhoy/20120115/p1

今回は自作のpythonクラスで、minメソッドとmaxメソッドを作成しており、それが勝手にマクロ展開されてしまいました。

なので、/DNOMINMAXコンパイルオプションを追加します。

Cythonで、pyximportでも、コンパイルオプションを付与したい
https://qiita.com/kei0425/items/a278cfc47a8e966fe939

```python:ほげほげ.pyxbld
import numpy

def make_ext(modname, pyxfilename):
    from distutils.extension import Extension
    ext = Extension(name = modname,
        sources=[pyxfilename],
        extra_compile_args=['/DNOMINMAX'],
        include_dirs = [numpy.get_include()])
#        extra_link_args[['-Lpath', '-lcustomlib'])
    return ext

#def make_setup_args():
#    return dict(script_args=['--verbose'])
```

それでも実はうまくいきませんでした。
io.hがC:\Program Files (x86)\Windows Kits\10\include\10.0.16299.0\ucrt\stdlib.hをインクルードしており、そちらでは、NOMINMAX関係なしにマクロmin,maxを定義しているためです。

```c:stdlib.h
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Non-ANSI Names for Compatibility
//
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#if _CRT_INTERNAL_NONSTDC_NAMES

    #ifndef __cplusplus
        #define max(a,b) (((a) > (b)) ? (a) : (b))
        #define min(a,b) (((a) < (b)) ? (a) : (b))
    #endif
```

ここで、残念ながらあきらめて、ほげほげ.cを作成した後に、手で#undef minをソースに埋め込みました。

出来上がった、pydファイルを配布すればまあだれでも利用できます。

## おわりに

完全に解決できず残念でしたが、いろいろ調べて勉強になりました。

## 追記

その後、Cythonの出力を調べたところ、以下の条件で、記述した名前がそのまま利用されることが分かりました。
（基本的には、CythonはCに変換するため、名前空間がないため、元の名前と異なる複雑な名前に変換されます。）

1. クラスメソッド
2. cpdef で定義した場合。

さらに、min,maxのような名前を使ってしまったのが敗因です。

Cythonはクラスをstructで表現しているため、cpdefで定義した名前がそのままメンバ名になっており、C用に定義されていました。python用はそのC用のメソッドを呼び出しているため、max(なんとか)と記述しているのでマクロ展開されます。

Windows環境でしか起きないですが、移植する可能性が０でない限りmin,max単体の名前は付けないほうがよさそうです。