for WPF developers
Home Profile Tips 全記事一覧

モダンなインストーラへの道 2 ~ msi インストーラパッケージを生成する ~

(2017/03/24 9:21:06 created.)

(2017/05/24 16:54:49 modified.)

前回はインストーラ開発のためのツールセット WiX をインストールしました。
今回はこの WiX を使って実際に msi インストーラパッケージを生成します。
その前に、インストーラでインストールさせるものが何もないとやりにくいので、
サンプルとして WPF アプリケーションを先に作成しましょう。

早速 Visual Studio で WPF アプリケーションのプロジェクトを作成します。
ここでは SampleWpfApplication という名前のプロジェクトにしています。


このアプリケーションがメインではないので特に触る必要はありませんが、せっかくなので外部 DLL を参照させ、exe ファイル単体では動作しないようにしておきます。
ここでは私が作成したコントロールライブラリ YKToolkit.Controls を使用します。


このライブラリを使用するために、
MainWindow.xaml および MainWindow.xaml.cs を次のように書き換えます。

MainWindow.xaml.cs
  1. namespace SampleWpfApplication
  2. {
  3.     using YKToolkit.Controls;
  4.  
  5.     /// <summary>
  6.     /// MainWindow.xaml の相互作用ロジック
  7.     /// </summary>
  8.     public partial class MainWindow : Window
  9.     {
  10.         public MainWindow()
  11.         {
  12.             InitializeComponent();
  13.         }
  14.     }
  15. }
MainWindow.xaml
  1. <YK:Window x:Class="SampleWpfApplication.MainWindow"
  2.            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.            xmlns:YK="clr-namespace:YKToolkit.Controls;assembly=YKToolkit.Controls"
  5.            Title="MainWindow" Height="100" Width="200">
  6.     <Grid>
  7.         <TextBlock Text="Sample!" TextAlignment="Center" VerticalAlignment="Center" />
  8.     </Grid>
  9. </YK:Window>

実行すると次のようなウィンドウに仕上がります。


それではこの WPF アプリケーションをインストールするためのインストーラを作成しましょう。WiX の Setup Project を新しいプロジェクトとして同じソリューションに追加します。ここでは Installer という名前のプロジェクトにしています。


追加すると Product.wxs というファイルだけを持つプロジェクトが追加されます。


このファイルを開いてみると、次のように XML による雛形のコードが書かれています。WiX はその名の通り、XML コードをコンパイル、ビルドすることでインストーラパッケージを生成します。

Product.wxs
  1. xml version="1.0" encoding="UTF-8"?>
  2. <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  3.   <Product Id="*" Name="Installer" Language="1033" Version="1.0.0.0" Manufacturer="Microsoft" UpgradeCode="293e235a-da12-4ce3-a998-85deeb52698b">
  4.     <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
  5.  
  6.     <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
  7.     <MediaTemplate />
  8.  
  9.     <Feature Id="ProductFeature" Title="Installer" Level="1">
  10.       <ComponentGroupRef Id="ProductComponents" />
  11.     </Feature>
  12.   </Product>
  13.  
  14.   <Fragment>
  15.     <Directory Id="TARGETDIR" Name="SourceDir">
  16.       <Directory Id="ProgramFilesFolder">
  17.         <Directory Id="INSTALLFOLDER" Name="Installer" />
  18.       </Directory>
  19.     </Directory>
  20.   </Fragment>
  21.  
  22.   <Fragment>
  23.     <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
  24.       <!-- TODO: Remove the comments around this Component element and the ComponentRef below in order to add resources to this installer. -->
  25.       <!-- <Component Id="ProductComponent"> -->
  26.         <!-- TODO: Insert files, registry keys, and other resources here. -->
  27.       <!-- </Component> -->
  28.     </ComponentGroup>
  29.   </Fragment>
  30. </Wix>

何やら色々とありますが、とりあえずコメントの TODO にあるように、ComponentGroup 要素の中の Component 要素のコメントを外し、その中にインストールさせたいファイルを追加しましょう。
Component 要素に対してファイルを指定するときは File 要素を使用します。

Product.wxs
  1. xml version="1.0" encoding="UTF-8"?>
  2. <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  3.   <Product Id="*" Name="Installer" Language="1033" Version="1.0.0.0" Manufacturer="Microsoft" UpgradeCode="293e235a-da12-4ce3-a998-85deeb52698b">
  4.     <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
  5.  
  6.     <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
  7.     <MediaTemplate />
  8.  
  9.     <Feature Id="ProductFeature" Title="Installer" Level="1">
  10.       <ComponentGroupRef Id="ProductComponents" />
  11.     </Feature>
  12.   </Product>
  13.  
  14.   <Fragment>
  15.     <Directory Id="TARGETDIR" Name="SourceDir">
  16.       <Directory Id="ProgramFilesFolder">
  17.         <Directory Id="INSTALLFOLDER" Name="Installer" />
  18.       </Directory>
  19.     </Directory>
  20.   </Fragment>
  21.  
  22.   <Fragment>
  23.     <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
  24.        <Component Id="SampleWpfApplication.exe" Guid="E30C960A-5D63-47E6-BBD7-25DA2F6B7901">
  25.          <File Source="../SampleWpfApplication/bin/Release/SampleWpfApplication.exe" />
  26.        </Component>
  27.       <Component Id="YKToolkit.Controls.dll" Guid="62E0220E-572C-4589-8B4E-A354FF1F2B70">
  28.         <File Source="../SampleWpfApplication/bin/Release/YKToolkit.Controls.dll" />
  29.       </Component>
  30.     </ComponentGroup>
  31.   </Fragment>
  32. </Wix>

