意外と知られていない Windows のエラーコードの数々

MSDN検索術が公開されたのでそれに合わせてメモなど。
開発者の皆様は (私もそうなのですが・・・) 毎日のようにとんでもない量のエラーメッセージを目にされていると思います。

ところで、これらのエラーメッセージ、エラーコードがどのような区分になっているかご存知でしょうか?今回は、よく目にするものと、その見分け方について簡単にご紹介させていただきたいと思います。

エラーを正しく理解することは正確な報告を可能にし、また問題解決にも役立ちます。

1. .NET Framework における例外

こちらは (見た目は) わかりやすいですよね。例えば何か存在しないファイルを開こうとすると…

Unhandled Exception: System.IO.FileNotFoundException: Could not find file 'C:\Demos\ExceptionDemo\bin\Debug\何か。'.
File name: 'C:\Demos\ExceptionDemo\bin\Debug\何か。'
   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)

   at System.IO.StreamReader..ctor(String path, Boolean detectEncodingFromByteOrderMarks)
   at ExceptionDemo.Program.Main(String[] args) in C:\Demos\ExceptionDemo\Program.cs:line 12

このような例外が発生すると思います。全ての例外は System.Exception クラスを継承しなければならないため、System.Exception クラスを通して、

Data …追加情報。この例では空のコレクション
HelpLink …ヘルプへの URN。この例では null
InnerException …この例外を発生させる元となった例外。この例では null
Message …例外の説明。この例では “Could not find file 'C:\Demos\ExceptionDemo\bin\Debug\何か。'.”
Source …例外を投げたアプリケーションやオブジェクト。この例では “mscorlib” つまり、.NET Framework の基本ライブラリ
StackTrace …例外が投げられるまでの呼び出しの流れ。この例では “at System.IO… ~ Program.cs:line 12”
TargetSite …例外を投げたメソッド。この例では {Void WinIOError(Int32, System.String)}

さらに、今回は System.Exception –> … –> System.IO.FileNotFoundException と継承されているので、FileNotFoundException 内の

FileName …ファイル名。この場合は @”C:\Demos\ExceptionDemo\bin\Debug\何か。”

などが利用できます。さて、問題解決には何が重要になるでしょうか?「例外が発生した!」と報告しても誰にも原因はわからないですよね。
例外の場合、例外の種類、StackTrace と引数が重要になります。どの関数を呼んだ事で例外が発生したのかを知り、呼び出した関数への引数に問題がなかったかがわかれば再現させることが容易になります。例えばこの場合ですと、

「Program.cs の 12行目、System.IO.StreamReader..ctor(String path, Boolean detectEncodingFromByteOrderMarks) の呼び出しにおいて System.IO.FileNotFound が発生、引数には “何か。” が与えられていた。」と報告すれば、「”何か。” という存在しないファイルを読み出そうとしたのだな」ということが確実に相手に伝わります。

複雑な問題においては、InnerException にも着目しましょう。根本的な原因がそこに書かれている場合もあります。フォーラムなどで質問する際には例外に含まれる情報を活用いただければと思います。

 

2. Win32, COM におけるエラーコード

.NET における例外はこのくらいにして Win32, COM (Component Object Model) においてはどのようなエラーが生じるのでしょうか。Native のプログラムを書いていると 0xC0000005 というエラーコードに遭遇することが多いと思いますが、これは Win32 における一般保護例外、つまりアクセスできない領域に対するエラーのコードです。はて、なぜこんなに長いエラーコードになっているのでしょう?実はエラーコードの先頭には意味がついています。エラーコードの 32bit の中身を分解すると以下のようになります。

Win32 の場合

| Severity Code (2bit) | Customer code flag (1bit) | Reserved (1bit) | Facility Code (12bit) | Code (16bit) |

COM の場合

| Severity Code (1bit) | Reserved (4bit) | Facility Code (11bit) | Code (16bit) |

ここで、Severity Code は重要度を表しています。Win32 の場合、00b が成功、01b が情報を含むもの、10b が警告、11b がエラーとなっています。Windows が返すエラーには Customer Code Flag は設定されていないので先頭 4bit …つまり、16進数表記をした際の最初の一文字がエラーコードの重要度となります。0xC0000005 は ‘C’ で始まっているので 0xC = 1100b つまりエラーを示しています。ただ、警告や情報は滅多に見かけないので 0xC0123456 といったエラーコードを目にすることになります。

一般保護例外 0xC0000005 は、Severity = 11b から「エラー」であり、Code = 5 (ERROR_ACCESS_DENIED) から「Access is denied. (アクセスが拒否された)」ことが読み取れるのです。

さて、COM の場合は先頭が少し異なりますよね? Severity Code が 1bit となっています。なので COM のエラーコード (HRESULT) は 0x80123456 といった形になります。

ここがポイントです!つまり、たいていの場合、0xC で始まっているものは Win32 のエラーコード、0x8 で始まっているものは COM のエラーコードという見分け方ができるのです。もし 10進数でエラーコードが –1073741824 のような負の大きな値で –1 始まりのものは Win32, –2147483648 のような –2 始まりのものは COM のエラーコードである可能性が高いです。

これは電卓をプログラマモード (関数モード) にすることで変換することが可能です。エラーコードを検索しても出てこない場合には、16進数のコードを 10進数に直してみる、あるいはその逆を行うことで解決策が引っかかる可能性が高くなります。

また、下位 16bit にエラーの内容を示すコードが入っているため、そこだけ抜き出して Error Lookup ツールなどを利用すると何かヒントが得られるかも知れません。

 

3. 一般的なエラーコード

C言語の関数の結果、またイベントログに記載されるエラーコードは、このような長いエラーコード形式ではないのでせいぜい数千までの値を取ることが多いと思います。そのような場合には 16進数で検索せず、10進数で検索するほうがよい結果が得られる可能性が高いと思います。Windows SDK に含まれる、WinError.h を開いて検索するというのも一つの手です。

 

まとめ。

例外など、型情報が得られるものについては例外の種類を見分ける。

8 で始まっている、または –21… という大きな負の値なら COM のエラーコードであることを疑う。
C で始まっている、または –10… という大きな負の値なら Win32 のエラーコードであることを疑う。
覚えられる範囲の数字なら一般的なエラーコードを疑う。

もしかしたら 10進数で負の値になっているものは 16進数に変換することで解決策が見つかるかも知れません。

このように、ちょっとした見分けかたを覚えておくだけでもエラーコードからより多くの情報、または検索に頼った問題解決のヒントが得られます。

# 訂正しました。mayukiさん、つーささん、ありがとうございます!