i_sparkling

Win8.1とフライトスティック奮闘記3

続き.今回は解決編です.

結論から言うと,vJoy不具合の原因はWin8.1は関係ありませんでした(汗
現象を発見したのがWin7からWin8.1へ切り替えた時期だったもので…

で真の原因は何だったかというと,こいつです.

2015-07-25 23.20.37

Kensington SlimBlade TrackballのTrackballWorksというユーティリティをインストールしている環境だと, Win7でもWin8でもvJoyが正常に動作しなくなる場合があります

というわけでそれに該当しない方はWin7だろうがWin8だろうがvJoyを安心してお使いいただけるはずです.

ただ,検証はしていないのですが似たようなポインティングデバイスの専用ユーティリティを使用している環境で 同様の問題が発生する可能性はあります.

以下解説です.

vJoyのAPIとvJoyInterface.dll

以前に解説したようにvJoyはその入力状態をソフトウェア制御できる仮想ゲームパッドドライバーです.

vjoy_joystick

これに関連するソフトウェアコンポーネントを示したのが次の図です.

vjoy_joystick_sc

通常のクライアントアプリケーション(ゲームなど)は,vJoyのドライバ(vjoy.sysやhidkmdf.sys)の情報をDirectInput経由で読み取ります.クライアント側からはvJoyデバイスと 物理的なゲームパッドと区別がつきません.

実際の(物理的な)ジョイスティックの入力状態などをvJoyデバイスに送り込むにはvJoyのAPIを使用する必要があります.vJoyInterface.dllがそのAPIを提供するDLLです. 問題はこのAPI側で発生していました

# そもそもコンパネのゲームパッドのプロパティではvJoyデバイスの軸やボタンの設定など正常に読み取られていたので,ドライバ側は問題なさそうだったんですよね…

vJoyInterface.dllにおけるvJoyデバイスの検索処理

vJoyInterface.dllではおおよそ以下のような手順でvJoyデバイスを見つけようとします.

  1. HIDクラスのデバイスを順番に反復する(SetupDiEnumDeviceInterfaces):
    1. HIDクラスの$$i$$番目のデバイスのデバイスインスタンスパスを取得する(SetupDiGetDeviceInterfaceDetail).
    2. Ⅰで得たデバイスインスタンスパスのデバイスを開いて(CreateFile),デバイスハンドルを取得する.
    3. ⅡのデバイスのベンダーIDとプロダクトIDを取得する(HidD_GetAttributes).
    4. Ⅲで得たベンダーIDとプロダクトIDが,vJoyのものと一致するならばそのデバイスをvJoyデバイスであると認識する.

Ⅰ~Ⅱまでの処理をしているのが,vJoyのソースツリー(リビジョンI050515)にあるapps/common/vJoyInterface.cppGetHandleByIndex関数(851行目),ループそのものとベンダーID/プロダクトIDの比較を行っているのが, GetDeviceIndexById関数(1010行目)です.

問題は先日も言及していたように,ステップⅡのCreateFileの呼び出しが失敗することです.

vJoyInterface.cppのロジックだとCreateFileが失敗した場合,直ちにvJoyデバイスの検索が打ち切られてしまいます

すると,HIDクラスに属するデバイスの並び順序(おそらくインストール順などに依存する)で,何らかの原因でCreateFileに失敗するデバイスを,vJoyデバイスよりも先にチェックしてしまった場合vJoyInterface.dllは vJoyデバイスを見つけることができず不正な入力値状態を報告します.

該当するコード上の位置も説明しておくと,まずCrateFile関数の失敗によりGetHandleByIndexがNULLを返し,

GetHandleByIndex(vJoyInterface.cpp:975)
    // 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が返るとその場でループを抜けてしまいます.

GetDeviceIndexById(vJoyInterface.cpp:1027)
    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ではGetHandleByIndexINVALID_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.dllx64/vJoyInterface.dllを上記の バイナリで差し替えてやるだけでとりあえず普通に動かせるようになりました.

もし似たような症状が出ている方がいたら試してみると良いかも知れません.


というわけで騒いだ割には環境固有の問題でしたがなんとか着地できました.これでようやく遊べる.

と思ったらもう来週にはWin10のアップグレードが来るんだよなぁ.変な問題が起こらないことを祈るのみ.

Leave a Reply