Android上のXamarinアプリでSSL接続できない

XamarinというかMonoの問題?
XamarinはC#Android/iOS向けアプリを作れて(Windows 10 mobileは死にました)、ライブラリも.NET Standardに対応させておけばWindowsデスクトップアプリとも共通化できる素晴らしいテクノロジー。
もっと流行ってほしい。
しかし、サーバーとSSLで通信するアプリを作ったところ問題が出た。
こんなコード:

try
{
    using (var client = new TcpClient())
    {
        client.Connect(hostName, port);

        using (var sslStream =
            new SslStream(client.GetStream(), false, (o, cert, chain, err) => true))
        {
            sslStream.AuthenticateAsClient(hostName);
            Debug.WriteLine(sslStream.SslProtocol);
        }
    }
}
catch (Exception e)
{
    Debug.WriteLine(e);
    if (e.InnerException != null)
    {
        Debug.WriteLine(e.InnerException);
    }
}

sslStream.AuthenticateAsClientで例外。

System.IO.IOException: The authentication or decryption has failed. ---> System.IO.IOException: The authentication or decryption has failed. ---> Mono.Security.Protocol.Tls.TlsException: The authentication or decryption has failed.

new SslStreamの第三引数には本来は証明書を検証する関数を渡す必要があるんだけど、その関数を実行する前に落ちる。
Windowsだと普通に動く。

調べてみるとMonoのSSLの実装が古いとかTLSに未対応とかなんとか…?
forums.xamarin.com
いずれにせよAndroidプロジェクトの設定を変えれば良さげ。
プロジェクトの設定からAndroidオプション→詳細設定で
f:id:boredbone:20180401145048p:plain
HttpClient実装とSSL/TLSの実装を変更。
f:id:boredbone:20180401145057p:plain
これでOK。

C#, Interactive Extensions でフィボナッチ数列のn番目の項を求める

Ix便利.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;

namespace Fibonacci
{
    class Program
    {
        static void Main(string[] args)
        {
            var fib = EnumerableEx.Generate(new { Z1 = BigInteger.One, Z2 = BigInteger.Zero },
                    _ => true, z => new { Z1 = z.Z1 + z.Z2, Z2 = z.Z1 }, z => z.Z1)
                .StartWith(0)
                .Memoize();

            var sw = new Stopwatch();

            while (true)
            {
                int num;
                if (int.TryParse(Console.ReadLine(), out num))
                {
                    sw.Restart();
                    var result = fib.Skip(num).First();
                    sw.Stop();

                    Console.WriteLine($"[{num}] = {result}, ({sw.ElapsedMilliseconds}[ms])");
                }
            }
        }
    }
}

(Windowsデスクトップアプリ)タグ管理可能な画像ビューア"ShibugakiViewer"リリース

秋ごろにリリースしてました。↓
ShibugakiViewer by Boredbone


以前に作ったMikan Viewer
(Windowsストアアプリ)タグ管理可能な画像ビューア"Mikan Viewer"リリース - 濃厚ソースが絡むシェフこだわりスパゲティー
の後継。


UWPの開発が割と辛かったのでWPFで全面的に作り直し。
UWPはあくまでモバイル向けアプリがデスクトップでも動くっていうプラットフォームであって、デスクトップをメインターゲットにしたアプリを作る為のものじゃなかった。
従来の.NET Frameworkとデスクトップ用APIを使うようになったので、いろいろ細かい利便性が向上してるはず。
目立つとこだと常駐とか。指定されたフォルダを監視して、新しい画像ファイルが追加されたら自動でデータベースに登録するようになりました。


パフォーマンスにはかなり気を配ったつもりだけど、PCによっては結構重い。
どうも描画周りがボトルネックな感じ。WPFはかなり手軽に開発できるけど高速化となるとなかなか難しい。


あとModelもViewModelもReactive ExtensionsとReactivePropertyをかなり積極的に使用。
Rx超便利。これ無しでの開発はもう考えられない。
こんなことしたいなぁ→Rxなら楽にできるよ!のドラえもん的安心感。
Rxゴリゴリ書いてお金が貰えるお仕事ないかな。

Visual Studio 2015 Update3で(GUIから)テストできない

Visual Studio 2015を更新したらテストエクスプローラーウインドウに何も表示されなくなった。

ユニットテストプロジェクトを開くと
"Microsoft.VisualStudio.TestWindow.UI.TestWindowToolWindowControl" の初期化中に例外がスローされました"
と表示されて終了。

英語環境に切り替えればなんとかなるらしいけど。
Visual Studio 2015 "日本語" の環境に最新の更新 (KB3165756) を適用するとテストエクスプローラーが機能しなくなります
VisualStudio Community 2015 Update 3 適用でテストウィンドウに例外発生 ⇒ 言語設定を英語にすれば治りますけど・・・ - 銀の弾丸



