続き.今回は解決編です.
結論から言うと,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のアップグレードが来るんだよなぁ.変な問題が起こらないことを祈るのみ.


