しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

【Win10 Pre】 Windows 8.1 のランタイムブローカーで UWP アプリからデスクトップアプリを起動したい

Windows 10 リリース前の最後の休日ということで若干面倒なネタを

正式リリース後にいろいろ変わるかもしれませんが、UWP アプリでもやりたい放題したい!サイドローディングならできるんでしょ?ということで、以前 Microsoft の荒井さんが紹介していたアプリブローカー が使えないか試してみたいと思います

最初 Windows 10 SDK .NET Framework 4.6 でランタイムブローカーをビルドしようとしたんですが、Visual Studio 2015 RC の開発者コマンドプロンプト使ってもなぜかターゲットバージョンが Windows 8.1 になるので諦めたのは内緒!

・・・というわけで、Windows 8.1 のランタイムブローカーを拝借することにしました

※ 参考までに今回利用したソースファイル一式は下記の GitHub リポジトリにアップしています

Windows 10 Insider Preview Build 10240 時点での情報のため、正式リリース後仕様等が変更になっている可能性があります

きっと正式リリースしたら Windows 10 用の方法が公開されるはず!


まずは Windows 8.1 のランタイムブローカー用プロジェクトテンプレートを下記からダウンロードしてインストールします

visualstudiogallery.msdn.microsoft.com

インストールが終わったら Visual Studio 2013 を起動します

お次はプロジェクトの新規作成ダイアログを開き「Visual C#」の下の方にある「Brokered Windows Runtime Component」のテンプレートを選んでプロジェクトを作成します

f:id:matatabi_ux:20150725185016p:plain

プロジェクトが開かれたらデフォルトで作成される Class.cs は削除し、下記のインタフェースとその実装クラスを追加します

namespace RuntimeBroker.Broker
{
    /// <summary>
    /// Desktop Application launcher interfce
    /// </summary>
    [ComVisible(true)]
    public interface IAppLauncher
    {
        /// <summary>
        /// Launch desktop application from file name
        /// </summary>
        /// <param name="fileName">target application executable file name</param>
        void Launch(string fileName);
    }
}

こっちがインタフェース

namespace RuntimeBroker.Broker
{
    /// <summary>
    /// Desktop application launcher
    /// </summary>
    [ComVisible(true)]
    public sealed class AppLauncher : IAppLauncher
    {
        /// <summary>
        /// Launch desktop application from file name
        /// </summary>
        /// <param name="fileName">target application executable file name</param>
        public void Launch(string fileName)
        {
            Process.Start(fileName);
        }
    }
}

そして実装クラス本体です

クラス属性に COM 参照可能にする ComVisible 属性をつけることと、実装クラス本体を sealed クラスにすることを忘れないように気を付けます

ここまでできたらまたプロジェクト追加

今度は「他の言語」-「Visual C++」からやっぱり下の方にある「Brokered Windows Runtime ProxyStub」のテンプレートを選んでプロジェクトを作成します

f:id:matatabi_ux:20150725185029p:plain

プロジェクトができたらソリューションエクスプローラーのビューから追加したプロジェクトを選び、右クリックメニューから「プロパティ」を開きます

「共通プロパティ」-「参照」のツリーを選び、右側の「新しい参照の追加」ボタンで先ほど作った Brokered Windows Runtime Component のプロジェクト RuntimeBroker.Broker のプロジェクト参照を追加します

f:id:matatabi_ux:20150725185735p:plain

さらに「構成プロパティ」-「リンカー」を選択し、「出力の登録」を「はい」に変更します

f:id:matatabi_ux:20150725190222p:plain

変更したら「OK」ボタンでダイアログを閉じ、プロジェクトを保存して Visual Studio 2013 は終了、今度は管理者として Visual Studio 2015 RC を起動します*1

Visual Studio 2013 で開いていたソリューションを再度開き、さらに新しいプロジェクトを作成

ここで Universal Windows Platform アプリの Blank App を追加します

f:id:matatabi_ux:20150725192727p:plain

プロジェクトができたら、UWP プロジェクトにプロジェクト参照として ProxyStub の方の参照を追加します

f:id:matatabi_ux:20150725193005p:plain

さらに Package.appxmanifest を開き編集します

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

