CM_Get_Parent

CM_Get_Parentは、指定のデバイスのDevInstから、親のデバイスのDevInstを取得する。

CM_Get_Parent関数 (cfgmgr32.h) - Win32 apps | Microsoft Learn

これを使う前に、これまでのAPIなどを使って、いくつか関数を作る。
まず、

ドライブレターからボリュームのデバイスナンバーを取得する関数。
これを使って、

// _tmain関数の定義
int _tmain(int argc, TCHAR *argv[]){	// main関数のTCHAR版.

	// コマンドライン引数の数.
	_tprintf(_T("argc = %d\n"), argc);	// argcを出力.
	if (argc != 2){	// 2以外はエラー.
		_tprintf(_T("error: argc != 2\n"));	// "error: argc != 2"と出力.
		return -1;	// -1を返して異常終了.
	}

	// ドライブレターからボリュームのデバイスナンバーを取得.
	int iDeviceNumber = GetVolumeDeviceNumber(argv[1]);	// GetVolumeDeviceNumberでiDeviceNumber取得.
	if (iDeviceNumber != -1){	// iDeviceNumberが-1でなければ成功.
		_tprintf(_T("iDeviceNumber = %d\n"), iDeviceNumber);	// iDeviceNumberを出力.
	}

	// プログラムの終了.
	return 0;	// 0を返して正常終了.

}

とすると、

Dドライブのデバイスナンバーは1
Dドライブのデバイスナンバーは1

Dドライブのデバイスナンバーは1。(DドライブはUSBHDD。)
次に、

バイスパスからデバイスナンバーを取得するGetDeviceNumberByDevicePath、そしてそれを呼び出すGetVolumeDeviceNumberAndDevInst、こちらはドライブレターからボリュームのデバイスナンバーとDevInstを取得する関数。

// _tmain関数の定義
int _tmain(int argc, TCHAR *argv[]){	// main関数のTCHAR版.

	// コマンドライン引数の数.
	_tprintf(_T("argc = %d\n"), argc);	// argcを出力.
	if (argc != 2){	// 2以外はエラー.
		_tprintf(_T("error: argc != 2\n"));	// "error: argc != 2"と出力.
		return -1;	// -1を返して異常終了.
	}

	// ドライブレターからボリュームのデバイスナンバーとDevInstを取得.
	DWORD dwDeviceNumber = 0;	// dwDeviceNumberを0で初期化.
	DWORD dwDevInst = 0;	// dwDevInstを0で初期化.
	BOOL bRet = GetVolumeDeviceNumberAndDevInst(argv[1], dwDeviceNumber, dwDevInst);	// GetVolumeDeviceNumberAndDevInstでdwDeviceNumber, dwDevInstを取得.
	if (bRet){	// TRUEなら成功.
		_tprintf(_T("dwDeviceNumber = %d\n"), dwDeviceNumber);	// dwDeviceNumberを出力.
		_tprintf(_T("dwDevInst = %d\n"), dwDevInst);	// dwDevInstを出力.
	}

	// プログラムの終了.
	return 0;	// 0を返して正常終了.

}

とすると、

Dのデバイスナンバーが1に加えて、DevInstが3だとわかる。
Dのデバイスナンバーが1に加えて、DevInstが3だとわかる。

Dのデバイスナンバーが1に加えて、DevInstが3だとわかる。
ドライブレターからボリュームのDevInstだけ取得する方法は他にもある。

で、

// _tmain関数の定義
int _tmain(int argc, TCHAR *argv[]){	// main関数のTCHAR版.

	// コマンドライン引数の数.
	_tprintf(_T("argc = %d\n"), argc);	// argcを出力.
	if (argc != 2){	// 2以外はエラー.
		_tprintf(_T("error: argc != 2\n"));	// "error: argc != 2"と出力.
		return -1;	// -1を返して異常終了.
	}

	// ドライブレターからボリュームのDevInstを取得.
	int iDevInst = GetVolumeDevInst(argv[1]);	// GetVolumeDevInstでiDevInst取得.
	if (iDevInst != -1){	// iDevInstが-1でなければ成功.
		_tprintf(_T("iDevInst = %d\n"), iDevInst);	// iDevInstを出力.
	}

	// プログラムの終了.
	return 0;	// 0を返して正常終了.

}

とすると、

こうなる
こうなる

こうなる。
そして実は、ボリュームのデバイスナンバーと、そのボリュームがあるディスクのデバイスナンバーは同一らしい。

バイスナンバーからディスクのDevInstを取得する関数で、

// _tmain関数の定義
int _tmain(int argc, TCHAR *argv[]){	// main関数のTCHAR版.

	// コマンドライン引数の数.
	_tprintf(_T("argc = %d\n"), argc);	// argcを出力.
	if (argc != 2){	// 2以外はエラー.
		_tprintf(_T("error: argc != 2\n"));	// "error: argc != 2"と出力.
		return -1;	// -1を返して異常終了.
	}

	// デバイスナンバーからディスクのDevInstを取得.
	int iDeviceNumber = 1;	// iDeviceNumberを1で初期化.
	if (iDeviceNumber != -1){	// iDeviceNumberが-1でなければ.
		int iDiskDevInst = GetDiskDevInst((DWORD)iDeviceNumber);	// GetDiskDevInstでiDiskDevInstを取得.
		if (iDiskDevInst != -1){	// iDiskDevInstが-1でなければ成功.
			_tprintf(_T("iDiskDevInst = %d\n"), iDiskDevInst);	// iDiskDevInstを出力.
		}
	}

	// プログラムの終了.
	return 0;	// 0を返して正常終了.

}

