日別アーカイブ: 2009年2月6日

複数の HID から情報を読み出すには

以前公開したプログラムに関する質問のメールがほぼ同時に 2通ありました。
どちらも同じ内容でしたので、少々昔のプログラムを引っ張り出してみます。
「接続した複数の HID から情報を読み出すにはどうしたらいいのか」
制作時に参考にしたのはサンプル「\WinDDK\6000\src\hid\hclient\pnp.c」です。

HMODULE hModule= LoadLibrary( "hid.dll" );
HMODULE sModule= LoadLibrary( "setupapi.dll" );
GetProcAddress( hModule, "HidD_GetAttributes" ) )
GetProcAddress( hModule, "HidP_GetCaps" ) )
GetProcAddress( hModule, "HidD_FreePreparsedData" ) )
GetProcAddress( hModule, "HidD_GetPreparsedData" ) )
GetProcAddress( hModule, "HidD_SetOutputReport" ) )
GetProcAddress( hModule, "HidD_GetHidGuid" ) )
GetProcAddress( sModule, "SetupDiGetClassDevsA" ) )
GetProcAddress( sModule, "SetupDiEnumDeviceInterfaces" ) )
GetProcAddress( sModule, "SetupDiGetDeviceInterfaceDetailA" ) )
GetProcAddress( sModule, "SetupDiDestroyDeviceInfoList" ) )

以後列挙して目的のデバイスを探しています。

GUID	hidGuid;
HidD_GetHidGuid( &hidGuid );

HDEVINFO	hdevinfo;
hdevinfo= SetupDiGetClassDevs(
		&hidGuid,
		NULL,
		NULL,
		DIGCF_PRESENT
		|DIGCF_DEVICEINTERFACE
		|DIGCF_ALLCLASSES
	);


SP_DEVICE_INTERFACE_DETAIL_DATA*	funcClassData= NULL;

int	retCode= TRUE;
for( int i= 0 ; i< DEVICELIST_LIMIT ; i++ ){
	SP_DEVICE_INTERFACE_DATA	deviceInfoData;
	memset( &deviceInfoData, 0, sizeof(SP_DEVICE_INTERFACE_DATA) );
	deviceInfoData.cbSize= sizeof( SP_DEVICE_INTERFACE_DATA );
	if( SetupDiEnumDeviceInterfaces(
			hdevinfo,
			0,
			&hidGuid,
			i,
			&deviceInfoData
			) ){
		ULONG	reqLength= 0;
		SetupDiGetDeviceInterfaceDetail(
				hdevinfo,
				&deviceInfoData,
				NULL,
				0,
				&reqLength,
				NULL );

		funcClassData=
			reinterpret_cast(
					dAlloc( reqLength ) );
		if( sizeof(void*) == 4 ){
			funcClassData->cbSize= 5;
		}else{
			funcClassData->cbSize= 
				sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
		}
		if( !funcClassData ){
			retCode= FALSE;
			break;
		}
		if( !SetupDiGetDeviceInterfaceDetail(
				hdevinfo,
				&deviceInfoData,
				funcClassData,
				reqLength,
				&reqLength,
				NULL ) ){
			retCode= FALSE;
			break;
		}
		if( _OpenTestHidDevice( funcClassData->DevicePath,
					VENDOR_ID, PRODUCT_ID ) ){
			// 目的のデバイスを保存しておく
			CheckDeviceOpen( funcClassData->DevicePath );
		}
		if( funcClassData ){
			dFree( funcClassData );
			funcClassData= NULL;
		}
	}else{
		if( GetLastError() == ERROR_NO_MORE_ITEMS ){
			break;
		}
	}
}

SetupDiDestroyDeviceInfoList( hdevinfo );
if( funcClassData ){
	dFree( funcClassData );
	funcClassData= NULL;
}

VENDOR_ID, PRODUCT_ID はデバイスマネージャーのプロパティから
詳細→ハードウエア ID で調べることが出来ます。
このあたりのコードは力業です。たしか作成時 1つ 1つ試しながらだったので、
あまりきれいじゃないと思いつつ書いていた覚えがあります。

int _OpenTestHidDevice( const char* path, int vid, int pid )
{
	HANDLE	hdev= CreateFile(
					path,
					GENERIC_READ|GENERIC_WRITE,
					FILE_SHARE_READ|FILE_SHARE_WRITE,
					NULL,
					OPEN_EXISTING,
					0,
					NULL
				);
	int	found= FALSE;
	if( hdev == INVALID_HANDLE_VALUE ){
		return	FALSE;
	}

	HIDD_ATTRIBUTES	attr;
	if( HidD_GetAttributes( hdev, &attr ) ){
		if( attr.VendorID == vid && attr.ProductID == pid ){
			found= TRUE;
		}
	}
	CloseHandle( hdev );
	return	found;
}

検索して条件に合うデバイスをリストに保持し、それぞれ個別に通信を行っています。
CheckDeviceOpen() は内部でリストに追加しているだけです。
見つかった最初のデバイスだけ見ているか、それとも全部列挙しているかの違い
だけだと思います。

実際のデータ送受信はこのあとですが、Bluetooth Stack の違いを吸収するために
最初に API のテストや通信タイムアウトの判定などを行っています。
ここでの注意点は、複数のデバイスがあっても API の判定は最初の一度だけで
いいということ。

最初にリリースしたバージョンでは新たなデバイスが接続されるたびに API テストが
走ってしまい、すでにつながってるデバイスの通信も一時中断されてしまう問題が
ありました。このあたりの処理をきれいに組み込めずに少々苦労した覚えがあります。

また定期的に接続されているデバイスを確認して、動的な接続&切断に備えています。
すでにオープンしたデバイスかどうかの区別が必要となりますが、結局保持している
パスの文字列比較というもっとも安易な方法で済ませました。