d.sunnyone.org
sunnyone.org

ページ

2014-05-11

MSILでわかるC# のラムダ式

そういえばLINQ to Objectsでコードを書いたときに裏でどういう動きになっているのかなぁ、とILでイメージができなかったので読んでみたら、単にC#のラムダ式がどうなっているかという話だけだったのでまとめる。

長いのでサマリ:
  • ラムダ式を使うと、基本的に入れ子クラスが作られ、ラムダ式が使う変数をとっておかれる。
  • ラムダ式の中身に書いたものは、基本的に入れ子クラスのメソッドとして定義される。
  • 入れ子クラスは不要なときには作られない。

評価スタックでピンとこない方は「C#でHelloWorldプログラムを作成する」を読んでおくのをおすすめする。

今回の説明用のサンプルコードはこちら。「.Count(x => x == val)」がどうなっていくのか、というお話。
namespace ConsoleApplicationLinq
{
    class Program
    {
        static void Main(string[] args)
        {
            int val = 100;
            var format = "Count of {0}: {1}";
            
            var array = new int[0];
            var count = array.Count(x => x == val);

            System.Console.WriteLine(format, val, count);
        }
    }
}

サイズ0のint配列に100がいくつあるか調べてWriteするというコード(0に決まっている)。説明の都合、順番などが不自然な感じになっている。

このソースをILに逆アセンブルした結果がこちら。
https://gist.github.com/sunnyone/0d3ed8285b5d91495399
別ウィンドウで開きながら説明を見るといいかも。

さて、これからILを見ていく。書いた部分がコンパイルされているであろうMainメソッドの実装を見ようとすると、そのMainメソッドの前に見知らぬ「<>c__DisplayClass1」という入れ子クラスが作られていることがわかる。

そのクラスの内容はこうなっている。

.class auto ansi sealed nested private beforefieldinit '<>c__DisplayClass1'
         extends [mscorlib]System.Object
  {
    (CompilerGeneratedAttributeの部分は省略)
    
    .field public int32 val // valフィールド
    
    (ここにコンストラクタの定義があるが省略)
    
    // <Main>b__0メソッドの定義
    .method public hidebysig instance bool 
            '<Main>b__0'(int32 x) cil managed
    {
      .maxstack  8
      
      // xをロード(評価スタックにpush)
      IL_0000:  ldarg.1
      
      // valフィールドをロード
      IL_0001:  ldarg.0
      IL_0002:  ldfld      int32 ConsoleApplicationLinq.Program/'<>c__DisplayClass1'::val
      
      // 二つの値=xとvalの値を比較し、同じなら1・異なるときは0
      IL_0007:  ceq
      
      // return
      IL_0009:  ret
    } // end of method '<>c__DisplayClass1'::'<Main>b__0'

  } // end of class '<>c__DisplayClass1'

このクラスは要は「x => x == val」の部分を<Main>b__0というメソッドに実装し、加えてvalフィールドを持っている。

次に本体のMainメソッド。Mainメソッドはこの入れ子クラスを活用して動作する。

