tmytのらくがき

個人の日記レベルです

せっかくなのでUno Platformの話がしたい

Uno Platformというものがある

UWPのコードをベースにして、自力でXAMLを解釈して、Xamarinの上でViewをいい感じに動かすっていうアプローチ。Xamarin.Formsのラッパーではない。

俺たちが期待してるUniversalはこれなんだよ、いいからMSは早く買収するんだ。という気持ちを抑えながら、公式ページで図解されている構造を見てみるとこういう感じ。

https://s3.amazonaws.com/uno-website-assets/wp-content/uploads/2019/08/21141002/diagram.png

あれこれなんか4年前に似たような図を描いたような気がする*1

簡単なコードなら普通に動くし、複雑なコードもUno Platformをターゲットに最初から作ればわりかしちゃんと動く。よくできている。

実際にWindows 10の電卓が移植されていて、Play StoreとかApp Storeからダウンロードできるので、とりあえず「すげーーー」って言うには電卓を入れるのがおすすめ。

Uno Calculator

Uno Calculator

  • nventive
  • 仕事効率化
  • 無料
apps.apple.com play.google.com

ただ、本物のUWPと、UWPに似せたAPIを提供したXamarinだとどうしても違うことがあるので現行のUWPのコードを突っ込めばそのまま動くか?と聞かれれば答えは”ほぼNo”という感じ。 実際にコピペしたら全然動かんかった。

implicit styleが動かんという話

さて、いろいろ動かないことはあるんだけども一番困ったimplicit styleが動かんというトピックを紹介しておきます。 アプリの見た目をそろえるときにこういう書き方をすることがあります。

<Application
    x:Class="UnoApp2.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Application.Resources>
    <Style TargetType="TextBlock">
      <Setter Property="Foreground" Value="Red" />
    </Style>
  </Application.Resources>
</Application>

すべてのTextBlock の文字色が赤になることを期待しています。当然UWPで実行すると赤で表示されます。

f:id:tmyt:20191130044459p:plain

ところが、Uno Platformでビルドすると次のメッセージが得られます。

Error reading response 1>MSBUILD : error : Generation failed: System.AggregateException: 1 つ以上のエラーが発生しました。 ---> System.InvalidOperationException: Generation failed for Uno.UI.SourceGenerators.XamlGenerator.XamlCodeGenerator. System.AggregateException: 1 つ以上のエラーが発生しました。 ---> System.Exception: Processing failed for file ***\UnoApp2.Shared\App.xaml ---> System.Exception: Implicit styles in inline resources are not supported (Application, Line 1:2) 1>MSBUILD : error : 場所 Uno.UI.SourceGenerators.XamlGenerator.XamlFileGenerator.RegisterResources(XamlObjectDefinition topLevelControl) 1>MSBUILD : error : 場所 Uno.UI.SourceGenerators.XamlGenerator.XamlFileGenerator.BuildApplicationInitializerBody(IndentedStringBuilder writer, XamlObjectDefinition topLevelControl) 1>MSBUILD : error : 場所 Uno.UI.SourceGenerators.XamlGenerator.XamlFileGenerator.InnerGenerateFile() 1>MSBUILD : error : 場所 Uno.UI.SourceGenerators.XamlGenerator.XamlFileGenerator.GenerateFile()

なんやかんや書いてますが、要するにx:Key が指定されていない暗黙的なリソースはサポートされてないですよ。ということらしいのです。めっちゃ困る。

これに関連したIssue(というかPR)をGitHubで探すとこれが見つかります。

github.com

ここの、 What is current behavior? に書いてあるのですが

Uno only supports global-level resources, including implicit styles

らしいのです。このglobal-levelがポイントです。

解決する話

先のPRから現状の実装ではglobal-levelならimplicit stylesが動く。と読み取れます。 じゃぁglobal-levelなstyleとはなんぞや。という話があり、ドキュメントなどから雰囲気をつかむと要するにこれです。

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Style TargetType="TextBlock">
    <Setter Property="Foreground" Value="Red" />
  </Style>
</ResourceDictionary>

てきとうにXAMLを作って、ResourceDictionaryを書いて、その中ならimplicit styleと呼ばれているものを書きまくってもいいということらしいのです。実際動かすと動きます。

f:id:tmyt:20191130045520p:plain:w220 f:id:tmyt:20191130045903p:plain:w220

そんじゃこれ、UWPで動くんかというと動きません。実行結果はこれです。

f:id:tmyt:20191130044539p:plain

ではこれが、全部の環境で期待値となるXAMLはどうなるか。というと、App.xamlでResourceDictionaryとして読めばいい。

<!-- Themes/ImplicitStyles.xaml -->
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style TargetType="TextBlock">
        <Setter Property="Foreground" Value="Red" />
    </Style>
</ResourceDictionary>

