19

私は最近、Win32 API との相互運用に関連する多くのコードを作成しており、Windows API 関数の呼び出しによって発生するネイティブ (アンマネージ) エラーを処理する最善の方法は何かを考え始めています。

現在、ネイティブ関数の呼び出しは次のようになっています。

// NativeFunction returns true when successful and false when an error
// occurred. When an error occurs, the MSDN docs usually tell you that the
// error code can be discovered by calling GetLastError (as long as the
// SetLastError flag has been set in the DllImport attribute).
// Marshal.GetLastWin32Error is the equivalent managed function, it seems.
if (!WinApi.NativeFunction(param1, param2, param3))
    throw new Win32Exception();

例外を発生させる行は、次のように同等に書き直すことができます。

throw new Win32Exception(Marshal.GetLastWin32Error());

Messageこれで、設定された Win32 エラー コードと、(通常は) 人間が判読できるエラーの説明をException オブジェクトのプロパティとして適切に含む例外がスローされるという点で、これは問題ありません。ただし、これらの例外のすべてではないにしても、少なくとも一部を変更/ラップして、もう少しコンテキスト指向のエラー メッセージ、つまり、ネイティブ コードがどのような状況でもより意味のあるエラー メッセージが表示されるようにすることをお勧めします。使用されています。これにはいくつかの代替案を検討しました。

  1. のコンストラクタでカスタム エラー メッセージを指定するWin32Exception

    throw new Win32Exception(Marshal.GetLastWin32Error(), "My custom error message.");
    
  2. を別の Exception オブジェクトにラップしてWin32Exception、元のエラー コードとメッセージの両方が保持されるようにします (Win32Exceptionは現在InnerException、親例外の です)。

    throw new Exception("My custom error message.", Win32Exception(Marshal.GetLastWin32Error()));
    
  3. Win32Exception別のものをラッパー例外として使用することを除いて、2 と同じです。

  4. 2 と同じExceptionですが、ラッパー例外として から派生したカスタム クラスを使用します。

  5. 適切な場合に親として BCL (Base Class Library) 例外を使用することを除いて、2 と同じです。InnerExceptionこの場合、 をに設定することが適切かどうかWin32Exceptionはわかりません (おそらく、低レベルのラッパーの場合ですが、Win32 相互運用が舞台裏で行われていることを明らかにしない高レベル/抽象化されたインターフェイスではありませんか?)

基本的に私が知りたいのは、.NET で Win32 エラーを処理する際に推奨される方法は何ですか? さまざまな方法でオープンソース コードで行われているのを見てきましたが、設計ガイドラインがあるかどうかに興味がありました。そうでない場合は、ここであなたの個人的な好みに興味があります. (おそらく、上記の方法のどれも使用していませんか?)

4

3 に答える 3

4

これは、Win32 例外に固有のものではありません。問題は、いつ 2 つの異なるエラー ケースが 2 つの異なる Exception 派生型によって識別されるべきか、そしていつ、異なる値が内部に格納された同じ型をスローするべきかということです。

残念ながら、コードが呼び出されるすべての状況を事前に知らなければ、これに答えることは不可能です。:) これは、例外をタイプでしかフィルタリングできないという問題です。大まかに言えば、2 つのエラー ケースを異なる方法で処理することが有用であると強く感じている場合は、異なる型をスローします。

それ以外の場合、Exception.Message によって返される文字列をログに記録するか、ユーザーに表示するだけでよい場合がよくあります。

追加情報がある場合は、Win32Exception を独自のより高レベルなものでラップします。たとえば、ファイルに対して何かを実行しようとしていて、実行中のユーザーにはそれを実行する権限がありません。Win32Exception をキャッチし、それを独自の例外クラスにラップします。そのメッセージには、ファイル名と試行されている操作が示され、その後に内部例外のメッセージが続きます。

于 2009-03-23T16:10:11.103 に答える
3

私の見解では、これを処理する適切な方法は、対象となる聴衆とクラスがどのように使用されるかによって異なります。

クラス/メソッドの呼び出し元が、何らかの形で Win32 を呼び出していることに気付く場合は、指定したオプション 1) を使用します。これは私にとって最も「明確」に思えます。(ただし、この場合は、Win32 API が直接使用されることを明確にする方法でクラスに名前を付けます)。そうは言っても、BCL には例外があり、実際には Win32Exception をラップするだけでなく、より明確にサブクラス化しています。たとえば、SocketException は Win32Exception から派生します。私は個人的にそのアプローチを使用したことはありませんが、これを処理するための潜在的にクリーンな方法のように思えます。

あなたのクラスの呼び出し元が、あなたが Win32 API を直接呼び出していることに気付かない場合は、例外を処理し、定義したカスタムのより説明的な例外を使用します。たとえば、私があなたのクラスを使用していて、あなたが Win32 API を使用している兆候がない場合 (特定の明白でない理由で内部的に使用しているため)、私がそれを疑う理由はありません。 Win32Exception を処理する必要がある場合があります。これはいつでも文書化できますが、それをトラップして、特定のビジネスコンテキストでより意味のある例外を与える方が合理的だと思います. この場合、最初の Win32Exception を内部例外 (つまり、ケース 4) としてラップする可能性がありますが、内部例外の原因によっては、ラップしない場合があります。

また、ネイティブ呼び出しから Win32Exception がスローされることはよくありますが、BCL にはより関連性の高い他の例外があります。これは、ラップされていないネイティブ API を呼び出している場合に当てはまりますが、BCL にラップされている同様の関数があります。その場合、おそらく例外をトラップし、期待どおりであることを確認しますが、標準の BCL 例外をその場所にスローします。この良い例は、Win32Exception をスローする代わりに SecurityException を使用することです。

ただし、一般的には、リストしたオプション 2 と 3 は避けます。

オプション 2 は、一般的な例外タイプをスローします。これを完全に回避することをお勧めします。特定の例外をより一般化された例外にラップするのは不合理に思えます。

オプション 3 は冗長に思えます - オプション 1 に勝る利点はありません。

于 2009-03-23T16:18:23.720 に答える
2

個人的には#2か#4…できれば#4です。状況依存の例外内に Win32Exception をラップします。このような:

void ReadFile()
{
    if (!WinApi.NativeFunction(param1, param2, param3))
        throw MyReadFileException("Couldn't read file", new Win32Exception());
}

このようにして、誰かが例外をキャッチした場合、問題が発生した場所をかなりよく知ることができます。テキストエラーメッセージを解釈するにはキャッチが必要なので、#1はしません。そして#3は、実際には追加情報を提供しません。

于 2009-03-23T16:18:37.103 に答える