.NET 5なWPFからWinRTのAPIを呼ぶ方法

# はじめに

Microsoftは11月10日(現地時間) 「.NET Framework」と「.NET Core」フレームワークを統合した「.NET 5.0」のリリースが発表されました。

［速報］マイクロソフト「.NET 5」正式リリース。1つのフレームワークでWindows／Mac／Linuxのデスクトップ、サーバアプリ、Webアプリなどが開発可能に
https://www.publickey1.jp/blog/20/net_51windowsmaclinuxweb.html

以前、Non UWPな環境から WinRTにアクセスするのが割と面倒だった記憶があるのですが、
.NET5からWindows10向けAPI(WinRT)にアクセスするのが簡単になったとの噂を聞いたので早速試してみました。

# テスト環境

Microsoft Visual Studio Professional 2019 Version 16.8.0
.NET 5.0 SDK(https://dotnet.microsoft.com/download/dotnet/5.0)

サンプルコード
https://github.com/DandyMania/WinRT_Test

# 共通の手順

- .NET 5.0 SDKをインストール
- VisualStudioを開いて、プロジェクトの新規作成から WPF App(.NET)を選択
- *.csprojをテキストエディタ等で開いて 

```XML
 <Project Sdk="Microsoft.NET.Sdk">
 ...
 <TargetFramework>net5.0-windows10.0.19041.0</TargetFramework>
```
に変更する。
※.NET5からはTargetFramework タグに net5.0-windows10.0.17763.0 のように Windows であることと
  対象のバージョン番号を付けるだけで良くなったということらしい。

- Nugetにて、Microsoft.Windows.CsWinRT と Microsoft.Windows.SDK.Contrac　をインストール 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31791/41965088-ca71-29c3-109b-6cfbc022a095.png)

- あとは、using Windows.??? を定義してAPIを呼べばOK

# トーストを表示する方法

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31791/bbab60d6-a14a-a638-25f1-8df0310b9eb8.png)

トーストに関しては上記手順を踏めば特に苦労すること無く呼び出すことが可能です。


```cs:MainWindow.xaml.cs
using System;
using System.Windows;

// toast
using Windows.UI.Notifications;

using System.IO;
using System.Reflection;

namespace WinRT_Test
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        /// <summary>
        /// トースト
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Toast_Click(object sender, RoutedEventArgs e)
        {
            var template = ToastTemplateType.ToastImageAndText04;
            var content = ToastNotificationManager.GetTemplateContent(template);
            var images = content.GetElementsByTagName("image");
            var src = images[0].Attributes.GetNamedItem("src");
            // 画像ファイルはexeと同じフォルダにコピー
            src.InnerText = "file:///" + Path.GetFullPath("sample.jpg");

            var texts = content.GetElementsByTagName("text");
            texts[0].AppendChild(content.CreateTextNode("Title"));
            texts[1].AppendChild(content.CreateTextNode("ToastMessage"));

            // AppIDの代わりにアセンブリ名を突っ込んでおく
            Assembly assembly = Assembly.GetExecutingAssembly();
            AssemblyName asmName = assembly.GetName();

            var notifier = ToastNotificationManager.CreateToastNotifier(asmName.Name);
            notifier.Show(new ToastNotification(content));
        }
    }
}

```
```xml:MainWindow.xaml
<Window x:Class="WinRT_Test.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WinRT_Test"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Button Content="Toast" HorizontalAlignment="Left" Margin="30,50,0,0" VerticalAlignment="Top" Click="Toast_Click"/>
    </Grid>
</Window>

```


# MessageDialogを表示する方法
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31791/6d2dec1a-5089-b2fe-91a4-06cbae9946b9.png)

ウィンドウハンドルを渡す方法が特殊なことと、async/awaitが使われている事により少々苦労しました。

API呼び出し自体は以下で大丈夫なはずですが、