コメント文にはありませんでしたが、Component 要素にはそれぞれ GUID を必ず与える必要があります。
Component 要素は Windows Installer にとって操作単位を表していて、どの操作によってファイルまたはレジストリを追加したかを認識するために GUID を利用しています。
また、バージョンアップ時にはどの操作が必要なのかについてもこの GUID によって識別しているため、同じ GUID を持つ Component 要素は同じファイルを持たなければいけません。
逆に、ファイルが以前のものと異なる場合は Component 要素の GUID を変更する必要があります。
このことから、Component 要素は File 要素をひとつだけ持つようにしたほうが良いでしょう。

File 要素では Source 属性にファイルの実体を絶対パスまたは相対パスで指定します。
ここでは同一ソリューション内にある SampleWpfApplication プロジェクトのビルド出力を参照しています。
ところで、インストーラを作成するということはビルド構成は Release となるはずです。したがって、ここで参照しているファイルは Release フォルダを参照するようにしています。

以上の設定ができたら早速ビルドしてみましょう。このとき、「プロジェクト」→「プロジェクト依存関係」メニューから、Installer プロジェクトが SampleWpfApplication プロジェクトに依存している設定をしておくと、ビルド順序が自動的に設定されるので便利です。
まだ SampleWpfApplication プロジェクトが最新状態でビルドされていないのに、先に Installer プロジェクトがビルドされてしまうことを防止できます。
SampleWpfApplication プロジェクトのビルド出力がクリーンされた状態なら Installer プロジェクトをビルドした時点でエラーが発生するので問題に気付きますが、古い出力ファイルが残っている場合、その古いファイルを参照してしまうことになります。
このようなことを起こさないようにするためにも、プロジェクトの依存関係の設定はしておいたほうが良いでしょう。


それではビルドして出力されたファイルを見てみましょう。


cab ファイルはインストールされるファイル群が圧縮されたものです。msi ファイルが Windows Installer のためのインストーラになります。wixpdb ファイルは通常の pdb ファイルと同じくデバッガが使用するデバッグ情報です。
インストーラを配布するときは cab ファイルと msi ファイルを公開し、wixpdb ファイルはもしものときのために開発元がセットで管理しておけば良いでしょう。

早速 msi ファイルを実行してみると、唐突にインストールが開始されます。ユーザアカウント制御が有効の場合は変更の許可を求められるので、「はい」を選択してください。


もし cab ファイルが同じフォルダにない状態で msi ファイルを実行すると、次のように怒られてしまいますので、インストーラを配布するときは注意しましょう。


インストールが完了したら、Program Files フォルダを確認してみましょう。
SampleWpfApplication.exe がインストールされています。
ただし、フォルダ名が "SampleWpfApplication" ではなく "Installer" になってしまっています!


これはよろしくないですね。実はこの辺の設定を Product.wxs で変更する必要があります。コードを編集する前にとりあえずインストールしてしまったものをアンインストールしましょう。
コントロールパネルから「プログラムと機能」を開いてアンインストールしましょう。


あぁ、こっちも "Installer" になってる…。

アンインストールしたら Product.wxs に戻りましょう。実は 3 行目の Product 要素にある Name 属性を変更する必要があります。

Product.wxs
  1.   <Product Id="*" Name="SampleWpfApplication" Language="1033" Version="1.0.0.0" Manufacturer="YKSoftware" UpgradeCode="293e235a-da12-4ce3-a998-85deeb52698b">

Name 属性が "Installer" となっていたので、これを "SampleWpfApplication" に修正します。また、Manufacturer 属性が "Microsoft" となっていたので、ついでにこれも変更しています。

この修正をしてからビルドし、もう一度 msi ファイルを実行してインストールしてみましょう。


そういえば表示されていた Windows Installer のダイアログのキャプションなども "Installer" だったのが、"SampleWpfApplication" に変わっていますね。

それでは Program Files フォルダを覗いてみましょう。


あれ?やっぱり "Installer" というフォルダ名になっている…。
どうやら設定が足りなかったようです。
もう一度アンインストールしましょう。


お、こちらは変更した通りになっていますね。
Product 要素の属性はあくまでも製品名に関する情報のようです。

Product.wxs のソースをもう一度確認してみましょう。良く見てみると、中ほどに Direcotry 要素があるのがわかります。

