D言語のWindows64bit環境に関して

# はじめに

今日ではメモリが8GBだの16GBだの搭載されているPCが常識になってきており、徐々に32bitのOSの市民権はなくなってきている。
言うに及ばず、64bitのマシンの性能を最大限に引き出せるのは64bitのOSであり、64bitのOSの性能を最大限に引き出せるのはやはり、64bitのユーザーアプリケーションなのである。
タスクマネージャを起動してみよう。

<img src="https://qiita-image-store.s3.amazonaws.com/0/11147/22b19976-09e1-d4fc-25a0-27ec863fcace.png" width="600">

<img src="https://qiita-image-store.s3.amazonaws.com/0/11147/7ef22485-4869-8357-6451-90bfde432305.png" width="640">

時代に置いてけぼりにされたままいつまでも32bitのアプリケーション。馬鹿にされるのも必定というものである。

さて、D言語はWindowsでも64bit環境をサポートしている。
そもそもここで言う64bit対応というのは、ターゲット環境(プログラムを実行する環境)が64bitのWindowsな実行ファイルやDLL等が作成できること、である。
具体的に64bit対応は何ができればいいかというと、コンパイラ、リンカ(ライブラリアン)が64bitの機械語を吐ければいいわけである。

64bit対応以前のD言語(Windowsのdmd)は、Digital Marsが作成した自前のC++コンパイラ(dmc)時代から使われていた自前のリンカ(link.exe)やライブラリアン(lib.exe)を使用していた。
このため、コンパイラ(dmd.exe)が64bitに対応しても、それを実行ファイルやDLL、LIBファイルに落としこむリンカやライブラリアンが64bit化できなければ完全対応とはいかなかった。
このせいで、Windowsの64bit対応はPOSIX系OSに比べると遅れた。
これに対応するため、Windows環境での64bit対応には、Microsoftが無償で提供しているVisualStudioやWindowsSDK付属のリンカやライブラリアンを使用することで解決するよう舵をとったようだ。
さて、Microsoftのリンカやライブラリアンを使用すると言ってもdmdの吐いた機械語がすぐにこれらで使用できるかというと、そんなことはなかった。
Microsoftのコンパイラ(cl.exe)が吐き出す機械語のオブジェクトファイル(objファイル)はCOFF形式であり、DigitalMarsのコンパイラ(dmc/dmd)が吐き出す機械語のオブジェクトファイルはOMF形式である。
dmdが吐いたOMF形式のオブジェクトファイルをCOFF形式を取り扱うリンカやライブラリアンに渡しても「取り扱えない形式だ」といって突っぱねられてしまう。
そこで、dmdはOMFではなく、COFF形式のオブジェクトファイルを吐き出せるようにした。
これでdmdでD言語のソースファイル(.dファイル)をCOFF形式のオブジェクトファイル(.objファイル)にして、それをMicrosoft製のリンカ(link.exe)に渡して実行ファイル(.exeファイル)にすることが可能に……すなわち、64bit対応が可能となったわけである。

この記事では、64bit対応のD言語環境の構築方法についてまとめる。
VisualStudioは2015、WindowsSDKは10をインストールした環境を想定して説明する。
具体的には、以下について説明。

* 通常インストールする場合の環境構築方法
* 64bit対応に関する環境設定
* 64bit実行ファイル(.exe)の作成
* 64bit GUIツールのコンパイル方法
* (付録1) dmdのコンパイル
* (付録2) druntime/phobosの64bitコンパイル

# 通常インストールする場合の環境構築方法
WindowsSDKやVisualStudioがインストールされた状態で、dmdのインストーラーでインストールすることで64bitコンパイルが出来るようになる。
予めVisualStudioやWindowsSDKをインストールしておくことで、以下に述べる環境設定をインストーラーが自動で行ってくれるのだ。