```cs:MainWindow.xaml.cs
// MessageDialog
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Windows.Foundation;
using Windows.UI.Popups;
using System.Windows.Interop;
using WinRT;

...
        /// <summary>
        /// WPFからMessageDialogを呼ぶ場合のおまじない
        /// https://qiita.com/okazuki/items/227f8d19e38a67099006
        /// </summary>
        [ComImport]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        [Guid("3E68D4BD-7135-4D10-8018-9FB6D9F33FA1")]
        public interface IInitializeWithWindow
        {
            void Initialize(IntPtr hwnd);
        }

        /// <summary>
        /// メッセージダイアログ
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void MessageDialog_Click(object sender, RoutedEventArgs e)
        {
            var dlg = new MessageDialog("メッセージ", "タイトル");
            // It doesn't work on .NET 5
            // ((IInitializeWithWindow)(object)dlg).Initialize(new WindowInteropHelper(this).Handle);
            var withWindow = dlg.As<IInitializeWithWindow>();
            // Windowハンドルを渡して初期化
            withWindow.Initialize(new WindowInteropHelper(Application.Current.MainWindow).Handle);

            await dlg.ShowAsync();
        }
```

```
 エラー	CS0012	型 'IAsyncAction' は、参照されていないアセンブリに定義されています。アセンブリ 'Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime' に参照を追加する必要があります。	WinRT_Test	D:\oss\WinRT_Test\MainWindow.xaml.cs	59	該当なし
 エラー	CS0012	型 'IAsyncActionWithProgress<>' は、参照されていないアセンブリに定義されています。アセンブリ 'Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime' に参照を追加する必要があります。	WinRT_Test	D:\oss\WinRT_Test\MainWindow.xaml.cs	59	該当なし
 エラー	CS0012	型 'IAsyncOperation<>' は、参照されていないアセンブリに定義されています。アセンブリ 'Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime' に参照を追加する必要があります。	WinRT_Test	D:\oss\WinRT_Test\MainWindow.xaml.cs	59	該当なし
 エラー	CS0012	型 'IAsyncOperationWithProgress<,>' は、参照されていないアセンブリに定義されています。アセンブリ 'Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime' に参照を追加する必要があります。	WinRT_Test	D:\oss\WinRT_Test\MainWindow.xaml.cs	59	該当なし
```

と何故か怒られるので、以下のクラスを追加します。
※WindowsRuntimeの拡張クラスに含まれてるはずなのですが…

```cs
    /// <summary>
    /// 'IAsyncAction' は、参照されていないアセンブリに定義されていますと言われるので自前で定義
    /// https://www.moonmile.net/blog/archives/8584
    /// </summary>
    public static class TaskEx
    {
        public static Task<T> AsTask<T>(this IAsyncOperation<T> operation)
        {
            var tcs = new TaskCompletionSource<T>();
            operation.Completed = delegate  //--- コールバックを設定
            {
                switch (operation.Status)   //--- 状態に合わせて完了通知
                {
                    case AsyncStatus.Completed: tcs.SetResult(operation.GetResults()); break;
                    case AsyncStatus.Error: tcs.SetException(operation.ErrorCode); break;
                    case AsyncStatus.Canceled: tcs.SetCanceled(); break;
                }
            };
            return tcs.Task;  //--- 完了が通知されるTaskを返す
        }
        public static TaskAwaiter<T> GetAwaiter<T>(this IAsyncOperation<T> operation)
        {
            return operation.AsTask().GetAwaiter();
        }
    }
```


以上。


# 参考ページ

.NET 5 から Windows Runtime API を呼ぶのが凄い楽になってる
https://qiita.com/okazuki/items/acf95b3ebb21d4d5083b

[C#] デスクトップアプリ (WPF) から手軽にWinRT APIを活用しよう
https://qiita.com/everylittle/items/62ce313fe09883c6da5f

.NET 5 で Microsoft Store のアプリ内課金の API の呼び方
https://qiita.com/okazuki/items/227f8d19e38a67099006

Microsoft OCR をデスクトップのWFPアプリで動かす方法
https://www.moonmile.net/blog/archives/8584

非同期メソッド入門 (10) - WinRTとの相互運用
https://blog.xin9le.net/entry/2012/11/12/123231

Walkthrough: Generate a .NET 5 projection from a C++/WinRT component and distribute the NuGet
https://docs.microsoft.com/en-us/windows/uwp/csharp-winrt/net-projection-from-cppwinrt-component