これで、

ディスクのDevInstは2。
ディスクのDevInstは2。

ディスクのDevInstは2。

さて、ここまでやってきたのは、指定のドライブレターのUSBデバイスを取り出すためである。

CM_Request_Device_Eject 実行時に"コンピュータから安全に取り外すことができます。"が表示されない。

上記リンクによると、ディスクのDevInstの親のDevInstに、CM_Request_Device_Ejectを投げればいいらしい。
親のDevInstは、

CM_Get_Parent関数 (cfgmgr32.h) - Win32 apps | Microsoft Learn

CM_Get_Parentで取れる。

CM_Request_Device_Eject 実行時に"コンピュータから安全に取り外すことができます。"が表示されない。

このページのサンプルを動かしたら、ディスクのDevInstは2、親のDevInstは3だとわかった。
"3"ということは、親デバイスはボリュームなのか?
これまでの関数を全部呼び出して、

// _tmain関数の定義
int _tmain(int argc, TCHAR *argv[]){	// main関数のTCHAR版.

	// コマンドライン引数の数.
	_tprintf(_T("argc = %d\n"), argc);	// argcを出力.
	if (argc != 2){	// 2以外はエラー.
		_tprintf(_T("error: argc != 2\n"));	// "error: argc != 2"と出力.
		return -1;	// -1を返して異常終了.
	}

	// ドライブレターからボリュームのデバイスナンバーを取得.
	int iDeviceNumber = GetVolumeDeviceNumber(argv[1]);	// GetVolumeDeviceNumberでiDeviceNumber取得.
	if (iDeviceNumber != -1){	// iDeviceNumberが-1でなければ成功.
		_tprintf(_T("iDeviceNumber = %d\n"), iDeviceNumber);	// iDeviceNumberを出力.
	}

	// ドライブレターからボリュームのデバイスナンバーとDevInstを取得.
	DWORD dwDeviceNumber = 0;	// dwDeviceNumberを0で初期化.
	DWORD dwDevInst = 0;	// dwDevInstを0で初期化.
	BOOL bRet = GetVolumeDeviceNumberAndDevInst(argv[1], dwDeviceNumber, dwDevInst);	// GetVolumeDeviceNumberAndDevInstでdwDeviceNumber, dwDevInstを取得.
	if (bRet){	// TRUEなら成功.
		_tprintf(_T("dwDeviceNumber = %d\n"), dwDeviceNumber);	// dwDeviceNumberを出力.
		_tprintf(_T("dwDevInst = %d\n"), dwDevInst);	// dwDevInstを出力.
	}

	// ドライブレターからボリュームのDevInstを取得.
	int iDevInst = GetVolumeDevInst(argv[1]);	// GetVolumeDevInstでiDevInst取得.
	if (iDevInst != -1){	// iDevInstが-1でなければ成功.
		_tprintf(_T("iDevInst = %d\n"), iDevInst);	// iDevInstを出力.
	}

	// デバイスナンバーからディスクのDevInstを取得.
	int iDiskDevInst = -1;	// iDiskDevInstを-1で初期化.
	if (iDeviceNumber != -1){	// iDeviceNumberが-1でなければ.
		iDiskDevInst = GetDiskDevInst((DWORD)iDeviceNumber);	// GetDiskDevInstでiDiskDevInstを取得.
		if (iDiskDevInst != -1){	// iDiskDevInstが-1でなければ成功.
			_tprintf(_T("iDiskDevInst = %d\n"), iDiskDevInst);	// iDiskDevInstを出力.
		}
	}

	// iDiskDevInstの親のDevInstを取得.
	DWORD dwDevInstParent;	// 親のDevInstであるdwDevInstParent.
	if (iDiskDevInst != -1){	// iDiskDevInstが-1でなければ.
		CM_Get_Parent(&dwDevInstParent, (DWORD)iDiskDevInst, 0);	// CM_Get_ParentでiDiskDevInstの親のデバイスのDevInstであるdwDevInstParent取得.
		_tprintf(_T("dwDevInstParent = %lu\n"), dwDevInstParent);	// dwDevInstParentを出力.
	}

	// プログラムの終了.
	return 0;	// 0を返して正常終了.

}

としてみた。
すると、

なんと、ディスクのDevInstは6、親のDevInstは7になった。
なんと、ディスクのDevInstは6、親のDevInstは7になった。

なんと、ディスクのDevInstは6、親のDevInstは7になった。
いろいろいじってみたら、どうもSetupDiGetClassDevsを使う関数を2回以上呼ぶとこうなってしまう。
そこで、

SetupDiGetClassDevsを1回しか使わない方法でやったら、

親のDevInstが3になった。
親のDevInstが3になった。

親のDevInstが3になった。
"3"はボリュームではないのか?
親のデバイスは何なのか?
謎は深まるばかり・・・。

Sample/winapi/CM_Get_Parent/CM_Get_Parent/src/CM_Get_Parent at master · bg1bgst333/Sample · GitHub