まずメソッドとローカル変数の定義。format, array, countのほかに、先ほどの<>c__DisplayClass1が「CS$<>8__locals2」として用意されているのがわかる。valはないことに注意。
.method private hidebysig static void  Main(string[] args) cil managed
  {
    .entrypoint
    .maxstack  3

    .locals init ([0] string format,
             [1] int32[] 'array',
             [2] int32 count,
             [3] class ConsoleApplicationLinq.Program/'<>c__DisplayClass1' 'CS$<>8__locals2')

ここから処理開始だが、記述したC#コードに対応する部分に先立って、<>c__DisplayClass1がnewされ、ローカル変数に入る。
// <>c__DisplayClass1クラスのインスタンスを生成してローカル変数3番目にセット
    IL_0000:  newobj     instance void ConsoleApplicationLinq.Program/'<>c__DisplayClass1'::.ctor()
    IL_0005:  stloc.3

次に「int val = 100;」の部分。ここがポイント。
C#コード上ではローカル変数に見えているが、実際には内部の<>c__DisplayClass1クラスのフィールドになっている。
入れ子になったクラスにあるラムダ式の実体がこの変数を使うためにフィールドに入れている。
// CS$<>8__locals2と「100」をロードして、CS$<>8__locals2のvalにセット
    IL_0006:  ldloc.3
    IL_0007:  ldc.i4.s   100 
    IL_0009:  stfld      int32 ConsoleApplicationLinq.Program/'<>c__DisplayClass1'::val

次に「var format = "Count of {0}: {1}";」の部分。このように、ラムダ式と関係ない部分はふつうのローカル変数になる。
// 説明省略
    IL_000e:  ldstr      "Count of {0}: {1}"
    IL_0013:  stloc.0

次に「var array = new int[0];」だが、ここもローカル変数に入れるだけ。
// 説明省略
    IL_0014:  ldc.i4.0
    IL_0015:  newarr     [mscorlib]System.Int32
    IL_001a:  stloc.1

次に実際のラムダ式が登場する「var count = array.Count(x => x == val);」の部分。
先ほどvalのために生成したDisplayClassのメソッドを使ってFuncオブジェクトを生成し、Countメソッドに渡している。

// ローカル変数1番目:arrayをロード
    IL_001b:  ldloc.1
    
    // <Main>b__0メソッドのポインタをロード
    IL_001c:  ldloc.3
    IL_001d:  ldftn      instance bool ConsoleApplicationLinq.Program/'<>c__DisplayClass1'::'<Main>b__0'(int32)
    
    // <Main>b__0メソッドのポインタを使って、Funcオブジェクトを生成
    IL_0023:  newobj     instance void class [mscorlib]System.Func`2::.ctor(object,
                                                                                        native int)
                                                                                        
    // System.Linq.Enumerable::CountメソッドにarrayとFuncオブジェクトを渡す
    IL_0028:  call       int32 [System.Core]System.Linq.Enumerable::Count(class [mscorlib]System.Collections.Generic.IEnumerable`1,
                                                                                 class [mscorlib]System.Func`2)

    // ローカル変数2番目にセット
    IL_002d:  stloc.2

最後に「System.Console.WriteLine(format, val, count);」の部分。
ポイントはvalを使うのにラムダ式用の<>c__DisplayClass1を使っているところ。
// ローカル変数0番目: formatをロード
    IL_002e:  ldloc.0
    
    // ローカル変数3番目: CS$<>8__locals2のvalフィールドをロード、intなのでboxingする
    IL_002f:  ldloc.3
    IL_0030:  ldfld      int32 ConsoleApplicationLinq.Program/'<>c__DisplayClass1'::val
    IL_0035:  box        [mscorlib]System.Int32
    
    // ローカル変数2番目: countをロード, boxing
    IL_003a:  ldloc.2
    IL_003b:  box        [mscorlib]System.Int32
    
    // WriteLine
    IL_0040:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                  object,
                                                                  object)
    IL_0045:  ret
  } // end of method Program::Main

このように、ラムダ式は、必要な変数を入れ子クラスのインスタンスにとっておいて、ラムダ式に書いた内容のメソッドが実行される、という形で実装されている。
しかし、ラムダ式があれば必ず入れ子クラスが作られるかというとそうではなく、変数をとっておく必要がない場合、違う形にコンパイルされる。

たとえば、先のコードのint val = 100;にconstをつけてconst int val = 100;にするだけで、ILはこうなってしまう。

.class private auto ansi beforefieldinit ConsoleApplicationLinq.Program
       extends [mscorlib]System.Object
{
  // ProgramクラスそのものにFuncのフィールドが用意される
  .field private static class [mscorlib]System.Func`2<int32,bool> 'CS$<>9__CachedAnonymousMethodDelegate1'
  
  (CompilerGeneratedAttributeの部分は省略)
  
  .method private hidebysig static void  Main(string[] args) cil managed
  {
    // ローカル変数の定義などなど
    .entrypoint
    .maxstack  3
    .locals init ([0] string format,
             [1] int32[] 'array',
             [2] int32 count)
    IL_0000:  ldstr      "Count of {0}: {1}"
    IL_0005:  stloc.0
    IL_0006:  ldc.i4.0
    IL_0007:  newarr     [mscorlib]System.Int32
    IL_000c:  stloc.1
    IL_000d:  ldloc.1
    
    // CS$<>9__CachedAnonymousMethodDelegate1をロードして、存在すればこの先の処理まで飛ばす
    IL_000e:  ldsfld     class [mscorlib]System.Func`2 ConsoleApplicationLinq.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
    IL_0013:  brtrue.s   IL_0026

    // なければProgramクラスに定義された<Main>b__0メソッド(「x => x == val」の実装)を使ってFuncオブジェクトを作る
    IL_0015:  ldnull
    IL_0016:  ldftn      bool ConsoleApplicationLinq.Program::'<Main>b__0'(int32)
    IL_001c:  newobj     instance void class [mscorlib]System.Func`2::.ctor(object,
                                                                                        native int)
    // 作ったらフィールドに格納して、ロードしておく
    IL_0021:  stsfld     class [mscorlib]System.Func`2 ConsoleApplicationLinq.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
    IL_0026:  ldsfld     class [mscorlib]System.Func`2 ConsoleApplicationLinq.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
    
    // 以下同じ
    IL_002b:  call       int32 [System.Core]System.Linq.Enumerable::Count(class [mscorlib]System.Collections.Generic.IEnumerable`1,
                                                                                 class [mscorlib]System.Func`2)
    IL_0030:  stloc.2
    IL_0031:  ldloc.0
    IL_0032:  ldc.i4.s   100
    IL_0034:  box        [mscorlib]System.Int32
    IL_0039:  ldloc.2
    IL_003a:  box        [mscorlib]System.Int32
    IL_003f:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                  object,
                                                                  object)
    IL_0044:  ret
  } // end of method Program::Main

  (コンストラクタの定義は省略)
  
  .method private hidebysig static bool  '<Main>b__0'(int32 x) cil managed
  {
    (CompilerGeneratedAttributeの部分は省略)
    
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  ldc.i4.s   100 // 「100」はここに埋め込まれるので、フィールドから取る必要がない
    IL_0003:  ceq
    IL_0005:  ret
  } // end of method Program::'<Main>b__0'

} // end of class ConsoleApplicationLinq.Program

