Visual C++/CLI でのBluetooth機器名とCOM番号取得

#背景
いつもWindowsソフト（？）を作るときは Visual C++/CLI で開発をしている私です。
GUI部分は主にマネージ(CLI)で、メインロジックはネイティブC++で書くスタイルです。
C/C++が好きでホントは全部ネイティブで書きたいのですが、もうWindowsのGUI部分ってMFCじゃなくて.NetFrameworkがメインじゃないですか。
CLIのGUIでできることもMFCでは出来ないことが多くなってきましたし。
なのでうまく使い分けするスタイルに落ち着きました。

さて最近Bluetoothを使うプログラムを書く必要が出てきまして、COM番号を取得しなければいけなくなりました。
「あ～、Win32 APIをゴリゴリ叩かなきゃいけないのかなぁ～。時間もあんまりかけられないしシンドイなぁ・・・」
と思ってGoogle先生に聞いていたところ、こんな素敵なページを見つけました。

>**技術レポート**
>**「WindowsでのBluetooth通信アプリケーション開発について」**
>https://www.softech.co.jp/mm_170705_tr.htm

*おお！　まさに欲しかった情報ドンピシャだ！*
*（T．H．）さんありがとう！！*

と思ったのもつかの間、

**これコード C# ジャン・・・**

ガックシ・・・。
まあいま花形の言語ですし、今時C++/CLIで開発する人なんてそうそう多くないでしょうからそりゃそうでしょうけど・・・

**ちぇ・・・**

そう思ったのですが、

**あ、そうだ。**
**C# って.NetFramework上で動いてるじゃん？**
**だったらCLIで基本おなじ関数使えるから移植可能じゃん？**

そう思い立ち挑戦。
「本当はWin32 APIゴリゴリ叩いてネイティブ実装を目指すべきなのかもしれないけど、結局GUIに表示して選択させるために使うわけなのでマネージでいいや、時間かけられないし」
などと思いながらゴリゴリ格闘。

**できました。**

思ったより時間がかかり、半日かかってしまいましたけど。
C#になっていろいろ隠されているところとか、基本ハンドル操作だとか、C++にない命令をどう置き換えるかとか、独自の言語仕様の意味を理解する（代入してるように見えて実は型変換してるだけ、とかとか）のに手間取りましたが、なんとかできました。
元のソースが短かったことも幸いでした。

以下、C++/CLI環境で使用する方の為に公開しておきます。
MITライセンス下で自由にお使いください。

#説明
PCに登録されているBluetoothデバイスがいくつあるかわからないので、
**System::Collections::Generic::List**
クラスを利用して可変長のリストに結果を格納する関数にしてあります。
COM番号を入れるリストと、bluetooth機器名を入れるリストの２つのマネージハンドルを渡してあげてください。

```C++:
System::Collections::Generic::List<System::String^>^ なんかリスト名 = gcnew System::Collections::Generic::List<System::String^>();
```

で作って引数に渡してあげればいいです。

基本、こちらの元ページ
>**技術レポート**
>**「WindowsでのBluetooth通信アプリケーション開発について」**
>https://www.softech.co.jp/mm_170705_tr.htm

のものを移植しただけですが、少し無駄を省いたり自分なりにカスタマイズしてあります。
また、マネージなのでガベージコレクションに任せればいいのですが、C/C++の流儀上気持ち悪くて必要のない（？）delete をしてます。
（ただ、一つだけ GetBluetoothRegistryName 関数の deviceName だけは return しなきゃいけないから delete できなかった・・・。なんか悔しい・・・）

皆さまのお役に立てば幸いです。

```C++:bluetoothFind.h
//	Copyright(c) 2019 Kazuhiro Hidaka
//	Released under the MIT license
//	https://opensource.org/licenses/mit-license.php


#pragma once

#using <system.dll>
#using <System.Management.dll>

void GetBluetoothDrvice(System::Collections::Generic::List<System::String^>^ COMNumberList, System::Collections::Generic::List<System::String^>^ bluetoothNameList);
System::String^ GetBluetoothRegistryName(System::String^ address);
```

