kazuk は null に触れてしまった

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

カテゴリーアーカイブ: Visual Studio

Visual Studio で kindle 本をビルドする


ネタ的には msbuild と Visual Studio のカスタマイズネタでございます。

Kindle 本のソース

Kindle 本を書く上でのソースは、html と pdf, word 文書等が選べるようです。

細かい事はこっちを参照 Amazon.com: Kindle Direct Publishing: HelpType of Formats

んで、今回は Visual Studio の ASP.NET Web Application の csproj を魔改造して html から mobi 形式で kindle 本をビルドするという形にします。プロジェクトのドキュメントとか、ソリューション内に kindle 本のプロジェクト置けば電子書籍化できるという誰得な物ですね。

というわけで ASP.NET Web Application のプロジェクトを作ります。 MsBuildBook とか付けてますけど、こんな本が Amazon の Kindle ショップに並んでるはずがないよね!

目次を index.html として作ります。

imageimage

この目次はあくまでもコンテンツです、本の中身の目次だと思っても良いでしょう。そういう意味で好き勝手な事を書いて良いです。

目次情報ファイル ncx と opf の作成

とりあえず将来的には T4 とか msbuild カスタムタスクで吐かせるつもりでも一端手書きで書いておきます。

opf も書いておきましょう。

imageimage

ビルドツールの入手

Download KindleGen 2.8 から kindlegen を入手します。プロジェクト内に buildTools フォルダを作り exe を配置しました。

とりあえず opf を叩くとビルドされます。おめでとう。

ビルドされた mobi を kindle の doucments 以下に USB 接続で送り込むと実際に表示を確認する事ができます。

image  IMG_0040IMG_0041

表紙と目次をそれぞれ表示した所、ちゃんと表示できてますね。

