ささいなことですが。

Windowsアプリテスト自動化ライブラリFriendly開発者の日記です。

Friendly.Windows.2.15.0(.NetCore対応 その2)をリリースしました。

前回のリリースでCore対応をしたのですが、.NetCoreへのアタッチの仕方が微妙やったんですよね。WindowsAppFriendのコンストラクタにcoreclr.dllのフルパスを渡すという鬼仕様。
ishikawa-tatsuya.hatenablog.com
この目的は以下のもの。

  1. 対象が.NetCoreアプリかどうか判別する
  2. coreclr.dllのパスの特定

対象アプリのロードしてるdllを取得することで解決

どうしようかなーって思ってたら、@tmytが「ホストプロセスが読み込んでるモジュール列挙で解決できるきがする」って教えてくれました。サンプルコードも書いてくれた。あざっす!
detect_netcore.cs · GitHub

この手法で対象プロセスにcoreclr.dllが読み込まれているか否かが判定できて、かつcoreclr.dllのパスが取得できるので問題が両方解決しました。

.NetFrameworkのアプリと全く同じコード

こんな感じで全く同じコードで書けるようになりました。やったね。

[TestCase]
public void TestCore()
{
    //普通にプロセス取得
    var targetApp = Process.GetProcessesByName("TestTargetCore")[0];

    //普通にアタッチ
    var app = new WindowsAppFriend(targetApp);

    //普通に操作
    var w = app.Type<Application>().Current.MainWindow;
    w.Title = "タイトル変更";

    //RM.Friendly.WPFStandardControlsとかも利用可能
    var textBox = new WPFTextBox(w._textBox);
    textBox.EmulateChangeText("abc");
}

Friendly.Windows.Grasp.2.12.0もリリースしました。

もちろん他のも更新してバージョンあげてリリースしたんですけど(超めんどい)Friendly.Windows.Graspは修正もしました。問題があったのです。

.NetCoreでサポートされないメソッド

いやいや、.NET Portability Analyzer でチェックしたら100%やったやん・・・
f:id:ishikawa-tatsuya:20190820162310p:plain

ダメだったのはこれ。故あって対象アプリ内でソースコードからコンパイルしてる部分があったんですけど、まあそりゃ.NetCoreではRoslynつかうよね・・・。

public static CompilerResults Compile(string[] reference, string code)
{
    if (reference == null)
    {
        throw new ArgumentNullException("reference");
    }
    CSharpCodeProvider codeProvider = new CSharpCodeProvider();
    CompilerParameters param = new CompilerParameters();
    param.GenerateInMemory = true;
    for (int i = 0; i < reference.Length; i++)
    {
        param.ReferencedAssemblies.Add(reference[i]);
    }
    param.IncludeDebugInformation = true;

    //ここで例外 System.PlatformNotSupportedException
    return codeProvider.CompileAsemblyFromSource(param, code);
}

Friendlyは古いアプリもサポートしてるから、無条件にRoslynってわけにはいかなくて、別の手法で対応しました。

今後は.NetCoreのテスト増やしていかねば

.NET Portability Analyzer 通ったんで、まあいいかってしてましたが、今後は.NetCoreのテストを増やして検証していきます。

IR + サラウンド が凄すぎた

突然のギターネタです。感動したので夜中に突然書き出しました。

結論

