kazuk は null に触れてしまった

C# / .NET 系技術ネタ縛りでお送りしております

カテゴリーアーカイブ: VS Extention

Metro Style Application 開発の為の Visual Studio Project Template(1)


Visual Studio 2012 RC には Metro Style Application のためのプロジェクトテンプレートが幾つか含まれています。

これらのプロジェクトテンプレートからプロジェクトを起こしてみての解説を2回ぐらい書きましたが、今回はこのプロジェクトテンプレートの実体がどのように定義されているか、その内容を確認するとともに今後の開発で使うプロジェクトテンプレートの準備などに焦点を当ててみたいと思います。

標準のプロジェクトテンプレートのインストール場所

Visual Studio 2010 RC においてプロジェクトテンプレートの標準のインストールパスは以下の通りです。(Windows 8 RP x64にインストールした場合)

C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\ProjectTemplates\CSharp\Windows Metro style

このフォルダを開くと1041フォルダ配下に実際のプロジェクトテンプレート毎にフォルダがあり、プロジェクトテンプレートの実体が格納されています。

imageimage

このファイル群をベースに、プロジェクトテンプレートを修正してもいいですし、プロジェクトテンプレートを新規に作成してもいいでしょう。

Visual Studio SDK によるプロジェクトテンプレートの作成

プロジェクトテンプレートを作成するには簡単な方法としてはある程度作りこんだプロジェクトを元に Visual Studio から [ファイル]-[テンプレートのエクスポート]があるのですが。根がマゾいので簡単な方法は使わずにプロジェクトテンプレートを一から作成する事にしましょう。

この作業を行うには Visual Studio SDK が必要になります。

Download: Visual Studio 2012 RC SDK – Microsoft Download Center – Download Details

をインストールしてください。インストールを完了すると Visual Studio からプロジェクトの作成時に機能拡張配下からプロジェクトテンプレートが作成できるようになります。

image

今回はMetro Style Application の為のクラスライブラリを単体テストプロジェクトと共に生成し、ビルドのカスタマイズ等も(比較的)簡単にできるようビルドのカスタマイズプロジェクトも設定するという形でちょっと規模の大きいプロジェクトテンプレートを作成してみます。

Metro Style Application の為のプロジェクト要件の確認

ここで既存のプロジェクトテンプレートの内容を確認しましょう。プロジェクトテンプレートの中核を担うのは vstemplate という拡張子がついたファイルで、このファイルをもとに関連ファイルが取り込まれてプロジェクトが生成されますので Metro Style Application としての基本的な設定事項を把握しなければ話が始まりませんが、歴代の vstemplate はその詳細まで解説された事は殆ど皆無じゃないかな程度に情報量が少ないので、自分達の手元にあるVSにインストールされた vstemplateを見るのを最初にするのをおすすめです。

標準のプロジェクトテンプレートの ClassLibrary の vstemplate の内容とプロジェクトテンプレートを作成する場合の vstemplate には以下の要素に違いがある事がわかります。

<TemplateGroupID>WinRT-Managed</TemplateGroupID>
<RequiredFrameworkVersion>4.5</RequiredFrameworkVersion>
<TargetPlatformName>Windows</TargetPlatformName>
<RequiredPlatformVersion>8</RequiredPlatformVersion>
<CreateInPlace>true</CreateInPlace>

実際には当然に TemplateID などは違うのですが、そりゃテンプレートが違えばちがうわなで今回はスルーします。

TargetPlatformName や RequiredPlatformVersion 等が明らかに増えていますので、これを持つ vstemplate を作ります。

Metro Style Application の為の csproj 設定

いやー増えてますねー、Any CPU, x86, x64, ARM それぞれに Debug/Release でビルドするので当然ですが。全部ビルドしようとかすると単純に時間は20~30%増しって計算にちょっとgkbr

