Wallpapper Engineみたいなのを作りたい その2

前回の続きです
前回を見てない方はこちらからどうぞ
[Wallpaper Engineみたいなのを作りたい その1](https://qiita.com/Raitichan82/items/eee6ef7b5a37cf469740)

取りあえず何も無しからいきなり作り始めるのは無理なので今分かってることから仕様をある程度決めていきましょう

#仕様
- 開発言語はVisualC++
- 第一の機能は壁紙としてコンソール(conhost.exe)を表示させる
- 勿論操作もできるように

プロジェクト名はCustom Wallpaperとでも

#環境
- Windows 10 Home Edition 1803
- CPU i7-6700k
- GPU GTX 750Ti
- RAM 16GB 2133MHz
- Visual Studio 2017

#conhost.exeを起動する
`conhost.exe`とはまぁ簡単に言えば`コマンドプロンプト`や`PowerShell`などの入出力デバイスです
といってもわかりませんよね
試しに`Win + R`を押して`conhost.exe`と入力し実行してみてください。
きっと`コマンドプロンプト`が起動するはずです。
ですが`cmd.exe`と`conhost.exe`は別物ですよ
今度は`コマンドプロンプト`で`conhost.exe`を起動してみてください
新しいウィンドウが立ち上がりましたね?
通常`コマンドプロンプト`上で`cmd.exe`を起動した場合新しいウィンドウは立ち上がらずその`コンソール`の中で新しい`コマンドプロンプト`が起動しますよね
![2019-03-20_014042.png](https://qiita-image-store.s3.amazonaws.com/0/379631/39f35ed0-991a-031e-2b42-2424dcaddf1c.png)
`コマンドプロンプト`のプロセスです`Windows コマンド プロセッサ`と`コンソールウィンドウホスト`が起動していますね
![2019-03-20_014218.png](https://qiita-image-store.s3.amazonaws.com/0/379631/4ebde024-1084-1913-65e3-e73a275e3bbf.png)
`cmd.exe`を2つ`コマンドプロンプト`から起動しました
`Windows コマンド プロセッサ`が3つ起動していますね。
ですが、`コンソールウィンドウホスト`は1つだけです。
では次`PowerShell`を起動してみましょう
![2019-03-20_013044.png](https://qiita-image-store.s3.amazonaws.com/0/379631/019ba733-868b-02cd-2645-606b3cb349bb.png)
`Windows PowerShell`と`コンソール ウインドウ ホスト`が起動していますね
では、次、`コマンドプロンプト`から`PowerShell`を起動してみましょう
![2019-03-20_014423.png](https://qiita-image-store.s3.amazonaws.com/0/379631/be13d19c-aef0-5b35-af4b-673a88fd131b.png)
はい、`Windows Powershell` `Windows コマンド プロセッサ` `コンソール ウィンドウ ホスト`
の3つが起動しています。
多分もうお判りでしょう
よく`コマンドプロンプト`のことを`コンソール`という方が居ますが、間違っています
いや完全に間違っているとは言い切れないんですが、、
コンソールはあの文字がだーってある__ウィンドウ__のことを指します
その`コンソール`を管理しているのが`conhost`(`コンソール ウィンドウ ホスト`)です
`conhost`を単体で起動した場合自動的に標準の`コマンドプロセッサ`も立ち上がるので、`conhost.exe`を起動したときもコマンドプロンプトが立ち上がりました。
で`コマンドプロンプト`とはその`コンソール`と、内部の`コマンドプロセッサ`のこと、または内部の`コマンドプロセッサ`のことを指します。
`cmd.exe`や、`powershell`を起動したときに一緒に`conhost`を起動し、関連付けられます。
`コマンドプロンプト`または`powershell`から起動した場合既に親の`conhost`は存在するのでその`conhost`に関連付けられます
ちなみにその`conhost`と`コマンドプロンプト`や`powershell`の中継として`condrv`というドライバが存在します
![2019-03-20_020610.png](https://qiita-image-store.s3.amazonaws.com/0/379631/5f0ec64f-fad2-3465-5f29-71e87d4e0d32.png)
コンソールアプリケーションの標準出力(`$CONOUT` `$CONIN`)などもこのドライバを経由して`コンソール`に表示されます。

で今回起動するのは`conhost`の方です
理由はあとで説明します。
とりあえずプロジェクト作成
VisualC++ の空のプロジェクトを作成
[CustomWallpaper-github](https://github.com/raiti-chan/CustomWallpaper)
でいろいろ初期設定を
`pch.h`と一応`cph.cpp`を作成

```cpp:pch.h
#pragma once

#include <iostream>
#include <Windows.h>
```

プリコンパイル済みヘッダーですね
作成後、プロジェクトのプロパティから`c/c++ > プリコンパイル済みヘッダー`
`プリコンパイル済みヘッダー`を`作成 (/Yc)`
`プリコンパイル済みヘッダーファイル`を`pch.h`に設定
後は`main.h`と`main.cpp`を作成
エントリーポイントは`WinMain`を仕様します。

```cpp:application.h
#pragma once

class application final {
	static application* _instance;
	int _exit_code = 0;
	HINSTANCE _app_instance;
public :
	static application* instance() {
		return _instance;
	}
	explicit application(HINSTANCE h_instance) : _app_instance(h_instance) {
		_exit_code = 0;
		_instance = this;
	}
	void init();
	void run();
	void fin();
	int exit_code() const {
		return this->_exit_code;
	}
	HINSTANCE app_instance() const {
		return this->_app_instance;
	}
};
```

```cpp:application.cpp
#include "pch.h"
#include "application.h"

application* application::_instance;

void application::init() {
}
void application::run() {
}
void application::fin() {
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int /*nShowCmd*/) {
	application app(hInstance);
	app.init();
	app.run();
	app.fin();
	return app.exit_code();
}
```

まぁ説明は必要ないですね。
で、まずコンソールが無いとデバッグがめんどくさいです。
なのでアプリケーションに関連付けたコンソールを起動します。
`AllocConsole()`関数を実行すると自身のアプリケーションと関連づいたコンソールが起動します
でも、コンソールからアプリケーションを起動した場合新しいコンソールが起動します。それは煩わしいですよね。
なので`AttachConsole`を利用し、それで失敗した場合に`AllocConsole`でコンソールを確保します
[AttachConsole](https://docs.microsoft.com/en-us/windows/console/attachconsole)
[AllocConsole](https://docs.microsoft.com/ja-jp/windows/console/allocconsole)

ただしこのままでは標準入出力はこのコンソールではありません。なのでコンソールを標準入出力にします。
`freopen_s`関数を使うとストリームを割り当てることができます。
`freopen_s`のファイルパスを`CONOUT$`にすると標準出力
`CONIN$`のにすると標準入力です。
`freopen_s`で開いたものはちゃんと最後に`fclose`してあげてください。
[freopen_s](https://docs.microsoft.com/ja-jp/cpp/c-runtime-library/reference/freopen-s-wfreopen-s?view=vs-2017)
[fclose](https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/fclose-fcloseall?view=vs-2017)
あと`AttachConsole`や`AllocConsole`で関連付けたコンソールも`FreeConsole`で解放してください。
[FreeConsole](https://docs.microsoft.com/en-us/windows/console/freeconsole)
書いたコードはこんな感じ
今回追記した部分だけです

```cpp:application.h
class application final {
	FILE *out = nullptr, *err = nullptr, *in = nullptr;
}
```

```cpp:application.cpp
void application::init() {
	if (!AttachConsole(ATTACH_PARENT_PROCESS)) {
		AllocConsole();
	}
	freopen_s(&this->out, "CONOUT$", "w", stdout);
	freopen_s(&this->err, "CONOUT$", "w", stderr);
	freopen_s(&this->in, "CONIN$", "r", stdin);
	std::cout << "initialize.\n";
}
void application::fin() const {
	std::cout << "finalize.\n";
	fclose(this->out);
	fclose(this->err);
	fclose(this->in);
	FreeConsole();
}
```

さて、あとはrunメソッドにコードを書き込んでいくだけですが。
今回`conhost`の起動は`CreateProcess`関数を使います。
[CreateProcess](https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createprocessa)

```cpp:application.cpp
void application::run() {
	std::cout << "running.\n";
	PROCESS_INFORMATION process_information = { 0 };
	STARTUPINFO startup_info = { sizeof(STARTUPINFO) };
	char cmd_line[] = "conhost.exe powershell.exe";
	bool result = CreateProcess(nullptr, cmd_line, nullptr, nullptr, false,
				  NORMAL_PRIORITY_CLASS, nullptr, nullptr, &startup_info, &process_information);
	if (!result) {
		write_error_message(GetLastError());
		system("pause");
		this->_exit_code = GetLastError();
		return;
	}
	this->ps_process_id = process_information.dwProcessId;

	system("pause");
}
//application.hに static void write_error_message(DWORD error_code);を追加
void application::write_error_message(DWORD error_code) {
	LPSTR lp_message_buffer = nullptr;
	FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr, 
	error_code, LANG_USER_DEFAULT, reinterpret_cast<LPSTR>(&lp_message_buffer), 0, nullptr);
	std::cerr << error_code << " : " << lp_message_buffer << "\n";
	LocalFree(lp_message_buffer);
}
```

これで新しいコンソールが起動します。

で、なぜ`conhost`を起動したかを説明します
試しに `cmd_line`の`conhost.exe`を`cmd.exe`に変えてみましょう。
![2019-03-21_012845.png](https://qiita-image-store.s3.amazonaws.com/0/379631/530a67b4-c430-23c4-5fe8-5bde526b1c78.png)
新しいコンソールは起動せず、既に存在するコンソールの上でコマンドプロンプトが起動してしまいます。
`powershell.exe`とした場合も同様です。
なので新しいコンソールを立ち上げるために`conhost.exe`を起動しました。
今ま`powershell`の使用を推奨されているので`powershell`を起動するようにしておきましょう
`cmd_line`の文字列を`conhost.exe powershell`とすれば`powershell`が起動します。

ということで壁紙に貼り付けるアプリケーションの起動は完了しました。
次回はこの起動した`powershell`を壁紙として貼り付けるコードを考えてみます。