続き.今回は解決編です.
結論から言うと,vJoy不具合の原因はWin8.1は関係ありませんでした(汗
現象を発見したのがWin7からWin8.1へ切り替えた時期だったもので…
で真の原因は何だったかというと,こいつです.
Kensington SlimBlade TrackballのTrackballWorksというユーティリティをインストールしている環境だと, Win7でもWin8でもvJoyが正常に動作しなくなる場合があります.
というわけでそれに該当しない方はWin7だろうがWin8だろうがvJoyを安心してお使いいただけるはずです.
ただ,検証はしていないのですが似たようなポインティングデバイスの専用ユーティリティを使用している環境で 同様の問題が発生する可能性はあります.
以下解説です.
vJoyのAPIとvJoyInterface.dll
以前に解説したようにvJoyはその入力状態をソフトウェア制御できる仮想ゲームパッドドライバーです.
これに関連するソフトウェアコンポーネントを示したのが次の図です.
通常のクライアントアプリケーション(ゲームなど)は,vJoyのドライバ(vjoy.sysやhidkmdf.sys)の情報をDirectInput経由で読み取ります.クライアント側からはvJoyデバイスと 物理的なゲームパッドと区別がつきません.
実際の(物理的な)ジョイスティックの入力状態などをvJoyデバイスに送り込むにはvJoyのAPIを使用する必要があります.vJoyInterface.dll
がそのAPIを提供するDLLです.
問題はこのAPI側で発生していました.
# そもそもコンパネのゲームパッドのプロパティではvJoyデバイスの軸やボタンの設定など正常に読み取られていたので,ドライバ側は問題なさそうだったんですよね…
vJoyInterface.dllにおけるvJoyデバイスの検索処理
vJoyInterface.dll
ではおおよそ以下のような手順でvJoyデバイスを見つけようとします.
- HIDクラスのデバイスを順番に反復する(
SetupDiEnumDeviceInterfaces
):- HIDクラスの
i
番目のデバイスのデバイスインスタンスパスを取得する(SetupDiGetDeviceInterfaceDetail
). - Ⅰで得たデバイスインスタンスパスのデバイスを開いて(
CreateFile
),デバイスハンドルを取得する. - ⅡのデバイスのベンダーIDとプロダクトIDを取得する(
HidD_GetAttributes
). - Ⅲで得たベンダーIDとプロダクトIDが,vJoyのものと一致するならばそのデバイスをvJoyデバイスであると認識する.
- HIDクラスの
Ⅰ~Ⅱまでの処理をしているのが,vJoyのソースツリー(リビジョンI050515)にあるapps/common/vJoyInterface.cpp
の
GetHandleByIndex
関数(851行目),ループそのものとベンダーID/プロダクトIDの比較を行っているのが,
GetDeviceIndexById
関数(1010行目)です.
問題は先日も言及していたように,ステップⅡのCreateFileの呼び出しが失敗することです.
vJoyInterface.cppのロジックだとCreateFile
が失敗した場合,直ちにvJoyデバイスの検索が打ち切られてしまいます.
すると,HIDクラスに属するデバイスの並び順序(おそらくインストール順などに依存する)で,何らかの原因でCreateFile
に失敗するデバイスを,vJoyデバイスよりも先にチェックしてしまった場合vJoyInterface.dllは
vJoyデバイスを見つけることができず不正な入力値状態を報告します.
該当するコード上の位置も説明しておくと,まずCrateFile
関数の失敗によりGetHandleByIndex
がNULLを返し,
// Get a handle to the device HANDLE HidDevice = CreateFile (functionClassDeviceData -> DevicePath, NULL, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, // no SECURITY_ATTRIBUTES structure OPEN_EXISTING, // No special create flags 0, // Open device as non-overlapped so we can get data NULL); // No template file if (INVALID_HANDLE_VALUE == HidDevice) { if (LogStream) _ftprintf_s(LogStream,_T("\n[%05d]Error: GetHandleByIndex(index=%d) - Failed to CreateFile(%s)"), ProcessId, index, functionClassDeviceData->DevicePath); LocalFree(functionClassDeviceData); SetupDiDestroyDeviceInfoList (hardwareDeviceInfo); return NULL; }
GetDeviceIndexById
側ではGetHandleByIndex
の戻り値をループの継続条件に使っているため,NULL
が返るとその場でループを抜けてしまいます.
while (h = GetHandleByIndex(i++)) { if (h==INVALID_HANDLE_VALUE) continue; BOOL gotit = HidD_GetAttributes(h, &Attributes); CloseHandle(h); if (gotit && (Attributes.VendorID == VendorId) && (Attributes.ProductID == ProductId) && (iFound==-1)) { iFound = i - 1; } if (LogStream) _ftprintf_s(LogStream,_T("\n[%05d]Info: GetDeviceIndexById(BaseIndex=%d) - index=%d; VendorId=%x; ProductId=%x; VersionNumber=%x"), ProcessId, BaseIndex, i, Attributes.VendorID, Attributes.ProductID, Attributes.VersionNumber); };
CreateFile
が失敗するケースは想定していない(普通は起こらないため?)のかも知れませんが,処理全体を打ち切るほどの事態でもなさそうです.
GetDeviceIndexById
ではGetHandleByIndex
がINVALID_HANDLE_VALUE
を返すとそのインデックスをスキップするので,GetHandleByIndex
側を
そのように書き換えてやります.
--- vJoyInterface.cpp 2015-06-07 21:25:56 +0900 +++ vJoyInterface.new.cpp 2015-07-25 23:09:08 +0900 @@ -971,7 +971,7 @@ _ftprintf_s(LogStream,_T("\n[%05d]Error: GetHandleByIndex(index=%d) - Failed to CreateFile(%s)"), ProcessId, index, functionClassDeviceData->DevicePath); LocalFree(functionClassDeviceData); SetupDiDestroyDeviceInfoList (hardwareDeviceInfo); - return NULL; + return INVALID_HANDLE_VALUE; }
vJoyのインストールフォルダ(C:\Program Files\vJoy
など)の中にあるx86/vJoyInterface.dll
とx64/vJoyInterface.dll
を上記の
バイナリで差し替えてやるだけでとりあえず普通に動かせるようになりました.
もし似たような症状が出ている方がいたら試してみると良いかも知れません.
というわけで騒いだ割には環境固有の問題でしたがなんとか着地できました.これでようやく遊べる.
と思ったらもう来週にはWin10のアップグレードが来るんだよなぁ.変な問題が起こらないことを祈るのみ.