目的は自動化だ!(いや、それ手段ですし

さて、手順は解ったという事で自動化していきましょう。

まず、 build そのものをプロジェクトのビルドでできるようにしましょう。

おもむろにプロジェクトをアンロードして 編集に入ります。とりあえず実行してたコマンドを Exec で叩きます。

  <Target Name="MobiBuild" AfterTargets="Build">
    <Exec Command="buildTools\kindlegen MSBuildBook.opf" 
          StdOutEncoding ="utf-8" 
          StdErrEncoding="utf-8"  />
  </Target>

AfterTargets で Build を指定しているので標準の build が動いた後でこのターゲットが実行されますね。

コマンドプロンプトで build したらちゃんと実行結果を得られました。プロジェクトを再読み込みし、リビルドすると、はいできたー。

imageimage

opf 書くのだるいよね

うん、だるいね。本体のHTMLをどんどん書いていけば出来上がるのが理想だね。

csproj には html の一覧あるし、これを元に出したいね。というわけで opf 作成の msbuild のカスタムタスクを書く。

using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Xml.Linq; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace Build.Utilities { public class GenerateOpf : Task { [Required] public ITaskItem Metadata { get; set; } [Required] public ITaskItem[] Contents { get; set; } [Required] public string OutputFile { get; set; } public override bool Execute() { var metadata = XDocument.Load(Metadata.ItemSpec); var doc = new XDocument( new XElement( "package", new XAttribute("unique-identifier", "uid"), CreateMetadataElement(metadata), CreateManifestElement(Contents), new XElement("spine", new XAttribute("toc", "toc")), new XElement("guide") )); doc.Save(OutputFile, SaveOptions.OmitDuplicateNamespaces); return true; } private static XElement CreateManifestElement(IEnumerable<ITaskItem> contents) { var manifestElement = new XElement("manifest"); manifestElement.Add( new XElement( "item", new XAttribute("id", "toc"), new XAttribute("media-type", "application/x-dtbncx+xml"), new XAttribute("href", "toc.ncx"))); foreach (var item in contents.Where( i => Path.GetExtension(i.ItemSpec) == ".html")) { var itemSpec = item.ItemSpec; Debug.Assert(itemSpec != null, "itemSpec != null"); var idValue = Path.GetFileNameWithoutExtension(itemSpec); manifestElement.Add( new XElement( "item", new XAttribute("id", idValue), new XAttribute("media-type", "text/x-oeb1-document"), new XAttribute("href", itemSpec))); } return manifestElement; } private static XElement CreateMetadataElement( XDocument metadata) { return new XElement( "metadata", CreateDcMetadataElement(metadata), CreateXMetadataElement() ); } private static XElement CreateXMetadataElement() { return new XElement( "x-metadata", new XElement( "output", new XAttribute("encoding", "utf-8"), new XAttribute("content-type", "text/x-oeb1-document")), new XElement( "EmbeddedCover", new XText("images/Cover.jpg")) ); } private static XElement CreateDcMetadataElement( XDocument metadata) { XNamespace dc = "http://purl.org/metadata/dublin_core"; XNamespace obe = "http://openebook.org/namespaces/oeb-package/1.0/"; var dcMetadataElement = new XElement( "dc-metadata", new XAttribute(XNamespace.Xmlns + "dc", dc.NamespaceName), new XAttribute(XNamespace.Xmlns + "oebpackage", obe.NamespaceName) ); foreach (var element in metadata.Element("dc-metadata").Elements()) { dcMetadataElement.Add(element); } return dcMetadataElement; } } }

これを csproj に組み込むには以下のように。

  <UsingTask 
    TaskName="GenerateOpf" 
    TaskFactory="CodeTaskFactory" 
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
    <ParameterGroup>
      <Metadata ParameterType="Microsoft.Build.Framework.ITaskItem"
            Required="true" />
      <Contents ParameterType="Microsoft.Build.Framework.ITaskItem[]" 
            Required="true" />
      <OutputFile ParameterType="System.String" Required="true" />
    </ParameterGroup>
    <Task>
      <Reference Include="System.Xml" />
      <Reference Include="System.Xml.Linq" />
      <Code Type="Class" Language="cs"
          Source="..\Build.Utilities\GenerateOpf.cs" />
    </Task>
  </UsingTask>
  <Target Name="OpfGenerate" BeforeTargets="Build">
    <GenerateOpf 
         Metadata="Metadata.xml" 
         Contents="@(Content)" 
         OutputFile="MSBuildBook.opf" />
  </Target>

dublin core なメタデータだけ Metadata.xml に書けば、こんな opf が吐かれる。

<?xml version="1.0" encoding="utf-8"?>
<package unique-identifier="uid">
  <metadata>
    <dc-metadata xmlns:dc="http://purl.org/metadata/dublin_core" xmlns:oebpackage="http://openebook.org/namespaces/oeb-package/1.0/">
      <dc:Title>msbuild をこねる本</dc:Title>
      <dc:Language>en-us</dc:Language>
      <dc:Creator>kazuk</dc:Creator>
      <dc:Description>msbuildをこねてビルドの自動化とかを色々カスタマイズする本です</dc:Description>
      <dc:Date>26/03/2013</dc:Date>
    </dc-metadata>
    <x-metadata>
      <output encoding="utf-8" content-type="text/x-oeb1-document" />
      <EmbeddedCover>images/Cover.jpg</EmbeddedCover>
    </x-metadata>
  </metadata>
  <manifest>
    <item id="toc" media-type="application/x-dtbncx+xml" href="toc.ncx" />
    <item id="index" media-type="text/x-oeb1-document" href="index.html" />
    <item id="MSBuildProjects" media-type="text/x-oeb1-document" href="MSBuildProjects.html" />
    <item id="VSAndMSBuild" media-type="text/x-oeb1-document" href="VSAndMSBuild.html" />
  </manifest>
  <spine toc="toc" />
  <guide />
</package>

html の列挙とかは csproj にある Content のリストからやってるだけだから Web アプリケーションプロジェクトに HTML ページを突っ込んで行けば自動的に opf に組み込まれるので手間がかからない。

ncx も吐いてやろうか!

似たような方法を取れば ncx も吐けます。目次の順序とかは各HTMLからキー要素をなんか抽出してあげるとかすれば良いので手間は似たようなもんでしょう。

まとめ

今日のところはこんな感じ、普通に VS でHTMLを書いてビルドすれば kindle で読める mobi を生成するプロジェクトを作る事ができました。

多分 VS Express でも同じ事はできるはずなんで、無料のExpress でHTMLシコシコ書けば普通に kindle 本を書くのはできそうです。画像とかそういうのを使ったときにどうなるとか、その辺解ったら続編を書くかもしれません。

こんな事はだれもやらねーよかもしれませんけど、VS に markdown から HTML への変換のカスタムツールとか仕込めば、markdown とかでジャカジャカ書いたものを電子書籍にするのも簡単かもしれませんね。

まとめではないこと

自分が本書いて amazon の direct publishing 出したら誰か買いますか?>買うわけないよね!

広告

Visual Studio 2012 でのパフォーマンスプロファイリング


どーも、どーも、どーもです。

Visual Studio Advent Calender 2012 が中々枠が埋まり切らないという悲鳴が上がってたので、参加しましたです。

というわけで、表題通りパフォーマンスプロファイリングについて書かせてもらいます。

Visual Studio でのパフォーマンスプロファイリング機能は Visual Studio 2010 までは Premium 以上で有効、Visual Studio 2012 では Professional 以降で有効という事で、Visual Studio の上位エディション機能がやっと普通のエディションで使えるようになったという事になります。

というわけで、プロファイリングによってなんかしらプログラムのボトルネックが検出できるかという事で緩く募集したら msgpack-cli をプロファイリングしてみてほしいという事で試してみました。

https://twitter.com/kazuk/status/282008544600023040

Visual Studio でのパフォーマンスプロファイリングの開始方法

テストプロジェクトがあれば、テストの実行中にプロファイリングを行う事ができます。「テスト」メニュー「テストのプロファイル」ただし、プロファイリングがストアアプリのプロファイリングにまだ対応していないので msgpack-cli に対して即座に試す事はできませんでした。実際問題としてパフォーマンス問題を抱えたところをテストとして作りこむとCIサーバとかでスローテストになって邪魔になるのでお勧めしませんです。

image

コンソールアプリケーションを含む一般的な実行可能なものであればプロファイルは可能ですので、今回はテストの中で[Explicit]で除外されてる物から TestStringMedium を選び、コンソールアプリケーションに同等物をこさえてみました。

現物ソースはこちら。

https://gist.github.com/4357661

最初オリジナルのテストを数1000回実行するようにしたのですが、結果的にテスト側での StringBuilder での文字列構築がボトルネックとなったので、一回構築した文字列に1万回のシリアライズ、デシリアライズを行う様にしてみました。

このコンソールアプリケーションをスタートアッププロジェクトに指定して、パフォーマンスプロファイラを起動します。

「分析」メニューから「パフォーマンスウィザードの起動」でプロファイリング方法などの設定を行いながらプロファイリングを開始します。

imageimage

初期状態として「CPUサンプリング」がプロファイリングの方法として選択されています。このプロファイリングでは一定時間毎に実行スレッドを止めスタックトレースを取って何を実行しているかを記録する方法が使われます。なので相当に実行時間のかかる処理以外はプロファイリングできませんし、ほとんど実行時間を使わない部分については結果に現れません。

性能のボトルネックがどこなのかを把握するのに最も適したプロファイリング方法になります。次にプロファイル対象プロジェクトの選択になります。自分で用意したプロジェクトを指定して次へ進みます。

imageimage

完了時にプロファイルを開始するを指定していればパフォーマンステストが開始されます。

image

相当しつこく実行するようにしているのでプログラムは中々終わりません、定常実行状態になって1分ちょいでも実行すれば十分なサンプルが集まりますのでコンソールアプリケーションの実行ウィンドウを閉じて終了させます。

分析していますの表示後に以下のような分析結果が表示されます。

image

最初に表示されているのは CPU 使用率のグラフで、このテストの間 1 Core をほぼ占有してプログラムは動作していた事になります。次にホットパスとしてサンプリング結果が示していた関数が最も多く実行していた関数が表示されています。

92.4% のCPU時間が TestStringMedium で消費されているのでプロファイリングとしてはターゲットをちゃんと実行している事になります。TestString は実際に Pack/Unpack を行っていますが、実際にはPackの方が 59% を占有、Unpackは8%弱しか使っていない事が解ります。

imageimage

デシリアライズの性能改善が可能であるかという話なので、少ない側ですがUnpack に降りていきましょう。

imageimageimageimageimageimageimageimage

単純に呼び出し先が mscorlib とかマングリングされたネイティブメソッドになるまで降りた結果としては上記の流れになります。

8メソッドにわたって降りて行きましたが最終的には new byte[] と Stream.Read に処理時間が使われている事がこれでわかります。ここの時間消費を削るにはどんな方法があるでしょうか。 new byte[] を削るにはバッファ管理を入れて同一の長さのバッファを再利用する等の方策もあったりしますけど、上位の構造を大きくいじらない限りには無理でしょう。Stream.Read の処理時間を削る方法は多分無いと思います。メモリストリームからの読み出しですから単純にバイト列のコピーに時間を取られているはずです。

実際のところとして new byte[] は 0初期化を含んでいます、0で初期化したうえで Stream.Read で内容をすべて上書きするのはちょっと無駄と言えば無駄です、しかしこれをプログラミング的に避ける事はできません。

さて Stream からバイト列を読む方法にはもう一つあり BinaryReader.ReadBytes があります。これを使った場合どうなるでしょう。

image

結果的には若干のコスト増になってしまいました、BinaryReader を new しているところで 0.3%の増加が最も大きく響いています、before / after で見ると new byte[] と Stream.Read  で 8% だった物が BinaryReader.ReadBytes の呼び出しで 8.1% と若干の増加も見られます。失敗と見ていいでしょう。 mscorlib に対しては NGen で結構厳しく最適化かけているという噂もなかったりしますけど、 BinaryReader.ReadBytes にはその効果は及んでいないと見て良いでしょう。

imageimage

実際 BinaryReader.ReadBytes の IL を眺めてみても Stream.Read で上書きされる事が明らかな配列の初期化を省略するような確保方法は行われていませんから順当な結果です。

それでも自分は変質的なスピード狂だったりする

Stream.Read には改善の余地はなくとも、バッファの確保と0初期化を避けるためにできる事はいくらかあったりします。

というわけでバッファの管理と再利用を実装してバッファを使いまわしてあげましょう。

人様のライブラリに勝手に API を追加しちゃいます。 MessagePackObject に Release メソッドを追加

        public void Release()
        {
            if (this._handleOrTypeCode is MessagePackString)
            {
                var packString = this._handleOrTypeCode as MessagePackString;
                packString.Release();
            }

        }

MessagePackString に Release と Alloc を追加してバッファを再利用できるようにします。

        static Dictionary<int, Stack<byte[]>> _buffers = new Dictionary<int, Stack<byte[]>>();

        public void Release()
        {
            Stack<byte[]> bufferStack;
            if (_buffers.TryGetValue(this._encoded.Length,out bufferStack))
            {
                bufferStack.Push(this._encoded);
            }
            else
            {
                _buffers.Add(this._encoded.Length, new Stack<byte[]>( new[] { this._encoded }));
            }
        }

        public static byte[] Allocate(int length)
        {
            Stack<byte[]> bufferStack;
            return _buffers.TryGetValue(length, out bufferStack) 
                ? bufferStack.Pop() 
                : new byte[length];
        }

テストコード側から Release を呼ぶようにして、バッファの再利用を促すのと同時に読み込みコードで Allocate によってバッファを確保するようにしてプロファイルした結果は以下。

image

単純比較はできないかもしれませんが、3割強の性能改善が実現できました。(まぁ、バッファの管理系はシングルスレッドだから動いてるレベルの実装なのでマルチスレッドに対応するともっと性能は下がってしまうかもしれません。)

まとめ

無理やりチューニングすれば早くできるところは多少なりともあったりしますが API 変わってしまうとそれを使っている所は作り直しになってしまいます。だから早いうちから API の利用者が増えないうちに、常にプロファイリングを取り続けてチューニングしましょう。パフォーマンス問題がAPI変更に波及すると出来上がってからの性能改善、チューニングはまず無理です。

まぁ、アプリケーションの枝葉末端はアプリケーションへの性能影響は殆ど無いのも事実ですので、アプリケーションへの性能影響の無い末端はチューニングする必要もありませんので、このコードがシステムの性能を支えている物なのかどうなのか、そこを把握するためにもプロファイリングは行ってくださいね。

今回は CPU サンプリングによる CPU 時間を元にチューニングをしましたが、他のプロファイリング方法も重要といえば重要です。システムがメモリ枯渇に陥っているなど GC が頻繁に活動して性能が下がっている等状況に応じて使い分けていきましょう。

Azure and Visual Studio 2012 : サーバーエクスプローラーでつなぐ


新ポータルで表示まわりが変わりーの、Visual Studio 2012 で設定まわりが変わりーのでわけわかめなのでメモ

Visual Studio のサーバーエクスプローラーで Azure Storage を見るための設定など。

VS側のサーバーエクスプローラーでAdd New Storage Account でアカウントを登録するダイアログを出す。

image

image

ポータル上の表示は Account name はストレージの表示名(作成時の URL の欄で入力したもの)、Account key は Access Key を入れる。

imageimage

まとめ

用語が揺れ揺れすぎてもう… Azure でアカウントと言ったら請求関連のあれのどっかかなーとか、そういう類推は効きません。ってことが必要知識です!

BUILD ネタという事で – C# Advent Calender 12/9


//BUILD/ と思わせておいて msbuild と Visual Studio のネタ。

という訳で、「Hay U! 思い通りにビルドをコントロールできてるかい?」

うちの Blog はたびたび msbuild のネタを取り上げるという事で、生粋のビルドヲタクである事は読者の皆様にはバレバレだと思いますが、C# Advent Calender に参加して、ビルドヲタたるものこれは知っとくべきとか、こんぐらい当然なのよっていう物を世間に知らしめておきたいかなと。

msbuild の基礎の基礎(1) 処理対象の項目一覧 ItemGroup とアイテム

msbuild は処理対象の項目の一覧を ItemGroup として定義します。シンプルに csproj の中でファイルやアセンブリ参照が ItemGroup として定義されています。

VIsual Studio でクラスライブラリのプロジェクトを作り、デフォルトである Class1 に加えてClass2,Class3を作った状態の csproj には以下の2つのItemGroupが定義されています。

<ItemGroup>
<
Reference Include=System/>
<
Reference Include=System.Core/>
<
Reference Include=System.Xml.Linq/>
<
Reference Include=System.Data.DataSetExtensions/>
<
Reference Include=Microsoft.CSharp/>
<
Reference Include=System.Data/>
<
Reference Include=System.Xml/>
</
ItemGroup>
<
ItemGroup>
<
Compile Include=Class1.cs/>
<
Compile Include=Class2.cs/>
<
Compile Include=Class3.cs/>
<
Compile Include=Properties\AssemblyInfo.cs/>
</
ItemGroup>

ソースファイルは Compile Itemが作成されていて、参照設定はReference になっていますね。

この Compile/Reference は最終的に Import されている Microsoft.CSharp.Targets を介して csc タスク(C# Compiler Task)に引き渡されてビルドされるというわけ。

単純にコンパイルするソースであれば Compile で単純に足せばよいという事ですが、コンパイル出来ればビルドとか素人の考え、色々な処理の仕方に応じた分類はいくらでも増やしたいものです。

Web やってれば画像をなんらか処理したり、cssやjsをminify したり色々やる事があるでしょう、その他やっている事に応じて色々やりたいことなんて幾らでもあるでしょう。

やる事を増やすには Target / Task を新たに定義してって事になるのですが、処理対象としてはこの ItemGroup を種類で抽出して受け取る事になるので結局は種類を増やす以外は有りません。

単純に Visual Studio でテキストファイルを追加すると種別は Content として入ります。

<ItemGroup>
<
Reference Include=System/>
<
Reference Include=System.Core/>
<
Reference Include=System.Xml.Linq/>
<
Reference Include=System.Data.DataSetExtensions/>
<
Reference Include=Microsoft.CSharp/>
<
Reference Include=System.Data/>
<
Reference Include=System.Xml/>
</
ItemGroup>
<
ItemGroup>
<
Compile Include=Class1.cs/>
<
Compile Include=Class2.cs/>
<
Compile Include=Class3.cs/>
<
Compile Include=Properties\AssemblyInfo.cs/>
</
ItemGroup>
<
ItemGroup>
<
Content Include=TextFile1.txt/>
</
ItemGroup>


この状態のソリューションエクスプローラーとプロパティは以下の通り。

image

このプロパティでのビルドアクションを「なし」に変えると ItemGroup 定義は以下の様になります。

<ItemGroup>
<
None Include=TextFile1.txt/>
</
ItemGroup>

これで ItemGroup の種別が ビルドアクションで選択される事はみなさんの知識になりました。

要するに Visual Studio のファイルプロパティでビルドアクションのリストに必要な ItemGroup 種別を足すことができればテキストなりXMLなりを足してそれに必要な ItemGroup 種別を割り当てる事ができ、それに応じた処理をさせる事ができます。

ここで特殊な ItemGroup を csproj に追加します。

<ItemGroup>
<
AvailableItemName Include=TextToBitmap>
<
Visible>false</Visible>
</
AvailableItemName>
</
ItemGroup>
<
ItemGroup>
<
TextToBitmap Include=TextFile1.txt/>
</
ItemGroup>

AvailableItemName という ItemGroup にTextToBitmap をVisibleメタデータをfalse で追加しました、ここで追加したTextToBitmap を TextFile1.txt に割り当てています。このファイルのプロパティと、ビルドアクションのドロップダウン内容が以下になります。

imageimage

AvailableItemName で指定した項目がビルドアクションのドロップダウンに追加されています、他のクラスとかにも TextToBitmap が選択できるようになったのが確認できるでしょう。

これでビルドの細かい事とか理解していない非ビルドヲタの人にはビルドアクションを指定してねだけで済ませる事ができるわけです。

ItemGroup の操作についてはターゲット内での操作とか含めてこの先もチョロチョロ出てきますが基本中の基本としてはビルド作業の細かい処理に送りつけるファイルを分類する事ができるという事です。

C# Advent Calender なのに C# がここまでで一行も出てこないとか、気にしないで下さい、先の方でちょっとだけ出てきますので。

msbuild の基礎の基礎(2) ターゲット実行順序の制御

ターゲットの構造は Target の属性による入出力ファイルの指定や依存関係によるビルド処理の順序指定と、Target内に記述されたタスクでの処理の定義に分かれます。

標準で Import されている Microsoft.CSharp.Target やその中から Import される Microsoft.Common.Targets の内容を熟知すると立派なビルドヲタなわけですが必要知識が極端に上がるだけで説明の邪魔なので一端 Microsoft.CSharp.Targets の Import をコメントアウトして Build ターゲットを自作してみます。

<ItemGroup>
<
Reference Include=System/>
<
Reference Include=System.Core/>
<
Reference Include=System.Xml.Linq/>
<
Reference Include=System.Data.DataSetExtensions/>
<
Reference Include=Microsoft.CSharp/>
<
Reference Include=System.Data/>
<
Reference Include=System.Xml/>
</
ItemGroup>
<
ItemGroup>
<
Compile Include=Class1.cs/>
<
Compile Include=Class2.cs/>
<
Compile Include=Class3.cs/>
<
Compile Include=Properties\AssemblyInfo.cs/>
</
ItemGroup>
<
ItemGroup>
<
AvailableItemName Include=TextToBitmap>
<
Visible>false</Visible>
</
AvailableItemName>
</
ItemGroup>
<
ItemGroup>
<
TextToBitmap Include=TextFile1.txt/>
</
ItemGroup>
<!–
<Import Project=”$(MSBuildToolsPath)\Microsoft.CSharp.targets” /> —>
<
Target Name=Build>
<
Error Text=まだ作ってないよ/>
</
Target>

Error タスクを使ってメッセージを出して単純に失敗する Build ターゲットを作りました、結果はこの通り。

image

意図通り動いている事が確認できました。まず依存関係での処理順序の制御をしてみましょう。

<!– <Import Project=”$(MSBuildToolsPath)\Microsoft.CSharp.targets” /> —>
<
Target Name=BeforeBuild>
<
Warning Text=BeforeBuild is running/>
</
Target>
<
Target Name=BuildCore>
<
Warning Text=BuildCore is running/>
</
Target>
<
Target Name=AfterBuild>
<
Warning Text=AfterBuild is running/>
</
Target>
<
Target Name=BuildDependsOnTargets=BeforeBuild; BuildCore; AfterBuild>
<
Error Text=まだ作ってないよ/>
</
Target>

DependsOnTargets で各タスクを作成しました、実行結果は以下の通り、今回は出力ウインドウも付けています。出力ウインドウの表示は本当に実行順序設定どおりなのですが、エラー一覧は色々な並べ替えがあるので注意が必要です。エラー一覧の2番目のカラムの数値で並べ替えすると出力順序に一致します。

結果としては DependsOnTargets で指定したターゲットが指定の順番で実行され、各ターゲットの実行が完了した後にDependsOnTargets を指定したターゲットが実行される事が解ります。これでターゲットの実行順序は設定できる事が解ります。

image

msbuild 4より前ではこれが唯一のビルドターゲットの実行順序の制御方法でした、msbuild 4では AfterTargets, BeforeTargets 属性によって既存のターゲットの実行順序の指定の位置でターゲットを実行する事を指定する事ができます。

After – Before という順序で書いたのはアルファベット順などという事でなく、実行順序の関係がそうだという事です。 AfterTargets で指定したターゲットの後、BeforeTargets で指定したターゲットの前で指定を行ったターゲットが実行されます。

以下のターゲット定義を設定する事で BeforeBuild, BuildCore の間で Transform が実行されます。

<Target Name=Transform
AfterTargets=BeforeBuild
BeforeTargets=BuildCore>
<
Warning Text=Transformが割り込んでみた/>
</
Target>

image

この関係を利用すると Microsoft.CSharp.Targets や Microsoft.Common.Targets での定義済みターゲットの実行が行われる間に割り込みが可能である事が解ります。

msbuild の基礎の基礎 (3) ItemGroup の処理、一括処理と順次処理

一度 Build のみの簡単な構成に戻して Task の呼び出しの例を紹介しましょう。

<Target Name=Build>
<
Warning Text=Example1: @(Compile)/>
<
Warning Text=Example2: @(Compile->’%(filename).generated%(extension)’)/>
<
Warning Text=Example3: %(Compile.filename)/>
</
Target>

若干ややこしそうな記述になりましたがこれをビルドすると以下の出力が出ます。

warning : Example1: Class1.cs;Class2.cs;Class3.cs;Properties\AssemblyInfo.cs
warning : Example2: Class1.generated.cs;Class2.generated.cs;Class3.generated.cs;AssemblyInfo.generated.cs
warning : Example3: Class1
warning : Example3: Class2
warning : Example3: Class3
warning : Example3: AssemblyInfo

Example3 のWarning が1行に関わらず4回、各 cs について実行されている事が解ります。

このようにパラメータの与え方によってmsbuild は自動的にタスクの呼び出し方を区別し繰り返し制御を実行します。

@(項目種別) で指定した場合、項目の集合がそのまま渡されます。

@(項目種別->’射影定義’) で指定した場合、項目の集合が射影されて項目集合として渡されます。(集合の変換)

%(項目種別 .参照するメタデータ) と記述すると参照するメタデータを参照する項目定義を要素に分割して繰り返しタスクないしはターゲットに渡します。

これによりにファイルを個別に処理する方法と複数のファイルを一括してタスク処理する方法を理解した事になります。

しかし標準タスクでできる事なんてたかが知れてるぜっていうビルドヲタの人は次のステップへ進みましょう。

msbuild ビルドヲタの第一歩 msbuild inline タスク

やっと C# Advent Calender らしい内容になりますよー、C#コードで msbuild のビルド処理で使われるタスクを記述する事にします。

まずは普通の inline タスクです。

<UsingTask TaskName=SampleTask
TaskFactory=CodeTaskFactory
AssemblyFile=$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll>
<
ParameterGroup />
<
Task>
<Code Type=FragmentLanguage=cs>
<![CDATA[
Log.LogWarning(“this is inline task”);
]]>
</
Code>
</
Task>
</
UsingTask>
<
Target Name=Build>
<
SampleTask />
</
Target>

Type属性で Fragment を指定した場合 Taskクラスの派生クラスの Execute メソッドの内部を記述する事になります。上記例では TaskクラスのLogプロパティを介して Warning メッセージを出力しています。

image

簡単に言えば Execute メソッド内のコードとして有効なコードであれば基本的には何でも書けます。 メソッドの分割が必要なレベルの複雑なコードでも Func<>, Action<> デリゲートとラムダで上手くやれば何とでもなるのが C# の素晴らしさ、記述規模さえ気にしなければどうにでもなります。

タスクにパラメータが必要であれば ParameterGroup で指定すれば良く、外部アセンブリを参照する事も using による名前空間参照も指定できますので必要であれば LINQ を使う事もできますし、XML処理をXLINQでも簡単です。

ゆとる msbuild ビルドヲタ msbuild inline タスクのソースファイルオプション

ドキュメントには Source 指定ができるとしか書いてなく、例も何にもなくという酷い扱いのSource 指定でのカスタムタスク定義を紹介しましょう。

Source オプションを使う場合にはソースを収容するクラスライブラリプロジェクトを用意すると便利です、自分は半分お約束的に Build.Utilities という名前のクラスライブラリをソリューションのトップに用意しています。

最も簡単な例として単にWarningを吐くタスクから。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Build.Utilities;

namespace Build.Utilities
{
    public class SourceBasedCustomTask : Task {
        public override bool Execute()
        {
            Log.LogWarning("this is Source based custom task");
            return true;
        }
    }
}

これを読み込む csproj 側の記述は以下の通り。

<UsingTask TaskName=SourceBasedCustomTask
TaskFactory=CodeTaskFactory
AssemblyFile=$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll>
<
ParameterGroup />
<
Task>
<
Code
Type=Class
Language=cs
Source=..\Build.Utilities\SourceBasedCustomTask.cs/>
</
Task>
</
UsingTask>

なにがゆとれているか、そうです、インテリセンスです。作成したBuild.Utilitiesプロジェクトで、Microsoft.Build.FrameworkとMicrosoft.Build.Utilities.v4.0を参照する事で普通のコードを書いているのと同様の完全な Visual Studio 支援下でカスタムタスクを書く事が出来ています。

image

Source の位置指定は該当プロジェクトファイルからの相対となりますのでソース管理にそのまま突っ込んであげれば、ビルドサーバだったり同僚のマシンでも普通に動きます。

msbuild のインラインタスクが出る前はビルドを細かく制御したいというビルドヲタクをするには一つ厄介な問題がありました、カスタムタスクアセンブリの配布です、カスタムタスクアセンブリは csproj と共に共同して開発を行う同僚やCI ビルドサーバへ配布する必要がありました。ソリューションの中にバイナリを入れてしまえば良い、それは確かにそうなのですが、そのバイナリを更新する方法や手段は手動?ビルドヲタクに手動でなんかやれって言って素直にやると思って?

これらの問題は単純にソースでタスクを渡せる事で一発解消です、ソース管理にソースを入れるという単純な事で何とでもなってしまいます。

さて、ここまでの内容をもってすれば、ビルド時に少なくともC#で書ける事であれば何でもでき、それをVisual Studioの支援下で記述できるというのは納得してもらえるでしょうか。

出来るのは解ったけど性能は?:インクリメンタルビルドのサポート

はい、ビルドでは性能重要です。ちょっと変な事するとあっというまに毎回コンパイルが走り、依存しているプロジェクトすべてが毎回ビルドされるようになってしまいます。

そうなると開発作業の大半がビルド待ちにもっていかれるわけで、ビルド時にやってくれる事の価値によってはぶっちゃけ邪魔に感じる事でしょう。

出来るのは良い、本当に必要な時だけ実行されるならもっと良いという訳で、ファイルの更新状況に応じてそれをやるかやらないか制御されなければなりません。

msbuildはもちろんインクリメンタルビルドをサポートしていて、ターゲットの Inputs / Outputs 属性でこれが制御されます。

制御ルールとしては単純で、Inputs に記述されたファイルより Outputs に記述されたファイルが新しければターゲットの実行は省略されます。

Inputs / Outputs それぞれは@(項目グループ)で ItemGroup を受ける事が出来ますので、複数のファイルの更新関係を元にターゲットのそれぞれを実行するかを制御できるという事になります。

まとめ

csproj を開いたことないとか言う人は悔い改めてちょうだい。

[VS Tips] WMI クラスをサーバーエクスプローラーで一括削除する方法


WMI 便利ですよね。 クラス追加して、WebManagement 配下とか一気に取り込んで必要な物をマネージクラスの生成すればすぐにそれを操作したり、情報とるプログラムが書けます。

imageimage

imageimage

これすると当然にサーバーマネージャの管理クラス配下はすさまじく大量なクラスであふれる事になりまする。

これを消すのには…「クラスの追加」のダイアログで一気に選んで「削除」です。

追加ダイアログでやるとは中々思わないので Tips って事でした。

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を求める人は挑戦する価値はある素材だと思います。

NuGet パッケージプロジェクトテンプレート


日本最速?

ってわけで NuGet パッケージを作るためのプロジェクトテンプレートをインストールしてくれる NuGet パッケージを作ってNuGet.org に放り込んでみました。

インストールスクリプトを使ってるんでコンソールから入れろって言われます。

image

おとなしくコンソールから行ってください(お願いします)。

イカ、コンソールでのコマンドとレスポンスでゲソ。

以下と変換しようとしたらイカが出たので侵略されたゲソ。太字が入力ゲソ。

PM> Get-Help NuGet
TOPIC
    about_NuGet
   
SHORT DESCRIPTION
    Provides information about NuGet Package Manager commands.
          
LONG DESCRIPTION
    This topic describes the NuGet Package Manager commands. NuGet is an integrated package
management tool for adding libraries and tools to .NET projects.

                
The following NuGet Cmdlets for are included.

Cmdlet Description
—————— ———————————————-
Get-Package Lists the set of packages available from the package source.

List-Package An alias for Get-Package. This is the more widely used command
for listing packages.

Install-Package Installs a package and its dependencies into the project.

Uninstall-Package Uninstalls a package. If other packages depend on this package,
the command will fail unless the –Force option is specified.

Update-Package Updates a package and its dependencies to a newer version.

New-Package Creates a new package when supplied with a Nuspec package specification file.

Add-BindingRedirects Examines all assemblies within the output path for a project and adds binding
redirects to the application (or web) configuration file where necessary.
                           
Get-Project Returns a reference to the DTE (Development Tools Environment) for the active
or specified project.

SEE ALSO
    Online documentation: http://go.microsoft.com/fwlink/?LinkID=206619
    Get-Package
    Install-Package
    Uninstall-Package
    Update-Package
    New-Package
PM>

dir ‘C:\Users\kikuchi\Documents\Visual Studio 2010\Templates\ProjectTemplates\Visual C#’
PM> List-Package -Remote -Filter NuGet

Id                                    Version                               Description                         
—                                    ——-                               ———–                         
NuGet.CommandLine                     1.0.11220.26                          NuGet command line tool used to cr…
NuGet.CommandLine                     1.1.2113.118                          NuGet command line tool used to cr…
NuGet.CommandLine                     1.1.2120.134                          NuGet command line tool used to cr…
NuGet.CommandLine                     1.1.2120.136                          NuGet command line tool used to cr…
NuGet.CommandLine                     1.1.2121.140                          NuGet command line tool used to cr…
Nuget.Core                            1.0.1120.104                          NuGet.Core is the core framework a…
NuGetPackageProject                   0.1.0                                 Customized build for NuGet pack.    
WebActivator                          1.0.0.0                               A NuGet package that allows other …

 

PM> Install-Package NuGetPackageProject
Successfully installed ‘NuGetPackageProject 0.1.0’
Successfully added ‘NuGetPackageProject 0.1.0’ to MvcApplication1
Installing Project Template! not executing now
PM> dir ‘C:\Users\kikuchi\Documents\Visual Studio 2010\Templates\ProjectTemplates\Visual C#’

    ディレクトリ: C:\Users\kikuchi\Documents\Visual Studio 2010\Templates\ProjectTemplates\Visual C#

Mode                LastWriteTime     Length Name                                                               
—-                ————-     —— —-                                                               
-a—        2011/01/21     17:52     103760 NuGetPackageProject.zip                                            

PM>

ってわけで、個人の ProjectTemplates に NuGetPackageProject.zip が追加されてるんで後はプロジェクトを追加する事の NuGetPackageProject を選ぶ。

image

NuGetPackage.tt が現れるので編集して説明その他を足す。

image

ソリューションエクスプローラーで適当にファイルを放り込むなりなんなりしてから華麗にビルド

image

ビルド成功ってわけ。

bin\Debug bin\Release に nupkg ができるので、nuget.org にポイするなり、社内のローカルパッケージリポジトリに放り込むと作業完了ですね。

ってわけで、このパッケージもこのパッケージで作られておりますな自己再帰的関係をむにゃむにゃやってみました。

パッケージ開発の注意事項

Install.ps1 / Uninstall.ps1 は Contents が空だと動きません。(これで結構はまりました。)

ローカルリポジトリを使ってインストール動作等は確認しましょう。

ディレクトリ構造はかなり決め打ちされてますので NuGet のドキュメントをよく読みましょう。

インストールスクリプトを使うと UI でのインストールはできなくなります。

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


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

image

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

image

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

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

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

MsBuild 4.0の新機能でビルド時コード生成とか


MsBuild 4.0 には結構良い新機能が入っております。

ここんとこツイッターでモニョモニョ言ってる MsBuild インラインタスクとか。

MsBuild ターゲット

  • RunBeforeTargets および RunAfterTargets (MSBuild 4.0)

(原文ママ、スキーマ見た感じでは BeforeTargets / AfterTargets が正しい)

これを使って TFS ビルド中にバージョンを埋めるタスクを作りましたのでおすそ分け

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build"
         xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask
      TaskName="CreateAssemblyVersionSource"
      TaskFactory="CodeTaskFactory"
      AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
    <ParameterGroup>
      <Path ParameterType="System.String" Required="true" />
      <VersionAndRevision ParameterType="System.String" Required="true" />
      <BuildUri ParameterType="System.String" Required="true"  />
    </ParameterGroup>
    <Task>
      <Code Type="Fragment" Language="cs">
<![CDATA[
        string buildNumber = new Uri( BuildUri ).Segments.Last();

        Log.LogMessage( "CreateAssemblyVersionSource Path: {0}",Path );
        StringBuilder sb = new StringBuilder();
        sb.AppendLine( "using System;" );
        sb.AppendLine( "using System.Reflection;" );
        sb.AppendLine( "using System.Runtime.InteropServices;" );
        sb.AppendLine( "using System.Resources;" );
        sb.AppendFormat( "[assembly: AssemblyVersion(\"{0}.{1}\")]"            ,VersionAndRevision, buildNumber ).AppendLine();
        sb.AppendFormat( "[assembly: AssemblyFileVersion(\"{0}.{1}\")]"            ,VersionAndRevision,buildNumber ).AppendLine();
        File.WriteAllText( Path, sb.ToString() );
]]>
      </Code>
    </Task>
  </UsingTask>

  <Target Name="LabelAssembly" Condition="'$(BuildUri)'!=''"
          AfterTargets="PreBuildEvent" BeforeTargets="CoreCompile"  >
    <Message Text="LabelAssembly target running" />

    <CreateAssemblyVersionSource
        Path="AssemblyVersion.cs"
        VersionAndRevision="$(VersionAndRevision)"
        BuildUri="$(BuildUri)" />

    <ItemGroup>
      <Compile Include="AssemblyVersion.cs" />
    </ItemGroup>

  </Target>
</Project>

やっている事としては結構単純で、 File.WriteAllText で cs ファイルを吐きだす事の AssemblyVersion / AssemblyFileVersion アセンブリ属性を。

それを Compile ItemGroup にInclueしてるだけです。

TFSビルド時には BuildUri にビルドの識別Uriがきますので、これがある時だけターゲットを定義するように Condition しており。

AfterTargets/BeforeTargets を使って CoreCompile ターゲットの前に実行される様に設定しています。

AfterTargets/BeforeTargetsによって関連するターゲットには何も変更を入れる必要がなくターゲットを差し込む事ができるというのが素晴らしい所でございます。

<PropertyGroup>

<VersionAndRevision>1.0.0</VersionAndRevision>

</PropertyGroup>

<Import Project=”ターゲットを保存した場所”/>

をcsprojの末尾でやるとTFSでのビルド番号がバージョンに反映されます。

このインラインタスクでやってる StringBuilder での編集をT4でPreprocessorしたテンプレートによって ITask実装しておくと、ビルドシーケンス中でのコード生成はもはやお手の物って訳で、組み合わせ次第で夢が広がりますねーなお話。

MSの中の人には MsBuild 4.0 関連の情報を MSDN Library のメニューにマージして頂きたいとか…。

TIPS: VS2010 でHTMLタグの選択のキーボードショートカットを作る


前回のポストでタグの整形規則を良い感じにしたらデザインビュー上でテキストをパタパタと打っていける便利な環境ができたわけだったりしますけど。

Heading のいくつかとか、pとか、キーボードからショートカットが無いのが非常に残念な今日この頃。ショートカットが無いだけならまだしも単発コマンドも無いんで、キーボードへのマッピングもやりようが無く…

てな訳で、マクロのレコーディングからやってみたら結構さっくりできたので公開。

マクロエクスプローラでこんなモジュールを登録して

Option Strict Off
Option Explicit Off
Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports EnvDTE90a
Imports EnvDTE100
Imports System.Diagnostics

Public Module HtmlEditorExtention
    Sub Para()
        DTE.Commands.Raise(“{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}”, 245, “段落 <p>”, Customout)

    End Sub
    Sub Heading1()
        DTE.Commands.Raise(“{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}”, 245, “見出し 1 <h1>”, Customout)

    End Sub

    Sub Heading2()
        DTE.Commands.Raise(“{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}”, 245, “見出し 2 <h2>”, Customout)

    End Sub

    Sub Heading3()
        DTE.Commands.Raise(“{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}”, 245, “見出し 3 <h3>”, Customout)

    End Sub

    Sub Heading4()
        DTE.Commands.Raise(“{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}”, 245, “見出し 4 <h4>”, Customout)

    End Sub
End Module

ツールのオプションからキーボードでマクロと結合!

20101111191200

どういうマッピングにするかはお好みですが、自分は Word も見出し1~4をCtrl+1~Ctrl+4にしてる人なんで当然に統一して登録。(標準段落はCtrl+スペースです)

アウトライン的なタグとかで良く選ぶタグはショートカットがあると素晴らしく便利ですよと。

HTMLがきれいにできるのとそれをパタパタ効率よくできるって面でこれでVS最強であります。

Expression Webが残念な件については触れないでおこう。