別にVisualStudioのGUIを使わなくても、コマンドラインでテストはできる。
準備として、テストプロジェクト(と参照しているプロジェクト)はビルドしておく。

で、ここを参考にテストプロジェクトのdllを指定してテストを行う。
チュートリアル: コマンド ライン テスト ユーティリティの使用


スタートメニューのVisualStudio2015フォルダからDeveloper Command Prompt for VS2015を起動。

カレントディレクトリを変更。

cd /d D:\WorkSpace\Solution1\UnitTestProject\bin\Debug

からのテスト開始。

MSTest /testcontainer:UnitTestProject.dll

これで全てのテストを実行してくれる。


…で、今までは問題なかったはずのテストが全滅。
テスト結果はカレントディレクトリのTesuResultsフォルダの中に保存されている。
開いてみると、テスト対象プロジェクトがSQLiteを使っていたので
SQLite.Interop.dllが読み込めないと言って例外を吐いていた。



そんな場合はdllを読み込むように設定してやる。
テストプロジェクトのbin\Debugフォルダ内にTestSettings.testsettingsとかの名前でファイルを作って
以下のように記述。

<?xml version="1.0" encoding="UTF-8"?>
<TestSettings name="TestSettings" 
              id="4aae19cc-9966-4c8e-9bb8-c804679343fb" 
              xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010">
  <Deployment>
    <DeploymentItem filename="SQLite.Interop.dll" />
  </Deployment>
</TestSettings>

このidって何でもいいんですかね。調査不足。
とりあえずこれでテスト時に読み込むdllを指定できるので、
あとはこの設定ファイルを使ってテスト開始。

MSTest /testcontainer:UnitTestProject.dll /testsettings:TestSettings.testsettings

今度は問題なく成功。


これでテストだけはできるけど、テストのデバッグ実行はできないので
やっぱり早めにVSの修正来てほしいなあ。

WPFでベクタ画像(EMF)を表示

.emfのベクタ画像をきれいに拡大して表示できるビューアを探してたけど
良いのが見つからなかったので作った。

WPFでemf画像を表示するのは案外簡単だった。

System.Drawing.Image.FromFileで読み込めるので、
あとはBitmapに変換したりとかいい感じに扱える。

WPFのImageコントロールを使って表示するには
System.Drawing.BitmapをSystem.Windows.Media.BitmapSourceにWin32無しで変換する。 - 亀岡的プログラマ日記
を参考というか丸パクリさせて頂きつつ。

最終的にこんなん。

using System.Drawing;
using System.Windows.Media.Imaging;

namespace EmfView
{
    class MetaImage
    {
        public static BitmapSource LoadFromFile(string path, double rate)
        {
            using (var image = Image.FromFile(path))
            using (var canvas = new Bitmap((int)(image.Size.Width * rate), (int)(image.Size.Height * rate)))
            using (var graphics = Graphics.FromImage(canvas))
            {
                graphics.DrawImage(image, 0, 0, (int)(image.Size.Width * rate), (int)(image.Size.Height * rate));
                return canvas.ToWPFBitmap();
            }
        }
    }
}

pathにファイルパス、rateに拡大率を設定する感じで。
Image.FromFileが対応してるファイルなら
ベクタ画像(*.emf, *.wmf)に限らずラスタ画像(*.bmp, *.png, ...)も読める。
SVG(*.svg)は残念ながら…

表示側は

<Image Name="image1" Stretch="None"/>

からの

var path = @"D:\pic\pic1.emf";
var rate = 1.0;
this.image1.Source = MetaImage.LoadFromFile(path, rate);

みたいに。



作ったアプリ。
github.com

RoslynのC#スクリプト

Roslynの恩恵の一つに、自作アプリにC#コードのコンパイル・実行機能を組み込めるというのがある。
これによってC#スクリプトとして扱える。
アプリの実行中にcsファイルを読み込んでメソッドを実行したりとか。
しかもそのスクリプトからアプリ固有の機能を呼べる。
いろいろ面白いことが出来そう。
というわけで試してみた。

ここ読めば全部書いてあるんだけど、
機能が開発中なので仕様変更があったらしく使い方が少し変わってた。


まずプロジェクトの対象フレームワーク.NET Framework 4.6に設定する。
そしてnugetから
Microsoft.CodeAnalysis.Scripting.CSharp
をインストール。
他の必要なパッケージは一緒に入る。

バージョンによってはnuget上に必要なパッケージが上がってないことがあるのでインストールに成功するバージョンを探す。
1.1.0-beta1-20150928-02で動作確認。


