ネタ元:Debugging Custom Actions: Leaked Handles
久しぶりに、ちょっとまじめな記事w
MSI のカスタムアクションをDLLで作った場合のデバッグテクニックの紹介ですね。
ちょっと凝ったカスタムアクションを作ると、どうしてもMSIのデータベースを直接的に触らないと...という場合があります。
ここで、注意したいのが、リソースリーク。MSIのデータベースへのアクセスを行うと、MSIHANDLE という内部識別子がもらえます。
不要になったらハンドルをクローズするために、MsiCloseHandle を呼び出すのですが、忘れてしまうとリークしてしまいます。
こういう場合に、役に立つのがこの記事ですね。
詳細ログに、
MSI (s) (CC:1C) [17:40:29:055]: Doing action: MyCustomAction
MSI (s) (CC:58) [17:40:29:165]: Invoking remote custom action. DLL: C:\WINDOWS\Installer\MSI1B.tmp, Entrypoint: MyCustomAction Action start 17:40:29: MyCustomAction.
MSI (s) (CC:58) [17:40:29:906]: Leaked MSIHANDLE (219) of type 790531 for thread 3676
MSI (s) (CC:58) [17:40:29:906]: Leaked MSIHANDLE (190) of type 790540 for thread 3676
MSI (s) (CC:58) [17:40:29:906]: Leaked MSIHANDLE (189) of type 790540 for thread 3676
MSI (s) (CC:58) [17:40:29:906]: Leaked MSIHANDLE (188) of type 790540 for thread 3676
MSI (s) (CC:58) [17:40:29:906]: Leaked MSIHANDLE (179) of type 790541 for thread 3676
MSI (s) (CC:58) [17:40:29:906]: Note: 1: 2769 2: MyCustomAction 3: 5
DEBUG: Error 2769: Custom Action MyCustomAction
did not close 5 Action ended 17:40:30: MyCustomAction.
Return value 3.
な感じで残されるから、チェックしておけ!ということだそうです(先頭の数字は後述する解説のために入れています)。
ちなみに、type の数字(3~7行目)は内部定義のものらしく、Windows Installer team がどこかでフォローアップしてるらしいです。
記事で紹介されているのは
- MSIHANDLE type 790531 identifies a record
- MSIHANDLE type 790540 identifies a view
- MSIHANDLE type 790541 identifies a database
この3種類だけですが、これ以外で使うことはない(というかこれしか種類がないw)ので、これだけメモっておけば問題ないでしょうw
さて、せっかくなのでちょっと解説もw
まず、この詳細ログを利用したリークハンドルの検出ですが、可能なのは、DLLのカスタムアクションだけです。インプロセスで動作するカスタムアクションには、スクリプト(VBS/JS)もありますが、こちらはスクリプト終了時に自動的にクリーンナップ(GCのシステムがある...COMだからなんだけどねw)されるので、改めて意識する必要はありません。
その代わり、スクリプトブロッカー(多くのアンチウィルスソフトが提供している)が働く可能性があるため、動かない場合も多々ありますが...w
さて、続き。まずはログの左側。
MSI (s) (CC:1C) [17:40:29:055]:
を見てみましょう。
MSI で始まる行は、msi.dll が用意しているログ出力をさします(ま、それ以外の経路はないので当たり前ですがw)。
(s) は、サーバーサイドを表します。こちらはついになるものに(c)のクライアントサイドがあります。
(CC:1C)や、(CC:58)は、ログを出力したプロセスの識別子。このサンプルログでは、1Cがサービスとして動作している msiexec になり、58 が、カスタムアクションを実行する専用プロセスとなります。
さて、ここで注意。
インストーラは、システムそのものにかなりの変更を与える権利を与えられているため、万が一にもクラッシュしてしまう可能性を可能な限り排除するという安全性に傾倒した作りになっています。そのため、ユーザーコードにあたるカスタムアクション類は、必ず独立した専用プロセスで動作するようになっており、クライアント<->サーバーについても、クラッシュダウンがあってもOS自体のブルーバックにはならないように作られています(もちろん、100%発生しないということではありませんが...w)。
そのため、サーバーサイド、クライアントサイドともにカスタムアクション専用のプロセスを用意して動作するように作られています。その簡単な識別が、上記の(CC:1C)や(CC:58)となります。ただし、この数値自身は環境依存なのでたんある識別子でしかありませんが。
さて、次は右側を見てみることにしましょう。
1行目は、そのまま。MyCustomAction(という名前で識別できるカスタムアクション)を実行します。という意味です。単なるスタートアップ識別ポイントですね。
2行目は、「カスタムアクションをリモート呼び出しします。」種別は、DLLで、C:\Windows... は呼び出すモジュールのパス(この場合は、内部保持のDLLなので必要に応じてテンポラリに展開される)、呼び出す関数は、MyCustomAction で、開始時間は、17:40:29 ということです(最後の MyCustomAction は、識別子です)。
3~7行目は、ハンドルのリークを検出したというエラーリポートです。
8行目がその注釈です。1 は、msi の内部エラーコード(Error Tableにあるコードを指す)、2は、発生したアクションの名前、3 はオプション(内部コードにより意味が変わる)となります。この場合は、5個という意味ですね。
エラーコードの詳細は Windows Installer Error Messages にあります。ちなみに、日本語版インストーラであれば、このエラーコードは全部翻訳しないと本当はだめです(ソフトによっては出ないものもあるので、それは入れなくてもよい)。
本当は、デフォルトデータとして、ローカライズされた msi.dll に内包してもらいたいところなんですが...今はどうなんだろうなぁ?
9行目は、デバッグメッセージです。動作上支障があるかどうかにかかわらずデバッグに必要と思われるエラーが発生した場合に表示されます。
10行目は、その詳細。ここは2つ合わせて、具体的にどんなエラーが発生したか?を表現しています。
戻り値もありますが、具体的な意味については、MyCustomAction の中身を見てみないとわからないので何とも言えませんねw
おまけ...
テーブル参照を行うと、SQL文が発行されます(中身は、RDBですからねw<MDBと同じようなものですがw)。それも詳細ログには出てきます。
いろいろやりたいなぁ...という人は、簡単なインストーラを書いて、詳細ログをとってみるとどんなことをしているか?などがわかりますよ。
また、他にもいくつかのデバッグの際のテクニックが Debugging Custom Actions に出ています。カスタムアクション(VSのカスタム動作ではない)を作る際には、一度目を通しておくとよいでしょう。