Product.wxs
  1.   <Fragment>
  2.     <Directory Id="TARGETDIR" Name="SourceDir">
  3.       <Directory Id="ProgramFilesFolder">
  4.         <Directory Id="INSTALLFOLDER" Name="Installer" />
  5.       </Directory>
  6.     </Directory>
  7.   </Fragment>

実はこの Directory 要素であらかじめフォルダ構成を記述しており、入れ子にすることでそのツリー構造を表現しています。
Id 属性はそれぞれ固有の識別子となっていますが、一部予約語があるようです。
例えば "ProgramFilesFolder" という Id は "C:\Program Files" というようないわゆるデフォルトのプログラムフォルダを指すようになります。
この Id を持つ Directory 要素の子要素として Name="Installer" という値を持つ Directory 要素が設定されています。つまり、"C:\Program Files\Installer" フォルダを表しているわけですね。
そしてこの Directory 要素の Id は "INSTALLFOLDER" となっていることに注目します。この Id がどこで使われているかというと、最初にファイルを指定した Component 要素を含む ComponentGroup 要素で使用されています。

Product.wxs
  1.     <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
  2.        <Component Id="SampleWpfApplication.exe" Guid="E30C960A-5D63-47E6-BBD7-25DA2F6B7901">
  3.          <File Source="../SampleWpfApplication/bin/Release/SampleWpfApplication.exe" />
  4.        </Component>
  5.       <Component Id="YKToolkit.Controls.dll" Guid="62E0220E-572C-4589-8B4E-A354FF1F2B70">
  6.         <File Source="../SampleWpfApplication/bin/Release/YKToolkit.Controls.dll" />
  7.       </Component>
  8.     </ComponentGroup>

つまり、この ComponentGroup 要素に含まれるファイル群は "INSTALLFOLDER" で指定されたフォルダ "C:\Program Files\Installer" にコピーしてくださいと書いてあることになります。
そういうわけで、17 行目の Directory 要素を変更します。

Product.wxs
  1.   <Fragment>
  2.     <Directory Id="TARGETDIR" Name="SourceDir">
  3.       <Directory Id="ProgramFilesFolder">
  4.         <Directory Id="ROOTFOLDER" Name="YKSoftware">
  5.           <Directory Id="INSTALLFOLDER" Name="SampleWpfApplication" />
  6.         </Directory>
  7.       </Directory>
  8.     </Directory>
  9.   </Fragment>

17 行目の Directory 要素の前に Id="ROOTFOLDER" を持つ Directory 要素を追加し、Name 属性を "YKSoftware" というように開発元の名前を指定しています。そしてその Direcotry 要素の子要素として Id="INSTALLFOLDER" を持つ Directory 要素を指定し、その Name 属性を "SampleWpfApplication" としています。

これでようやくほぼ完成です。ビルドして msi ファイルを実行してインストールしてみましょう。


晴れて意図したフォルダにアプリケーションをインストールすることができました!

その他まだ編集していない要素などがありますが、基本的にはデフォルトのままで良いでしょう。
ただし、Product 要素の Language 属性は Windows Installer の言語対応を指定していますが、デフォルトで英語が指定されています。日本語を指定する場合は "1041" という値に修正しましょう。

Product.wxs
  1.   <Product Id="*" Name="SampleWpfApplication" Language="1041" Version="1.0.0.0" Manufacturer="YKSoftware" UpgradeCode="293e235a-da12-4ce3-a998-85deeb52698b">

ビルドし直して msi ファイルを実行してみると、確かに Windows Installer のダイアログが日本語表記になっていることが確認できます。


だいぶ長くなりましたが、今回はここで終了します。
Product.wxs による詳細な設定については下記のサイトが参考になると思います。


次回はカスタム UI によるインストーラを開発するための Bootstrapper Project を紹介します。




ブログ機能の拡張

(2017/03/23 10:04:33 created.)

ブログの機能を少し拡張しました。

  1. Twitter Card の設定追加
  2. Twitter/Facebook ボタンの追加
  3. ページ切替機能の追加

Twitter でここの URL をつぶやいてもサムネイル化されていなかったので、
Twitter Card の設定をしてみました。
ついでに Facebook でシェアした場合もサムネイル化されるようになって良かった。
下記のサイトを参考にしました。

どうせなら Twitter のボタンなんかも追加しちゃえ!ということで、
全記事の末尾に Twitter および Facebook のボタンを追加しました。
これは下記のサイトを参考にしました。

そして今さらですが、ブログの記事のページ切替機能を追加しました。
今まではなんとブログの全記事を表示させているという恐ろしい仕様になっていました。
記事が増えてきたのでようやく重い腰を動かして PHP をコーディングしました。
何にハマったかというと SQL で件数指定の構文を作成するところ。
配列を使ってやれば楽勝じゃんとか思ってたら、
文字列型の配列だということに気付かず、
数値として入れるべき部分に文字列型が入っていたようで、
ずっと SQL サーバからエラーが返ってきて記事がまったく表示されなかった…。
やっぱり PHP のデバッグがきちんとできるように環境構築したほうがいいなぁ。
今はなんとダイレクトに web サーバにアップロードして実機で確認ということをしています。
せめてテストページを用意してそっちでやるか…。

