clang+boostでWinアプリ開発

今までWindowsgccとかって面倒そうなので敬遠していたけど、自分の周りが全部Win7 64bitになってしまい、TurboC++/C++Builder2006がXP-modeを入れないと使えなくなってしまった。実際にXP-modeを使ってみたらデータのやり取りが超面倒で死にそうになったので仕方なく移行することにした。
で、bcc64も使っていることだしclangを入れてみることにした。

環境は

  • Win7 Pro 64bit SP1
  • MinGW 20120426(mingw-get-inst-20120426.exe)
  • clang 3.2
  • boost 1.53.0

以下やったことのメモ。

clangのインストール

ここ*1とここ*2を参考にさせてもらってMinGWを入れてからclangを上書き解凍した。ここは特にはまらなかった。
環境変数類は

set MINGW=C:\MinGW
set PATH=%PATH%;%MINGW%\bin
set C_INCLUDE_PATH=%MINGW%\include;%MINGW%\lib\gcc\mingw32\4.6.2\include
set "CPLUS_INCLUDE_PATH=%C_INCLUDE_PATH%;%MINGW%\lib\gcc\mingw32\4.6.2\include\c++;%MINGW%\lib\gcc\mingw32\4.6.2\include\c++\mingw32"

に設定した。
%MINGW%\lib\clang\3.2\include\にもヘッダがあるけど、指定していないが今のところは何も不都合は起きていない。

boostのビルド

これが超大変だった…orz

memoryヘッダ

こちら*3にあるように#includeの先で大量のエラーが吐き出されてビルドどころではない。ことごとくコンパイルエラーになる。参照先に書かれている通りにmemoryヘッダを修正したらコンパイルエラーは消えた。

bjamのバージョン

Webに転がってた3.1.18-1-ntx86ってのを使ってビルドするとmkdirのパスがどうとかとか言われてエラーになっちゃう。
bjamの引数を間違えたかと思ってJamファイルを見まくったけど、そういうことじゃなくて単にbjamのバージョンが古いだけだったらしい。てか、バージョンで挙動変わるのな…
どうやってbjamをビルドしたものか困ったけど、結局boostのトップディレクトリで

bootstrap.bat mingw

ってやったらビルドできた。

bjamの引数

海外の掲示板とか見まくって結局これでビルドした。

bjam --without-python --without-mpi --build-type=complete -j4 link=static runtime-link=static toolset=clang cxxflags=-std=c++11 address-model=32 architecture=x86 define=WINVER=_WIN32_WINNT_WIN7 define=_WIN32_WINNT=_WIN32_WINNT_WIN7

なんとなくdefineとかがいらないような気がするけど、もう一度やり直すのが面倒なので試してない。ビルドはかなり重かった。
i5-3320Mが3.1GHz張り付きで80℃をキープするくらいに。

regex

で、上記引数だとregexがビルド失敗してた。
なんかchar型配列に128以上の数値を入れようとしている箇所で、「暗黙的な切り捨てできるかーヽ(`Д´)ノ」とかなんとかお怒りになってた。

error: constant expression evaluates to -123 which cannot be narrowed to type 'wchar_t' [-Wc++11-narrowing]

とかなんとか。
C++11の機能?かなんかっぽいけど、(´・ω・`)知らんがなってことで

bjam --build-type=complete -j4 link=static runtime-link=static toolset=clang "cxxflags=-std=c++11 -Wno-c++11-narrowing" address-model=32 architecture=x86 define=WINVER=_WIN32_WINNT_WIN7 define=_WIN32_WINNT=_WIN32_WINNT_WIN7 --with-regex

てな感じで「-Wno-c++11-narrowing」をつけたらビルドできた。

math

rmコマンドがないせいでこけてるっぽい。特に使わないのでスルー。
他にもいろいろこけまくってるけど、いらないのでスルー。
stage/libディレクトリに以下の.libができていた。

  • atomic(2ファイルしかない。シングルスレッド版がない?これで正常?)
  • chrono
  • date_time
  • exception
  • graph
  • iostreams
  • prg_exec_monitor
  • random
  • regex
  • serialization
  • signals
  • system
  • thread(2ファイルしかない。シングルスレッド版がない?これで正常?)
  • timer
  • wave
  • wserialization

clangってboostのフルビルドできるって話じゃなかったっけ?Linuxの話?
ま、いいや。

C++プログラム

とりあえず

clang -std=c++11 source_file.cpp -lstdc++

コンパイルできた。実行も大丈夫っぽい。

  • msvcrt.dll
  • libstdc++-6.dll

に依存してるけど、実行はできた。
「-static」をつけるとlibstdc++-6.dllの依存が消えてサイズが2MB以上になる。msvcrt.dllへの依存は残ったまま。まぁVCのランタイムなら大抵のPCには入ってるんじゃない?(適当)

Winアプリ

「-mwindows」をつけるだけで特に問題なさそう。Windowsの.libもたとえば「iphlpapi.lib」は「-liphlpapi」でリンクできた。
ただ、MinGW付属のヘッダがWINVER値がすごく古いっぽいので新しいAPIを使うときには「-DWINVER=0x0501」的(XP相当?の場合)な引数が必要っぽい。
それからC++Builderのヘッダとは定義ヘッダファイルが違うのがあったりする。注意が必要だね。まぁwindows.hをincludeしておけば済むような気もするけど。
で、定義場所がかわったりした関係で最小のwindowsインクルードはこんな感じになりそう。

