[Windows.h]接続されているCOMデバイスとその詳細を取得（備忘録）

# 初めに
Windows.hを使うことでレジストリにアクセスすることができる。
今回は、現在接続されているデバイスのCOMポート一覧の取得方法をまとめました。

:::note info
 windows限定です
:::

# プログラム
int serchDevice()関数は接続されているデバイスの数を返します。x64環境で作成しております。

:::note warn
 勉強中なので、プログラムにはミスが含まれている可能性があります。
:::

```C++
#include <iostream>
#include <string>
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>
#include <setupapi.h>

#pragma comment(lib,"hid.lib")
#pragma comment(lib,"setupapi.lib")

int serchDevice(){
    HDEVINFO hDevInfo;
	int max = 0;

	// デバイス一覧の情報を取得
	hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_COMPORT, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
	if (hDevInfo == INVALID_HANDLE_VALUE) { printf_s("ERROR:COMPORT NOT FOUND\n"); return -1; }

    // デバイス情報セット
	SP_DEVINFO_DATA Data = { sizeof(SP_DEVINFO_DATA) };
	Data.cbSize = sizeof(Data);

	// 個々のデバイスの情報を取得
	while (SetupDiEnumDeviceInfo(hDevInfo, max, &Data)) {
		char *port = nullptr;
		char name[256] = { 0 };

		// 個々のデバイスの構成情報のレジストリキーを開く
		HKEY key = SetupDiOpenDevRegKey(hDevInfo, &Data, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE);
		if (key != INVALID_HANDLE_VALUE) {
			DWORD type = 0;
			DWORD size = 256;

			//デバイス情報(レジストリキー)の情報群からCOMPortの名前を取り出す
			RegQueryValueEx(key, "PortName", NULL, &type, (LPBYTE)name, &size);
			port = name;
		}else continue;
		
		DWORD size = 0;
		char *buf = nullptr;

		// デバイスの詳細の取得
        // デバイスの詳細に必要な文字数をsizeに収納
		SetupDiGetDeviceRegistryProperty(hDevInfo, &Data, SPDRP_DEVICEDESC, NULL, NULL, 0, &size); 
		buf = new char [size * 2];

        BOOL success = FALSE;
		// デバイスの詳細の文字列をbufに収納
		success = SetupDiGetDeviceRegistryProperty(hDevInfo, &Data, SPDRP_DEVICEDESC, NULL, (PBYTE)buf, size, &size);

        if (success == FALSE) printf_s("ERROR:CANT GET THE DETAIL\n");
        
        // ここで、COMポートの名前(port)と詳細(buf)が取得できる。ここを改良して出力してください
		printf_s("%s, %s\n", port, buf); 

		delete[] buf;
		max++;
	}
    SetupDiDestroyDeviceInfoList(hDevInfo); // デバイス情報セットの解放
    return max;
}
```

# 実行例
ターミナルの出力
```
COM20, Bluetooth リンク経由の標準シリアル
COM1, 通信ポート
COM21, Bluetooth リンク経由の標準シリアル
```

デバイスマネジャー
![デバイスマネージャー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1531622/bf749984-bc55-efa6-d462-542391d851d2.png)

# 解説
ほとんどMicrosoftのWINAPIリファレンスから引用です。

:::note warn
間違えている可能性が高いです。レジストリなどよくわかっていません…参考でお願いします。
:::

