Windows IoT Coreドライバー制御ソフトを作る(Inbox Driver編)

Make a Windows IoT Core driver control software(Inbox Driver)
前回は独自に開発したドライバーをWindows IoT Coreにインストールする方法を紹介しました。今回はそのドライバーを制御するためのソフトを作るテクニックを独学した成果をご紹介します。

デスクトップに比べ、OSへのドライバー導入は敷居が低いですが、UWPからのアクセスは至難の業です。そこで今回はCUIベースのアプリでGPIOからのデータを受け取り、ドライバーを動かすことで結果としてUWPに反映させたいと思います。

[コンパイルのための下地作り]
Windows IoT Core Project Templatesを導入すると、Visual C++の新規プロジェクトに「Blank Windows IoT Core Console Application」が追加されるので、このプロジェクトを選択します。

続いてWinRT APIを使えるようにします。C++プロジェクトのプロパティーで「Windowsランタイム拡張機能の利用」を「はい(/ZW)」に、「最小リビルドを有効にする」を「いいえ(/Gm-)」にします。次にWinRT APIが定義されている「Platfowm.winmd」と「Windows.winmd」を含めるため「追加の#usingディレクトリ」にそれぞれのファイルが含まれるフォルダー「$(VC_ReferencesPath_VC_x86)\store\references;$(UniversalCRTSdkDir)UnionMetadata(※開発環境によってパスが異なる場合があります)」を指定します。

こちらは任意ですが、「pch.h」ファイルが不要であれば、「プリコンパイル済みヘッダー」「プリコンパイル済みヘッダーファイル」の2カ所を削除します。

Inbox DriverによるGPIO制御はC#でのプログラムとほぼ同じです。もちろん、C++/CXとの言語の壁はありますので、こちらについては「C#と比較するC++/CX基礎知識」の項目をご覧ください。
auto gpio = GpioController::GetDefault(); auto pin = gpio->OpenPin(6); pin->SetDriveMode(GpioPinDriveMode::InputPullDown); auto pv = pin->Read();

インストールしたドライバーはCM_Get_Device_Interface_List()で対象のGUIDを保有するドライバーを検索し、取得したIDをCreateFile()で開き、そのハンドルを取得することで操作することができるようになります。

ここで気をつけておきたいのは、デバイス列挙APIが含まれる「cfgmgr32.h(およびcfgmgr32.lib)」はARM向けのWindows SDKには含まれていないという点です。Raspberry Piで利用するのであれば、Windows Driver Kitをあらかじめインストールしておきましょう。

[スクリーンサイズを取得する]
Raspberry Piにおける開発ではもう一つ問題があります。HIDデバイスではXとY座標は画面のサイズにかかわらず、取得される数値は相対値(プロファイルにもよりますが、基本的には0~65535の間)になります。

[HIDデバイスに送る座標値例]

そのため、座標を適切な値に変換するには画面のピクセルサイズを知る必要があります。が、Raspberry Piではスクリーンサイズを取得するWin32 APIは機能しません。例えば、「GetSystemMetrics(SM_CXSCREEN)」を呼んでも常に0が返ります。

管理人はシステムファイルを解析することでこの問題を解決しています。Raspberry Pi向けWindows IoT CoreはRaspbianと同様にconfig.txtにシステムデータが格納されています。サンプルプログラムではこのファイルから読み取ったファイルにある「hdmi_group」と「hdmi_mode」の値をRPiconfigによる定義と照らし合わせてスクリーンサイズを推測しています。

[制御プログラムをGUIアプリから呼び出す]
本当はRaspberry Piの起動時に自動で実行させたかったのですが、schtasksコマンドを使ってタスクを登録しても動作せず、開発者のコミュニティーでも回答が得られなかったので、苦肉の策として、UWPから直接起動させるという方法をとりました。

もちろんUWPアプリですから、セキュリティー云々の理由で、プロセスの実行は基本的に禁止されています。が、Windows 10 IoT Coreの10.0.10586以降であれば、あらかじめ所定のレジストリに実行ファイルのパスを登録し、ホワイトリストにしておくことで、「ProcessLauncher.RunToCompletionAsync」メソッド(Windows IoT Core Extensionを参照に追加することで使用できます)により起動させることができます。

以下はデスクトップPCのPowershellにおける、レジストリへの登録例です。
reg.exe ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\EmbeddedMode\ProcessLauncher" /v AllowedExecutableFilesList /t REG_MULTI_SZ /d "c:\data\tnkHidInjectTest.exe\0"

サンプルプログラムでは、制御ソフトにおいてミューテックスとイベントハンドラによる多重起動防止の機能を追加しており、また、起動時のコマンドライン引数に「e」が含まれていたら、制御プログラムのプロセスを終了させてます。これは、「kill.exe」をホワイトリストに追加する手間を省くためと、きちんとプロセスを終了させるためです。

[コンソールアプリをデバッグする]
対象の機器のIPアドレスなどを自動取得してくれるUWPデバッガーと違い、C++コンソールアプリはちょっとした手間が必要となります。

まずはブラウザーか、Windows 10 IoT Core DashboardよりWindows Device Portalを開き、「Debug」の欄で「Start Visual Studio Remote Debugger」の「Start」ボタンを押します。
正常に動作すれば、リモート先のIPアドレスが表示されます。表示されない場合はリモートデバッグツールを導入する必要がありますが、これは適当なUWPアプリを作ってデバッグを実行すると、勝手に環境を整えてくれます。

また、リモートデバッグを実行している間はGUIアプリのリモートデバッグはできません。もしリモートデバッガーを止めてもGUIのデバッグができないようであれば、デバイスポータルの「Process」を表示して「msvsmon.exe」のプロセスを止めましょう。

対象のプロジェクトのデバッグを設定します。「リモートコマンド」はビルドしたファイルのコピー先を、「作業ディレクトリ」「追加の配置ファイル」にはその実行ファイルのあるフォルダーのパスを指定します。「リモートサーバー」はデバイスポータルのリモートデバッガーで表示された成功メッセージに記載されているアドレスを、「接続」では「認証なしでリモート接続する」、「デバッガーの種類」は「ネイティブのみ」をそれぞれ指定します。
これにより、Visual StudioからF5キー一発(「ビルド構成」の「配置」へのチェックを忘れずに!)で、ブレークポイントやウォッチリストが使えるデバッグ作業が行えるようになります。

サンプルプログラムのソースでは、起動しているGUIアプリに「tnksoft」のキーボード入力を転送するとともに、タクトスイッチを押すとA/Dコンバーターの定番「MCP3002」によってSPI経由で取得した可変抵抗の抵抗値をX座標に変換した場所で、タッチ操作をシミュレートします。また、ダウンロードできるソースコードにはX軸とY軸の値の両方を使ってマウスカーソルを移動させる例も追加しています。
2017/05/08