* [Windows 10 SDK](https://developer.microsoft.com/ja-jp/windows/downloads/windows-10-sdk)
* [dmd](http://dlang.org/download.html#dmd)


# 64bit対応に関する環境設定
具体的には、リンカ(Microsoftのlink.exe)の場所や、インポートライブラリ(.lib)の場所についての設定方法を説明する。
Windowsのdmdでは、これらの設定は、```sc.ini```というファイルによって行う。
```dmd.exe```の保存されているフォルダを開くと、同じ場所に```sc.ini```というファイルがある。これを編集することで設定を変更することができる。

```ini
[Version]
version=7.51 Build 020


; environment for both 32/64 bit
[Environment]
DFLAGS="-I%@P%\..\import"

; optlink only reads from the Environment section so we need this redundancy
; from the Environment32 section (bugzilla 11302)
LIB="%@P%\..\lib"


[Environment32]
LIB="%@P%\..\lib"
LINKCMD=%@P%\link.exe


[Environment64]
LIB="%@P%\..\lib64"

;
;
; (以下略)
```

上記が```sc.ini```ファイルの例である。

※ これ以降の記事は推測で書いています、間違いがあったら指摘していただけるとありがたいです。

INIファイルは、複数のセクションと、各セクションに配置されるパラメータによって構築される。

| セクション名         | 説明 |
|----------------------|-----|
|```[Environment]```  | 共通設定項 |
|```[Environment32]```| OMF形式の32bitコンパイル時に使用 |
|```[Environment64]```| COFF形式の64bitコンパイル時に使用 |
|```[Environment32mscoff]```| COFF形式の32bitコンパイル時に使用 |

## [Environment] セクション
環境変数に指定されたパラメータを追加するらしいセクション。

<dl>
<dt> DFLAGSパラメータ </dt>
<dd> dmdに渡すデフォルトのコンパイルスイッチを記述する。 </dd>
<dt> LIBパラメータ</dt>
<dd> <code>%@P%</code>は<code>sc.ini</code>入っているフォルダで、.libファイルをまとめて入れておくフォルダを指定する。私はとりあえず32bitのOMF形式のファイルがまとめてあるところを指定しているが、本当にこれであっているかは不明。[Environment32]セクションに移動したが、うまく行かなかった……？どうもコメントを見る限り、OPTLINK(DigitalMars製のリンカ(link.exe))が参照しているよう。</dd>
</dl>

## [Environment32] セクション
dmdでOMF形式のコンパイル(従来の32bitコンパイル)を行う場合に使用される(と思われる)セクション。

<dl>
<dt> LIBパラメータ</dt>
<dd> 私はとりあえず[Environment]セクションのものと同じものを記載した。</dd>
<dt> LINKCMDパラメータ </dt>
<dd> DigitalMars製のリンカ(link.exe)の位置を記載。例のごとく <code>%@P%</code>は<code>sc.ini</code>の入っているフォルダに置き換わるので、同じ場所にlink.exeが居ることを確認して <code>%@P%\link.exe</code> とか書いておけばいいと思う。</dd>
</dl>

## [Environment64] セクション
今回のメインとなる、64bit COFF形式でコンパイルを行う場合に使用される(と思われる)セクション。

<dl>
<dt> LIBパラメータ </dt>
<dd> インポートライブラリのフォルダを指定。ここで、64bit COFF形式のインポートライブラリが入っている場所も指定する。ところで、sc.ini の各セクションには、同じパラメータを複数記載することができ、2回めのパラメータには1回めのパラメータを<code>%LIB%</code>という感じで書くことで使用することができる。<br />
デフォルトの<code>sc.ini</code>には、次のような感じでたくさん指定があった。WinSDKの6～10あたりで、とりあえずどれかにヒットすればいいからと、当てずっぽうで指定しまくっているようだ。<br />
<ul>
<li> <code>"%@P%\..\lib64"</code> </li>
<li> <code>%LIB%;"%VCINSTALLDIR%\lib\amd64"</code> </li>
<li> <code>%LIB%;"%UniversalCRTSdkDir%\Lib\%UCRTVersion%\um\x64"</code> </li>
<li> <code>%LIB%;"%UniversalCRTSdkDir%\Lib\%UCRTVersion%\ucrt\x64"</code> </li>
<li> <code>%LIB%;"%WindowsSdkDir%\Lib\winv6.3\um\x64"</code> </li>
<li> <code>%LIB%;"%WindowsSdkDir%\Lib\win8\um\x64"</code> </li>
<li> <code>%LIB%;"%WindowsSdkDir%\Lib\x64"</code> </li>
<li> <code>%LIB%;"%DXSDK_DIR%\Lib\x64"</code> </li>
</ul>
</dd>
<dt> LINKCMDパラメータ </dt>
<dd> VisualStuduioのリンカを指定する。<br />
<code>LINKCMD=%VCINSTALLDIR%\bin\link.exe</code>
</dd>
<dt> DFLAGSパラメータ </dt>
<dd> デフォルトで指定したものを上書きしなければならないご様子。<code>%DFLAGS% -L/OPT:NOICF</code>とか指定しておかないと怒られるバグがあるらしい？ </dd>
<dt> VCINSTALLDIRパラメータ </dt>
<dd> VisualStudio(MSVC)のインストールディレクトリを指定。VisualStudio2015の場合、デフォルトだと<br />
<code>C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC</code><br />
</dd>
<dt> WindowsSdkDirパラメータ </dt>
<dd> Windows SDKのインストールディレクトリを指定。Windows SDK 10.0 の場合<br />
<code>C:\Program Files (x86)\Windows Kits\8.1</code><br />
たぶんWindowsのターゲットOSによって変える……のかな？
</dd>
<dt> UniversalCRTSdkDirパラメータ </dt>
<dd> Cランタイム(?)のインストールディレクトリを指定。Windows SDK 10.0 の場合<br>
<code>C:\Program Files (x86)\Windows Kits\10</code>
</dd>
<dt> UCRTVersionパラメータ </dt>
<dd> Cランタイムのバージョン(?)を指定。Windows SDK 10.0 の場合、
<code>10.0.10240.0</code> や <code>10.0.10586.0</code> 等。<br />
<code>C:\Program Files (x86)\Windows Kits\10\Include</code><br />
の中に入っているフォルダを確認してバージョンを指定した。
</dd>
</dl>

## [Environment32mscoff] セクション
32bit番のCOFF形式の指定。[Environment64]と同じ感じで書けば良い。

# 64bit実行ファイル(.exe)の作成
いつも通りのコンパイルオプションの指定に加えて、 -m64 コンパイルスイッチを追加する。

-m64スイッチは、有効にすることでコンパイラの出力するオブジェクトファイルが64bitのCOFF形式になり、使用するインポートライブラリ(.lib)も64bitのCOFF形式でなければならなくなる。
この時、ライブラリで使用しているSDKのバージョンが違うなどするとリンカに怒られることがある。
_MSC_VER がアプリケーションの方は1900だけど、1800でコンパイルしたライブラリとマッチしなくてダメですよ、みたいなエラーが出たりする。この場合ライブラリのほうをコンパイルし直すことで解決する。(ちなみに、私はOpenCVのインポートライブラリでこれが発生した)

```d
import std.stdio;

void main()
{
	version (Win64)
		writeln("Hello, 64bit world.");
	version (Win32)
		writeln("Hello, 32bit world.");
}
```

```bat
> dmd -m64 -run main.d
Hello, 64bit world.
```

ちなみに、dubを使用して64bitコンパイルをする場合、`--arch=x84_64`のオプションを指定してやれば良い。

```
dub build --arch=x84_64 -f -b=release
```


# 64bit GUIツールのコンパイル方法

従来、32bitでコンパイルする場合でGUIツールを作るときには、コマンド画面が出ないよう、dmdに　`-L/exet:nt/su:windows:4.0` としてリンカオプションを指定する方法が[知られている](http://www.kmonos.net/alang/wnd/topics/winapi.ja.html)。

ここで、このリンカオプションはDigitalMars謹製のリンカ用であるため、Microsoftのリンカでは通用しない。

```
> dmd -m64 -L/exet:nt/su:windows:4.0 main
LINK : warning LNK4044: オプション '/exet:nt/su:windows:4.0' は無効です。無視されます。
```

無視される(悲しい)。

`-m64`を使ってMicrosoft製のリンカで同じようなことをする場合、代わりに `-L/SUBSYSTEM:WINDOWS -L/ENTRY:mainCRTStartup` を指定してやることで同様の効果が得られる。

```
> dmd -m64 -L/SUBSYSTEM:WINDOWS -L/ENTRY:mainCRTStartup main
```


# まとめ
* dmdで64bitコンパイルする場合、Microsoftのリンカ・ライブラリアンが使われ、それらを使うために `sc.ini` ファイルで各種環境設定を行う
* `-m64` をdmdに指定すると64bitコンパイルができる。dubの場合は`--arch=x84_64`
* `-L/SUBSYSTEM:WINDOWS -L/ENTRY:mainCRTStartup` をdmdに指定すると64bitでGUIツールが作れる


# (付録1) dmdのコンパイル
今現在D言語のコンパイラのdmdはD言語によって書かれている。
したがって、64bit対応したdmdでdmdをコンパイルすることができれば、64bit対応したdmd.exeを作成することが出来る。
実際、dmdのソースの中にwin64.makというファイルが存在するので、これを使用してコンパイルすれば、64bit版のコンパイラを作成することが出来る(多分)。
しかしながら、これを作成することにはあまり有り難みがない。32bit版のコンパイラでもD言語のソースコードは64bit COFF形式でコンパイルすることができるからである。

# (付録2) druntime/phobosの64bitコンパイル
dmdは64bit版をコンパイルする必要はないが、druntime/phobosは(これを改造したり、最新版を検証したりしたい場合)コンパイルする必要がある。

ここにもやはりwin64.makファイルがあるので、これを使ってコンパイルすれば良い。
ただし、どうやら現在そのままではコンパイルに失敗する場合があるようだ。

```
etc\c\zlib\zconf.h(421): fatal error C1083: include ファイルを開けません。'sys/types.h':No such file or directory
```

問題は、zlibのコンパイル中、WindowsSDKのCランタイムのヘッダファイルが見つからないというものだ。
どうもzlibのコンパイルにはVisualStudio2010(VC10)を想定しているらしい？

下記のように使えるVisualStudio(WinSDK)へのパスをきちんと指定するよう、makefileを書き換えることでコンパイルできるようになる。

```basemake:etc/c/zlib/win64.mak
# Makefile for zlib64

MODEL=64
VCDIR=\Program Files (x86)\Microsoft Visual Studio 14.0\VC

CC="$(VCDIR)\bin\amd64\cl"
LD="$(VCDIR)\bin\amd64\link"
LIB="$(VCDIR)\bin\amd64\lib"

CFLAGS=/O2 /nologo /I"$(VCDIR)\INCLUDE" /I"C:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\ucrt"

# (以下省略)
```

以下に私がdruntimeやphobosをコンパイルする際のバッチ処理を記載しておく。

```bat

@set HOSTDMD_ROOT=%CD%\env
@set HOSTVC_DIR=C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC
@set VCINSTALLDIR=%HOSTVC_DIR%
@set HOSTWINSDK_DIR=C:\Program Files (x86)\Windows Kits\10
@set HOSTEXTINCLUDE_DIR=C:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\ucrt
@set DMDTARGET=release

pushd druntime
make -f win64.mak import copydir copy VCDIR="%HOSTVC_DIR%" SDKDIR="%HOSTWINSDK_DIR%" CFLAGS="/Z7 /nologo /I\"$(VCDIR)\INCLUDE\" /I\"$(SDKDIR)\Include\" /I\"%HOSTEXTINCLUDE_DIR%\""
popd

pushd phobos
make -f win64.mak LIB=phobos.lib DFLAGS="-conf= -m$(MODEL) -O -release -w -dip25 -I$(DRUNTIME)\import" VCDIR="%HOSTVC_DIR%" SDKDIR="%HOSTWINSDK_DIR%"
```