その他、投稿用のページの仕様を改善するなど、いろいろ手を付けました。




モダンなインストーラへの道 1 ~ WiX をインストールする ~

(2017/03/22 13:36:59 created.)

(2017/06/06 12:48:30 modified.)

WPF の登場以来、モダンな外観のアプリケーション開発が容易になり、
これまでのメニューバー + ステータスバーのような
決まった形とは違った雰囲気のアプリケーションが多くなってきました。
しかし、アプリケーション自体がいくらモダンな外観を持っていても、
インストーラが古いダイアログ形式だとあまり面白くありません。
やはりインストーラを起動したときからモダンでありたいですよね。

そこで、インストーラを WPF で開発できないかと探っていたところ、
WiX (うぃっくす:Windows Installer XML) と出会いました。
これは元々 Microsoft が開発していたオープンソースのインストーラ開発用ツールセットで、
インストーラ用の UI を DLL から読み込ませることで
用意にカスタム UI にすることができるそうです。
さらに、Visual Studio に組み込むことができるため、
デスクトップアプリケーションを開発しているプロジェクトと同一の
ソリューション内に含めることができ、開発効率が非常によくなります。

というわけで早速 WiX をインストールしましょう!
WiX はオフィシャルサイトからダウンロードできます。
複数のバージョンがありますが、安定している stable 版の最新版が良いでしょう。
私の場合は V3.10.3 でした。

Firefox でダウンロードしましたが、
なぜかダウンロードをブロックされてしまったので
右クリックからブロックを解除する必要がありました。


ダウンロードした exe ファイルを実行すると、早速モダンなインストーラが姿を表します。
この時点でちょっと期待感が高まります。自分もこんなの作りたい!


"Install" と書かれているボタンを押すとギアの絵が回転し、インストールが始まります。


インストール完了後、Visual Studio を開いて新しいプロジェクトのダイアログを確認してみましょう。
左側ツリーに "Windows Installer XML" というメニューが追加されています。



というわけで今回はここまで。
次回から WiX によるインストーラ開発について説明していきます。




モバイルから記事を投稿してみた

(2017/03/17 23:16:01 created.)

web ページは何度か作成したことがあるけど、モバイルからアクセスして、そのまま更新するとかやったことなかったなー。

キーボードのほうが圧倒的に速いのであんまり使うことはないかもしれないけど、 ちょっとしたことを簡単に投稿できるのはなかなかいいかもしれない。

最近は WiX を使ったインストーラおよびその UI の構築に興味があって調べています。 そのうちここでも構築方法とかをまとめていく予定です。
これをマスターすれば、Visual Studio のようなインストーラが自分でも作れるようになるので、頑張ります。




WPF+C# でプロセスモニタを作ってみた

(2017/03/16 11:15:51 created.)

(2017/05/24 15:17:56 modified.)

System.Diagnostics.PerformanceCounter クラスを使用することで、CPU 使用率やメモリ使用量などのリソース情報を取得できます。
これを利用して下のようなプロセスモニタを作成しました。ソースは GitHub に公開しています。
これから少しずつ機能追加していくつもり。


特定のプロセスを選択することで、そのプロセスのメモリ使用量やページフォルトの頻度などをグラフ化しています。

せっかくなのでこれを作ったときに調べたことをまとめておきます。

Windows のメモリ豆知識

よく耳にするメモリとは、CPU とは別に搭載される RAM のことを指すことが多いと思います。
HDD のような大容量を扱うことはできませんが、アクセス速度は HDD と比べて 10 万~ 100 万倍と非常に高速です。
どういうことかというと、HDD を使ってデータを処理するのに 1 秒かかるとすると、メモリ上で同じ処理をおこなうと 10 万分の 1 秒、つまり 10 マイクロ秒で終わってしまうことになります。
じゃあ HDD なんてやめて全部 RAM にすればいいじゃん!なんてことは誰もが考えることで、
インメモリコンピューティングというシステム構成ではすべてのデータ処理を RAM 上でおこないます。
実際に稼働しているものもあるようですが、RAM は電源を落とすとデータが飛んでしまうので、一般向きではありません。

これ以上は蛇足になるので割愛。 話がそれました。元に戻りましょう。

メモリは CPU とは別に搭載されるので、よく "物理メモリ" とも呼ばれます。これは実際にモノとしてそこにあるメモリ、という意味ですね。なぜ "物理メモリ" という言葉が生まれたかというと、Windows では "仮想メモリ" というものを扱っており、これと区別するためです。

