複数バージョンのVisual Studioで開けるC++のプロジェクトを作る


解決策1: CMakeをつかう

まあ一番確実だし、他のコンパイラでも使えるしコンパイラのバージョンなんでいうわかりにくい指標ではなく対応している言語機能で制約することもできる(らしい)が、まあそれは他のサイトに解説を譲る。

解決策2:あえてCMakeを使わないという選択肢

今回の本題。

Condition attribute

C++プロジェクトの実体は.vcxprojという拡張子のxmlファイルである。xmlのattributeをハックして条件分岐できるようになっている、いわゆるCondition attributeだ、汚いです。

こいつを活用する。

複数バージョンで利用するときの障害となるのはなにか

そもそもなんで普通に作ったプロジェクトが他のVisual Studioのバージョンで開けないのかを確認する。

まあ理由は簡単でPlatform Toolsetが指定されているものと開いたVSの持っているVC++のバージョンが合わないと「アップグレードしますか?」というダイアログが出るわけだ。

C++ Property Pages - List of all C++ Platform Toolsets

.vcxprojの中で該当する部分を見てみよう。下のxmlは普通にC++のプロジェクトを作ったときのものである

.vcxproj
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <!-- 中略 -->
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <UseDebugLibraries>true</UseDebugLibraries>
    <PlatformToolset>v141</PlatformToolset>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <UseDebugLibraries>false</UseDebugLibraries>
    <PlatformToolset>v141</PlatformToolset>
    <WholeProgramOptimization>true</WholeProgramOptimization>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <UseDebugLibraries>true</UseDebugLibraries>
    <PlatformToolset>v141</PlatformToolset>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <UseDebugLibraries>false</UseDebugLibraries>
    <PlatformToolset>v141</PlatformToolset>
    <WholeProgramOptimization>true</WholeProgramOptimization>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <!-- 中略 -->
</Project>

自動生成の設定ファイルでもCondition attributeが使われているあたりなんか怖いが、問題となっているのはPlatformToolset tagだ。

PlatformToolsetConditionで分ければいいじゃない

vcxcompat.props
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup Label="Configuration" Condition="'$(VisualStudioVersion)' == '12.0'">
    <PlatformToolset>v120</PlatformToolset>
  </PropertyGroup>
  <PropertyGroup Label="Configuration" Condition="'$(VisualStudioVersion)' == '14.0'">
    <PlatformToolset>v140</PlatformToolset>
  </PropertyGroup>
  <PropertyGroup Label="Configuration" Condition="'$(VisualStudioVersion)' == '15.0'">
    <PlatformToolset>v141</PlatformToolset>
  </PropertyGroup>
</Project>

つまりこういうファイルを作ってこれを読み込めばいい。なんでVisualStudioVersion15.0のときPlatformToolsetv150じゃないんや!という疑問については
Visual C++ 2017のバージョン番号がややこしすぎる件
を参照してほしい。主にはbinary capabilityの問題だ。
上記ではVS2013/VS2015/VS2017に対応させたが、もちろん必要に応じて適宜いじれば良い。

具体的には、これをvcxcompat.propsという名前で(説明の都合上).vcxprojがある場所に(説明の都合上、ただの相対パス指定)UTF-8で(BOMはどっちでも)保存し、.vcxproj

.vcxproj
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <!-- 中略 -->
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
  <Import Project="vcxcompat.props" />
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <UseDebugLibraries>true</UseDebugLibraries>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <UseDebugLibraries>false</UseDebugLibraries>
    <WholeProgramOptimization>true</WholeProgramOptimization>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <UseDebugLibraries>true</UseDebugLibraries>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <UseDebugLibraries>false</UseDebugLibraries>
    <WholeProgramOptimization>true</WholeProgramOptimization>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <!-- 中略 -->
</Project>

というふうに書き換えればよい。何が変わったかはまあdiffればいいと思うが、Import tagが増えて、PlatformToolsetタグが消滅した。

実例

追記

なんかあの有名なサクラエディタの開発者ドキュメントにこの記事のURLを見つけてめっちゃわろてます。 @m-tmatma さんが出したPRがきっかけのようで。わざわざリンクして頂いてありがとうございます。

をみるとわかるように、Windows SDKの切り替えにも活用できます。

まあでも良い子のみんなは新しくプロジェクト始めるときは素直にCMake使おうな?

License

CC BY 4.0