[備忘録Rust] Windows32bit用DLLをLinuxから作る

# 環境
Rustの公式Dockerイメージを使った。

rustup 1.23.1
rustc 1.49.0


# リンカーとtargetの準備

まず、Windows32bitのDLLおよびEXEを作成するために必要なライブラリやツールなどをまとめたものである**ツールチェイン**を手に入れます。

```bash
rustup target list
//
// （略）
// i586-unknown-linux-gnu
// i586-unknown-linux-musl
// i686-linux-android
// i686-pc-windows-gnu（これが、Win32bit用のやつ）
// i686-pc-windows-msvc
// i686-unknown-freebsd
// （略）
//

rustup target add i686-pc-windows-gnu
```

このtoolchainにはリンカーが含まれていないため、リンカーを別途用意します。
LinuxからWindowsへのクロスコンパイルであれば、Mingw-w64を使うのが一般的。
（というか私はそれ意外知らない。）

```
apt update
apt install mingw-w64
```

これによってインストールされる「i686-w64-mingw32-gcc」がWindows32bit用のリンカーです。

（今更だけど、リンカーとはオブジェクトファイルとかライブラリをつなぎ合わせて、DLLとかEXEにするアプリのこと）

これでDLLを作るための開発環境は整った。

# DLLの作成のためのプロジェクト作成
とりあえず、「sample」という名前でクレートを作成。
そして、ちょろっとプログラムを書き換える。

```
cargo new sample --lib
cd sample/src
cat << EOF > lib.rs
extern fn hello() {
    println!("HELLO");
}
EOF
```

試しに、この状態で普通にコンパイルしてみる。

```
cargo build

//    Compiling sample v0.1.0 (/share/sample)
// warning: function is never used: `hello`
//  --> src/lib.rs:1:11
//   |
// 1 | extern fn hello() {
//   |           ^^^^^
//   |
//   = note: `#[warn(dead_code)]` on by default
//
// warning: 1 warning emitted
//
//     Finished dev [unoptimized + debuginfo] target(s) in 0.15s
```

多分、こんな感じになってコンパイルができると思います。
次に、これをWindows32bit用のDLLとしてコンパイルしてもらおうと思います。

そこで、cargo.tomlで動的ライブラリを作成するように指定します。

```:cargo.toml
[package]
name = "sample"
version = "0.1.0"
authors = ["mutuya"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

// これを追記
[lib]
crate-type = ["cdylib"]

[dependencies]
```

cargo.tomlを編集したら、targetをwindows32bitに指定してコンパイルしてみる。

```
cargo build --target i686-pc-windows-gnu

//    Compiling sample v0.1.0 (/share/sample)
// warning: function is never used: `hello`
//  --> src/lib.rs:1:11
//   |
// 1 | extern fn hello() {
//   |           ^^^^^
//   |
//   = note: `#[warn(dead_code)]` on by default
// 
// error: linking with `i686-w64-mingw32-gcc` failed: exit code: 1
//   |
//   = note: "i686-w64-mingw32-gcc" "-fno-use-linker-plugin" "-Wl,--nxcom　（略）
// 
// /usr/bin/i686-w64-mingw32-ld: /usr/local/rustup/toolchains/1.46.0-x86_64-unknown-linux-gnu/lib/rustlib/i686-pc-windows-gnu/lib/libstd-e4e40256d0c086c9.rlib(std-e4e40256d0c086c9.std.2uozmyed-cgu.0.rcgu.o):/rustc/04488afe34512aa4c33566eb16d8c912a3ae04f9\src\libcore\ptr/mod.rs:184: more undefined references to `_Unwind_Resume' follow
//           collect2: error: ld returned 1 exit status       
// 
// error: aborting due to previous error; 1 warning emitted
// 
// error: could not compile `sample`.
// 
// To learn more, run the command again with --verbose.
// 
```

このようにコンパイルエラーが発生するかと思います。

# 解決策

cargo.tomlに次の内容を追記します。

```
[package]
name = "sample"
version = "0.1.0"
authors = ["mutuya"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
crate-type = ["cdylib"]

// これを追記
[profile.dev]
lto = true

[dependencies]

```

コンパイルしてみます。

```
cargo build --target i686-pc-windows-gnu

//
//    Compiling sample v0.1.0 (/share/sample)
// warning: function is never used: `hello`
//  --> src/lib.rs:1:11
//   |
// 1 | extern fn hello() {
//   |           ^^^^^
//   |
//   = note: `#[warn(dead_code)]` on by default
// 
// warning: 1 warning emitted
// 
//     Finished dev [unoptimized + debuginfo] target(s) in 0.52s
// 
//
```

コンパイルが通りました。

# まとめ
なぜかわからないけど、最適化の１つであるLTOを有効化すると、32bit向けのDLLを作れました。。
ちなみに、64bit向けであれば、ツールチェインとリンカーを用意して、target指定のbuildを行えば作ることができます。

エラーを見ると「_Unwind_Resumeが定義されていない」と書かれていることから、LTOの有効化によって、遠くにあった_Unwind_Resumeがインライン展開されるなどし、定義されてない・見つからないといった問題が消えたのかな？と思わなくもない。

今どき、Windows32bit向けにコンパイルすることが稀なので、あんまり役に立つ情報なのかわからない。。。とりあえず備忘録として記録します。

# 追記
これだけだと、うまく行かない場合があるのがわかりました。

そういうときは、.cargo/config.tomlに次の内容を記述すると改善する場合があるようです。

```toml:.cargo/config.toml
[build.i686-pc-windows-gnu]
rustflags = "-C panic=abort"
```

これで改善されるエラー内容は「_Unwind_Resumeが定義されていない」ですので、このエラーで悩まされて、この方法を試していなかったらやってみる価値はあるかと。。