<Package
  xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
  xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
  xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
  IgnorableNamespaces="uap mp">

  <Identity
    Name="7304a498-82e6-4db2-86f8-2660b5580812"
    Publisher="CN=matatabi-ux"
    Version="1.0.0.0" />

  <mp:PhoneIdentity PhoneProductId="7304a498-82e6-4db2-86f8-2660b5580812" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>

  <Properties>
    <DisplayName>RuntimeBroker</DisplayName>
    <PublisherDisplayName>matatabi-ux</PublisherDisplayName>
    <Logo>Assets\StoreLogo.png</Logo>
  </Properties>

  <Dependencies>
    <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.10069.0" MaxVersionTested="10.0.10069.0" />
  </Dependencies>

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

  <Applications>
    <Application Id="App"
      Executable="$targetnametoken$.exe"
      EntryPoint="RuntimeBroker.App">
      <uap:VisualElements
        DisplayName="RuntimeBroker"
        Square150x150Logo="Assets\Logo.png"
        Square44x44Logo="Assets\SmallLogo.png"
        Description="RuntimeBroker"
        BackgroundColor="#464646">
        <uap:SplashScreen Image="Assets\SplashScreen.png" />
      </uap:VisualElements>
    </Application>
  </Applications>

  <Capabilities>
    <Capability Name="internetClient" />
  </Capabilities>

  <Extensions>
    <Extension Category="windows.activatableClass.inProcessServer">
      <InProcessServer>
        <Path>clrhost.dll</Path>
        <ActivatableClass ActivatableClassId="RuntimeBroker.Broker.AppLauncher" ThreadingModel="both">
          <ActivatableClassAttribute Name="DesktopApplicationPath" Type="string" Value="%RuntimeBrokerPath%" />
        </ActivatableClass>
      </InProcessServer>
    </Extension>
  </Extensions>
  
</Package>

変更したのは最後の の部分の追記です・・・%RuntimeBrokerPath% の部分は後で設定します

  <Extensions>
    <Extension Category="windows.activatableClass.inProcessServer">
      <InProcessServer>
        <Path>clrhost.dll</Path>
        <ActivatableClass ActivatableClassId="RuntimeBroker.Broker.AppLauncher" ThreadingModel="both">
          <ActivatableClassAttribute Name="DesktopApplicationPath" Type="string" Value="%RuntimeBrokerPath%" />
        </ActivatableClass>
      </InProcessServer>
    </Extension>
  </Extensions>

この状態で一度ソリューションをビルド!

警告は無視してとりあえず適当に MainPage.xaml を修正

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

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        
        <Button Content="Launch calc app" 
                FontSize="32"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                Click="Button_Click"/>

    </Grid>
</Page>

コードビハインドはこんな感じで、ボタンが押されたら AppLauncher からデスクトップアプリの電卓を起動するようにしてみました

/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
    }

    /// <summary>
    /// Launch a desktop application 'calc.exe'
    /// </summary>
    /// <param name="sender">event sender</param>
    /// <param name="e">event aruguments</param>
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        new AppLauncher().Launch("calc.exe");
    }
}

コーディングとしてはこれで終わりですが、まだ準備が必要です

まずは UWP を実行する Windows 10 環境の適当な場所に DLL や winmd を配置するためのフォルダを作ります

f:id:matatabi_ux:20150725201922p:plain

今回は安直に「C:\RuntimeBroker」というフォルダを作りました

フォルダができたら Visual Studio のソリューションエクスプローラー上で右クリックメニューから「エクスプローラーでフォルダを開く」を選び、ソリューションフォルダ内の「Debug」または「Release」配下にあるであろう「RuntimeBroker.ProxyStub」フォルダの中の DLL などを全部、先ほどの「C:\RuntimeBroker」にコピーします

f:id:matatabi_ux:20150725200023p:plain

コピーができたら Windows 10 上で左下の Windows ロゴボタンを右クリックして、「システム」を開きます

f:id:matatabi_ux:20150725200345p:plain

左側に表示される「システムの詳細設定」を選び表示された「システムのプロパティ」の「詳細設定」タブ、下の方の「環境変数」ボタンを押します

環境変数」のダイアログが開いたら「システム環境変数」の方の「新規」ボタンを押して「RuntimeBrokerPath」の名前で「C:\RuntimeBroker」のフォルダパスを値に入力して「OK」ボタンで保存します

f:id:matatabi_ux:20150725202257p:plain

さらにさらに・・・まだまだ続く、Windows 10 上で左下の Windows ロゴボタンを右クリックして「コマンドプロンプト(管理者)」を選んでコマンドプロンプトを管理者モードで開きます

f:id:matatabi_ux:20150725201157p:plain

プロンプトが起動したら次のコマンドを実行・・・

cd %RuntimeBrokerPath%

icacls . /T /grant *S-1-15-2-1:RX

regsvr32 RuntimeBroker.ProxyStub.dll

f:id:matatabi_ux:20150725202532p:plain

先ほどの C:\RuntimerBroker フォルダをアプリから読み書きできるようにするために icals のコマンドを実行し、さらに RuntimeBroker.ProxyStub.dll の DLL を COM に登録しました

さてこれで、すべての準備が整ったはずなので UWP の RuntimeBroker プロジェクトをストートアッププロジェクトに設定し、プラットフォームを x86 に変更しておためし実行!

f:id:matatabi_ux:20150725203209g:plain

できましたー!!電卓が起動しましたね

ランタイムブローカーは管理者権限昇格の UAC が必要な処理や通信が重いなどちょっと制限があるみたいですが、業務アプリを UWP で作るときには心強いコンポーネントですね・・・正式リリースではきっと Windows 10 SDK.NET Framework 4.6 でも作れるようになるはずだし!

*1:途中のビルドで管理者権限がないと失敗するため