こっちのコードだとご丁寧にもFuncオブジェクトをstaticフィールドにキャッシュしている。

ちなみに、valをローカル変数ではなくフィールドに持つようなクラスを作った場合も上述のconstの形に近くなり、入れ子クラスは作られない(Funcオブジェクトのキャッシュはしなくなる)。

まぁ、この差異はプログラム全体からしたらたいしたことはないと思うので、書くときに意識することはないと思うが、知っておいても悪くない…かな?
(一応、array.Count(x => x == val)を10000000回実行したら、valがconst/フィールド/ローカル変数それぞれの場合でStopwatchクラス読みで240ms/240ms/300msだった)

こうしてILコードを見ていると、ラムダ式がどういうものなのかしっくりきたのだけど、みなさまはどうだろうか?

2014-04-24

MSBuild Launcher 0.2.3 リリース

MSBuild Launcher 0.2.3をリリースしました。

今回の大きな変更点の一つ目はパフォーマンス改善です。生産性が上がるというレベルの変化なのでぜひ上げてください。処理待ちよりもログ出力待ちのほうが多いなんてビルドをされている方には、体感でわかるレベルで違うはずです。PowerShellで適当に出力して確認していたら、2倍ぐらい速くなっていました。


確認に使っていたビルドファイル:


もうひとつはキャンセルをするときに再帰的にプロセスを停止するようにしたことです。今まではエンジン用プロセスだけ停止していたので、Execで起動するようなケースではプロセスが残ってしまったりしたのですが、子供から殺すのでいなくなるはずです。

2014-01-04

Jenkins入門 (LibreOfficeをビルドするまで)

新年あけましておめでとうございます。今年もよろしくおねがいします。
今年はJenkinsさんの記事でスタートです。この記事は主にLibreOfficeの開発者や翻訳者に向けて作られています…が、Jenkinsの部分はLibreOfficeに限った話ではないので、なんでも使えます。