## SetupDiGetClassDevs(const GUID *ClassGuid, PCSTR Enumerator, HWND hwndParent, DWORD Flags)
[SetupDiGetClassDevsA 関数 (setupapi.h)](https://learn.microsoft.com/ja-jp/windows/win32/api/setupapi/nf-setupapi-setupdigetclassdevsa)
```c++
/*デバイス一覧の情報を取得
HDEVINFO SetupDiGetClassDevs(
    デバイスの種類(GUID_DEVINTERFACE_COMPORT=COMPORTのみ), 
    PnP(コンピュータに差し込む装置)を識別するID,USBやPCMCIA等(NULL可),
    ユーザーインターフェースの最上ウィンドウのハンドル? (NULL可),
    出力するデバイスのフィルター (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE = 現在存在するデバイス ∧ デバイスインターフェースのみ)
);
戻り値:引数に一致するデバイス一覧を返す
*/
hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_COMPORT, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
```

## SP_DEVINFO_DATA
[SP_DEVINFO_DATA構造体 (setupapi.h)](https://learn.microsoft.com/ja-jp/windows/win32/api/setupapi/ns-setupapi-sp_devinfo_data)
詳細不明です…Dataには最終的に取得した個々のデバイスの情報(GUIDなど)が入ります。
```C++
// デバイス情報セット
SP_DEVINFO_DATA Data = { sizeof(SP_DEVINFO_DATA) };
Data.cbSize = sizeof(Data);
```

## SetupDiEnumDeviceInfo(HDEVINFO DeviceInfoSet, DWORD MemberIndex, PSP_DEVINFO_DATA DeviceInfoData)
[SetupDiEnumDeviceInfo 関数 (setupapi.h)](https://learn.microsoft.com/ja-jp/windows/win32/api/setupapi/nf-setupapi-setupdienumdeviceinfo)
```C++
/*個々のデバイスの情報を取得
BOOL SetupDiEnumDeviceInfo(
    デバイス一覧,
    デバイスのインデックス,
    デバイス一覧から、インデックスで指定された個々のデバイス情報を渡す
);
戻り値:成功したか否か(成功=TRUE)
*/
SetupDiEnumDeviceInfo(hDevInfo, max, &Data)
```
## SetupDiOpenDevRegKey(HDEVINFO DeviceInfoSet, PSP_DEVINFO_DATA DeviceInfoData, DWORD Scope, DWORD HwProfile, DWORD  KeyType, REGSAM samDesired);
[SetupDiOpenDevRegKey 関数 (setupapi.h)](https://learn.microsoft.com/ja-jp/windows/win32/api/setupapi/nf-setupapi-setupdiopendevregkey)
```C++
/*個々のデバイスの構成情報のレジストリキーを開く
HKEY SetupDiOpenDevRegKey(
    デバイス一覧,
    デバイス一覧から一つを指定するためのSP_DEVINFO_DATA構造体,
    開くレジストリキーを選択(DICS_FLAG_GLOBAL=構成情報を取得),
    ハードウェアの情報の選択(0=現在の状態のハードウェアプロファイル)
    開くレジストリキーの種類(DIREG_DEV デバイスのハードウェアキー),
    開くキーに使うレジストリセキュリティアクセス
);
返り値:レジストリキー
*/
HKEY key = SetupDiOpenDevRegKey(hDevInfo, &Data, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE);
```
## RegQueryValueExA(HKEY hKey, LPCSTR  lpValueName, LPDWORD lpReserved, LPDWORD lpType, LPBYTE  lpData, LPDWORD lpcbData);
[RegQueryValueExA function (winreg.h)](https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regqueryvalueexa)
```C++
/*デバイス情報(レジストリキー)の情報群から一つを選ぶ
LSTATUS RegQueryValueEx(
    レジストリキー,
    取得したい情報,
    NULL(予約済み),
    値を収納する変数へのポインタ(値)(NULL可),
    値を収納する変数へのポインタ(文字)(NULL可),
    文字を収納する場合の文字列バッファサイズ(文字ポインタがNULLのときはNULLでよい)
);
返り値:エラーコード(ERROE_SUCCESS=成功)
*/
RegQueryValueEx(key, "PortName", NULL, &type, (LPBYTE)name, &size);
```

## SetupDiGetDeviceRegistryProperty(HDEVINFO DeviceInfoSet, PSP_DEVINFO_DATA DeviceInfoData, DWORD Property, PDWORD  PropertyRegDataType, PBYTE PropertyBuffer, DWORD PropertyBufferSize, PDWORD RequiredSize);
[SetupDiGetDeviceRegistryPropertyA 関数 (setupapi.h)](https://learn.microsoft.com/ja-jp/windows/win32/api/setupapi/nf-setupapi-setupdigetdeviceregistrypropertya)

```C++
/*デバイスの説明文の取得
SetupDiGetDeviceRegistryProperty(
    レジストリキー,
    取得したい情報,
    取得したい情報の選択(SPDRP_DEVICEDESC=デバイスの解説),
    返り値のデータの型(NULL可),
    返り値を入れるバッファのポインタ(NULL可),
    返り値を入れるバッファのサイズ,
    取得した文字列の長さ
);
返り値:成功したか否か(成功=TRUE)
※「返り値を入れるバッファのポインタ」と「返り値を入れるバッファのサイズの両方がNULL」のとき、「取得した文字列の長さ」には必要なバッファの大きさが返る
*/
SetupDiGetDeviceRegistryProperty(hDevInfo, &Data, SPDRP_DEVICEDESC, NULL, (PBYTE)buf, size, &size);
```

# 参考
[COMポートの一覧を表示・プロパティの変更及びポートの選択をする(ダイアログボックス版)](http://yamatyuu.net/computer/program/vc2013/gcomlist/index.html)

[デバイスとドライバーのインストールのリファレンス](https://learn.microsoft.com/ja-jp/windows/win32/api/_devinst/)

# 変更履歴
* コメントより、`SetupDiGetClassDevs`と`SetupDiOpenDevRegKey`が失敗したとき、`INVALID_HANDLE_VALUE`を返すのに0でチェックを行っていたところを修正
* コメントより、例外処理を`SetupDiGetDeviceRegisterProperty`関数にも追加