以下の名前空間をusing。

using Microsoft.CodeAnalysis.Scripting;
using Microsoft.CodeAnalysis.Scripting.CSharp;

まずは簡単なスクリプトでテスト。

class Program
{
    static void Main(string[] args)
    {
        var worker = new Worker();

        var task = worker.WorkAsync();

        Console.ReadLine();
    }
}

class Worker
{
    public async Task WorkAsync()
    {
        var options = ScriptOptions.Default
           .WithNamespaces("System")
           .WithReferences(typeof(object).Assembly);

        var code = @"Console.WriteLine(""Hello World"");";

        var script = CSharpScript.Create(code, options);

        await script.RunAsync();
    }
}

最初は、スクリプトの実行開始に数秒かかる。
違うスクリプトでも、二回目からはすぐに動くようになる。
JITか何か?


ScriptOptionsでスクリプトから使えるアセンブリを教えたり
namespaceのusing相当の名前空間の省略をできるように設定する。
例えばLinqを使うならこんな感じ。

public async Task WorkAsync2()
{
    var options = ScriptOptions.Default
        .WithNamespaces("System", "System.Collections.Generic", "System.Threading.Tasks", "System.Linq")
        .WithReferences(typeof(object).Assembly)
        .WithReferences(typeof(System.Linq.Enumerable).Assembly);


    var code = @"
    var array = new[] { 1, 2, 3, 4, 5 };

    var sequence = array
        .Where(y => y % 2 == 1)
        .Select(y => y * y + 0.5)
        .OrderByDescending(y => y);

    foreach(var item in sequence)
    {
        Console.WriteLine($""value = {item}"");
    }";
    
    var script = CSharpScript.Create(code, options);

    await script.RunAsync();
}

スクリプトからアプリのプロパティやメソッドにアクセスするには、
一つグローバルなクラスを作ってやって、そのインスタンスを渡す。

こんな感じのクラスを作る。publicでないとダメらしい?

public class GlobalClass
{
    public double Number { get; set; }
    
    public void Method(double arg)
    {
        Console.WriteLine($"arg = {arg}");
    }

    public async Task<double> MethodAsync(double arg)
    {
        Console.WriteLine("start");
        await Task.Delay(1000);
        Console.WriteLine("end");
        return arg * this.Number;
    }
}

使うときはスクリプトの生成時にグローバルクラスの型を与えて、
実行時にインスタンスを渡す。
スクリプトの中からは何も付けずに呼ぶ。
スクリプトの中でawaitが使えるので、asyncメソッドも呼べる。

public async Task WorkAsync3()
{
    var global = new GlobalClass();

    global.Number = 0.5;
    
    var options = ScriptOptions.Default
        .WithNamespaces("System", "System.Collections.Generic", "System.Threading.Tasks", "System.Linq")
        .WithReferences(typeof(object).Assembly)
        .WithReferences(typeof(System.Linq.Enumerable).Assembly);

    var code = @"
    Method(3.0);

    var result = await MethodAsync(7.0);

    Console.WriteLine($""result = {result}"");";

    var script = CSharpScript.Create(code, options, typeof(GlobalClass));

    await script.RunAsync(global);
}

グローバルクラスのシグネチャに独自クラスがあるとか、
アセンブリ参照が必要な場合はScriptOptionsのWithReferencesで与えてやればOK。

Windows 10でモニタごとに別の壁紙を設定

Windows10使ってたら、マルチモニタ環境でそれぞれのモニタごとに別の壁紙画像を設定できなくなっていることに気付いた。
Windows8.1ではできたのに。

昔それっぽいツールを自作したことがあったので、せっかくだし作り直してgithubに上げた。

github.com

一応コンパイル済みの実行ファイルも。
https://github.com/Boredbone/MultiMonitorTools/releases/download/v1.0/MultiMonitorTools.zip



上げたとは言っても完全に自分用ツールなので使用者に優しくない。

機能は三つあって、
・各モニタに対して、モニタの方向ごとに異なる壁紙を設定
・デスクトップ上の他の位置にあるウィンドウを手元に移動させる
・指定されたモニタを回転させる


壁紙の設定は
IDesktopWallpaper interface (Windows)
をComImportで引っ張ってきて
IDesktopWallpaper::SetWallpaper method (Windows)
を呼び出す。だそうです。偉い人に教えてもらった。



画面の回転とウィンドウ移動はP/Invoke。
ウィンドウの位置とサイズを変更するSetWindowPlacementを使えば、
WPFでアプリ終了時のウィンドウ位置を次回起動時に復元することもできる。


COMとかP/Invokeとかよく分かってないので勉強しなければ。