Jenkinsを使いはじめる上で一番難しいのは、機能や手順を調べたりすることではなく「どうやって使ってみたらいいのか」を認識することだと思っているので、その点について掴んでもらえれば、この記事の目指すところはおしまいです。

環境はUbuntu(記事を書いたときに試したのは12.04)を想定していますが、Windowsでもインストーラがあるし、ほとんどがWeb UIでの操作なので読みかえは容易だと思います。

Jenkinsとは

JenkinsはCI, Continuous Integration「継続的インテグレーション」をするためのツールです(以前は Hudson という名前でした)。聞いたことない人にとっては、なんか恐そうな感じですが、Jenkinsに関してはだいたい「Web UIのついたすごいcron」だと思ってもらっていいです。まぁそれじゃ本筋じゃないので一応書いとくと、開発者が独自に個別に部品を作ってどんどん進んじゃうと、いよいよ結合だーバーンってやったらそりゃあひどいことになるよね、よくないよねと。結合するフェーズがやってきたらくっつけるんじゃなくて、毎日とか、数時間ごととか、コミットごととかに、ビルドしたりテストかけたりして、継続的に合体できて動くことを確認しましょうね、っていうのを実現するためのツールです。

なので、本当はもっといろいろなことができるわけですが、今回はそのJenkinsのビルド(しかも手動)という側面だけとりあげて書きます。


今回の構成

今回の構成を絵にすると以下の通りです。upstream以外、すべてローカルPCを想定しています。


upstreamのgit repositryから、pullしてきて、そこでなんだかんだ作業します。そのなんだかんだ作業したローカルのレポジトリをJenkinsさんにcloneしてもらって、ビルドして成果物を作ってもらいます。

(ここは翻訳の確認のためのビルドという使い方を想定しているので、普通のCI環境とは違うところだと思います。"upstream"にあたる共有の作業レポジトリを定期的にビルドしてもらうというのがCIという意味では多いと思います。)



手順

LibreOfficeを普通にビルドできるようにする

まずはビルドする環境で、Jenkinsなしで対象としたいプロダクトがビルドできるように準備します。LibreOfficeをUbuntuでビルドするケースでは簡単です。ほとんどWikiの「How_to_Build」や「How_to_Build/localized」に書かれている通りにするだけ。

sudo apt-get build-dep libreoffice
sudo apt-get install translate-toolkit dos2unix

これでビルド環境の準備はOK. あとは、絵の中段の作業レポジトリをcloneして作ります。今回の作業対象となる「libreoffice-4-1」ブランチをcheckoutしておきます。
(話を簡単にするためothersも読めるホームディレクトリ直下にcheckoutすることを想定していますが、(あとで作られる)jenkinsユーザも読めるディレクトリであればどこでもいいです。)
git clone git://anongit.freedesktop.org/libreoffice/core libo
cd libo
git checkout libreoffice-4-1

念の為、普通にビルドできることも確認しましょう。
./autogen.sh --with-lang="ja"
make

instdir/program/sofficeから起動できましたね?

Jenkinsをインストールする


さて本題のJenkinsです。こちらも簡単で、手順JenkinsのWikiの「Installing Jenkins on Ubuntu」に書かれているので、その通りやればOK. apt-lineが用意されているので、ちょちょいと追加してapt-get installするだけです。

wget -q -O - http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key | sudo apt-key add -
sudo sh -c 'echo deb http://pkg.jenkins-ci.org/debian binary/ > /etc/apt/sources.list.d/jenkins.list'
sudo apt-get update
sudo apt-get install jenkins

おわり。あとは/etc/init.d/jenkins startで上がってくるので、
http://127.0.0.1:8080/
にブラウザからアクセスすれば画面を見ることができます。

が、LibreOfficeはレポジトリがでかいので、gitのcloneがローカル内なのにデフォルトタイムアウトの10分を越えてしまいました(VMのせいかな?)。なので、startする前に以下の通り/etc/default/jenkinsファイルを修正して、タイムアウトを延長しておくのをおすすめします。
JAVA_ARGS="-Dorg.jenkinsci.plugins.gitclient.Git.timeOut=120"
「O」は大文字なので気をつけてください。
参考:https://issues.jenkins-ci.org/browse/JENKINS-20387