```C++:BluetoothFind.cpp
//	Copyright(c) 2019 Kazuhiro Hidaka
//	Released under the MIT license
//	https://opensource.org/licenses/mit-license.php


#include "bluetoothFind.h"

void GetBluetoothDrvice(System::Collections::Generic::List<System::String^>^ COMNumberList, System::Collections::Generic::List<System::String^>^ bluetoothNameList)
{
	System::Text::RegularExpressions::Regex^ regexPortName = gcnew System::Text::RegularExpressions::Regex("(COM\\d+)");
	System::Management::ManagementObjectSearcher^ searchSerial = gcnew System::Management::ManagementObjectSearcher("SELECT * FROM Win32_PnPEntity");  // デバイスマネージャーから情報を取得するためのオブジェクト

	System::Management::ManagementObjectCollection^ objCollection;
	System::Management::ManagementObject^ obj;

	System::String^ name;
	System::String^ classGuid;
	System::String^ devicePass;

	cli::array<System::String^>^ tokens;
	cli::array<System::String^>^ addressToken;

	System::String^ bluetoothAddress;
	System::String^ bluetoothName;
	System::String^ comPortNumber;
	

	// デバイスマネージャーの情報を列挙する
	objCollection = searchSerial->Get();
	for each (obj in objCollection)
	{		
		if (obj->GetPropertyValue("Name") == nullptr) { continue; }
		if (obj->GetPropertyValue("ClassGuid") == nullptr) { continue; }
		if (obj->GetPropertyValue("DeviceID") == nullptr) { continue; }

		name = obj->GetPropertyValue("Name")->ToString();
		classGuid = obj->GetPropertyValue("ClassGuid")->ToString();
		devicePass = obj->GetPropertyValue("DeviceID")->ToString();


		// デバイスインスタンスパスからBluetooth接続機器のみを抽出
		// {4d36e978-e325-11ce-bfc1-08002be10318}はBluetooth接続機器を示す固定値
		if (System::String::Equals(classGuid, "{4d36e978-e325-11ce-bfc1-08002be10318}",	System::StringComparison::InvariantCulture))
		{
			tokens = devicePass->Split('&');
			addressToken = tokens[4]->Split('_');

			bluetoothAddress = addressToken[0];
		
			if (regexPortName->Match(name)->Success) {
				// COM番号を抜き出す
				comPortNumber = regexPortName->Match(name)->Groups[1]->ToString();

				if (System::Convert::ToUInt64(bluetoothAddress, 16) > 0)
				{
					//名前取得
					bluetoothName = GetBluetoothRegistryName(bluetoothAddress);

					//結果格納
					if(bluetoothName != ""){
						COMNumberList->Add(comPortNumber);
						bluetoothNameList->Add(bluetoothName);
					}
				}
			}
		}
	}


	delete regexPortName;	regexPortName = nullptr;
	delete searchSerial;	searchSerial = nullptr;

	delete objCollection;	objCollection = nullptr;
	delete obj;				obj = nullptr;

	delete name;			name = nullptr;
	delete classGuid;		classGuid = nullptr;
	delete devicePass;		devicePass = nullptr;

	delete tokens;			tokens = nullptr;
	delete addressToken;	addressToken = nullptr;

	delete bluetoothAddress;	bluetoothAddress = nullptr;
	delete bluetoothName;	bluetoothName = nullptr;
	delete comPortNumber;	comPortNumber = nullptr;

	return;
}



System::String^ GetBluetoothRegistryName(System::String^ address)
{
	System::String^ deviceName = "";

	// 以下のレジストリパスはどのPCでも共通
	System::String^ registryPath = "SYSTEM\\CurrentControlSet\\Services\\BTHPORT\\Parameters\\Devices";
	System::String^ devicePath = System::String::Format("{0}\\{1}", registryPath, address);

	Microsoft::Win32::RegistryKey^ key = Microsoft::Win32::Registry::LocalMachine->OpenSubKey(devicePath);
	

	if (key != nullptr && key->GetValue("Name") != nullptr)
	{
			// ASCII変換
			deviceName = System::Text::Encoding::ASCII->GetString(static_cast<array<unsigned char>^>(key->GetValue("Name")));
	}


	delete registryPath; registryPath = nullptr;
	delete devicePath; devicePath = nullptr;
	delete key; key = nullptr;

	// NULL文字をトリミングしてリターン
	return deviceName->TrimEnd('\0');

}

```

#謝辞
株式会社ソフテックの（T．H．）さん、ありがとうございます。