"物理メモリ" は実際にモノとしてある RAM ですが、"仮想メモリ" は HDD 上に割り当てられてメモリと同じ役割をするものです。なぜこのようなものが用意されているかというと、物理メモリは一般的には小容量で節約しながら使わないとすぐに一杯になってしまうからです。
そこで、Windows はページングやスワッピングという仕組みを使って仮想メモリを使い、上手に物理メモリをやりくりしているわけです。
既にお気付きかと思いますが、仮想メモリを頻繁に使用すると処理速度が著しく低下します。仮想メモリが HDD 上にあるからです。仮想メモリに頻繁にアクセスして処理速度が低下する現象のことを "スラッシング" と呼びます。よく「パソコンの処理が重い。メモリが貧弱過ぎる。」という論理展開を見ますが、これはまさに物理メモリが足りなくてスラッシングを起こしている、ということを指しています。

スラッシングを起こさないようにするには次のような対策が挙げられます。

  1. 物理メモリの容量を増設する
  2. そもそも仮想メモリが使われる理由は物理メモリの不足によるものですから、物理メモリが十分に大きければ仮想メモリが頻繁に使われることはなくなります。
    ただし、仮想メモリの使用を前提とするようなアプリケーションもあるため、仮想メモリをまったく使わなくなるようなことはありません。

  3. 仮想メモリを無効にする
  4. 仮想メモリを無効にしてしまえばスラッシングは起きなくなります。
    ただし、物理メモリだけで処理が賄えないとき、アプリケーションがメモリ不足によって不具合を起こし、スラッシング以外で問題が発生するためオススメしません。

  5. HDD を SSD に換装する
  6. 最近は SSD の寿命も延びてきているため、HDD を SSD に換装して、SSD 上に仮想メモリを割り当てることも一般的になってきました。HDD に比べて SSD はアクセス速度が速いため、スラッシングの発生頻度が下がることが期待されます。
    しかし、HDD と比較してアクセス速度が速いとはいえせいぜい 10 ~ 100 倍程度で、物理メモリのアクセス速度には到底およばないため、仮想メモリの使用頻度があまりにも高いと効果は期待できません。

まあ、結局は仮想メモリを使う前提で物理メモリはできるだけ大容量にして、HDD より SSD を使ったほうがいい、と。つまり、スラッシングに関してはお金で解決することが一番、というわけですね。

パフォーマンスカウンタ

C# では System.Diagnostics.PerformanceCounter クラスを使用することで、動作中のプロセスに関する情報などを取得できます。ここで、PerformanceCounter クラスを使用する上で必要な予備知識をまとめます。

PerformanceCounter クラスは 1 つのパフォーマンスカウンタを表します。パフォーマンスカウンタによって様々なリソース情報が取得できますが、1 つのパフォーマンスカウンタで取得できる情報は 1 つのリソース情報のみです。
リソース情報には CPU 使用率やメモリの使用量、各プロセスに関してはそのプロセスがプロセッサを使用した時間の割合や仮想アドレス領域を占有しているサイズなど様々なものがあります。
パフォーマンスカウンタによって得られる情報の種類は非常に数が多いため、これをカテゴリで分類分けして管理されています。このカテゴリを PerformanceCounterCategory クラスで扱います。
パフォーマンスカウンタひとつひとつがどこかのカテゴリに属しているため、パフォーマンスカウンタを使用するときはこのカテゴリを知る必要があります。
登録されているすべてのカテゴリを取得するには PerformanceCounterCategory.GetCategories() メソッドを使います。

すべてのカテゴリを取得する
  1. var categories = PerformanceCounterCategory.GetCategories().OrderBy(x => x.CategoryName);

取得したカテゴリの情報の一部を表にすると次のようになります。


Category CategoryType CategoryHelp
IPv4 SingleInstance IP パフォーマンス オブジェクトには、IP プロトコルを使用して送受信される IP データグラムの速度を計測するカウンターがあります。IP プロトコルのエラーを監視するカウンターがあります。
Memory SingleInstance Memory パフォーマンス オブジェクトには、物理メモリおよび仮想メモリの動作を表示するカウンターがあります。物理メモリはランダム アクセス メモリの領域です。仮想メモリは物理メモリ内とディスク上の領域からなります。メモリのカウンターの多くは、ページング (ディスクと物理メモリの間で起こるコードとデータのページ移動) を監視します。過度なページングによるメモリ不足は、システム処理の遅延の原因となります。
Process MultiInstance Process パフォーマンス オブジェクトには、実行中のプログラムとシステム処理を監視するカウンターがあります。プロセス内のすべてのスレッドは同じアドレス領域を共有し、同じデータへアクセスします。

この他にも多くのカテゴリがあります。私の環境の場合、全部で 137 個のカテゴリがありました。

GetCategories() メソッドで取得した PerformanceCounterCategory クラスは、カテゴリ名の他にカテゴリタイプという情報を持っています。これは、そのカテゴリにインスタンスが 1 つだけか、または複数あるか、という情報です。
例えばメモリに関するパフォーマンスカウンタのカテゴリは "Memory" というカテゴリですが、メモリは 1 つしかないのでインスタンスは 1 つだけです。
これに対してプロセスに関するパフォーマンスカウンタのカテゴリは "Process" というカテゴリですが、プロセスは複数存在するため、インスタンスは複数となります。

