LoginSignup
0
1

More than 1 year has passed since last update.

UWP DesktopBridge プロジェクト構成

Last updated at Posted at 2022-06-25

プロジェクト概要

ローカルサーバーを立てて、適当なレスポンスを返却するアプリケーションです。
UWPだと同一PCからのアクセスは不可能なので、DesktopBridgeを使用してローカルサーバーを外部サービスとして実行する。

プロジェクト構成

必要最低限のプロジェクト構成は以下のようになる。

├─fts           パッケージプロジェクト
├─fts.APP       FullTrustで起動する外部APPプロジェクト
├─fts.Shared    共通のクラスライブラリ
└─fts.UWP       UWPプロジェクト

クラスライブラリ

クラスライブラリプロジェクト(.NetStandard2.0)
UWPプロジェクトと外部APPプロジェクトから参照する共通のライブラリ。
共通で使用する定数や、関数または2つのアプリケーション間の疎通に使用するメッセージクラスなど作成している。
UWPからも参照できるように、.NetStandard2.0を指定している。

Constant.cs
namespace fts.Shared
{
    public static class Constant
    {
        public static string AppServiceName = "ftsAppService";

        public static string RequestMessage = "RequestMessage";

        public static string ResponseMessage = "ResponseMessage";
    }
}

AppServiceNameは後述するマニフェストファイルに追加するAppService名を定義している。
UWP、外部APPからAppService名を使用するのでここに定義。

外部APPプロジェクト

コンソールアプリケーションプロジェクト(.Net6.0)
HttpListenerを使用してローカルサーバーを立てる。

fts.APP.csproj

対象のOSがWindows7や最小のOSバージョンが7だと後述のAppServiceConnectionが使用できないので
以下のように、対象のOSと最小のOSバージョンを指定する。(10以降ならOKのはず)

fts.APP.csproj.xml
<PropertyGroup>
  <OutputType>WinExe</OutputType>
  <TargetFramework>net6.0-windows10.0.17763.0</TargetFramework>
  <ImplicitUsings>enable</ImplicitUsings>
  <Nullable>enable</Nullable>
  <SupportedOSPlatformVersion>10.0.17763.0</SupportedOSPlatformVersion>
</PropertyGroup>

Main

HttpListenerを使用して待ち受けするHttpServerクラスをnewして終了を待つだけ。

HttpServer.cs
namespace fts.APP
{
    internal class Program
    {
        public static async Task Main(string[] args)
        {
            await new HttpServer().WaitClosed();
        }
    }
}

AppServiceConnection

受信したリクエストをUWPアプリケーションに送信するなど
UWP側とのメッセージのやり取りにはAppServiceConnectionを使用する。

HttpServer.cs
private readonly AppServiceConnection _connection;

public HttpServer()
{
    _connection = new AppServiceConnection
    {
        // 共通のクラスライブラリの定義から、AppService名を設定する。 
        AppServiceName = Constant.AppServiceName,
        PackageFamilyName = Package.Current.Id.FamilyName 
    }

     // コネクション切断後の処理
    _connection.ServiceClosed += Connection_ServiceClosed;

    // コネクションの確立とサーバーの起動 
    StartAsync()
}

private async void StartAsync()
{
    // コネクション確立
    var status = await _connection.OpenAsync(); 
    if (status != AppServiceConnectionStatus.Success)
    {
        Close();
        return;
    }

    // HttpListenerの起動&待ち受け処理...

   // 受信したリクエストボディをUWP側へ送信して、応答メッセージを受け取る。
   var massage = await SendMessageAsync(requestBody);

    // HttpListenerのレスポンス返却処理...
}

private async Task<string> SendMessageAsync(string message)
{
    var valueset = new ValueSet
    {
        { Constant.RequestMessage, message }
    };

    // AppServiceConnectionを使用して、UWP側へ送信する。
    var massage = await _connection.SendMessageAsync(valueset);

    // UWP側からの応答メッセージを取得する。
    return massage.Message[Constant.ResponseMessage]?.ToString() ?? string.Empty;
}

メッセージのやり取りに使用されるValueSetは、値として設定できるObjectに指定がある。
基本、プリミティブな型とstringくらい。
なにやらIPropertyValueを継承したらいけそうな気もする。
ただそんなことをするより、メッセージのクラスをJsonにシリアライズしてstringでやり取りするほうが楽そう。

UWPプロジェクト

Windows Desktop Extensions for the UWPの参照追加

FullTrustを使用したいので、参照を追加する。
image.png

App.xaml.cs

FullTrustで、外部AppServiceを起動

App.xaml.cs
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();

FullTrustで起動した外部サービスからのメッセージを受信できるように
OnBackgroundActivatedをオーバーライドして、AppServiceConnectionのRequestReceivedにイベントをハンドルする。

