かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

C++/WinRT で Windows Runtime Component を作って C# から呼ぶまで

ちょっと迷ったのでメモしておきます。

C+/WinRT自体についてはこちら

docs.microsoft.com

上のドキュメントの、このページの部分に関連してます。

docs.microsoft.com

docs.microsoft.com

Step 1: Windows Runtime Component プロジェクトの作成と idl の定義

Windows Runtime Component (C++/WinRT) のプロジェクトを作成します。今回は名前は MyComponents にしました。 Class.idl があるのですが、これは消します。

そして Person.idl を作成するのと Person.hPerson.cpp も作成します。 Person.idl に Person クラスを定義します。

namespace MyComponents
{
    runtimeclass Person : Windows.UI.Xaml.Data.INotifyPropertyChanged
    {
        Person();
        String Name;
    }
}

インテリセンスとかないので辛いです。

Step 2: Person クラスの作成

idl を書いてビルドしてプロジェクトのすべてのファイルを表示すると Person.g.hPerson.g.cpp が生成されています。

f:id:okazuki:20190920135105p:plain

ここには winrt::MyComponents::implementation::Person_base クラスと、その別名の winrt::MyComponents::implementation::PersonT と、winrt::MyComponents::factory_implementation::PersonT が定義されています。この PersonT という名前の 2 つのクラスを継承して Person クラスを自分のコードに追加します。

Person.h は以下のような感じになります。

#pragma once

#include "Person.g.h" // 生成されたヘッダーを include

namespace winrt::MyComponents::implementation
{
    struct Person : PersonT<Person>
    {
    };
}

namespace winrt::MyComponents::factory_implementation
{
    struct Person : PersonT<Person, implementation::Person>
    {
    };
}

PersonT とか Person とか同じ名前が出てきますが、これは名前空間が違うので気を付けてみましょう。

Step 3: Person クラスの実装

Person クラスの中を作っていきます。idl ファイルに定義していた Name プロパティと INotifyPropertyChanged の実装をします。Person.h に、ここらへんを追加していきましょう。

#pragma once

#include "Person.g.h" // 生成されたヘッダーを include

namespace winrt::MyComponents::implementation
{
    struct Person : PersonT<Person>
    {
        Person() = default;
        winrt::hstring Name() const;
        void Name(winrt::hstring const& name);

        winrt::event_token PropertyChanged(winrt::Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler);
        void PropertyChanged(winrt::event_token const& token);

    private:
        winrt::hstring _name;
        winrt::event<winrt::Windows::UI::Xaml::Data::PropertyChangedEventHandler> _propertyChanged;
    };
}

namespace winrt::MyComponents::factory_implementation
{
    struct Person : PersonT<Person, implementation::Person>
    {
    };
}

Person.cpp に実装を書いていきます。頭にお決まりの include を書いて namespace を定義したらヘッダーに戻って実装したいメソッドで Ctrl + . で以下のように、いい感じにひな型が作られます。

#include "pch.h"
#include "Person.h"
#include "Person.g.cpp" // 自動生成された cpp を include

namespace winrt::MyComponents::implementation
{
    winrt::hstring Person::Name() const
    {
        return winrt::hstring();
    }
    void Person::Name(winrt::hstring const& name)
    {
    }
    winrt::event_token Person::PropertyChanged(winrt::Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
    {
        return winrt::event_token();
    }
    void Person::PropertyChanged(winrt::event_token const& token)
    {
    }
}

中身を実装してこんな感じ。

#include "pch.h"
#include "Person.h"
#include "Person.g.cpp" // 自動生成された cpp を include

namespace winrt::MyComponents::implementation
{
    winrt::hstring Person::Name() const
    {
        return _name;
    }
    void Person::Name(winrt::hstring const& name)
    {
        _name = name;
        _propertyChanged(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs(L"Name"));
    }
    winrt::event_token Person::PropertyChanged(winrt::Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
    {
        return _propertyChanged.add(handler);
    }
    void Person::PropertyChanged(winrt::event_token const& token)
    {
        _propertyChanged.remove(token);
    }
}

Step 4: C# から使う

C# の UWP アプリを作ります。ここでは CSharpUWPApp にしました。 CSharpUWPApp から MyComponents に参照設定を追加します。ここらへんは普通の C# のクラスライブラリ使うときと一緒ですね。という`か、ちゃんと C++/WinRT で Windows ランタイムコンポーネントが定義出来ていれば C# から使うのは普通の C# のクラスを使うときと変わりありません。

ということで MainPage.xaml.csPerson` クラスをプロパティに持たせて…

using MyComponents;
using Windows.UI.Xaml.Controls;

// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409

namespace CSharpUWPApp
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        private Person Person { get; } = new Person { Name = "Tanaka" };
        public MainPage()
        {
            this.InitializeComponent();
        }
    }
}

MainPage.xaml で適当にバインドしましょう。

<Page
    x:Class="CSharpUWPApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:CSharpUWPApp"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <StackPanel>
        <TextBox Text="{x:Bind Person.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        <TextBlock Text="{x:Bind Person.Name, Mode=OneWay}" />
    </StackPanel>
</Page>

実行するとちゃんと使えています。

f:id:okazuki:20190920151918g:plain

まとめ

ハイパフォーマンスで美しい Windows Runtime Component を C++/WinRT で!!…作れるようになる道のりは自分にとってははるか長いように見える。 がんばろっと。

ソースコードはこちら。

github.com