「結局どのインスタンスのどのパフォーマンスカウンタを使えばいいの?」ということがまったくわからなかったので、私は全インスタンスの全パフォーマンスカウンタを csv ファイルに落とし込むという荒業を実行しました。
次のコードが実際に使用したものになります。

Program.cs
  1. namespace ConsoleApplication1
  2. {
  3.     using System;
  4.     using System.Diagnostics;
  5.     using System.IO;
  6.     using System.Linq;
  7.     using System.Text;
  8.  
  9.     class Program
  10.     {
  11.         static void Main(string[] args)
  12.         {
  13.             var str = new StringBuilder();
  14.             var categories = PerformanceCounterCategory.GetCategories().OrderBy(x => x.CategoryName);
  15.             foreach (var category in categories)
  16.             {
  17.                 var counters = category.CategoryType == PerformanceCounterCategoryType.SingleInstance ?
  18.                     category.GetCounters() :
  19.                     category.GetInstanceNames().OrderBy(x => x).SelectMany(x => category.InstanceExists(x) ? category.GetCounters(x) : Enumerable.Empty<PerformanceCounter>()).ToArray();
  20.                 foreach (var counter in counters)
  21.                 {
  22.                     var help = "";
  23.                     try
  24.                     {
  25.                         // なぜかそんな名前のカウンタはないよと言われて
  26.                         // InvalidOperationException が発生するものがあるので
  27.                         // とりあえず catch しておく
  28.                         help = counter.CounterHelp;
  29.                     }
  30.                     catch
  31.                     {
  32.                     }
  33.                     str.Append(string.Join(",", new string[] {
  34.                         "\"" + category.CategoryName + "\"",
  35.                         "\"" + counter.InstanceName + "\"",
  36.                         "\"" + counter.CounterName + "\"",
  37.                         "\"" + help + "\"",
  38.                         Environment.NewLine,
  39.                     }));
  40.                 }
  41.             }
  42.  
  43.             using (var writer = new StreamWriter("PerformanceCounters.csv"))
  44.             {
  45.                 writer.Write(str);
  46.             }
  47.         }
  48.     }
  49. }

try しているのに catch で何もしないという禁じ手を使っていますが、csv ファイルに落ちればいいので良しとします。
というか原因がわからない…。メソッドから返ってきたカウンタ名を使って処理してるだけだから、「そんな名前のカウンタは存在しないよ」 とか言われてもこっちとしては「知らんがな」としか言えない…。

何はともあれ、これですべてのインスタンスに対するすべてのパフォーマンスカウンタを把握できます。ただし、場合によっては 30,000 行以上のデータになるので、いきなり Excel とかで開くと Excel がフリーズするかもしれませんのでご注意を。テキストファイルとしてメモ帳などで開いたほうが賢明かもしれません。

C# でプロセスのメモリ使用量を取得する

というわけで、いよいよ本題に入るわけです。

そもそもの発端は、私が使用している firefox というブラウザが妙にメモリを消費している気がしてならなかったため、そのメモリ使用量の時間推移をロギングしたいと思ったことです。

そんなわけで、とりあえず firefox というプロセスのメモリ使用量を監視したい!ということで早速 "Process" カテゴリに "firefox" という名前がいないかどうかを探すわけです。そして次のような 28 個のパフォーマンスカウンタがあることがわかりました。