<!-- App.xaml -->
<Application
    x:Class="UnoApp2.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Application.Resources>
    <ResourceDictionary Source="Themes/ImplicitStyles.xaml" />
  </Application.Resources>
</Application>

これで最初の画像と同じ結果が得られます(見た目変らないので画像省略)。

だんだん奇妙なXAMLになってきましたね。さすがはCross Platform…たまんないですね…

WPFの子ウィンドウとしてUnityを実行してみる

UnityってWPFの中に表示したりできひんよね?って聞かれたからドキュメント読んだらできるって書いてたからやってみたらできました。という話。

TL;DR

  • -parentHWND 0x**** で子ウィンドウにできる
  • HwndHostでUnityのプロセスを子ウィンドウとして起動するだけ

Unityを子ウィンドウで起動する

Windows向けにビルドしたUnityはコマンドライン引数で -parentHWND を付けて起動するとUnity Playerは子ウィンドウとして起動します。その時の画面幅などはSTARTUPINFOで指定されたものを使用します。と、Unityのドキュメントに書いてあります。

Windows スタンドアロンアプリケーションを別のアプリケーションに埋め込みます。これを使用する場合は、親アプリケーションのウィンドウハンドル (HWND) を Windows スタンドアロンアプリケーションに渡す必要があります。

これ、日本語だと特に書いてないんですけど、0xXXXXの形で16進数として渡すと成功します。 追記: 0xなしで10進でもOKでした

簡単にC#から起動するにはこんな感じ。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Loaded += MainWindow_Loaded;
    }

    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        StartUnity();
    }

    void StartUnity()
    {
        var apppath = @"C:\doko\soko\unity-app.exe";
        var cmdline = $"-parentHWND {new WindowInteropHelper(this).Handle}";
        Process.Start(apppath, cmdline);
    }
}

実行すると、UnityがWPFの中で動いているような感じになりました。が、リサイズしてもウィンドウサイズが追従しないのでいまいちです。

f:id:tmyt:20190926065827p:plain

HwndHostをそれとなく実装していく

HwndHostを実装すれば子ウィンドウのサイズをWPFが面倒見てくれるようになります。Unityを子ウィンドウとして起動してくれるHwndHostを実装したクラスを書いてリサイズをいい感じに面倒見てもらいます。

class UnityHost : HwndHost
{
    private Process _childProcess;
    private HandleRef _childHandleRef;

    public string AppPath { get; set; }
    public HandleRef Child => _childHandleRef;

    protected override HandleRef BuildWindowCore(HandleRef hwndParent)
    {
        var cmdline = $"-parentHWND {hwndParent.Handle}";
        _childProcess = Process.Start(AppPath, cmdline);
        while (true)
        {
            var hwndChild = User32.FindWindowEx(hwndParent.Handle, IntPtr.Zero, null, null);
            if (hwndChild != IntPtr.Zero)
            {
                return (_childHandleRef = new HandleRef(this, hwndChild));
            }
            Thread.Sleep(100);
        }
    }

    protected override void DestroyWindowCore(HandleRef hwnd)
    {
        _childProcess.Dispose();
    }
}

static class User32
{
    [DllImport("user32.dll")]
    public static extern IntPtr FindWindowEx(IntPtr hParent, IntPtr hChildAfter, string pClassName, string pWindowName);
}

急にコードが増えた気がしますが気のせいです。BuildWindowCoreの中でUnityを起動して、できたウィンドウのハンドルを返してるだけです。 このままだと、起動するパスがどこか明示されていないのでXAMLから指定します。

<Window x:Class="WpfApp4.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:WpfApp4"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <local:UnityHost AppPath="C:\doko\soko\unity-app.exe" />
    </Grid>
</Window>

これでリサイズできるようになりました。

起動時にWPFのクライアントエリア全体にUnityが出ないようにする

ここまでのコードだと、XAMLでUnityHostをGridにいれてサイズを変えたりしていても、Unityを起動したときにクライアントエリア全体にUnityが一瞬表示されます。

f:id:tmyt:20190926070602p:plain

これを回避するにはUnityのドキュメントに書いてあったようにSTARTUPINFOを指定すればいいんですが、残念ながらそれはP/Invokeです。ということでP/Invoke使っていろいろ実装したものがこちらです。

gist.github.com

P/Invokeのせいでやたらと長くなった…やな感じ… 実際の処理はBuildWindowCoreを参照してください。STASRTUPINFO構造体を埋めて、CreateProcessを呼び出しているだけです。

おしまい

  • Unityプレイヤーを-parentHWNDコマンドライン引数を使って、WPFに埋め込んでみました。
  • UnityプレイヤーをHwndHostを使ってWPFの子ウィンドウとして管理しました

あるプロセスが読み込んでいる.NET Coreランタイムを特定したい