IRキャビネットシミュレータ -> サラウンド機能のあるヘッドフォン
今までの常識を覆すほど良いギターサウンドをヘッドフォンで聞くことが出来る
(※あくまで個人の感想です

去年にこんなブログを書きました。

ishikawa-tatsuya.hatenablog.com
ちなみにアンプは壊れたので、Boutiqueに変更しました。
www.digimart.netそれで約一年弾いてたんですけど、「まあいいんだけど、もうちょっとなんだよなー。ヘッドフォンではこれが限界かな・・・」って思ってました。しかし・・・!

実はこのシステムには隠された実力が眠っていたのです!

何処にそんな実力が眠っていたかというと、ヘッドフォンです。ATH-DWL550を使っていました。ワイヤレスだけど音も良くて遅延もほぼない(体感上はないといっても過言ではない)優れたヘッドフォンです。
www.audio-technica.co.jp

バーチャルサラウンドシステム

しかし、それだけではなかったのです。いくつかのエフェクト機能が存在していました。

  1. バーチャルサラウンドシステム
  2. ゲームモード
  3. クリアボイスモード

その中の「バーチャルサラウンドシステム」が凄かったのです。いやいや、一年も使ってて何をいまさらって感じですよね。全く使ったことないってわけではないんですけど、買った順番が①アンプ、②ヘッドフォン、③IRのキャビネットシミュレータだったので、すっかり騙されて(?)いました。アンプのヘッドフォン出力→ヘッドフォンってつないでるときにバーチャルサラウンドシステムを試したんですけど「ちょっと面白い感じになるな」くらいで、まあエフェクトは必要ないよねって感じでそれ以来さわってなかったんですよね。IRキャビネットシミュレータを入れた後も・・・。

IRとサラウンドを組み合わせると凄かったんです!

ああ・・、なんで一年近くもやってなかったんだ。後悔しかない。今日たまたまバーチャルサラウンド入れてみたんですよね。「え?なにこれ?めっちゃ音良いし、音に立体感がある。広がりが凄い」いやもうこれはヘッドフォンの音なんで録音もできないんですけど、今までの音が何だったんだってくらいに凄い音になったんですよ。IRに詳しいわけではないですけど、空間の音響のシミュレート(今までのリバーブとかディレイとはちょっと違うみたい)なんでサラウンドには相性いいんでしょうね。多分。それと広がりがあるのはリバーブとかそんな感じの演出ではなく、ヘッドフォンの音が出てる部分が増えてる感じなんですよ。ハード的にも頑張ってんのかな?

ギターサウンドで一番重要な部分が強化される!

ギター博士によると割合としてギターは「10%」、アンプは「40%」、キャビネットは「50%」の影響があるらしいっす。どこが情報ソースかはわかりませんが、言われてみると確かにそうかもって感じ。それでこのキャビネット部分で一番大事なIRが強化されるので特にヘッドフォンで聞くときのボトルネックが解消されたと考えました。いや専門家ではないので素人がそう考えただけですけどw。

アンプ+IRキャビネットシミュレータ

去年揃えたシステムも気に入ってるんですけど、MV50+BluBoxはちょっと高い感じ。今はこれが気になってるんですよね。これとATH-DWL550でコスパの良いヘッドフォン環境が構築できそう。
www.cherubtechnology.jp

キャビネットシミュレータはIRじゃないと効果なかった

最近のはIRが多いですけど、そうじゃないのもあるんですよね。例えばMV50のヘッドフォン出力にもついてましたけど、これはアナログ式。音の好みは置いておいてサラウンドにしても効果(音がすごくよくなるとか)はありませんでした。

年々シミュレータ環境が良くなってますね。

家で引くことが多いアマチュアギタリストにとってはうれしい限りですね。まだありものの組み合わせって感じですけど、IR+サラウンドヘッドフォンに特化した凄いギター用製品とか出てきたらいいなー。

追記 バーチャルサラウンドだからいいのかも?

とは言え、元のソースにステレオ以上の情報は含まれてないわけで。そう考えるとなんでIRの時の方が格段に音が良くなったように聞こえるんだろう?気のせい?時間あるときにもっと実験してみよう・・・(詳しい人教えて)

Friendly.Windows.2.14.0(.NetCore対応)をリリースしました。

.NetCore対応しました!プレビューくらいの位置づけです。
github.com
コードはこんな感じ。今までと違うのはWindowsAppFriendのコンストラクタの引数にcoreclr.dllのパスを渡しているとこだけです。今はプレビューなのでパスを渡してますけど、もう少ししたらこれは別の方法も提供するかも。(まだ最適な方法がわかってないんですよね)

[TestCase]
public void TestCore()
{
    //普通にプロセス取得
    var targetApp = Process.GetProcessesByName("TestTargetCore")[0];

    //※ここだけ違う
    //WindowsAppFriendのコンストラクタにcoreclr.dllのパスを渡す
    var dllPath = @"C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.0.0-preview7-27912-14\coreclr.dll";
    var app = new WindowsAppFriend(targetApp, dllPath);

    //後は今まで通り操作可能
    var w = app.Type<Application>().Current.MainWindow;
    w.Title = "タイトル変更";

    //RM.Friendly.WPFStandardControlsとかも利用可能
    var textBox = new WPFTextBox(w._textBox);
    textBox.EmulateChangeText("abc");
}

以前にプロトでFriendly.DotNetCoreを作りましたが、最終的には全然違うアプローチにしました。Friendly.Windowsをちょっとだけ修正して対応するというものです。

.NetFrameworkで使ってたコードがそのまま使えます。

正確にはWindowsAppFriendのコンストラクタは変更しないとダメですけど、ほぼそのまま使えます。つまり.NetFrameworkのアプリがあって、それに対応するテストを作っておけば、そのアプリを.NetCoreに変更したら、その後の回帰検査にそのテストを使うことができます。

.NetCore対応は何をしたか(何をしなければならないと思っていたか)

Friendlyの初期化処理です。こんな感じになってます。
f:id:ishikawa-tatsuya:20190815133115p:plain
以下二つの理由でFriendly.Windowsでは動かないと思っていました。

  1. ホストAPIでは.NetCoreのランタイムにはアクセスできない
  2. .NetCoreから.NetFrameworkのdllは参照できない

なんで、.NetCore対応は超面倒って思ってました。でもよく調べてると、両方とも誤解だということがわかりました。

ホストAPI

結論から言うと.NetCoreのランタイムを操作するホストAPIがありました。
docs.microsoft.com
ICLRRuntimeHostってのを使ってたんですけど、.NetCoreに対応したICLRRuntimeHost4ができてたようです。

.NetCoreから.NetFrameworkのdllは参照できる

これも結論から言うとできるんですよ。びっくりしてちょっと前にもブログ書きました。
ishikawa-tatsuya.hatenablog.com
.NetStandardか.NetCoreじゃないと参照できないって思ってましたけど。じゃあ別にexeだけ.NetCore対応したらそれでよくね?
一応ポータビリティをチェックしましたが、Friendly系のライブラリは.NetCoreへのポータビリティは100%でした。
marketplace.visualstudio.com

最終的にやったこと

.NetCore用のホストAPIを使うネイティブのdllを新しく作って、.NetCoreの場合はそれを呼びだすようにしました。操作方法は数種類あって、調査時間かかりそうだなーって思ってましたが、@tmyt@nak763が手伝ってくれたんで、サクッと終わりました。ありがとうございました!
https://github.com/Codeer-Software/Friendly.Windows/blob/master/Project/CodeerFriendlyWindowsCore/dllmain.cpp
でもそれだけで.NetCore対応できたってすごいなー。現行のWinFormsとかWPFも結構さらっと移行できるんじゃないかなー。(こなみかん

不具合対応

実は前回の2.13.1に不具合がありました。操作プロセスが管理者権限で対処プロセスが普通権限の場合に操作対象が不正終了してしまうようでした。修正しておきました。不具合報告をくれた方、ありがとうございました!

LambdicSqlサンプル

LambdicSqlの書き方の質問来たんで、久しぶりに書きます。

あえとす 8/18 ボドゲ会 (@aetos382) | Twitterさん、ありがとうございます!

お題のクエリです。

select
  foo,
  bar,
from
  table
where
  foo = 1
  and bar = 2
union all
select
  foo,
  bar,
from
  table
where
  foo = 3
  and bar = 4

LambdicSqlで書いたらこんな感じ

var sql = Db<DB>.Sql(db =>
    Select(new
    {
        db.table.foo,
        db.table.bar
    }).
    From(db.table).
    Where(db.table.foo == 1 && db.table.bar == 2).

    Union(All()).

    Select(new
    {
        db.table.foo,
        db.table.bar
    }).
    From(db.table).
    Where(db.table.foo == 3 && db.table.bar == 4).
);

LambdicSqlはクエリを分けて書くことができます。

var select1= Db<DB>.Sql(db =>
    Select(new
    {
        db.table.foo,
        db.table.bar
    }).
    From(db.table).
    Where(db.table.foo == 1 && db.table.bar == 2)
);
var select2 = Db<DB>.Sql(db =>
  Select(new
    {
        db.table.foo,
        db.table.bar
    }).
    From(db.table).
    Where(db.table.foo == 3 && db.table.bar == 4).
);
var union = Db<DB>.Sql(db => Union(All()));

//+で演算
var sql 1=  select1+ union + select2;

//これもOK
var sql 2= Db<DB>.Sql(db =>select1 + Union(All()) + select2);

whereとかで便利です。

var select = Db<DB>.Sql(db =>
    Select(new
    {
        db.table.foo,
        db.table.bar
    }).
    From(db.table)
);

var where1 = Db<DB>.Sql(db =>
    Where(db.table.foo == 1 && db.table.bar == 2).
);
var where2 = Db<DB>.Sql(db =>
    Where(db.table.foo == 3 && db.table.bar == 4).
);

//状況に応じて使い分け
bool is1 = false;
var sql = selectFrom + (is1 ? where1 : where2);

whereはさらに便利なのがあります。条件が有効なものだけ使われます。使われる条件がなくなるとwhereは消えます。

bool is1 = true;
bool is2 = false;
var sql = Db<DB>.Sql(db =>
    Select(new
    {
        db.table.foo,
        db.table.bar
    }).
    From(db.table).
    Where(new Condition(is1, db.table.foo == 1) &&
               new Condition(is2, db.table.bar == 2))
);

慣れると便利なので、是非使ってみてください!
github.com

Friendlyでx64のプロセスからx86のプロセスを操作する

Friendlyでは操作対象のプロセスと操作側のプロセスのCPUモードを合わせる必要がありました。そのため一つの操作プロセスからx86とx64のプロセスを操作することができませんでした。Friendly.Windows/2.13.1からできるようになりました。
www.nuget.org

IntPtrのサイズに注意してください。

基本はx64のプロセスからx86のプロセスを操作してもらうのが良いと思います。理由は後述します。

操作件をバイナリ化して引き継ぎます

何を言ってるんだ・・・。ってなると思います。残念なことにAttachは引き続き同一プロセスからしかできないのです。そのためアタッチしてその権限を委譲するという処理になります。

操作するためのx64のプロセスです

public void Test()
{
    //対象プロセス(x86)
     var targetApp = Process.Start(targetExePath);

    //x86のプロセスでアタッチしてその通信情報を引き継ぐためのバイナリを生成
    var myProcess = Process.GetCurrentProcess();
    var binPath = Path.GetTempFileName();
    Process.Start(attachExePath, $"{targetApp.Id} {myProcess.Id} {binPath}").WaitForExit();

    //バイナリを元にWindowsAppFriend生成
    var bin = File.ReadAllBytes(binPath);
    File.Delete(binPath);

    //以降はx64のプロセスからx86のプロセスが操作できる
    var app = new WindowsAppFriend(targetApp.MainWindowHandle, bin);
}

アタッチに使うx86のプロセスです。

namespace Attachx86
{
    class Program
    {
        static void Main(string[] args)
        {
            var app = new WindowsAppFriend(Process.GetProcessById(int.Parse(args[0])));
            //※今回作ったAPI
            //操作権の移譲 byte[]になる
            var bin = app.HandOverResources(int.Parse(args[1]));
            //ファイルに保存
            File.WriteAllBytes(args[2], bin);
        }
    }
}

セキュリティソフトに注意

Attachx86はそれぞれで作ってもらうのですが(基本は↑のコードでよいと思います)、セキュリティソフトに怒られないように注意してください。もしされたらホワイトリストに設定してください。Friendlyは強力な操作能力があるので野良exeから使うと怒られる場合があります。通常はNUnitやVSTestといった信頼のおけるexeから利用するので怒られません。でもFriendlyを使った普通のアプリを作ると怒ってくるセキュリティソフトもあります。

そもそもなんで同一CPUモードからじゃないと操作できなかったか?

理由は二つありました。

CreateRemoteThread利用時の関数のアドレスの計算が同一CPUでなければできない

何か対策があるかもしれませんが現在はわかっていないので対応できていません。そのためアタッチは引き続き同一CPUモードで行ってもらいます。ただ、いったんアタッチするとこれは使わないので、その処理を行って操作件を得た後にその情報をバイナリ化して引き継ぐという対応を今回入れました。

IntPtrのサイズが異なる

Friendlyは同一プロセスの中にあるように対象を操作できます。好きなようにメソッドを呼び出せます。ということはIntPtrを受け渡しするときに処理できないサイズのものが来る可能性があります。この問題も引き続き残ります。なので気を付けて使てくださいw。基本的には操作側をx64にしてもらってデータを送るときにはサイズを超えるものを送らないというアプローチが良いと思います。

x64とx86のアプリを使ったテストもかける

複数のアプリの絡みのテストも書きやすくなったと思います。最近結構この要望言われてたんですよねー。また感想お待ちしております。

Friendlyを更新しました。

FriendlyとFriendly.Windowsを更新しました。
www.nuget.org
www.nuget.org

DynamicAppVarからの変換

コンストラクタにAppVarをとる型に変換できるようにした。こんな感じに書けるようにしました。人によっては気持ち悪いと思うかもしれませんが便利さ優先。

WPFTextBox textBox = app2.Type<Application>().Current.MainWindow._textBox;

WindowDriver作るときにちょっと楽になります。

public class EntryControl_Driver
{
    public WPFUserControl Core { get; }

    // new書かなくてよい
    public WPFTextBox Name => Core.Dynamic()._textBoxName;
    public WPFTextBox email => Core.LogicalTree().ByBinding("Mail.Value").Single().Dynamic();
    public WPFButtonBase Entry => Core.LogicalTree().ByType("System.Windows.Controls.Button").ByType<ContentControl>().ByContentText("Entry").Single().Dynamic();

    public EntryControl_Driver(AppVar core)
    {
        Core = new WPFUserControl(core);
    }
}

環境によって対象アプリのCPU使用率が高くなる不具合の修正

プルリク来ました。
けーすけ (@_ksuke) | Twitterさんありがとうございました!
github.com

WindowsAppFriendで接続状態の移譲

こんな感じのコードが書けます。

//アプリにアタッチ
var app = new WindowsAppFriend(targetApp);
var targetWindowHandle = targetApp.MainWindowHandle;

//接続状態を移譲
//これをするとappは所有権を失います。
var bin = app.HandOverResources(newOwnerProcessId);

//接続権限を引き継ぎ
var app2 = new WindowsAppFriend(targetWindowHandle, bin);

???
なんの役にやつの?
次のブログで解説します。

Facebookに宣伝出してみた

Codeerも一応Facebookページ作ってます。たまに写真とかアップしてます。「いいね」押していただけるとありがたいっす!
https://www.facebook.com/codeer.soft

Facebookからちょこちょこ「宣伝だしませんか?」的な通知が来てて気になってたんですよね。どれくらい効果あるのかなーって。ちょうど勉強会開催するのでやってみました。

効果あった?

効果あったのかどうかはわからんw。勉強会でアンケートとればよかった。クリックしてくれた人が来てくれたかどうかわかんないんですよね。(でも多分来てくれた人はそれ以外のソースな気がする)

金額

1000円かららしい。それで日数を減らすか、金額を増やすとリーチ数が増えるらしい。あと次の対象も絞ると恐らく表示される回数とか増えるんかなーと。

今回実験した対象

  1. 大阪在住の18-65才
  2. 大阪在住の18-65才でページにいいねした人
  3. 東京在住の18-65才でページにいいねした人とその友達

大阪の方は①やってて、途中で「いや、それは広すぎてイマイチやろ」って思って②もやりました。つまり2000円払ってます。東京の方は③だけで1400円分やりました。なんだけど、実は東京のは設定間違えてたんですよね・・・。

結果

いやいや、30クリックに3000円とか高すぎやしw

若干掲載期間に差はあるものの「ページにいいねした人とその友達」がやっぱり一番効果高いかなーって感じですね。
f:id:ishikawa-tatsuya:20190319230322p:plain
f:id:ishikawa-tatsuya:20190319230505p:plain
しかも東京にしたと思ったけど、間違えて大阪在住の人にしてた・・・。
f:id:ishikawa-tatsuya:20190319230652p:plain

大阪

15人枠に対して13人申し込みキャンセル2名
friendly.connpass.com

東京

20人枠に対して12人申し込みキャンセル4名
friendly.connpass.com

皆様ご参加ありがとうございました!

反省

ページに「いいね」が少ない

「いいね」増やすともっと効果あったかも。マイナーページなんで。100人くらいなんですよねw。ていうか真面目にもっと更新しないとダメですね。後せめてHPからリンク張っておこう。