Jenkinsを設定する(Gitプラグインの追加)

Jenkinsを設定するために、まずはトップ画面(http://127.0.0.1:8080/)を表示します。


「新しいジョブを作成してください」と言っていますが、デフォルトではGitが使えないので、まずはプラグインを入れます。

 プラグインを追加するには、画面左の「Jenkinsの管理」を表示したあと、「プラグインの管理」をクリックします。
 (ただ、セキュリティを設定の警告が出ている通り、現在のJenkinsは誰でもアクセスできてしまう危険な状態なので、自分だけしかいないネットワークじゃなければ、プラグインを入れる前に、セキュリティを設定をクリックして設定しておくのがいいと思います。ここでは割愛します。)

そこで利用可能のタブを選ぶと、使えるプラグインの一覧が出てきます。その中に「Git Plugin」があるので、チェックして「ダウンロードして再起動後にインストール」をクリックします。


するとインストールがはじまるので、「インストール完了後、ジョブがなければJenkinsを再起動する」にチェックを入れれば、Jenkinsが再起動されてプラグインのインストールは完了します。

Jenkinsの設定(ジョブの追加)

いよいよ、ジョブを追加していきます。ジョブを追加するには、左側の「新規ジョブ作成」をクリックします。そうすると、入力画面がでてくるので、ここでは「Build-LibreOffice」などの好きなジョブ名を入力し、「フリースタイル・プロジェクトのビルド」を選択して「OK」をクリックします。

 そうすると、ジョブの設定画面がでてきます。名称などの基本的な設定が一番上にあり、その次にでてくるのが「ソースコード管理」です。

これはなにかというと「成果物を作成するためのソースをどこからもってくるのか」という設定です。「Git」のラジオボタンを選択して、レポジトリの場所を「Repositry URL」に、ビルドしたいブランチを「Branches to build」に入力します。さきほどの下準備通りであれば場所は「/home/USERNAME/libo」、ブランチは「libreoffice-4-1」みたいな感じになっているでしょう。もちろん「git://anongit.freedesktop.org/libreoffice/core」にしても、ビルドはできます。


スクリーンショットでは選択していませんが、Additional Behavioursで「Clean after checkout」を選んでおくほうがいいかもしれません。

次はこのジョブの肝、 ビルド処理とビルド後の処理を設定します。まずは「ビルド手順の追加」をクリックし「シェルの実行」を選ぶと、「シェルスクリプト」の入力欄がでてきます。ここに、プロダクトをビルドするコマンドを入力します。

ただし、成果物をあとで取得しやすくするために、tarでまとめるコマンドを追加しています。

./autogen.sh --with-lang="ja"
make
tar zcf libreoffice.tar.gz instdir

あとは「ビルド後の処理の追加」をクリックし「成果物を保存」を選ぶと、「保存するファイル」欄がでてきます。これはなにかというと、ビルドの結果としてJenkinsが残しておきますというファイルです(Webからダウンロードできます)。*とかも使えますが、ここでは先程tarした「libreoffice.tar.gz」を入力します。

あとは保存を押せば設定終了です。

Jenkinsジョブをビルドする


ビルドする方法はWeb画面からでもいくつかあるのですが、普段一番触ると思われるトップからやってみます。

トップにジョブの一覧があるので、右側にある時計のマークをクリックするとビルドがはじまります。


あとは左下にある「ビルド実行状態」にビルド中表示が出るので、ここの#ビルド番号 のリンクをクリックすると、ビルド状況がわかります(「コンソール出力」でビルドログをリアルタイムに表示)。今、上の画面で灰色になっている○が、青くなれば成功です!赤くなったら失敗です…


完了すると、ジョブの画面の「最新成功ビルドの成果物」からlibreoffice.tar.gzをダウンロードすることができます。


あとは何度もビルド実行をすれば、がんばってビルドしてくれます。


使い方はイメージつきましたでしょうか?本当は、コミットを検知してビルドを開始したり、ビルドが終わったらメールを送ったりすることができますが、たぶんここまでこれればあとは検索で戦えるでしょう。

自分が開発しなくても、upstreamを追いかけるなどで何度もビルドするような方は便利だと思います。作業時間を覚えてて推定残り時間をプログレスバーに出してくれたり、ログを残しておいたりしてくれるので、単なるバッチ管理に使うのも便利です。


Tips - どこでビルドされているのか?


成果物にしていなかったものでも、前回ビルド時のファイル群を見てみたいことがあります。そんなときは、/var/lib/jenkins/jobs/{ジョブ名}/workspaceを見に行くと便利です。ここで作業が行なわれています。

2013-12-22

PowerShellにMSBuildLauncherで簡易GUIをつける

今日のPowerShell勉強会第1回の運営の方々、発表者の方々、そして参加者の方々お疲れ様でした。どれも面白かったり勉強になったりでよかったです。

さて、Lightning Talkで掲題の話をしたのですが、実用するには速すぎてわからなかったと思うので読み物としてわかるようにまとめたいと思います。

概要

MSBuild(.csprojなんかで使われている)はビルドには必要なんだけど、ちょっとコマンドライン叩くの面倒だよね、適当なGUIないよね。
  →MSBuild Launcherの誕生。

あれ、このGUIなら逆にビルドじゃない用途でも(スクリプト実行の用途でも)MSBuildにExec書いて使ったら便利なんじゃね?
  →MSBuild LauncherありきのMSBuildの邪道な使い方の誕生。

というお話です。

前提として、MSBuild Launcherをインストーラでインストール(関連付けオプション有効)を想定しています。

---

1. シンプルな使い方

単純に.ps1ファイルを置いて、それを実行するだけ。これでも十分に便利。一応、コマンドが長くなりすぎちゃうので、プロパティ(※1)としてpowershellコマンドを別出しにしておくのがおすすめ。

※1: パラメータのようなもの。PropertyGroup要素に好きな名前の要素を置くと、それがプロパティになる。



なお、-NonInteractiveは常に入れておくのがおすすめ。発表時も話したけども、例えば必須引数が足りなかったりすると通常のコンソールでは入力待ちで止まるけども、こいつはそんなことできないので、無言で待ちに入る。そして「進まない!」と悩む。なので-NonInteractiveで即座にエラーにする。

対応するスクリプトは好きな処理を書けばいいのだけど、一応例として。



動かすとこんな感じ。


2. プロパティを持たせる使い方

パラメータが欲しいときは、MSBuildにプロパティを持たせるとよい。そしてコマンドの中で$(Name)で使う。
ただし、Exec Command=""の中身の文字列はcmd解釈?のようなので注意。Command=をくくるのは""にして、プロパティを'$(Name)'という感じにくくるのがおすすめ。



プロパティはLTでは話さなかったけども、候補としてConditionを追加してあげると、コンボボックスの選択になるので、選択のときはそうしてあげるとよいかも。

スクリプトはこんな感じ。paramで受けてあげればいい。



動かすとこんな感じ。右側で設定できる。


3. 複数のターゲットを用意する便利な使い方

複数のやりたいことを一つの画面で行うには、もちろんスクリプトを複数用意してもよいのだけど、処理自体は少ない場合は、モジュール(.psm1)に関数を定義して、それをMSBuildから使う方式がおすすめ。

具体的には、PSCmdの中にImport-Moduleまで入れてしまって、Execの中では関数名を書く形にする。



スクリプトはこんな感じ。



動かすとこんな感じ。


4. PowerShellの便利機能を活用する

PowerShellを簡易GUIっぽくしちゃうことで有名なOut-GridViewやShow-Commandはここでも便利。
ただ1点注意があって、Execで実行するとコンソールと違ってホストが残らないので、そのままだとすぐ画面がいなくなってしまう。
なので、以下のように終了を待ってあげるのがいい。


---
今度こそぜひ活用してあげてください。

2013-12-16

Markdown.XAMLを使ってWPFでMarkdownテキストをレンダリングする

この記事は、XAML Advent Calendar 2013用です。今日は、MarkdownをWPFアプリケーションに表示する方法についてです。
---

Windows Phoneでmarkdownで書いたテキストをhtmlに変換して表示する を見て、そういえばMarkdownを表示するためのライブラリがあったことを思い出した。

その名もMarkdown.XAML。これは、MarkdownテキストをFlowDocumentに変換してくれる。使い方はざっくり言うと、TextToFlowDocumentConverterというコンバータが使えるようになるので、FlowDocumentScrollViewerのようなFlowDocumentを表示してくれるコントロールに、Markdownテキストが入ったプロパティとこのコンバータをセットでバインドしておくだけでOK.

詳しい使い方は以下の通り。

1. 下準備

Markdown.XAMLをclone or zipでダウンロードして、Visual Studioで開き、コンパイルする。

そうすると、build\Markdown.Xaml\(Configuration)のあたりにMarkdown.Xaml.dllができるので、これを、Markdownを表示させたいプロジェクトに持っていって、参照させる。

NuGetになさそうなのが残念だけど、すんなりコンパイルできるのでまぁ。

2. Markdownテキストを適当なプロパティに入れておく

ファイルから読んでとか、いろいろありそうだが、ここの本題ではないので、ここでは適当な文字列を返すプロパティを作っておく。


3. リソースに、MarkdownとTextToFlowDocumentConverterを定義する

TextToFlowDocumentConverterを作り、そこに実体であるMarkdownオブジェクトを紐づける。とりあえずこんなかんじ:


4. FlowDocumentをレンダリングするコントロールを配置する

FlowDocumentScrollViewerを配置して、さきほどのプロパティとコンバータをバインドする。


シンプルに表示したいだけなら、これだけ。

できたものはこんなかんじ:

活用編

FlowDocumentになっているので、もう少しWPFに寄った形で使える。

1. パネルの見た目を変更する

ちゃんとパネルも見えているので、たとえばパネルの背景色を変えたりすることができる。FlowDocumentScrollViewerのプロパティも当然使える。


2. スタイルを設定する

たとえば、文字色を変えたければ、このようにいつもの通りStyleを定義することで、変更することができる。


結果はこんなかんじ:


詳しくはデモがあるので見るとわかりやすいと思う。
https://github.com/theunrepentantgeek/Markdown.XAML/blob/master/src/Markdown.Xaml.Demo/MainWindow.xaml


----

どうでしょうか。わりと簡単に使えると思うので、適当なテキストを出したいときなんかにどうぞ。

2013-12-11

PowerDbg でデバッガ操作をオートメーションする

この記事は、PowerShell Advent Calendar 2013用です。本当は、誰も触れてないPowerShell v4.0の新機能について触れる予定だったのですが、新機能以外の部分で挫折したので実用的な内容に方針転換します。

今回の話題は、WinDbgの操作をオートメーションするスクリプト、PowerDbgの使い方です。これはなにかというと、WinDbgのコンソール版、cdb.exeのフロントエンドです。PowerShellがデバッガになるというよりは、windbgをコントロールできる感じです。

インストール方法

http://powerdbg.codeplex.com/ に行き、Downloadボタンを押すと、zipが落ちてくるので開きます。その中に「Install_PowerDbg.bat」というファイルがあるので、実行するとインストールされます。

といっても、横に置いてある「PowerDbg.psm1」を「%USERPROFILE%\Documents\WindowsPowerShell\Modules\PowerDbg」にコピーするだけなので、手でやっても変わらないです(もっと言うと、好きな場所に置いて毎回Import-Module .\PowerDbg.psm1でもいいです)。

あとは、いくつかの方法のどれかでcdb.exeのあるフォルダを指定します。でも一番簡単なのは.psm1の頭、param(...)のあとに以下のように記述することなので、ここではそれだけ書きます(どうせスクリプトは更新されなさそうだし)。
$debuggerRoot = "C:\Program Files (x86)\Windows Kits\8.0\Debuggers\x64"


使い方

「New-DbgSession」コマンドレットで、デバッガを起動できます。-command "コマンド..."で新コマンド起動、-process "プロセス名" でアタッチ、-dump "dumpファイルのパス" でダンプ読み込みです。

PS> New-DbgSession -command "C:\Program Files (x86)\MsbuildLauncher\MsbuildLauncher.exe"

そして、「Invoke-DbgCommand」でデバッガコマンドを送れます。
PS> Invoke-DbgCommand 'bp $exentry'

「g」は「Send-DbgGo」というコマンドレットが用意されているので使えます。
PS> Send-DbgGo
Send-DbgGoすると、止まるまでブロックします。Ctrl+Cで抜けることも可能。

終わりたいときは、「Exit-DbgSession」で終われます。
PS> Exit-DbgSession

.loadby sos clrみたいに拡張をロードしたいときは、Load-DbgExtensionが使えます。
PS> Load-DbgExtension sos clr

Invoke-DbgCommandで何を送るかが肝で、PowerShell固有の操作はほとんどありません。(引数名がcamelCaseなのがいけてない感じですがスルーでおねがいします…)

実用例1: 異常終了したときに.NETのスタックトレース等を表示するスクリプト

前にやった「WinDbgとSOS拡張でVSを使わずに.NETアプリをデバッグ - 異常終了時の調査」を自動でやれます。



実行するとこんな感じ。
PS> > .\PrintExceptionSecondChance.ps1 -Command "C:\temp\MsbuildLauncher-0.1.1\MsbuildLauncher\MsbuildLauncher.exe"
ModLoad: 000007fe`f7200000 000007fe`f7b60000   C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll
ntdll!ZwMapViewOfSection+0xa:
00000000`7755153a c3              ret
(1340.8e8): Unknown exception - code 04242420 (first chance)
ModLoad: 000007fe`f5a60000 000007fe`f5b8e000   C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clrjit.dll
ntdll!ZwMapViewOfSection+0xa:
00000000`7755153a c3              ret
(1340.8e8): C++ EH exception - code e06d7363 (first chance)
(1340.8e8): C++ EH exception - code e06d7363 (first chance)
(1340.8e8): C++ EH exception - code e06d7363 (first chance)
(1340.8e8): C++ EH exception - code e06d7363 (first chance)
(1340.8e8): C++ EH exception - code e06d7363 (first chance)
(1340.8e8): CLR exception - code e0434352 (first chance)     ←ここでエディタパスをおかしくして「Edit」ボタンをクリック
(1340.16c0): Unknown exception - code 000006ba (first chance)
(1340.8e8): CLR exception - code e0434352 (first chance)
(1340.8e8): CLR exception - code e0434352 (!!! second chance !!!)
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\system32\KERNELBASE.dll -
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\Microsoft.NET\Framework64\v4.0.3
0319\clr.dll -
KERNELBASE!RaiseException+0x3d:
000007fe`fd6a940d 4881c4c8000000  add     rsp,0C8h
PDB symbol for clr.dll not loaded
Exception object: 0000000002b0eeb8
Exception type:   System.ComponentModel.Win32Exception
Message:          指定されたファイルが見つかりません。
InnerException:   
StackTrace (generated):
(略)


実用例2: .NETなプログラムのプロセスのダンプファイルを読み取ってスタックトレースを出力するスクリプト

実はいつもやってる「clr.dllが読み込まれるまで進める」という処理が必要なければ、New-DbgSessionは-sosというオプションでsos.dllを自動で読んでくれるのです。それを利用すると、これだけ簡潔にできます。




どうでしょうか?今回の例はとくにPowerShellらしさはないですが、特定のブレイクポイントを一気に仕掛けるとか、結果に応じてどうこうするなんて用途には便利かと思います。

蛇足

WDK 8.1についてるWinDbgでSOS拡張をロードして、SOS系のコマンドを打つと、一発目がExceptionになって、二発目以降に成功するようになる。8.0ではそんなことはないので、WDK 8.0についてるWinDbgがおすすめ。






2013-11-30

MSBuild Launcher 0.2.2 をリリース

MSBuild Launcherの0.2.2をリリースしました。「ターゲットがたくさんあるMSBuildファイルで、何度も同じものを実行したいときに今のUIだとつらい」という要望があったので、前回実行したターゲットの色を変えるようにした、というのが今回の変更です。


あと、WindowsXPでクラッシュする問題への対策もだいぶ前に入れたので、今は大丈夫のはずです。