Counter Help
% Privileged Time プロセスのスレッドが特権モードでコードの実行に費やした経過時間の割合をパーセントで表示します。Windows のシステム サービスは呼び出されると、システム専用データへアクセスするために、しばしば特権モードで実行します。これらのデータはユーザー モードで実行するスレッドからはアクセスされません。システムの呼び出しは明示的に、またはページ フォールトや割り込みのように暗示的に行われる場合があります。以前のオペレーティング システムとは異なり、Windows は従来のユーザー保護および特権モードに加えて、サブシステム保護にプロセス境界を使用します。アプリケーションに代わって Windows が行う処理には、プロセスの Privileged Time に加え、別のサブシステム プロセス内で現れるものもあります。
% Processor Time 該当プロセスのスレッドすべてが、命令を実行するためにプロセッサを使用した経過時間の割合です。命令はコンピューター内の実行の基本ユニット、スレッドは命令を実行するオブジェクト、プロセスはプログラム実行時に作成されるオブジェクトです。任意のハードウェア割り込みやトラップ条件を処理するために実行されるコードもこのカウントに含まれます。
% User Time 該当プロセスのスレッドがユーザー モードでコードを実行するのに費やす時間の割合をパーセントで表示します。アプリケーション、環境サブシステムおよび統合サブシステムはユーザー モードで実行します。ユーザー モードで実行するコードは、Windows の executive、カーネル、デバイス ドライバーの整合性を損ないません。以前のオペレーティング システムとは異なり、Windows は従来のユーザー保護および特権モードに加えて、サブシステム保護にプロセス境界を使用します。アプリケーションに代わって Windows が行う処理には、プロセスの privileged time に加え、別のサブシステム プロセス内で現れるものもあります。
Creating Process ID プロセスを作成したプロセスのプロセス ID です。作成プロセスは終了された可能性があるため、この値は実行プロセスを認識しなくなる場合があります。
Elapsed Time 該当プロセスが実行している総経過時間 (秒) です。
Handle Count 該当プロセスが現在オープンしているハンドルの総数です。この値は、該当プロセス内の各スレッドが現在オープンしているハンドルの合計値に一致します。
ID Process 該当プロセスの一意の識別子です。この番号は再利用され、任意のプロセスを、そのプロセスが終了するまでの間のみ識別します。
IO Data Bytes/sec プロセスが I/O 操作でバイトの読み取りおよび書き込みを実行している率です。このカウンターは、ファイル、ネットワークおよびデバイスの I/O を含むプロセスが生成するすべての I/O 処理状況をカウントします。
IO Data Operations/sec プロセスが読み取りと書き込み I/O 操作を発している率です。このカウンターは、ファイル、ネットワークおよびデバイスの I/O を含むプロセスが生成するすべての I/O 処理状況をカウントします。
IO Other Bytes/sec プロセスが制御操作などのデータを含まない I/O 操作にバイトを発している率です。このカウンターは、ファイル、ネットワークおよびデバイスの I/O を含むプロセスが生成するすべての I/O 処理状況をカウントします。
IO Other Operations/sec プロセスが読み取りおよび書き込み以外の I/O 操作 (制御関数など) を発している率です。このカウンターは、ファイル、ネットワークおよびデバイスの I/O を含むプロセスが生成するすべての I/O 処理状況をカウントします。
IO Read Bytes/sec プロセスが I/O 操作からバイトを読み取っている率です。このカウンターは、ファイル、ネットワークおよびデバイスの I/O を含むプロセスが生成するすべての I/O 処理状況をカウントします。
IO Read Operations/sec プロセスが読み取り I/O 操作を発している率です。このカウンターは、ファイル、ネットワークおよびデバイスの I/O を含むプロセスが生成するすべての I/O 処理状況をカウントします。
IO Write Bytes/sec プロセスが I/O 操作にバイトを書き込んでいる率です。このカウンターは、ファイル、ネットワークおよびデバイスの I/O を含むプロセスが生成するすべての I/O 処理状況をカウントします。
IO Write Operations/sec プロセスが書き込み I/O 操作を発している率です。このカウンターは、ファイル、ネットワークおよびデバイスの I/O を含むプロセスが生成するすべての I/O 処理状況をカウントします。
Page Faults/sec 該当プロセスで実行しているスレッド内でのページ フォールトの発生率です。ページ フォールトは、スレッドがメイン メモリのワーキング セットにない仮想メモリ ページを参照するときに発生します。そのページがスタンバイ リストにあって既にメイン メモリ上にあることになる場合、またはそのページを共有している別のプロセスがそのページを使用中の場合、ページがディスクから取り出されない可能性があります。
Page File Bytes このプロセスがページング ファイルでの使用に予約していた仮想メモリ領域の現在の値をバイト数で表示します。ページング ファイルは、ほかのファイルには含まれないプロセスが使用するメモリのページを格納するのに使用されます。ページング ファイルはすべてのプロセスに共有され、ページング ファイルの領域が不足すると、ほかのプロセスはメモリを割り当てることができなくなります。ページング ファイルがない場合は、物理メモリでの使用に予約していた仮想メモリ領域の現在の値を表示します。
Page File Bytes Peak このプロセスがページング ファイルでの使用に予約していた仮想メモリ領域の最大値をバイト数で表示します。ページング ファイルは、ほかのファイルには含まれないプロセスが使用するメモリのページを格納するのに使用されます。ページング ファイルはすべてのプロセスに共有され、ページング ファイルの領域が不足すると、ほかのプロセスはメモリを割り当てることができなくなります。ページング ファイルがない場合は、物理メモリでの使用に予約していた仮想メモリ領域の最大値を表示します。
Pool Nonpaged Bytes ディスクに書き込まれずに、割り当てられる限り物理メモリ内に存在するオブジェクト用のシステム メモリの領域 (オペレーテイング システムで使用される物理メモリ) である非ページ プールのサイズをバイト数で表示します。Memory\\Pool Nonpaged Bytes と Process\\Pool Nonpaged Bytes は別々に算出されるので、Process\\Pool Nonpaged Bytes\\_Total とは異なる場合があります。このカウンターでは、平均値ではなく最新の監視値のみが表示されます。
Pool Paged Bytes 使用されていないときにディスクに書き込まれることが可能なオブジェクト用のシステム メモリの領域 (オペレーテイング システムで使用される物理メモリ) であるページ プールのサイズをバイト数で表示します。Memory\\Pool Paged Bytes は、Process\\Pool Paged Bytes とは別に算出されるので、Process\\Pool Paged Bytes\\_Total とは異なる場合があります。このカウンターは、平均値ではなく最新の監視値のみを表示します。
Priority Base 該当プロセスの現在の基本優先順位です。プロセス内のスレッドは、そのプロセスの基本優先順位に対応してスレッド自体の基本優先順位を上げ下げします。
Private Bytes 該当プロセスが割り当て、ほかのプロセスと共有できないメモリの現在のサイズをバイト数で表示します。
Thread Count 該当プロセスで現在アクティブ状態にあるスレッドの数です。命令はプロセッサ内の実行の基本ユニットで、スレッドは命令を実行するオブジェクトです。各実行中のプロセスには、少なくとも 1 つのスレッドがあります。
Virtual Bytes プロセスが使用している仮想アドレス領域の現在の大きさをバイト数で表示します。仮想アドレス領域の使用は、必ずしもディスクあるいはメイン メモリ ページを使用することにはつながりません。仮想領域は限定されており、プロセスがライブラリをロードする能力が限定されます。
Virtual Bytes Peak プロセスが任意の時点で使用した仮想アドレス領域の最大サイズをバイト数で表示します。仮想アドレス領域の使用は、必ずしもディスクあるいはメイン メモリ ページを使用することにはつながりません。仮想領域は限定されており、プロセスがライブラリをロードする能力が限定されます。
Working Set 該当プロセスのワーキング セットの現在のサイズをバイト数で表示します。ワーキング セットは、プロセスのスレッドが最後に参照したメモリ ページのセットです。コンピューターの空きメモリ領域がしきい値以上ある場合、ページは使用中でなくてもプロセスのワーキング セットに残されます。空きメモリ領域がしきい値を下回る場合、ページはワーキング セットから削除されます。削除されたページが必要な場合、ページがメイン メモリから出る前にページはワーキング セットに戻されます。
Working Set - Private このプロセスによってのみ使用され、ほかのプロセスと共有していない、また共有することもできないワーキング セットの大きさをバイトで表示します。
Working Set Peak 任意の時点での該当プロセスのワーキング セットの最大サイズをバイト数で表示します。ワーキング セットは、プロセスのスレッドが最後に参照したメモリ ページのセットです。コンピューターの空きメモリ領域がしきい値以上ある場合、ページは使用中でなくてもプロセスのワーキング セットに残されます。空きメモリ領域がしきい値を下回る場合、ページはワーキング セットから削除されます。削除されたページが必要な場合、ページがメイン メモリから出る前にページはワーキング セットに戻されます。