なんか表題のことがしたいらしく、よく聞いたら任意の.NET Coreプロセスが読み込んでいるcoreclr.dllのパスが知りたいという話でした。

coreclr.dllで.NET Core仮想マシンをホストしてるんだから、プロセスが読み込んでるモジュールを列挙したらわかるでしょ。とか思って、Processの情報を眺めたら、彼はそこに常にいたので、それを探したらお悩みは解決。

どうやらC#で解決したいらしいので、とりあえずpsapiからEnumしただけでおわり。

gist.github.com

EnumProcessesでプロセスを列挙してOpenProcessにPROCESS_VM_READとPROCESS_QUERY_INFORMATIONをつけてプロセスハンドルを手に入れて、EnumProcessModulesExでモジュールハンドルを列挙して、GetModuleFileNameExでモジュールのパスを取得する。そこにcoreclr.dllがあればそいつが.NET Coreプロセスだ!

という話でした。

最近のWindows 10で絵文字を書記素クラスタ単位で数える

UWPからP/Invokeすることが許可されているAPIが列挙されているページがあります。今日も特に理由もなく眺めていたらicuuc.dllが16299から使えるようになっていたことに今更気づきました。

docs.microsoft.com

icuuc.dllはICUの一部で、ICUはInternational Components for Unicodeの略で、Unicodeの難しいいろいろを面倒見てくれるライブラリです。 今日はこれを使って絵文字を書記素クラスタ単位で数えてみたいと思います。

今回のコードはこのページを参考にしました。C++からC#に書き直しただけでほぼそのままです。

qiita.com

書記素クラスタ

詳しいことはここを読むといいと思います。

ufcpp.net

Unicodeを1文字ずつ分割するときに使うアルゴリズムです。いろいろ調べても基本的にテーブル引きしないといけないようなことが書いてました。

.NETから呼ぶ

.NETから呼ぶにはP/InvokeすればOKです。今回は、書記素クラスタに分割するAPI周りだけ定義を作りました。

gist.github.com

これを使って、こんなコードを書くと書記素クラスタに基づいて、何文字で構成された文字列なのかがわかります。 参考にしたコードでは、utext_openUTF8を使っていたけれども、.NETのStringはUTF-16なのでutext_openUCharsを使ったほうが都合がいいです。

public static int TextLengthInGrapheme(string s)
{
    var iterator = Icuuc.ubrk_open(UBreakIteratorType.UBRK_CHARACTER, Icuuc.uloc_getDefault(), null, 0, out _);
    var utext = Icuuc.utext_openUChars(IntPtr.Zero, s, s.Length, out _);
    Icuuc.ubrk_setUText(iterator, utext, out _);
    var n = 0;
    while (Icuuc.ubrk_next(iterator) != Icuuc.UBRK_DONE) n++;
    Icuuc.ubrk_close(iterator);
    Icuuc.utext_close(utext);
    return n;
}

また、文字を切り出すにはこんな感じで呼べばよさそうです。

var iterator = Icuuc.ubrk_open(UBreakIteratorType.UBRK_CHARACTER, Icuuc.uloc_getDefault(), null, 0, out _);
var utext = Icuuc.utext_openUChars(IntPtr.Zero, Text, Text.Length, out _);
Icuuc.ubrk_setUText(iterator, utext, out _);
int current = Icuuc.ubrk_current(iterator);
while (current != Icuuc.UBRK_DONE)
{
    int next = Icuuc.ubrk_next(iterator);
    if (next == Icuuc.UBRK_DONE) break;
    int size = next - current;
    Debug.WriteLine($"{current}-{next}: {Text.Substring(current, size)}");
    current = next;
}
Icuuc.ubrk_close(iterator);
Icuuc.utext_close(utext);

比べてみる

今回作ったコードと、.NETのStringInfoクラスと、String[Index]でえられる結果を並べてみました。 左から、ICU、StringInfo、String[Index]です。

f:id:tmyt:20190420205012p:plain

ICUは1文字としてカウントされてます。StringInfoはサロゲートペアを認識はしてますが、書記素分割には対応してないので11文字、String[Index]はサロゲートペアをバラバラにカウントするので19文字に見えています。 StringInfoを使っていると👩🏻‍👩🏿‍👧🏼‍👧🏾が11文字としてカウントされていたのが正しく1文字になるのでTwitterクライアントの文字カウントがより正確になります*1

おまけ

f:id:tmyt:20190420205540p:plain

Twitter…お前…!!!!

*1:Aristeaは近日対応したのがリリースされます

プロセスのことが気になって夜も眠れないのでツールを書いた

TL;DR

  • Windows 10 on ARMで動いているプロセスがi386なのか、ARM32なのか、ARM64なのか気になって夜も眠れないのでツールを書きました。
  • GitHubにあります。

夜も眠れない

