tmytのらくがき

個人の日記レベルです

最近の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に書いてあるそうです。

しばらくあそべそう…

BuriKaigi2019に行ってきました

早くも1か月前の話になりますが… BuriKaigi2019に行ってきました。

toyama-eng.connpass.com

ありがたいことに、なにか話していいよという時間を頂いたので、App CenterとかVSTSでCIする話をしてきました。

基本的には以前にエントリしたこれ (https://blog.tmyt.jp/entry/2018/11/26/011621) と同じですが、 せっかく.NETトラックなのでXamarin.Formsでコードを用意していった…のですが、デバイスの画面をPCに出力するアプリと APKインストーラの相性が悪いのか、いまいちちゃんと動かなかったのが残念です。

www.slideshare.net

当日は25分しかなく、.NETのドの字すらほとんどない感じで、さらにコードをほとんどお見せする余裕すらなかったので ここで供養しておきます。

NuGetパッケージの追加

App Centerの各機能を使うライブラリはさすがMicrosoftがやってるサービスなだけあって、NuGetからインストールするだけで使えます。

.NET Standardで共有される部分のライブラリプロジェクトに対してNuGetパッケージの追加

f:id:tmyt:20190224220716p:plain

AppCenterと検索するとそれらしいライブラリが出てくるのでほしいものをインストールします。 ちなみに、認証済みマークがついてないで判別できますが、2個目の "AppCenter.Analytics.Metrics" はサードパーティライブラリです。

f:id:tmyt:20190224220847p:plain

今回はCrashes, Analytics, Distributeをインストールしておきました。

コードの呼び出し

App Centerと連携するためのコードを少しだけ書きます。具体的にはApp.xaml.csのOnStart()の中に次のコードをコピペします。

AppCenter.Start("ios={Your App Secret};android={Your App Secret};uwp={Your App Secret}",
    typeof(Analytics), typeof(Crashes), typeof(Distribute));

{Your App Secret} と書かれた部分にApp Centerへ接続するGUIDっぽい文字列を指定します。これは、App Centerのプロジェクトページを開くと、ここに書いてあるやつです。

f:id:tmyt:20190224221605p:plain

iOSAndroid、UWPとそれぞれ指定できるので、プラットフォームごとにApp Centerのプロジェクトを作ってそれぞれの値を埋めます。

これでだいたいApp Centerと連携できるようになりました。

まとめ

という、これらはDocs.com のこのあたり (https://docs.microsoft.com/en-us/appcenter/sdk/getting-started/xamarin) に書いてあります。 また、App CenterでXamarinプロジェクトを作ると、OverviewのXamarin Formsタブに同じことが書いてあります。ここをコピペすればOKですね。

f:id:tmyt:20190224221840p:plain

sakura.io通信モジュールを手に入れたのでNetduino 3 Wifiに接続した

sakura.ioというサービス?があって、それの通信モジュールを手に入れたわけです。

sakura.io

sakura.ioはさくらインターネットのIoTプラットフォームで、SoftBankとL2接続した閉域網を通じて、さくらインターネットに設置されたデータセンターと通信できるサービス。っていう感じみたいです。

モジュールの通信先は閉域網でつながっているのでサービスが提供するデータセンター固定。SIMの差し替えとかも不可。なんですが、データセンターから先にWebSocketとかMQTTとかで任意のWebシステムと連携できるので実質インターネットにつながっているようなもんですね。

とりあえず、ドキュメントに沿ってスケッチを書いてデータが送信できることまで確認しました

送信するデータがなにもないと寂しいのでLM35DZをつないで、室温を投げ続ける装置と化したsakura.io通信モジュールとArduino UNO R3の図。

せっかくなので.NETで使おう!

NetduinoというArduinoコンパチ風な.NET MicroFrameworkが動くマイコンボードが昔々ありました。.NET CoreだのWindows 10 IoTだの言われる前の時代のもので、スイッチサイエンスによると、掲載日が2015年だそうです*1

このボード、国内で使えるWiFiモジュールがついていて、SDカードリーダが付いていて、C#でプログラムが書けて、Visual Studioブレークポイントまで設定できて、Arduinoとピン配列互換という素晴らしいやつなんですが、まぁ流行った気は全くしないです。

ちなみにSDにログ書くなんてのはC#なのでこう書けば普通にできるようなやつでした。

using (var writer = new StreamWriter("\\SD\\log.txt", true))
{
    writer.WriteLine("Event happen!");
}

NetduinoはArduino UNOと違って、IOが3.3vの5vトレラントです。が、sakura.ioのArduinoシールドボードはIO電圧を5v/3.3vから選択できるとても優秀な設計なため、3.3vにしてあげればそのまま使えるはず…なのでArduino向けのライブラリをC#に移植しました。

github.com

もともとがとてもよくできたライブラリだったので、ほとんどそのまま移植してあります。名前とか定数をC#っぽくしたくらい。

その結果、アナログA0からデータを読んで、sakura.ioに送信するのはこう書けるようになりました。

public static void Main()
{
    var sakuraio = new SakuraIO.SakuraIO_I2C();

    for (;;)
    {
        if ((sakuraio.GetConnectionStatus() & 0x80) == 0x80) break;
        Debug.Print(".");
        Thread.Sleep(1000);
    }

    var input = new AnalogInput(Cpu.AnalogChannel.ANALOG_0);

    while (true)
    {
        Debug.Print((330 * input.Read()).ToString());
        sakuraio.EnqueueTx(0, 330 * input.Read());
        sakuraio.Send();
        Thread.Sleep(1000 * 30);
    }
}

Netduino 3 WiFiに載せるとこんな感じ。白色LEDがクソまぶしい。

Netduino 3 WiFi向けの話

2018年の暮れにもなって、.NET MicroFrameworkをNetduinoで使う人が何人いるかはわかりませんが、書き残しておきます。

Netduino 3 WifiはUSB 5vをVINへ流していないのでACアダプタが無いとsakrua.io通信モジュールに電源が供給されません。 ちゃんと電源を接続しましょう。

電源は7v~12vまで大丈夫なので、僕はマルツでArduino用に今回のために手に入れた9v/2.5A電源をそのまま流用しました。

Azure DevOpsとAppCenterでCI/CDといわれるやつをやった

Azure DevOpsとAppCenterを使ってCIできるようにした

やったこと

  • Azure DevOps(旧VSTS)でソース管理とビルドパイプラインの面倒を見る。AppCenterでテスターにバイナリを配布する。
  • AndroidiOS両方やる
  • ビルドマシンは自宅にmacOSな物理マシンを設置する

セットアップ

セットアップ自体は簡単。どれもすぐできるので省略します。

  • Android Studioをセットアップする
  • XCodeをセットアップする
  • Azure DevOpsのビルドエージェントをセットアップする

困ったこと

  • iOSのビルドがうまくいかなくて泣いた

iOSはProvisioning Profileとp12を設定しているのに署名がうまくいかなくて泣いた。 結局、VSTSのビルドタスクの実装を読んで、ビルド前にMagicを施すことでなぜかビルドできるようになった。

逆にAndroidはデフォルトでなにも困っていないので特に触れません。

泣いた結果のビルドパイプライン

Pipelineのところ。Schemeをちゃんと書く。

f:id:tmyt:20181126004828p:plain

なんかよくわからない文字列をpbxprojに書き込む

echo "/* tweaks
ProvisioningStyle = Manual;
*/"  >> ”プロジェクト名”.xcodeproj/project.pbxproj

DerivedDataのせいでビルドにこけることがあるので毎回消す

echo Cleaning DerivedData...
rm -rf /Users/<ユーザー名>/Library/Developer/Xcode/DerivedData/<プロジェクト名>-*

XCode Buildはこれでいいらしい。最初Manual署名とかいろいろやってみたけど、結局署名がうまくいかなかったりでよくわからない。 最初の謎の文字列をpbxprojに書き込むとビルドタスクがいい感じに動く。もうさっぱりわからない。

f:id:tmyt:20181126005134p:plain

ついでにGitのChangeLogをBuild artifactに入れておく。

echo "$(Build.SourceVersionMessage)" > $(build.artifactstagingdirectory)/CHANGELOG

結局ビルドタスクの実装に依存した謎の文字列を無理やり押し込んで解決したけど、こんなことしなくても通るはず…謎。もう無理。

リリースする

デフォルトでビルド後にAppCenterに投げるようなタスクが書かれてる。けどせっかく(?)なので、Releaseを使った。

トリガーに、CIビルドを設定。タスクにはAppCenterにDeployするタスクを設定しただけとても簡単。

Release notes fileに、ビルド時に生成したCHANGELOGを指定した。

f:id:tmyt:20181126010304p:plain

Gitのログを出力したのはここで使うためでした。

AppCenterのIn-app Updateを使う準備をする

In-app Updateを使うにはちゃんとバージョン番号が上がっていないとだめ。

  • iOSの場合CFBundleVersionが現在のバイナリより新しい場合
  • Androidの場合versionCodeが現在のバイナリより新しい場合

それぞれの場合に、アップデート通知が出る。なのでいい感じにバージョンを更新しないといけない。

調べるとVSTSのビルドタスクでできるよーとか書いてあったりいろいろ試したけどめんどくさくなった*1。 ので、macなんだろsedでいいだろsedで。という感じで解決しました。

Android

Androidはbuild.gradleのversionCode 1ってなってるところをsedで$(Build.BuildId)に置換するだけ。簡単

sed -i -e "s/versionCode 1/versionCode $(Build.BuildId)/" app/build.gradle

iOS

iOSはもっと簡単でmacなのでplistを編集するCLIツールがあるのでそれを使う。CFBundleVersionを文字列で$(Build.BuildId)に書き換える。こちらも簡単

plutil -replace 'CFBundleVersion' -string "$(Build.BuildId)" <プロジェクト名>/Info.plist

アプリにIn-app Updatesをセットアップする

実際にIn-app Updatesを有効にするには、アプリ側にライブラリをインストールして少しだけコードを書き足す必要があります。 ただこれも、使い方はdocs.comを読むと書いてます。

docs.microsoft.com

docs.com読めって言えばそれだけの話なんですが、とりあえず簡単に紹介だけしておきます。といってもdocs.comに書いてることと変わりません。

Android

Androidは簡単。Gradleに依存を書く。

dependencies {
   def appCenterSdkVersion = '1.10.0'
   implementation "com.microsoft.appcenter:appcenter-distribute:${appCenterSdkVersion}"
}

importを書いて、MainActivityのonCreateあたりに次のコードを埋める。

AppCenter.start(getApplication(), "{Your App Secret}", Distribute.class);

おわり。簡単。

iOS

iOSもだいたい同じ。

Cocoapodsからライブラリを入れる。

pod 'AppCenter/Distribute'

importを書いて、didFinishLaunchingWithOptionsあたりに次のコードを埋める。

MSAppCenter.start("{Your App Secret}", withServices: [MSDistribute.self])

ただ、iOSはInfo.plistをちゃんと書かないと動かない。 ドキュメントにもちゃんと書けよ。って書いてあるけど見落とすと動かなくて悩むのでちゃんと書きましょう*2

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>appcenter-${APP_SECRET}</string>
        </array>
    </dict>
</array>

おわり

*1:PowerShellタスクだからmacなので動かないとかあって嫌になった

*2:1時間くらい悩んだ