VSCode（Visual Studio Code）によるWindowsアプリ開発（WPF C# C/C++）

Visual Studioが重い！と思ってる私のような日曜プログラマー向け。

2021年2月現在、 **Visual Studio Code（以下VSCode）** と **Build Tools for Visual Studio 2019（以下BuildTools）** の組み合わせのみで、大抵のWindowsアプリ開発は出来る気がしている。デバッグも可。
本家Visual Studioなんて使わ…何でも無いです。[^comment]

[^comment]: ※歯切れが悪い理由は、[BuildToolsのライセンス](https://visualstudio.microsoft.com/ja/license-terms/mlt031519/ "Microsoft Visual Studio 2019 リモート デバッガー、スタンドアロン プロファイラー、IntelliTrace、スナップショット デバッガー、その他のデバッガーおよびビルド ツール - Visual Studio")を見るに「Visual Studio Community、Visual Studio Professional、Visual Studio Enterprise と共に使用する」とあるから。共に使用する、の解釈…何でも無いです。

:::note
2022年12月現在、BuildToolsも2022になっている。2019に比べて若干ライセンスが緩くなったようだが、相変わらず難解で解釈が難しい。

WPFに関しては.NET SDKを個別に、C++ネイティブに関してはVisual Studio Express 2017 for Windows Desktop（と別途新しめのWindows 10 SDK）を入れることで、BuildToolsを入れずとも（すなわちライセンス問題を気にせず）VSCodeで開発出来るのではと思いチョロっと試してみたらほぼ同様の環境を構築出来そうだったので、ガッツがあったらいずれ記事を書くかもしれない。
（Express2017がもしかしたら入手困難かもしれないが、頑張って検索したらインストーラーの直リンURLを発見出来たので希望は捨てないで！）
:::

## 試したこと
##### WPF（.NET 5以降）
一番開発しやすいと感じた。あとReadyToRunが適用出来る。
もうWindowsUpdateで.NET 5以降のランタイムも同梱してくれんかなあ。（「Windows の更新時に他の Microsoft 製品の更新プログラムを受け取る」をオンにしておけば入ってくるらしいが、どれだけの人がオンにしていることか…。）
WinFormsは今更な感じがするし、UWPは魅力的ではあるけど（ストア限定という点で）自由が無いし、ということでWPF。

##### WPF（.NET Framework 4.7）
4.7な理由は、Creators Update（2017年春）以降のWindows 10には標準でランタイムが入ってるから。
多くの人に使ってもらえる。

##### ネイティブ（C/C++）
個人的には、マネージドでは出来ないことをネイティブなDLLにしてマネージドから呼び出す用。
…と思ってたけど、[C++/WinRT](https://qiita.com/cubeundcube/items/93c210f8516c9468f5e5 "VSCodeでC++/WinRTを使ってネイティブなのにUIがXAMLな単体exeデスクトップアプリを作成する")のおかげでこちらも無視出来なくなってきた。場合によってはメインに返り咲く？

## ダウンロード
VSCodeもBuildToolsも[https://visualstudio.microsoft.com/ja/downloads/](https://visualstudio.microsoft.com/ja/downloads/ "Visual Studio 2019 for Windows および Mac のダウンロード")からGET出来る。
BuildToolsは分かりにくいが、ページの下の方の「> Visual Studio 2019 のツール」を展開したら、これまた下の方にある。

## インストール
#### BuildTools
「個別のコンポーネント」タブにして、下記のものをチョイスして入れた。

##### 【.NET】

* .NET 5.0 ランタイム
* .NET Core 3.1 ランタイム (LTS)
* .NET Framework 4.7 Targeting Pack
* .NET SDK

※Targeting Packは、使う予定のバージョンのものを入れる。
※SDKは「.NET SDK」だけで良い。多分。

##### 【SDK、ライブラリ、およびフレームワーク】

* Windows 10 SDK (10.0.19041.0)
* Windows ユニバーサル CRT

##### 【コード ツール】

* NuGet のターゲットおよびビルド タスク
* テキスト テンプレート変換

##### 【コンパイラ、ビルド ツール、およびランタイム】

* C++ 2019 再頒布可能パッケージの更新プログラム
* MSVC v142 - VS 2019 C++ x64/x86 ビルド ツール (v14.28)

##### 【開発作業】

* C++ Build Tools コア機能
* C++ コア機能

#### VSCode
「追加タスクの選択」で、「エクスプローラーの～追加する」となっている2か所にチェックを入れておくと便利。
![vscode01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1109951/9accd272-13eb-c1ad-467d-37f2a5e77688.png)

## VSCodeの初期設定
#### 最低限必要な拡張機能

* Japanese Language Pack for Visual Studio Code（UIの日本語化）
* C#（拡張機能の名前）
* C++（拡張機能の名前）
* NuGet Package Manager（NuGetを使う場合）

![vscode02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1109951/2e42dfff-f454-96f6-7a7b-07c66caf2a0c.png)
左にある積み木みたいなアイコンが拡張機能。こんな感じで検索して入れる。

#### 設定
VSCodeのデフォルトのエンコーディングはBOM無しUTF-8だが、VSのコンパイラはBOM無しUTF-8で書いたコードに日本語を入れた場合に盛大に文字化けするので、cppやcsharpのファイルは標準でBOM有りUTF-8になるようにしておいた方が良い。（Shift JISでも良いんだろうけど）
「ファイル」→「ユーザー設定」→「設定」で設定画面を開き、右上の書類にくるりと矢印が付いたようなアイコンをクリックすると、VSCodeのユーザー設定ファイル（%AppData%Roaming\Code\User\settings.json）を直接編集できる。
![vscode08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1109951/e6aa1268-1950-a7a3-1591-25d97620999a.png)
ここで記述を追加し、下記のようにする。

```json:settings.json
{
    "（すでに設定があれば、その直下に下記を追記）": "",
    "[cpp]": {
        "files.encoding": "utf8bom",
    },
    "[csharp]": {
        "files.encoding": "utf8bom",
    },
    "[plaintext]": {
        "files.encoding": "shiftjis",
    },
}
```

`"[plaintext]"`の項はオマケ。

その他、配色テーマ・フォントファミリ・フォントサイズ・デフォルトの折り返し設定等お好みで。

## VSCodeの言語共通の下準備
VSCodeは基本、フォルダ単位の管理になるので、適当な作業用フォルダを作っておき、VSCodeで開く。
「ターミナル」→「新しいターミナル」で、右下にターミナル（PowerShell）のペインを出しておく。

## WPF（.NET 5）の開発
#### スケルトン生成
ターミナルにて`dotnet new wpf`を実行すると、カレントフォルダに、空のウインドウを表示するプロジェクトのファイル群が自動生成される。
（ちなみに`wpf`の部分は`console`とか`winforms`にも出来る。）

#### デバッグ
「実行」→「構成の追加」→「.NET Core」で、デバッグ絡みのファイルが自動生成される。カレントフォルダに「.vscode」フォルダが、さらにその中に「launch.json」（デバッグ設定）と「tasks.json」（ビルド等設定）が出来ている。
.NET 5の場合、すでに必要な設定は記述してあるので、いじらなくてもそのままデバッグ出来る状態ではあるが、launch.json内の`"OS-COMMENT1"`等から始まる行は不要なので削除可。
「F5」キーを押すか、「実行」→「デバッグの開始」で、未保存のファイルが保存されたのち、ビルド→デバッグが実行される。

#### リリースビルド時にデバッグ情報が入らないようにする
プロジェクトファイル（.csproj）の`<PropertyGroup>`タグ内に、下記の要素を追加。
`<DebugType Condition="'$(Configuration)' == 'Release'">none</DebugType>`

#### 発行
ターミナルにて下記コマンドを実行。
`dotnet publish -c:Release -r:win10-x86 -p:PublishReadyToRun=true -p:PublishSingleFile=true --self-contained:false`

それぞれのスイッチの意味は

* `-c:Release` … リリースビルド
* `-r:win10-x86` … ターゲットをWindows 10 32bitにする（64bitアプリを作る場合は`-r:win10-x64`にする）
* `-p:PublishReadyToRun=true` … 事前コンパイル方式にする、つまり初回から起動が速くなる
* `-p:PublishSingleFile=true` … 出力ファイルを1つのexeファイルにまとめる
* `--self-contained:false` … ランタイムを含めない

なお、`--self-contained:true`にしてさらに`-p:IncludeNativeLibrariesForSelfExtract=true`も加えると、ランタイム込みの単一exeが爆誕するが、サイズはえらいことになる。

#### 発行コマンドをタスクに登録
「tasks.json」に下記のような感じで追記。

```json:tasks.json
{
	"version": "2.0.0",
	"tasks": [
		{ "（中略、下記追加）": "" },
		{
			"label": "publish(x86)",
			"command": "dotnet",
			"type": "process",
			"args": [
				"publish",
				"-c:Release",
				"-r:win10-x86",
				"-p:PublishReadyToRun=true",
				"-p:PublishSingleFile=true",
				"--self-contained:false",
			],
			"problemMatcher": "$msCompile"
		},
		{"（追加ここまで）": ""}
	]
}
```

「ターミナル」→「タスクの実行」で、上記`"label"`に設定した「publish(x86)」を選ぶと登録したタスクが実行される。

## WPF（.NET Framework 4.7）の開発
大まかな流れは前項の.NET 5と同じだが、4.7で動くようにいろいろいじる必要がある。

#### スケルトン作成
ターミナルにて `dotnet new wpf` を実行したのち、プロジェクトファイルを修正。

* `<Project Sdk="Microsoft.NET.Sdk">`を`<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">`に変更
* `<TargetFramework>net5.0-windows</TargetFramework>`を`<TargetFramework>net47</TargetFramework>`に変更

`net47`の部分は「TFM」というらしく、一覧表が[MS公式のページ](https://docs.microsoft.com/ja-jp/dotnet/standard/frameworks "SDK スタイル プロジェクトでのターゲット フレームワーク - .NET | Microsoft Docs")に載っている。もちろんBuildToolsのインストール時に、該当バージョンのTargeting Packをインストールしておく必要がある。

また、割と新しめの名前空間（`System.Net.Http`とか）を使おうとすると「存在しません」とか言われて怒られるので、プロジェクトファイルの`<PropertyGroup>`タグと同じ階層に下記を追加する。

```xml
  <ItemGroup>
    <Reference Include="System.Net.Http"/>
  </ItemGroup>
```

上記を全部適用して、ついでにリリースビルド時にデバッグ情報が入らないようにする場合、プロジェクトファイルは下記のようになる。

```xml:.csproj
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net47</TargetFramework>
    <UseWPF>true</UseWPF>
    <DebugType Condition="'$(Configuration)' == 'Release'">none</DebugType>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System.Net.Http"/>
  </ItemGroup>

</Project>
```

#### デバッグ
「実行」→「構成の追加」→「.NET Core」で、「launch.json」は作られるが、「tasks.json」が作られないので、「ターミナル」→「タスクの構成」と進み「テンプレートから tasks.json を生成」「.NET Core」を選択していき、「tasks.json」を生成させる。
「launch.json」がそのままでは動かないので、下記を修正。

* `"type"`の値を、`"coreclr"`から`"clr"`に変更（2か所あると思う）
* `"program"`の値を、`"${workspaceFolder}/bin/Debug/net47/（プロジェクト名）.exe"`に変更

プロジェクト名（.csprojの拡張子の前の部分）が「net47test」の場合、「launch.json」は下記のようになる。

```json:launch.json
{
	"version": "0.2.0",
	"configurations": [
		{
			"name": ".NET Core Launch (console)",
			"type": "clr",
			"request": "launch",
			"preLaunchTask": "build",
			"program": "${workspaceFolder}/bin/Debug/net47/net47test.exe",
			"args": [],
			"cwd": "${workspaceFolder}",
			"console": "internalConsole",
			"stopAtEntry": false
		},
		{
			"name": ".NET Core Attach",
			"type": "clr",
			"request": "attach",
			"processId": "${command:pickProcess}"
		}
	]
}
```

#### 発行
各種最適化オプションが効かないので、発行コマンドはシンプルになる。
`dotnet publish -c:Release -r:win10-x86`

## ネイティブ（C/C++）の開発
#### スケルトン作成
スケルトンを自動で作ってくれるコマンドは無さそうなので自力で作成する。
プロジェクトファイル（.vcxproj）は下記のような感じ。

```xml:cpptest.vcxproj
<Project DefaultTargets="Build" ToolsVersion="16.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
	<ItemGroup>
		<ProjectConfiguration Include="Debug|Win32">
			<Configuration>Debug</Configuration>
			<Platform>Win32</Platform>
		</ProjectConfiguration>
		<ProjectConfiguration Include="Release|Win32">
			<Configuration>Release</Configuration>
			<Platform>Win32</Platform>
		</ProjectConfiguration>
		<ProjectConfiguration Include="Debug|x64">
			<Configuration>Debug</Configuration>
			<Platform>x64</Platform>
		</ProjectConfiguration>
		<ProjectConfiguration Include="Release|x64">
			<Configuration>Release</Configuration>
			<Platform>x64</Platform>
		</ProjectConfiguration>
	</ItemGroup>
	<Import Project="$(VCTargetsPath)\Microsoft.Cpp.default.props"/>
	<PropertyGroup>
		<ConfigurationType>Application</ConfigurationType>
		<PlatformToolset>v142</PlatformToolset>
		<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
		<CharacterSet>Unicode</CharacterSet>
		<IntermediateOutputPath>obj\$(Configuration)\$(Platform)\</IntermediateOutputPath>
		<OutDir>bin\$(Configuration)\$(Platform)\</OutDir>
	</PropertyGroup>
	<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props"/>
	<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
		<ClCompile>
			<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
		</ClCompile>
		<Link>
			<GenerateDebugInformation>false</GenerateDebugInformation>
		</Link>
	</ItemDefinitionGroup>
	<ItemGroup>
		<ClCompile Include="*.cpp"/>
	</ItemGroup>
	<ItemGroup>
		<ClInclude Include="*.h"/>
	</ItemGroup>
	<ItemGroup>
		<ResourceCompile Include="*.rc"/>
	</ItemGroup>
	<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Targets"/>
</Project>
```

* 序盤の`<ItemGroup>`の部分はVSで使う設定らしいが、これを入れておかないとビルド時に怒られる。
* 3か所ある`<Import>`タグはほぼおまじない。かつそれぞれその位置にある必要があるっぽい。
* `<ConfigurationType>`が`Application`になっているが、これはEXE作成用。DLLを作る場合は`DynamicLibrary`、静的ライブラリーを作る場合は`StaticLibrary`にする。
* `<CharacterSet>`は、マルチバイトの場合は`MultiByte`にする。
* `<RuntimeLibrary>`は、ランタイムを別にしたい場合は`MultiThreadedDLL`にする。
* `<ClCompile ～`や`<ClInclude ～`の対象は、ワイルドカードが使える。

プロジェクトファイルはもうこれ全体がおまじないだと思って、必要な部分だけ変更するような感じでも差し支え無いと思う。
あとは適当に\*.cppとか\*.hとか書けばビルド通るはず。

なお、プロジェクトファイルのファイル名を「cpptest.vcxproj」にすると、プロジェクト名は自動的に「cpptest」となり、ビルドして生成される実行ファイルもデフォルトで「cpptest.exe」となる。（もちろんどちらもスクリプト内で変更可能…のはず。）

#### デバッグ
「実行」→「構成の追加」→「C++（Windows）」→「既定の構成」で「launch.json」が、「ターミナル」→「タスクの構成」→「テンプレートから tasks.json を生成」→「MSBuild」で「tasks.json」が生成される。

「tasks.json」は、`"command"`の値を`"C:/Program Files (x86)/Microsoft Visual Studio/2019/BuildTools/MSBuild/Current/Bin/MSBuild.exe"`のようにMSBuild.exeのフルパスで指定する。なおパスの区切りはバックスラッシュ（半角￥）じゃなくて通常のスラッシュでOK。

```json:tasks.json
{
	"version": "2.0.0",
	"tasks": [
		{
			"label": "build",
			"type": "shell",
			"command": "C:/Program Files (x86)/Microsoft Visual Studio/2019/BuildTools/MSBuild/Current/Bin/MSBuild.exe",
			"args": [
				"/property:GenerateFullPaths=true",
				"/t:build",
				"/consoleloggerparameters:NoSummary"
			],
			"group": "build",
			"presentation": {
				"reveal": "silent"
			},
			"problemMatcher": "$msCompile"
		}
	]
}
```

「launch.json」は`"preLaunchTask": "build",`を追加するのと、`"program"`の値を`${workspaceFolder}/bin/Debug/Win32/（プロジェクト名）.exe`のような感じで、デバッグ実行ファイルのパスで指定。
また、`"console"`の値はコンソールアプリの場合は`externalTerminal`のままで良い（デバッグ時に別途コマンドプロンプトが立ち上がるので便利）が、GUIアプリの場合は邪魔なので`internalConsole`に変更しておく。
プロジェクト名が「cpptest」の場合、「launch.json」は下記のようになる。

```json:launch.json
{
	"version": "0.2.0",
	"configurations": [
		{
			"name": "(Windows) 起動",
			"type": "cppvsdbg",
			"request": "launch",
			"preLaunchTask": "build",
			"program": "${workspaceFolder}/bin/Debug/Win32/cpptest.exe",
			"args": [],
			"stopAtEntry": false,
			"cwd": "${workspaceFolder}",
			"environment": [],
			"console": "internalConsole"
		}
	]
}
```

#### ビルド
ビルドは「MSBuild.exe」で行う。
ターミナルにて下記コマンドを実行。
`& "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\MSBuild.exe" /p:Configuration=Release /p:Platform=Win32`
（ターミナルがPowerShellなので、フルパスのコマンドを実行するには頭に「&」が要る。という理解で合ってる？）

もちろんタスクに登録も可。
というか、先述のデバッグも当然実行前にデバッグビルドしているわけで、「tasks.json」にリリースビルド用の記述を追加すればOK。
一応下記に、リリース用とリビルド用の設定を追加した「tasks.json」を載せておく。

```json:tasks.json
{
	"version": "2.0.0",
	"tasks": [
		{
			"label": "build",
			"type": "shell",
			"command": "C:/Program Files (x86)/Microsoft Visual Studio/2019/BuildTools/MSBuild/Current/Bin/MSBuild.exe",
			"args": [
				"/property:GenerateFullPaths=true",
				"/t:build",
				"/consoleloggerparameters:NoSummary"
			],
			"group": "build",
			"presentation": {
				"reveal": "silent"
			},
			"problemMatcher": "$msCompile"
		},
		{
			"label": "release",
			"type": "shell",
			"command": "C:/Program Files (x86)/Microsoft Visual Studio/2019/BuildTools/MSBuild/Current/Bin/MSBuild.exe",
			"args": [
				"/property:GenerateFullPaths=true",
				"/t:build",
				"/consoleloggerparameters:NoSummary",
				"/p:Configuration=Release",
				"/p:Platform=Win32"
			],
			"group": "build",
			"presentation": {
				"reveal": "always"
			},
			"problemMatcher": "$msCompile"
		},
		{
			"label": "rebuild",
			"type": "shell",
			"command": "C:/Program Files (x86)/Microsoft Visual Studio/2019/BuildTools/MSBuild/Current/Bin/MSBuild.exe",
			"args": [
				"/property:GenerateFullPaths=true",
				"/t:rebuild",
				"/consoleloggerparameters:NoSummary"
			],
			"group": "build",
			"presentation": {
				"reveal": "always"
			},
			"problemMatcher": "$msCompile"
		}
	]
}
```

これで、VSCodeのメニュー「ターミナル」→「ビルド タスクの実行」で、「release」を選択すればリリースビルドが、「rebuild」を選択すれば（デバッグの）リビルドが実行される。

生成物は、（今回のvcxprojの場合）プロジェクトフォルダの「bin\\[DebugまたはRelease]\\[Win32またはx64]」の中に出来る。

#### 注意点
* リソーススクリプト（\*.rc）は文字コードが「UTF-16 LE」である必要があるのだが、デフォルトではUTF-8なプレーンテキスト扱いになってしまっているので、変更しておく。<br />対象の\*.rcファイルを開いておいたうえで下部ステータスバーの右の方に「UTF-8」とか書いてある部分をクリックし、「エンコード付きで保存」→「UTF-16 LE」でOK。<br />もしくは拡張子rcのファイルをデフォルトでUTF16 LEになるように設定してしまう。[別記事](https://qiita.com/cubeundcube/items/015c4d25059f1194ee75 "VSCodeで未登録な拡張子を認識させて設定する")を参照されたし。
* Resource.hを使う場合、Resource.hの1行目にいきなりコードを書くとBOMの関係で動かなくなるので、1行目は空行かコメント行にしておく。