Windows 10 on ARMは、i386、ARM32、ARM64のPEが実行できる、とても愉快なOSだそうです。確かに、バイナリを眺めているとARM32とかi386の実行ファイルが紛れ込んでいるのが確認できます。

でも、タスクマネージャからはそのプロセスがターゲットにしてるCPUアーキテクチャが確認できません。確認できるのは32bit or 64bitのみ。

f:id:tmyt:20190401120237p:plain

僕が知りたいのはビット数じゃなくて、CPUアーキテクチャなんです。気になって夜も眠れなくなってきました。

確認方法

IsWow64Process2 という今欲しかったものが得られるAPIがあります。呼びましょう。

BOOL IsWow64Process2(
  HANDLE hProcess,
  USHORT *pProcessMachine,
  USHORT *pNativeMachine
);

これはプロセスのハンドル*1を渡すと、pProcessMachineにプロセスが何のCPU向けのバイナリなのかを返してくれます。このとき、実行環境のネイティブバイナリの場合は IMAGE_FILE_MACHINE_UNKNOWN が入っているので、WOW64で動いているのかそうでないのかがわかります。

ソースとバイナリ

これを全プロセスに対してチェックして、リストにするような感じに仕立てておきました。

f:id:tmyt:20190401120728p:plain

ソースとバイナリはGitHubにあります。

github.com

*1:OpenProcessの引数に"PROCESS_QUERY_INFORMATION"か"PROCESS_QUERY_LIMITED_INFORMATION"が必要です

Sakura.IOをWindows 10 IoTで使うライブラリを供養した

Sakura.IOをWindows 10 IoT上で使うライブラリをBuriKaigiで話そうかと思って作ったんだけど結局使わなかった。 せっかくなのでGitHubに公開しておきました。

github.com

もう少し便利なハイレベルAPI整備したほうが使いやすいと思うんですが、とりあえずほぼArduino版のPortです。 C#/UWPなのでawaitableな関数で実装しないといけないことがあったので、せっかくなので接続完了を待つメソッドも用意しておきました。

こんな感じで使えます。

var sakuraio = new SakuraIO_I2C();
await sakuraio.OpenAsync("I2C1");
await sakuraio.WaitForConnectionAsync();
var bytes = Encoding.UTF8.GetBytes("HELOWRLD");
sakuraio.EnqueueTx(0, BitConverter.ToUInt64(bytes, 0));
sakuraio.Send();

Sakura.IO + Raspberry Pi + Windows 10 IoT Core っていうパイの小さそうな組み合わせですが使えそうならぜひどうぞ。

Lenovo C630を買いました

Microsoft MVP Global Summtでシアトルにきているので、家電量販店をのぞいたらC630が売っていたので買いました。

とりあえずこの記事もC630で書いていますが、まーまー良くも悪くもただのWindowsです。 初回起動時のパフォーマンスも使っている分にはそんなに悪いと感じない程度には普通のWindowsです。

ちなみに量販店モデルは

  • Snapdragon 850
  • RAM 8GB
  • SSD 128GB
  • Officeなし(アメリカだからね)

という構成でした。

C630はLTEモデム内蔵なので、SIMを入れれば単体で通信できます。量販店でこいつはSIM Lockedか?って確認したら、そうだと思うよ。と言われながらも、 VerizonのLTEモデルだったらSIMロックかかってないはずだしな…と思い、試しにIIJmioDocomoと、T-Mobile (アメリカの現地SIM)を入れてみたところ、ちゃんと認識しました。

最近のWindowsは、ARMで実行できるだけでなく、ARM上でWSLがちゃんと実行できるところがとても偉いと思います。 WSLとDebianをインストールして、 uname -a とすると、ちゃんと aarch64 って出ます。

以前にx64版のWSLで32bit ELFを実行できたように、同じようにQEMUを入れて設定すると*1たぶんx86 ELFも動くのでしょう。たぶん。

ほかにも。ARM版のWindowsx86バイナリを実行時にARM命令にトランスレートする機能があるので、x86のPEが実行できます。 タスクマネージャで詳細タブのプラットフォームカラムを追加したときに32bitってでるやつはたぶんx86

f:id:tmyt:20190322015637p:plain

ここに見えているChromeとOneDriveはPEヘッダを確認したところ、どちらもx86版でした。 にこれがOneDrive.exeのヘッダ部分。選択箇所が 4c 01なのでx86バイナリです。

f:id:tmyt:20190322015848p:plain

ちなみにこっちが、Windows付属のnotepad.exeのヘッダ部分。64 aaと書いてるのでARM64バイナリです。

f:id:tmyt:20190322020113p:plain

トランスレーションで実行されててもそんなに遅いと思わないので、よくもまぁあの複雑なバイナリを…という感じです。 詳しいことはこの辺*2に書いてあるそうです。

しばらくあそべそう…