プロジェクト概要
ローカルサーバーを立てて、適当なレスポンスを返却するアプリケーションです。
UWPだと同一PCからのアクセスは不可能なので、DesktopBridgeを使用してローカルサーバーを外部サービスとして実行する。
プロジェクト構成
必要最低限のプロジェクト構成は以下のようになる。
├─fts パッケージプロジェクト
├─fts.APP FullTrustで起動する外部APPプロジェクト
├─fts.Shared 共通のクラスライブラリ
└─fts.UWP UWPプロジェクト
クラスライブラリ
クラスライブラリプロジェクト(.NetStandard2.0)
UWPプロジェクトと外部APPプロジェクトから参照する共通のライブラリ。
共通で使用する定数や、関数または2つのアプリケーション間の疎通に使用するメッセージクラスなど作成している。
UWPからも参照できるように、.NetStandard2.0を指定している。
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のはず)
<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して終了を待つだけ。
namespace fts.APP
{
internal class Program
{
public static async Task Main(string[] args)
{
await new HttpServer().WaitClosed();
}
}
}
AppServiceConnection
受信したリクエストをUWPアプリケーションに送信するなど
UWP側とのメッセージのやり取りにはAppServiceConnectionを使用する。
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の参照追加
App.xaml.cs
FullTrustで、外部AppServiceを起動
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
FullTrustで起動した外部サービスからのメッセージを受信できるように
OnBackgroundActivatedをオーバーライドして、AppServiceConnectionのRequestReceivedにイベントをハンドルする。
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;
}
}
全体
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」とする。
ネームスペースに以下を追加
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
<Applications>に以下を追加
<desktop:Extension Category="windows.fullTrustProcess" Executable="{外部APPプロジェクト名}\{外部APP名}.exe" />
全体
<?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プロジェクトを追加する。
ビルド
プラットフォームはx86かx64に、スタートアッププロジェクトはパッケージプロジェクトを指定する。
まとめ
UWPもDesktopBridgeを使用することで、かなり制限がなくなりました。(いいのか?)
以下の記事を参考になりました。