さて、大きな変更がありますね、vs2010までの csproj では Microsoft.CSharp.targets を見ていた物が $(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets を見ています。

XAMLのコンパイルタスクが必要なのはわかるのですが、このターゲットの解決に Visual Studio Version が入るってことは本気に Visual Studio インストールしないと(SDKだけだと)ビルドもできないのかも。ビルドサーバを用意する上でこれはちょっと難点ですね。最低限のExpressでも入れとけばビルドできるといえばそうなのかもしれませんけど。(ビルドしかできないかもしれませんけどね)

細かい詳細は基本的には丸コピー以外の選択肢は殆ど無い気がします、各 Configuration 毎に最低限のビルドオプションを設定したうえでソースはCompile ItemGroupにというのが基本的なクラスライブラリプロジェクトの構造です。

次の段階として、関連するテストを収容するための単体テスト用プロジェクトもこのテンプレートに取り込むのですが、順番にやるのが良いでしょう、ここで一旦 (1) として区切りをつけようと思います。

(1) までのまとめ

とりあえず github に push しときました。

リポジトリはこちら https://github.com/kazuk/MetroAppBase

ここまでの内容のコミットはこちら https://github.com/kazuk/MetroAppBase/commit/28e89e2623d54991cf9bcb57ea69dc5d270f0a29

予習したい人の為に (2) で何をするかという事で MSDN ドキュメントの参照リンクなど

方法 : 複数プロジェクトのテンプレートを作成する

(2)ではMetroスタイルアプリケーションのテストプロジェクトについて調べた上で、このプロジェクトテンプレートに組み込む事でクラスライブラリを作成と同時に単体テストを活用した開発ができる様にしようと思います。

まとめでは無いこと

MS的には Visual Studio 2012から TFS Express を使ってねとか色々あるみたいなんですが、少なくともこれまで経験から 「TFS は肌に馴染みませんでした。」(いいなーと思うところも無いわけではないけど、VSが落ちるとか落ちるとか落ちるとかで寧ろ足を引っ張られる印象の方が強く)というわけでVS2012での自分の開発環境での採用は無い感じです。

VS2012世代で自分の目論む開発環境ですが、基本的には git / github をソース管理のメインに据えてこれをビルドするビルドサーバは TeamCity か Jenkins か検討中、現状は VS2012のUltimateを90日試用期間で使ってますが(OSがCP/RP/RTMと行く流れで再インストールになりながらそれぞれ最大90日使えればまぁお得!)、最終的には Premium 辺りに落ち着くんじゃないかと思っています。

ソース管理の基本が TFS から git/github になる事で自分の開発環境整備(包丁を研ぐ段階)の物が表に出せるし出てしまうので晒しでやっていこうかなとか。git/github に出してる物は MIT ライセンスのつもり(どこに書くんだろう)なのでお好きにパクッテって頂戴。

githubでの pull request とか貰った事ないので、なんか楽しい変更があれば pull request も遠慮なく。

個人事業主になりまして昼間は他のお仕事しながら夜のお勉強タイム確保+お子ちゃま台風上陸という私事もありまして一日一時間も勉強に取れないので遅々として進まずの可能性もなきにしもですが今後うん十年仕事する上での準備なので着実に書いていければなーという感じです。

Multi Project な Project Template を作成する


元ネタは

How to: Create Multi-Project Templates
http://msdn.microsoft.com/en-us/library/ms185308.aspx

なんだけど、一回作ってる最中にうまくビルドできなくなったので、メモとりながら「やってみる」という blog

要 Visual Studio SDK です。

プロジェクトテンプレートのプロジェクトを作成。ソリューションエクスプローラー上での構造があうように作る。

imageimage

<?xml version="1.0" encoding="utf-8"?>
<VSTemplate Version="3.0.0" 
            Type="ProjectGroup"
            xmlns="http://schemas.microsoft.com/developer/vstemplate/2005">
  <TemplateData>
    <Name>LibWithUnitTest</Name>
    <Description>&lt;No description available&gt;</Description>
    <Icon>LibWithUnitTest.ico</Icon>
    <ProjectType>CSharp</ProjectType>
    <RequiredFrameworkVersion>2.0</RequiredFrameworkVersion>
    <SortOrder>1000</SortOrder>
    <TemplateID>526730eb-c930-4614-8398-64a21c9f008a</TemplateID>
    <CreateNewFolder>true</CreateNewFolder>
    <DefaultName>LibWithUnitTest</DefaultName>
    <ProvideDefaultName>true</ProvideDefaultName>
  </TemplateData>
  <TemplateContent>
    <ProjectCollection>
      <ProjectTemplateLink ProjectName="Implementation">
        ClassLibrary\ClassLibrary.vstemplate
      </ProjectTemplateLink>
      <ProjectTemplateLink ProjectName="UnitTest">
        UnitTest\UnitTest.vstemplate
      </ProjectTemplateLink>
    </ProjectCollection>
  </TemplateContent>
</VSTemplate>

あれ、ビルドできた。(ぉぃ

んでプロジェクトの追加したら普通にできた。(ぉぃ

 

企画倒れ!

…となぜ最初のは失敗したのかなと。 .cs のファイルプロパティで、ビルドアクションは「なし」にしてないと駄目。子プロジェクト側 vstemplate のビルドアクションは「なし」にした方が良いみたい。(VSTemplate ビルドアクションになってるとその分 Project Template の zipが吐かれますね)って事で、ファイルプロパティの設定間違ってるだけでした。

imageimage

っていう感じですね。

ってここまで書いてからこんなの見つけちゃった。

Multi-Project Templates with Wizard: Visual Studio 2010 Sample

http://vsix.codeplex.com/

プロジェクト作成時の Wizard まで実装してるね!コッチでも子プロジェクトの vstemplateのビルドアクションは無しになってますね。

http://vsix.codeplex.com/SourceControl/changeset/view/61761#1354286

<ItemGroup> <None Include="Children\Windows Library\ProjectTemplate.vstemplate" /> </ItemGroup>

ほら、無し(None)でしょ。

VS2010 後の世代のプログラミング (2) Pex


という訳でいくつもの連載するよと言っといて(2)が来ないとかやってるとアレなので、Visual Studio 2010 リリース後に出てきた物が開発にどう影響を与えるかという事で、Pex を取り上げます。

Pex って何?

Pex は内部的には Z3 証明器の論理的証明に対するインプットをメソッドシグニチャ、および、CLRのトレース機構と結びつける事でプログラムの動作が証明された状態を近づける事をサポートします。
って説明で解るわけないっすよね。
要するにです、プログラムの断片にどんな入力を与えたらどういう分岐が発生して、どういう条件が発生してというのをトレース情報を元に補完しながら解析分析するという事をします。
Z3証明器の結果としてプログラムの動作パスが何種類か得られ、その何種類かの動作パスに飛び込むにはこのパラメータセットでその処理を呼べば良いという事が解る物を、単体テストの形式で出力してくれるのが Pex な訳です。
中がどう動くかという事はPexがトレースを元にして進行的に解析しますので、実際に何が起こるのか解らない物に対してこういうパラメータセットを与えればこういう結果を返すというのを調べてくれるのでレガシー化してしまい文書化されていない物に対して非常に強い側面もありますが、停止性問題により全てのケースを網羅できない事は数学的に証明されていて、中身をいじる事ができない物、完全なブラックボックスに対してはどっかしらで限界がありますのである程度リファクタリングが可能な物に対して適用するのが最も望ましい結果を生み出します。(Pexがお手上げになった部分をリファクタリングで切り出してあげるとさらなる進行解析が可能となってより網羅性のある解析が実行されます。)
能書きはさておき、Pexを実際に使ってみましょう。

Pexによる実行パス解析の最も単純な例

C# での checked 演算を例にとって、四則および単項演算の単純ロジックをテストするとします。
namespace PexTarget
{
    public class Class1
    {
        public int Add( int p1, int p2 )
        {
            checked
            {
                return p1 + p2;
            }
        }
    }
}
このAdd を右クリックし Pex –> Create Parameterlized Unit Test した結果の出力は以下です。
// <copyright file="Class1Test.cs" company="Microsoft">Copyright ゥ Microsoft 2011</copyright>
using System;
using Microsoft.Pex.Framework;
using Microsoft.Pex.Framework.Validation;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PexTarget;

namespace PexTarget
{
    /// <summary>This class contains parameterized unit tests for Class1</summary>
    [PexClass(typeof(Class1))]
    [PexAllowedExceptionFromTypeUnderTest(typeof(InvalidOperationException))]
    [PexAllowedExceptionFromTypeUnderTest(typeof(ArgumentException), AcceptExceptionSubtypes = true)]
    [TestClass]
    public partial class Class1Test
    {
        /// <summary>Test stub for Add(Int32, Int32)</summary>
        [PexMethod]
        public int Add(
            [PexAssumeUnderTest]Class1 target,
            int p1,
            int p2
        )
        {
            int result = target.Add(p1, p2);
            return result;
            // TODO: add assertions to method Class1Test.Add(Class1, Int32, Int32)
        }
    }
}

属性を無視してみれば単純に呼び出すコードが生成されているのが解ります。
この Class1Test の Add を右クリックして Run Pex Explorations すると
imageimage
2つの動作パスが検出されそれぞれの動作パスにおいてどういう結果となるのかが解析されました。
OverflowExceptionが望まれる結果という事でAllowException し再度 Run Last Pex Exploarations するとすべてグリーンになります。
image image image
インデントがあれとかはご愛嬌って事ですが、Class1を生成し、Addに指定パラメータを与えて実行する単体テストになっています。(TestMethod属性修飾されてますよね)
結果として以下のコードが生成されます。
namespace PexTarget
{
    public partial class Class1Test
    {
[TestMethod]
[PexGeneratedBy(typeof(Class1Test))]
public void Add602()
{
    int i;
    Class1 s0 = new Class1();
    i = this.Add(s0, 0, 0);
    Assert.AreEqual<int>(0, i);
    Assert.IsNotNull((object)s0);
}
[TestMethod]
[PexGeneratedBy(typeof(Class1Test))]
[ExpectedException(typeof(OverflowException))]
public void AddThrowsOverflowException587()
{
    int i;
    Class1 s0 = new Class1();
    i = this.Add(s0, -2147409852, -1040186368);
}
    }
}
単体テストになってますのでそのまま動かしてテストが可能です。
image
では、このテストは本当に有効なのでしょうか。テスト構成のデータ収集でコードカバレッジを有効にして
image image
リビルドしカバレッジデータを取るためにすべてのテストを再実行します。
image
はい100% ですね、これ以上有効なテストは存在しないわけです。
Pexはトレースと証明器によって少なくとも追える範囲のテストを自動的に実装しますので、単純にコードを書いてPex Explorartionした結果が望ましい物であればそれを受け入れるとするだけでカバレッジレートの非常に高いテストクラスが得られます。

Pex Driven Contract based development

という訳でPexでは Explorations の結果を見て望ましい結果が出るようにコードを直していくというサイクルでコードを実装していくことができます。
では int Sum( IEnumerable<int> sequence ) を実装してみましょう。
public int Sum( IEnumerable<int> seq )
{
    var result = 0;
    foreach (var i in seq)
    {
        result += i;
    }
    return result;
}
何も考えないで書いて、Create Parametelized Unit Test します。出来上がった Parametelized Unit Test 上で Pex Explorations すると NullReferenceException を投げるケースがあるという事が解ります。
seq が null の場合ですね。ここで Code Contracts を有効にして Contract.Require を使って seq!=null を Pre condition にすると
image image
はい、Pre condition 記述されたパラメータは自動的にPexでも認識されて無効なパラメータによる呼出しとなりました。
OverflowException が飛んでないですね。確かに checked ブロックの記述を忘れています。checkedを追加してAllow Exception すると以下になります。
imageimage
という訳で、入力で通るべきすべてのパスを網羅したテストが出来上がりました。
/// <summary>
/// 合計を計算します
/// </summary>
/// <param name="seq"> integer sequence </param>
/// <returns> sum value of sequence </returns>
/// <exception cref="ArgumentNullException"> when seq is null </exception>
/// <exception cref="OverflowException"> when overflow </exception>
public int Sum( IEnumerable<int> seq )
{
    Contract.Requires<ArgumentNullException>(seq!=null);

    var result = 0;
    foreach (var i in seq)
    {
        checked
        {
            result += i;
        }
    }
    return result;
}
Explorationsの結果を見れば exception のドキュメントコメントも容易ですね。
まぁ、これで良いのかというと、呼出し側に pre condition 規制の難しい所で例外が外に飛んで行ってしまう可能性があるわけで、設計上望ましい挙動ではないかもしれません。これを Explorations から読み取る事ができるので 「TrySum を作る」という選択肢を少なくとも検討する機会が得られるのは非常に重要な事です。
ではTrySumを作るとしましょう。
さくっと軽く書きますとこうなりますね。
public bool TrySum( IEnumerable<int> seq, out int result )
{
    Contract.Requires<ArgumentNullException>(seq != null);

    result = 0;
    foreach (var i in seq)
    {
        try
        {
            checked
            {
                result += i;
            }
        }
        catch (OverflowException)
        {
            return false;
        }
    }
    return true;
}
これを Explorations するとこうなります。
image
false 時に out パラメータに無効値が出てるのが気に入らないとか思いませんか?思う人は例外安全とか失敗時挙動の健全性なんて物に敏感という訳で。
Contract.Ensures の出番ですね。false 時には out result には0 が返ると表明します。
Contract.Ensures(
    !Contract.Result<bool>()
    ? Contract.ValueAtReturn(out result) == 0 : true );
Contractのチェックオプションを Pre condition より強くしていればこのように契約違反例外が飛びます。
image
契約違反にならない様にするために tempResult変数を導入して、tempResult 上で計算したうえで、成功時だけ値を返すようにすると以下の通り。
public bool TrySum( IEnumerable<int> seq, out int result )
{
    Contract.Requires<ArgumentNullException>(seq != null);
    Contract.Ensures(
        !Contract.Result<bool>()
        ? Contract.ValueAtReturn(out result) == 0 : true );
    result = 0;
    var tempResult = 0;
    foreach (var i in seq)
    {
        try
        {
            checked
            {
                tempResult += i;
            }
        }
        catch (OverflowException)
        {
            return false;
        }
    }
    result = tempResult;
    return true;
}
image
このように Contracts を絡めるとPexは本当に威力を発揮するわけですね。
そして出来上がったテストを本当にテストコードに含める時は Explorationsから Promote すると、本体のPex class にマージされます。
image image
当然にカバレッジレートは 100 % になりました。
このように Pex とCode Contractsを組み合わせていく事でプログラムの動作は常に把握され、表明された状態に制約されて動作する事が保障され、なおかつカバレッジレートは相当に高いテストが出来上がります。テストが出来上がるだけでなく、書いたコードがどのような動作バリエーションを起こすかをPexは素早く列挙しますのでコードを書き、それが示す挙動が自分の想定通りかを簡単に確認しながら実装を進めていくことができます。

Pexが苦手とする物

Pexにはいくつか苦手とするものがあります。多くは単体テストという方法論自体が苦手とする領域でもありますが、ここで列挙してみましょう。
  1. 値域が非常に広い物、シーケンスの後方に依存する処理
  2. MEFインポート
  3. 環境値(日付時刻、ランダム値)
Pexはトレースを元に進行解析を行いますので、値域が非常に広いものを苦手とします、また、小さなデータの組み合わせを順次拡張しながらパターンを作りテストを実行してトレース結果から新たなパスを検出するのでシーケンスの後方、たとえば 3000文字以降に何かがあれば何かするとかは生成するのに非常な繰り返し回数を必要としてしまいきっと失敗します。処理を単位に分割する等で巨大なテストケースが必要とならない様にリファクタリングをする必要があるでしょう。
Pexは非常に便利なのですが、その方法論固有の問題を内在しています。しかし実際問題としてPexがお手上げになるケースは人が単体テストを書こうとしても問題となるケースである事が多いです。であれば苦労はPex(機械)にしてもらうというのは正しい選択でしょう。これらの問題にぶつかってPexがお手上げになるケースは多くの場合リファクタリング等設計を直した方が良いケースを暗示しますのでとりあえず書いたものをPexに投げ込んでみてお手上げケースは「リファクタリングした方がいいんじゃない?」というメッセージとして素直に受け取ってみる事をお勧めします。

Pex と Code Contracts と WCF と MEF

はい、いきなりWCFです。脈絡感じないかもしれませんが WCF には Service Contract, Operation Contract, Data Contract と色々と Contract が並んでいるという訳で、これらの Contract を使ってサービスのプロトコルを記述する事ができます。このサービスのプロトコルに対して Code Contracts でインターフェース契約を記述する事ができます。実装上リモートエンドポイントに対して WCF で結合するか、MEFで実装を取り込むかは「好きにすれば?」の世界であり別に自由な話です。要するに WCF のコントラクトとしての属性記述をしなかった場合にはその自由が失われるだけで、 MEF という選択肢は残ります、MEFしない場合これまでのリポジトリパターンのように自分でサービス解決をしなさいと言われるだけです。
要するに手間数が許すなら WCF プロトコル属性でのマークアップはやっとけと、何も失うものはなく、サービスをリモートにおける自由度を獲得できるぞと。
サービスを実装したら Code Contracts と Pexで高いカバレッジを持ったテストを実装する事ができます。サービスは多くの場合サービスの利用者に対してテスト可能性を提供するモックを必要とします。では、モックを使った場合に処理が失敗したケースにおいて「モックが悪い?それともモックを呼ぶ実装が悪い?」これはややこしい問題です。モックの実装が正しく契約に従っていない等は最も駄目な話でモックの正しさは重要です。しかし、「モックをテストする?」というとこれまではやってられない話でした。しかしPexによってモックのテストの実装の大よそは実現されます。Code Contracts でのインターフェース契約が整っていれば、モックを使う側の実装が悪いといえる状況に容易に持って行けるはずです。
Code Contracts でのインターフェース契約はインターフェースの設計の重要な要素です。設計だからそれを日本語で書いて脳みそで解釈するか、コードで書いて機械が解釈するかは表現と解釈機の違いでしかない、この解釈機の厳密性と精度と速度を比較したら大よそ勝負になんてならない事は自明でしょう。
「TDDで設計をテストに!」、実行できるメリットは存在する事に異存はありません、実行できるので高い厳密性があるのも異存はありません。しかしその設計を解釈して展開するときに人間の脳みそに頼るのがTDDのテストが設計書だという時の弱さです。Code Contractsは本当に機械解釈できる設計書となり、コードを書くとき、そのコードのテストを実装するときの参照メタデータとして普遍的な表現として使われ続けます。この普遍的表現の解釈機が Code Contracts であり Pexという訳です。

ルーズスキーマと dynamic、ストロングスキーマと Contracts

LLでの動的性、ルーズスキーマを取り込むのが dynamic での C#4.0の進化点と、それだけじゃないよ、VS2010のリリースにはこぼれたけどCode ContractsとPexはストロングスキーマな静的言語の厳密性の大きな進化です。
という訳で VS2010 後の世代のプログラミング (2) でした。Code Contracts / Pex が設計に厳密性や検証性をもたらした逆方向という意味で自由度を提供する物、MEFを取り上げます。

Implementing Visual Studio Custom Tool step by step


環境の準備

というわけで Visual Studio でのカスタムツールの実装方法です。

まず最初に Visual Studio 拡張のSDKが必要になりますので、ダウンロードしてインストールしてください。 Extending Visual Studio から Visual Studio 2010 SDK

で、非常に定型コードの多い、カスタマイズの必要のない部分が多いので、Razor Preprocessor のソースを持ってると非常に楽です(っていうか、とってきてそのままプロジェクトに入れて下さい的解説が多く出ます)ので zip は拾って展開しておいてください。

プロジェクトの作成

Visual Studio 2010 SDK をインストールすると、Extensibility 配下に VS Package プロジェクトが表示され、Visual Studio 拡張のパッケージを作成する事ができます、この VS Package プロジェクトにカスタムツールを実装する事になります。

プロジェクトを作成したらまず vsix のマニフェスト編集になります。ここで一番重要なのは統合対象のVSエディションの選択です、デフォルトでは Professional のみになってますので必要な統合先を選択してください。

image image

プロジェクトビルドオプションの設定

Visual Studio 2010 では基本的に MEF による拡張になっている為、VS Package はデフォルトで COM 公開になっていません。これを有効にします。プロジェクトのプロパティでアセンブリ情報をクリックし、アセンブリをCOM参照可能にします。

image image

参照設定

VS Package の標準参照設定では Custom Tool を実装する上でのインターフェース定義等いくらか不足しますので、これを参照設定を行い取り込みます。

image

追加されている主な物はEnvDTE80, Microsoft.VisualStudio.OLE.Interop, Microsoft.VisualStudio.Shell.Inteop, VSLangProj, VSLangProj80ぐらいですね。その他の System.Web 配下とかは今回 Razor のコード生成が必要だったのでくっついてるだけで、Custom Toolの実装に必要というわけではありません。Visual Studio の NoPIA のデフォルト動作が VS 連携がらみのシナリオとバッティングをおこしビルド時に警告が出るようになると思います。VS連携系のアセンブリの参照設定時には「相互運用機能の埋め込み」はFalseを指定してください。

image 

pkgdef 属性定義宣言

カスタムツールは COMモジュールであり、VS からレジストリを介して参照されますので、パッケージのインストール時に pkgdef によるレジストリ設定を行う必要があります。このレジストリ定義をvsixインストーラーが解釈可能な形で渡すためにアセンブリ内のクラス宣言に付与した属性から抽出する仕組みが Visual Studio SDKには含まれています。RegistrationAttribute(Microsoft.VisualStudio.Shell.RegistrationAttribute クラス) はあまりに汎用過ぎてモジュール定義的な内容しか出力できないので、この属性定義の派生を作成します。CodeGeneratorRegistrationAttributeクラスがこの実装になります。カスタムツールのpkgdefを生成する上でいじるところは特にないのでそのままソースをプロジェクトに組み込んでください。

IVsSingleFileGenerator の実装

IVsSingleFileGenerator を実装します、COMインターフェースなんでメモリ管理面等気を使わなければいけない部分が幾つかあるのですが、必要十分な実装が BaseCodeGenerator.cs にありますのでこれを取り込みます。

同じく IObjectWithSiteも実装しなければならないのですが BaseCodeGeneratorWithSite.cs に必要十分な実装があります、この実装を取り込む事で Visual Studio のプロジェクトシステム内のサービス提供モデルに組み込まれる事ができ、結果的に VSLangProj等のインターフェースが利用可能になります。(この時にエラーメッセージの文字列リソースとして Strings.resxが必要になります。 resx を取り込んでカスタムツールに ResXFileCodeGenerator を指定してください)

最終的に Custom Tool の実装はここで準備した BaseCodeGeneratorWithSite から派生されます。

カスタムツール本体の実装

BaseCodeGeneratorWithSiteの継承クラスを作成します。実装しなければならないメソッドは GenerateCode だけです。GenerateCodeのinputFileContentパラメータに文字列でカスタムツールが設定されているファイルの内容がもらえますので、これを元にツールの出力を byte[] で返す事になります。 byte[] で返すので出力はテキストでなくてイメージファイルだったりなんかのバイナリでも構いません。逆にテキストを返す場合にはちゃんと出力の文字コードへのエンコード処理を記述しなければなりません。
続いて継承クラスを CodeGeneratorRegistrationAttribute で属性修飾します。同時に ComVisibleをtrue、 Guid属性、ProvideObject 属性で実体型を示す必要があります。

    [ComVisible(true)]
    [Guid("0E28FE58-1755-465E-A2D9-8B4AFD875742")]
    [CodeGeneratorRegistration(typeof(className), 
        "説明", 
        vsContextGuids.vsContextGuidVCSProject, 
        GeneratesDesignTimeSource = true)]
    [ProvideObject(typeof(className))]
    public class className : BaseCodeGeneratorWithSite
    {
        protected override byte[] GenerateCode(string inputFileContent) 
        {
            // ここでカスタムツール固有の実装をします。
        }
    }

実装しなければならないメソッドは単純に言えば string->byte[] ですので、単体テストプロジェクトから色々呼び出してテストしておくと最終工程で VS デバッガの中にVS読み込んでの確認をする必要性が減ってが楽になるんじゃないかなーとは思います。

デバッグと確認

単純にBuildしてデバッグしようとしても必要なレジストリ登録とか vsixインストール時に行われるべき事がされてない形になります、bin\Debug配下にvsixが出力されていますので vsix をインストールしてからデバッグ実行します。

デバッガから抜ける前に機能拡張マネージャからアンインストールを叩いておいたほうが良いでしょう。(もう一度 vsix インストールしようとしてダブルクリックすると「すでにインストールされています」で断られますので)

プロジェクトの種類は何でも構わないのでカスタムツールに渡すファイルを持つためのプロジェクトを作り、テキストファイルを作ります。カスタムツールを作成したカスタムツール名に設定してカスタムツールを実行でカスタムツールが実行されます。ブレークポイントを貼っていればブレークされるはずなので好きなだけデバッグしてください。(ただし、デバッガ内でVS起動するとCore2 Duo 2.8GHzでも起動するまでに30秒以上かかりますので、最後の統合レベルのデバッグ以外はデバッガ実行でのデバッグはおすすめできません。)

まとめ

実装そのものは部品がそろってれば比較的簡単でした。躓きどころはMEFでなくてCOMだっていう点、これについての注意事項はこの文書にすべて盛り込んだつもりです。MS提供のほとんど使える部品がそろってるのでそれをベースに淡々と組みましょう。

T4が AppDomainの分離とかで切り離されるとかの絡みで触れないインターフェース、サービスその他があるのに比べてこっちは比較的なんでも行ける感があります。T4のPreProcessor使って生成周りのコード作って呼ぶだけなら生成周りの実装も簡単と思いますので、T4のヘビーユーザーで深いVS Integrationを求める人は挑戦する価値はある素材だと思います。

Visual Studio プロジェクトテンプレートの作り方


NuGet のプロジェクトテンプレートを公開してたりする人なので、レシピを公開しておきましょう。

最も簡単な方法は Visual Studio のファイルメニューからテンプレートの作成なんですが、今回はちっと難しい方法を。

Visual Studio SDK をインストールします。Download details: Visual Studio 2010 SDK

こいつをインストールすると、プロジェクトの種類として Extensibility が選べるようになり、Visual Studio の拡張パッケージを作成したり色々できる他に、その他色々のおまけがついてます。

image

今回は ASP.NET MVC3 のHttpModule を作成する為のプロジェクトテンプレートを書いてみます。プロジェクト名を設定して出てくる画面はこんな感じ。

image

いきなり vstemplate が開かれてますね。ソリューションエクスプローラーでの表示にはProjectTemplate.csproj 含め幾つかファイルが入ってますね。

というわけで、目的に応じて「あとは好きに致したまえ(ぇ」なんで、ASP.NET MVC3で使うHttpModuleの作成に使うテンプレートになるようにカスタムしていきます。

まず、Class1.cs で最初にできるべきコードを考える為に、本物を作ってみましょう。

クラスライブラリプロジェクトを追加して、そこの上で HttpModule を作るための作業をします。

image

System.Web とかを参照設定して

image

Class1.csをHttpModule1.csにリネームして IHttpModuleを派生して実装するのに十分な事をやります。

IHttpModule.DisposeのDisposeパターンを実装してやばそうなら Debug.Fail を呼ぶぐらいをデバッグ版でやるまでが今回想定できる範囲でしょうか。

image

ビルドしてちゃんとビルドできると解ったら、いきなり作ったクラスライブラリプロジェクトをアンロードします。

ソリューションエクスプローラーからプロジェクトのコンテキストメニューから「プロジェクトのアンロード」を選びます。

アンロードしたプロジェクトを右クリックで編集を選ぶと csproj の編集になります。(が見るだけで編集するわけではありません)

これを ProjectTemplate.csproj と並べたのがこんな感じ。

image

後はこの両者を比べながらProjectTemplate.csprojが「いい感じになるようにします(ぉぃ」ここでビルドシーケンスに仕込みとかができますね。NuGetパッケージプロジェクトの時はここで仕込みを入れました。(ちょっとだけ後述)

まぁ、参照設定の Reference の所が一番とっつき良いでしょうね。(MVC3入ってないじゃん、うん、裏でインストールやってて止まる罠にはまった為、単純にASP.NET HttpModuleのプロジェクトになってる)

いい感じになったらプロジェクトのコンテンツに行きましょう。

今回は HttpModule1.cs がコンテンツになりますのでコンテンツになるようにまた左右に並べて「いい感じになるようにします(ぉぃ」

image

こんな感じに。

$safeprojectname$ とかの暗号めいた物のリファレンスはこちら。

テンプレート名 ($if$ とかのドキュメントが無いように感じるのは自分だけでしょうか?)

お気づきかもですが、 NuGet の Configuration File and Source-Code Transformations がこの $で記号を挟む形式って事で、NuGetはVS内にあるテンプレート機構にオンラインリポジトリを結びつけた物という見方も出来たりするかもしれませんね。

んでこれをビルドするとプロジェクトテンプレートの zip ファイルが bin/構成 配下に吐き出されます。

これを自分の ドキュメントの Visual Studio 2010\Templates 配下の適切な場所に配置するとそれをベースとしてプロジェクトを作る事ができるようになります。

もともとビルド成功してた物を参考に「いい感じになるようにします(ぉぃ」だったので、プロジェクトを作ってみてビルドできない様であればなんかが間違ってるって事ですね。

csprojでのビルドシーケンスへの仕込みをする時の Tips

Visual Studio は結構激しく csproj と関連依存ファイルをキャッシュするので、csprojへの仕込みとデバッグは大変です。MSBuildのImportを使って分離した編集用ファイルを見るようにしておいてそれを編集しながらビルドを試すとかはこのキャッシュ機構によって、いくら編集して保存しても反映されない事になり「直したのに直らない→何がわるいのか解らない→挫折しちゃってもいいかなモードがひたすら上昇」の負帰還ループにはまります。

「csprojへの仕込みのデバッグは Visual Studio ではできない」これを最初に前提として進みましょう。

プロジェクトのアンロードをして csproj の編集に入ったら、スタートメニューから Visual Studio のコマンドプロンプトを開き、cd プロジェクトのフォルダで msbuild をコマンドプロンプトから叩けるようにしておいて(これをやってくれるVS拡張は色々あります) csproj を編集して保存したらビルドはコマンドプロンプトから実行します。ビルドがいい感じでできるようになるまではこの状況で進むのがおすすめです。

csproj内では msbuild のTaskやTargetを書く事になりますが、Visual Studio 2010 では MsBuild 4.0 の BeforeTarget/AfterTargetによって既存の Microsoft.Common.Tagets に手を入れずにシーケンスの途中に割り込めるようになりました。この機能の効果は結構絶大で MsBuild インラインタスクとかと組み合わせるとビルドシーケンスは結構思うがままです。(参考: MsBuild 4.0の新機能でビルド時コード生成とか)

注意事項ですが最終的にいい感じな csproj を作ったらテキストの複製を取って Visual Studio に csproj を再読み込みさせてすぐにアンロードして再度編集に入って比較するのがおすすめです。Visual Studioは自分に都合の悪い記述をデフォルト値に戻す動きを暗黙にやります。このようなVisual Studioによって削除されたりデフォルト値に戻された値についてはVisual Studioでのビルド、デバッグに支障がでない何らかの方策を実施する必要な項目として重要な情報ですが比較してみないことには解らないというのが結構厄介です。(この動作についてはMsBuild とVSの連携に関するところにドキュメントで書いてあった記憶はありますが見つからない…)

作ったプロジェクトテンプレートを NuGet 配布しちゃうぞ

って手前味噌ですが、自分の流した NuGetPackageProject を取ってください、とり方はNuGet パッケージプロジェクトテンプレートを参照。

するとNuGetパッケージの為のプロジェクトを作れます、これを作ってるソリューションに追加します。

image

プロジェクトテンプレートプロジェクトの出力、今回は bin\Debug\ に作成されている Mvc3HttpModuleProject.zip を Tools 配下に置くようにします。

今回はパッケージプロジェクトのビルド前イベントでcopy コマンドを使いました。

image

Package/content 配下に readme.htm を作ります。(本来的にはいらないんですが、何もないとinstall.ps1が動かないので)

image image

ソリューション配下の packages\NuGetPackageProject\Tools\install.ps1 からプロジェクトテンプレートへのコピー操作を取ってきて Package\Tools\install.ps1 に貼り付けます。(うわ、install.ps1が動かねーってデバッグしてた時のまんまになってる)

param($installPath, $toolsPath, $package, $project)

Copy-Item $toolsPath\*.zip -destination ([System.Environment]::ExpandEnvironmentVariables("%VisualStudioDir%\Templates\ProjectTemplates\Visual C#\"))Write-Host "Project Template installed"

って感じでどうでしょう。

NuGetPackage.tt を編集して好みの nuspec を書いてビルドすると貴方のプロジェクトテンプレートを配布する nupkg がめでたく出来上がったはずです。

ローカルリポジトリを使ってテストして後は nuget.org に流すだけです。アカウントとってアカウント承認してもらったらさっくり放流です。

ローカルリポジトリからの一連のあたりは neue cc – NuGetパッケージの作り方、或いはXmlエディタとしてのVisual Studio が詳しく書いてるのでそちらをどうぞ。

という訳で Visual Studio プロジェクトテンプレートの作り方からcsprojへの仕込み tips 、手前味噌の NuGet パッケージ作成の仕方でした。

VsPackage プロジェクトのデフォルト値の罠


Visual Studio SDK (download)を入れてると Visual Studio 内で動かせる VsPackage を作れるようになりまする。 (プロジェクトの作成時には言語下の Extensibility より)

image

んで、この VsPackage なんですが、最初に作られる時のエディション選択が Professional ノミになってますって事で要注意。

image

(さて、本来 AddOn をサポートしない Express Editions がリストされているアレ。msi とか作って無理やり捻じ込むと入るって事でしょうかねー)

Ultimate とか Premium を使ってるのは怖い人が多いんで、バンバン叩かれない様に注意しましょー。

スクリーンショット二つだけという簡単な blog 、先週土曜にやった生産技術勉強会でのネタをチロチロと出していくblogがしばらく続きますよって事。

Azure Table 用のエンティティデザイナ(書きかけ)


DSL Tools で表題のものを書きましたのでおすそ分け。スクリーンレコーディングを Expression のスクリーンキャプチャで取ったのですが、さすがにFullHDでのスクリーンキャプチャはマシン性能が厳しいですな。SkyDriveへあげるつもりだったんだけど、1ファイル50MBまでって事であがんね(w

 うげ、SkyDrive にあげた物の埋め込みタグが貼り付けられずに切られるんですが、これってアリなんでしょうか、同じ所が提供してるサービスとしてどうよな感じ。

←のバーに出している SkyDrive のパブリック配下に AzureTableEntityDesigner.zip がございますのでご自由にお持ちください。

 Spacesもう駄目か?