tmytのらくがき

個人の日記レベルです

C++/Win32からSurface Dialを制御する (RS2版)

Surface Dialが出たころはWindows 10 Anniversary Update (Build 14393)だったのが、今はCreators Update (Build 15063)で、RadialController周りも少しアップデートがあったことに今更気づいたのでC++から触ってみます。

追加されたインターフェース

RadialController周りで増えたのがこのあたり。

  • IRadialController2
  • IRadialControllerMenuItemStatics2
  • IRadialControllerRotationChangedEventArgs2
  • IRadialControllerScreenContactContinuedEventArgs2
  • IRadialControllerScreenContactStartedEventArgs2

このうちEventArgsとついているのは、これまで通りのイベント引数に対してQueryInterfaceすると読めます。

// たとえばIRadialControllerRotationChangedEventArgs2の場合
/*
using namespace Microsoft::WRL;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::UI::Input;
*/
EventRegistrationToken rotationChangedEvent;
// ComPtr<IRadialController
_controller->add_RotationChanged(
    Callback<ITypedEventHandler<RadialController*, RadialControllerRotationChangedEventArgs*>>(
    [this](IRadialController*, IRadialControllerRotationChangedEventArgs* args) {
        ComPtr<IRadialControllerRotationEventArgs2> args2;
        args->QueryInterface(IID_PPV_ARGS(&args));
        // 好きに使う
        // args2->get_SimpleHapticsController();
        return S_OK;
    }).Get(), &rotationChangedEvent);

残りのIRadialController2IRadialControllerMenuItemStatics2は、IRadialControllerIRadialControllerMenuItemStaticsに対して QueryInterfaceすれば読めます。

/*
using namespace Microsoft::WRL;
using namespace Windows::Foundation;
using namespace ABI::Windows::UI::Input;
*/
ComPtr<IRadialControllerMenuItemStatics> menuItemStatics;
ComPtr<IRadialControllerMenuItemStatics2> menuItemStatics2;
GetActivationFactory(
    HStringReference(RuntimeClass_Windows_UI_Input_RadialControllerMenuItem).Get(),
    &menuItemStatics);
menuItemStatics->QueryInterface(IID_PPV_ARGS(&menuItemStatics2));

だいたいQueryInterfaceすればどうにかなります。

C++/Win32でDialを使ったコードを書く

前に書いたやつを書き直してGitHubにサンプルプロジェクトを公開しました。VS2017+15063 SDKでそのまま実行できるはずです。

IVector実装の入手

システムアイコンを追加削除するには、任意のIVector実装が必要です。 正直実装するのは面倒なのでWin2Dで使用されている実装を拝借します。 この実装はMITライセンスで公開されています。

Win2Dから

  • ErrorHandling.h
  • LifeSpanTracker.h
  • Vector.h
  • WinStringBuilder.h
  • WinStringWrapper.h

を入手してプロジェクトに追加します。

プロジェクトの設定

プロジェクトの設定の変更が必要です。プロジェクトを右クリックした、プロパティから次の設定を変更します。 15063になって、WindowsSDK_UnionMetadataPathが使えるようになったのでちょっとだけ短くなりました。

  1. 構成プロパティ > 全般 > Windows SDK バージョン を “10.0.15063.0” に設定
  2. 構成プロパティ > C/C++ > 全般 > Windows ランタイム拡張機能の使用 を “はい (/ZW)” に設定
  3. 構成プロパティ > C/C++ > 全般 > 追加の #using ディレクトリを “$(VC_ReferencesPath_VC_x86)\store\references;$(WindowsSDK_UnionMetadataPath)” に設定
  4. 構成プロパティ > C/C++ > コード生成 > 最小リビルドを有効にするを “いいえ (/Gm-)” に設定
  5. 構成プロパティ > MIDL > 全般 > Windows ランタイムを有効にしますを “はい (/winrt)” に設定
  6. 構成プロパティ > MIDL > 全般 > 追加のメタデータ ディレクトリを “$(WindowsSDK_UnionMetadataPath)” に設定
  7. 構成プロパティ > MIDL > 詳細設定 > ‘ABI’ 名前空間を追加するを “はい (/ns_prefix)” に設定

f:id:tmyt:20170720002214p:plain f:id:tmyt:20170720002120p:plain f:id:tmyt:20170720002248p:plain f:id:tmyt:20170720002333p:plain f:id:tmyt:20170720002423p:plain

IDLの作成

すごく面倒なんですが、IVector<T>インスタンス化するにはIDLが必要です。 genericとC++のテンプレートと、COMの都合だそうです*1

プロジェクトを右クリックして、追加、コード、MIDL ファイルで追加します。 ここではとりあえずVector.idlとしておきます。

f:id:tmyt:20170720001939p:plain

今回はWindows.Foundation.Collections.IVector<Windows.UI.Input.RadialControllerSystemMenuItemKind>が使えればいいので、 こんな感じで書いておきます。namespaceで指定する名前空間はなんでもいいみたいです*2

import "inspectable.idl";
import "Windows.Foundation.idl";
import "Windows.UI.Input.idl";

namespace collections
{
    declare{
        interface Windows.Foundation.Collections.IVector<Windows.UI.Input.RadialControllerSystemMenuItemKind>;
    }
}

Vector_h.hの参照

だいたい終わりです。 IVector<T>インスタンスを作成するMicrosoft::WRL::Make<T>を読んでいるファイルで、 Vector_h.hをインクルードする必要があります。

このときのファイル名はIDLのファイル名から拡張子を除いた部分に_hを付けて拡張子idlをhに変更したものを 指定します。

#include"Vector_h.h"

おしまい

これでとりあえずRadialControllerの全機能が使えると思います。 IDL周り以外は大して難しくないはずなので遊んでみてください。

github.com

*1:http://mntone.hateblo.jp/entry/2014/08/29/095251

*2:15063 SDKを使っている分には問題はなさそうです