App.xaml.cs
protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
{
    base.OnBackgroundActivated(args);
    if (args.TaskInstance.TriggerDetails is AppServiceTriggerDetails details)
    {
        appServiceDeferral = args.TaskInstance.GetDeferral();
        args.TaskInstance.Canceled += OnTaskCanceled;
        
        var appService = details.AppServiceConnection;
        // 外部APPからのメッセージ受信イベント
        appService.RequestReceived += AppService_RequestReceived;
        // 外部APPのサービス終了イベント
        appService.ServiceClosed += AppService_ServiceClosed;
    }
}

全体

App.xaml.cs
using System;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.ApplicationModel.AppService;
using Windows.ApplicationModel.Background;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

namespace fts.UWP
{
    /// <summary>
    /// 既定の Application クラスを補完するアプリケーション固有の動作を提供します。
    /// </summary>
    public sealed partial class App : Application
    {
        private BackgroundTaskDeferral appServiceDeferral = null;

        /// <summary>
        /// 外部のAppServiceからのメッセージ受信イベント
        /// </summary>
        public static event Action<AppServiceConnection, AppServiceRequestReceivedEventArgs> RequestReceived;

        /// <summary>
        ///単一アプリケーション オブジェクトを初期化します。これは、実行される作成したコードの
        ///最初の行であるため、論理的には main() または WinMain() と等価です。
        /// </summary>
        public App()
        {
            Launch();
            InitializeComponent();
            Suspending += OnSuspending;
        }

        private async void Launch()
        {
            await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
        }

        protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
        {
            base.OnBackgroundActivated(args);
            if (args.TaskInstance.TriggerDetails is AppServiceTriggerDetails)
            {
                appServiceDeferral = args.TaskInstance.GetDeferral();
                args.TaskInstance.Canceled += OnTaskCanceled;

                var details = args.TaskInstance.TriggerDetails as AppServiceTriggerDetails;
                var appService = details.AppServiceConnection;
                appService.RequestReceived += AppService_RequestReceived;
                appService.ServiceClosed += AppService_ServiceClosed;
            }
        }

        private void AppService_RequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
        {
            RequestReceived?.Invoke(sender, args);
        }

        private void AppService_ServiceClosed(AppServiceConnection sender, AppServiceClosedEventArgs args)
        {
            if (appServiceDeferral != null)
            {
                appServiceDeferral.Complete();
            }
        }

        private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
        {
            if (appServiceDeferral != null)
            {
                appServiceDeferral.Complete();
            }
        }

        // 以下省略...
}

パッケージプロジェクト

Windowsアプリケーションパッケージプロジェクト

Package.appxmanifest

クリックで開いて以下のように、宣言にApp Serviceを追加
FullTrustで起動するサービスの名前を設定する。
なんでもいいのでここでは「ftsAppService」とする。
image.png
ネームスペースに以下を追加

xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"

<Applications>に以下を追加

<desktop:Extension Category="windows.fullTrustProcess" Executable="{外部APPプロジェクト名}\{外部APP名}.exe" />

全体

Package.appxmanifest.xml
<?xml version="1.0" encoding="utf-8"?>

<Package
  xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
  xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
  xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
  xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
  IgnorableNamespaces="uap rescap">

  <Identity
    Name="482aa3a4-eb49-4343-b97b-322c22a6e5cf"
    Publisher="CN=Yuki4"
    Version="1.0.0.0" />

  <Properties>
    <DisplayName>fts</DisplayName>
    <PublisherDisplayName>Yuki4</PublisherDisplayName>
    <Logo>Images\StoreLogo.png</Logo>
  </Properties>

  <Dependencies>
    <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
    <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14393.0" MaxVersionTested="10.0.14393.0" />
  </Dependencies>

  <Resources>
    <Resource Language="x-generate"/>
  </Resources>

  <Applications>
    <Application Id="App"
      Executable="$targetnametoken$.exe"
      EntryPoint="$targetentrypoint$">
      <uap:VisualElements
        DisplayName="fts"
        Description="fts"
        BackgroundColor="transparent"
        Square150x150Logo="Images\Square150x150Logo.png"
        Square44x44Logo="Images\Square44x44Logo.png">
        <uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" />
        <uap:SplashScreen Image="Images\SplashScreen.png" />
      </uap:VisualElements>
      <Extensions>
        <desktop:Extension Category="windows.fullTrustProcess" Executable="fts.APP\fts.APP.exe" />
        <uap:Extension Category="windows.appService">
          <uap:AppService Name="ftsAppService"/>
        </uap:Extension>
      </Extensions>
    </Application>
  </Applications>

  <Capabilities>
    <Capability Name="internetClient" />
    <rescap:Capability Name="runFullTrust" />
  </Capabilities>
</Package>

依存関係

プロジェクト参照の追加から、UWPプロジェクトと外部APPプロジェクトを追加する。
image.png

ビルド

プラットフォームはx86かx64に、スタートアッププロジェクトはパッケージプロジェクトを指定する。
image.png

まとめ

UWPもDesktopBridgeを使用することで、かなり制限がなくなりました。(いいのか?)
以下の記事を参考になりました。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1