色々調査した結果、物理メモリの使用量を見るには "Working Set" というパフォーマンスカウンタを監視すればいいようです。
ワーキングセットとは、そのプロセスが使用する仮想メモリにマッピングされている物理メモリの総量を表します。つまり、どれだけ仮想メモリが肥大化しようと、ワーキングセットが大きくならない限りそのプロセスが物理メモリを占有することはないということです。

物理メモリの使用量が少ないからといって安心してはいけません。仮想メモリへのアクセスが頻繁におこなわれることによるスラッシングを考慮すると、そのプロセスで起きるページフォルトの頻度も監視する必要があります。

ページフォルトとは、そのプロセスが仮想メモリのデータにアクセスするときに起きる割り込み処理のことです。OS の処理が割り込まれることになるので、ページフォルトが頻繁におこなわれることでシステム全体の性能が低下し、最悪の場合スラッシングが発生することになります。

それでは実際に C# で値を取得してみましょう。

Program.cs
  1. namespace ConsoleApplication1
  2. {
  3.     using System;
  4.     using System.Diagnostics;
  5.     using System.Threading;
  6.  
  7.     class Program
  8.     {
  9.         static void Main(string[] args)
  10.         {
  11.             var counter_WorkingSet = new PerformanceCounter("Process", "Working Set", "firefox");
  12.             var counter_PageFaults = new PerformanceCounter("Process", "Page Faults/sec", "firefox");
  13.  
  14.             while (true)
  15.             {
  16.                 Console.WriteLine("----------------");
  17.                 Console.WriteLine("    Working Set : " + counter_WorkingSet.NextValue());
  18.                 Console.WriteLine("Page Faults/sec : " + counter_PageFaults.NextValue());
  19.                 Thread.Sleep(1000);
  20.             }
  21.         }
  22.     }
  23. }


出力結果を見ると、ワーキングセットで 440[MB] ほど使用しているようです。
ページフォルトのほうですが、実は単位がわかりません!(致命的)
発生率ということなのでスケーリングされた [%] かとも思いますが、[%] ならカウンタ名の先頭に "%" が付いていてもよさそうなので違う気がしてならない…。誰か教えて!

まとめ

  • パフォーマンスカウンタは慣れないと使いづらい
  • Windows のメモリ管理を良く調べて何を監視すべきか理解する必要がある
  • web 上の資料が少ない、探しにくいで途中でもげる
  • WPF の話してない

参考サイト

以下、参考にしたサイトです。