#include <windef.h>     // 基本的な定義類
#include <stdarg.h>     // winbase.hに必要
#include <winbase.h>    // winuser.hに必要
#include <wingdi.h>     // winuser.hに必要
#include <winnt.h>      // windef, winbaseで足りない分(実はwindefが内部でincludeしてる)

_X86_とかのdefineがいらなくなって、wingdi.hが新たに必要になった。

boost使用プログラム

試しにregexを含んだやつをビルドしてみた。
「-DBOOST_REGEX_NO_LIB」のコンパイルオプションと「libboost_regex-clang32-mt-s-1_53.lib」を絶対パス指定でコンパイルに含めたらビルドできた。

  • Lのstage\libパス指定と「-lboost_regex-clang32-mt-s-1_53」の組み合わせだと.libファイルを見つけてくれなかった。.aファイルじゃないせい?

typeid使用プログラム

type_info::operator==が.cppとlibstdc++で多重定義になるっぽくてリンクでこけちゃう。

C:/Program Files (x86)/mingw/bin/../lib/gcc/mingw32/4.6.2\libstdc++.a(tinfo.o):(.text$_ZNKSt9type_infoeqERKS_+0x0): multiple definition of `std::type_info::operator==(std::type_info const&) const'
C:/Users/hoge/AppData/Local/Temp/WindowsApplication-920352.o:fake:(.text$_ZNKSt9type_infoeqERKS_[std::type_info::operator==(std::type_info const&) const]+0x0): first defined here
collect2: ld returned 1 exit status
clang: error: linker (via gcc) command failed with exit code 1 (use -v to see invocation)

「-fno-weak」をつけてもダメ。というか、#include がなくてもコンパイルは通って、リンクでこける。そもそも==を使った覚えがないんだけど…
いろいろ試した挙句、ldのオプションに「--allow-multiple-definition」があることを発見して「-Wl,--allow-multiple-definition」をclangの引数に追加したらあっさりリンクも通過した。

まとめ

PCのIPアドレスを調査する自作アプリ(regex使用)はこんな感じでビルドできた。
Makefileを作っておけばmingw32-makeでmakeできるっぽい。

set BOOST_TOP=C:\boost_1_53_0
set BOOST_LIBS=%BOOST_TOP%\stage\lib
clang -c -I%BOOST_TOP% -DBOOST_REGEX_NO_LIB -DWINVER=0x0501 -std=c++11 -mwindows regex-ip-app.cpp
clang -o win-test.exe -mwindows -static -Wl,--allow-multiple-definition *.o -liphlpapi %BOOST_LIBS%\libboost_regex-clang32-mt-s-1_53.lib -lstdc++

ちなみにコンパイル時だけじゃなくて、リンク時にも-mwindowsが必要。これがないとコマンドプロンプトの黒いウィンドウが出ちゃう。

まだやってないこと

DLLの作成と外部DLLの呼び出しはまだ試してない。
軽くググった感触だと、呼び出しはVC用.libがあれば単にその.libをコンパイル引数に含めるだけっぽいので楽ちんな模様。
DLLの作成はまじめにやると大変そうな予感…

あと、できるのかどうか知らないけど、64bitバイナリの生成ができるのかどうかやってみたいなぁ。

余談

classのメンバ関数をusingしてたらclangだとコンパイルエラーになっちゃった。

error: using declaration can not refer to class member

とかなんとか。
調べまくったらどうやらC++の規格*4で[namespace.udecl]の8に「ダメな例」でまんま掲載されていた。(正確には掲載されていたのはメンバ変数の方だけど)
理由はよくわからないけど、classメンバを個別にusingするのは文法的にできないっぽい。

「shall be a member-declaration」ってことはclass定義内でのusingはおk?

//8 A using-declaration for a class member shall be a member-declaration.
struct X {
    int i;
    static int s;
};
void f() {
    using X::i; // error: X::i is a class member
                // and this is not a member declaration.
    using X::s; // error: X::s is a class member
                // and this is not a member declaration.
}
class Y : public X{
    using X::i; // 追試。「: public X」してない場合はエラー
};

試してみたら親クラスとして継承した場合は大丈夫だった。



[2013/06/05 00:45追記]
「-static」無し版で実行ファイルを動かした件だけど、どうやらMinGW\binにパスが通ってないとDLLが見つからずにエラーになるらしい。

コンピュータにlibstdc++-6.dllがないため、プログラムを開始できません。この問題を解決するには、プログラムを再インストールしてみてください。

というか、binディレクトリに当該DLLが入ってた。
どうやらsystem32とかに入ってるってわけじゃないみたい。

*1:http://d.hatena.ne.jp/A7M/20120610/1339311486

*2:http://denspe.blog84.fc2.com/blog-entry-174.html

*3:http://d.hatena.ne.jp/xyuyux/20120915/1347682152

*4:自分が参照したのはN3242=11-0012のWorking Draftだけどおそらく正式版と同じと思われる