Генерация исключения Win32Exception

Недавно я написал много кода, включающего взаимодействие с 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());

Теперь все хорошо, поскольку он выдает исключение, соответствующим образом содержащее установленный код ошибки Win32, а также (обычно) удобочитаемое описание ошибки в виде свойства Message объекта Exception. Тем не менее, я подумал, что было бы целесообразно изменить/обернуть по крайней мере некоторые, если не все, из этих исключений, чтобы они давали немного более контекстно-ориентированное сообщение об ошибке, т.е. одно более значимое в любой ситуации, в которой работает собственный код. быть использованным. Я рассмотрел несколько вариантов для этого:

  1. Указание пользовательского сообщения об ошибке в конструкторе для Win32Exception.

    throw new Win32Exception(Marshal.GetLastWin32Error(), "My custom error message.");
    
  2. Оборачиваем Win32Exception в другой объект Exception, чтобы сохранить исходный код ошибки и сообщение (теперь Win32Exception является InnerException родительского исключения).

    throw new Exception("My custom error message.", Win32Exception(Marshal.GetLastWin32Error()));
    
  3. То же, что и 2, за исключением использования другого Win32Exception в качестве исключения оболочки.

  4. То же, что и 2, за исключением использования пользовательского класса, производного от Exception, в качестве исключения оболочки.

  5. То же, что и 2, за исключением использования исключения BCL (библиотеки базовых классов) в качестве родителя, когда это уместно. Не уверен, что в этом случае даже уместно установить InnerException на Win32Exception (возможно, для низкоуровневой оболочки, но не для высокоуровневого/абстрактного интерфейса, который не делает очевидным, что взаимодействие Win32 происходит за кулисами?)

По сути, я хочу знать: какова рекомендуемая практика работы с ошибками Win32 в .NET? Я видел, как это делается в открытом исходном коде самыми разными способами, но мне было любопытно, существуют ли какие-либо рекомендации по дизайну. Если нет, то мне были бы интересны ваши личные предпочтения здесь. (Возможно, вы даже не используете ни один из вышеперечисленных методов?)


person Noldorin    schedule 23.03.2009    source источник


Ответы (3)


На самом деле это не относится к исключениям Win32; вопрос в том, когда два разных случая ошибки должны быть идентифицированы двумя разными типами, производными от Exception, и когда они должны выдавать один и тот же тип с разными значениями, хранящимися внутри него?

К сожалению, на это невозможно ответить, не зная заранее всех ситуаций, в которых будет вызываться ваш код. :) Это проблема с возможностью фильтрации исключений только по типу. Вообще говоря, если у вас есть сильное ощущение, что было бы полезно по-разному обрабатывать два случая ошибок, выбрасывайте разные типы.

В противном случае часто бывает так, что строку, возвращаемую Exception.Message, просто необходимо зарегистрировать или отобразить пользователю.

Если есть дополнительная информация, оберните Win32Exception чем-то более высокоуровневым. Например, вы пытаетесь что-то сделать с файлом, а у пользователя, под которым вы работаете, нет разрешения на это. Перехватите Win32Exception, оберните его в собственный класс исключения, в сообщении которого указано имя файла и предпринимаемая операция, за которым следует сообщение внутреннего исключения.

person Daniel Earwicker    schedule 23.03.2009
comment
+1 за дополнительную информацию - мало что может быть более разочаровывающим, чем получение исключения «Файл не найден» без ссылки на то, какой файл не может быть найден. - person Harper Shelby; 23.03.2009
comment
@Earwicker: Вы делаете справедливое замечание ... Кроме того, я задаю этот вопрос конкретно в отношении Win32Exception, потому что они часто (хотя и не всегда) имеют тенденцию быть довольно расплывчатыми и бесполезными, как отмечает Харпер. - person Noldorin; 23.03.2009
comment
Чтобы уточнить ваш последний абзац, вы имеете в виду, что лучше добавить внутреннее сообщение об ошибке а также, чтобы обернуть его как внутреннее исключение? - person Noldorin; 23.03.2009
comment
@Noldorin - к сожалению, да, если вы не знаете, что вызывающий абонент будет следовать шаблону для создания составных сообщений (см. Обновление выше). - person Daniel Earwicker; 23.03.2009

Я всегда считал, что правильный способ справиться с этим зависит от целевой аудитории и от того, как будет использоваться ваш класс.

Если вызывающая сторона вашего класса/метода поймет, что они вызывают Win32 в той или иной форме, я бы использовал указанный вами вариант 1). Это мне кажется наиболее "понятным". (Однако, если это так, я бы назвал ваш класс таким образом, чтобы было ясно, что Win32 API будет использоваться напрямую). При этом в BCL есть исключения, которые на самом деле являются подклассами Win32Exception, чтобы быть более понятными, а не просто обертывать их. Например, SocketException является производным от Win32Exception. Я никогда лично не использовал этот подход, но он кажется потенциально чистым способом справиться с этим.

Если вызывающая сторона вашего класса не будет знать, что вы вызываете Win32 API напрямую, я бы обработал исключение и использовал специальное, более описательное исключение, которое вы определили. Например, если я использую ваш класс, и нет никаких указаний на то, что вы используете Win32 API (поскольку вы используете его внутри по какой-то конкретной, неочевидной причине), у меня нет причин подозревать, что я может потребоваться обработка исключения Win32Exception. Вы всегда можете задокументировать это, но мне кажется более разумным перехватить это и создать исключение, которое будет иметь большее значение в вашем конкретном бизнес-контексте. В этом случае я мог бы обернуть начальное исключение Win32Exception как внутреннее исключение (т. е. ваш случай 4), но в зависимости от того, что вызвало внутреннее исключение, я мог бы этого не делать.

Кроме того, во многих случаях исключение Win32Exception может быть вызвано собственным вызовом, но в BCL есть другие исключения, которые более важны. Это тот случай, когда вы вызываете собственный API, который не заключен в оболочку, но есть аналогичные функции, заключенные в BCL. В этом случае я, вероятно, перехватил бы исключение, убедился, что это именно то, что я ожидаю, но затем выдал бы вместо него стандартное исключение BCL. Хорошим примером этого может быть использование SecurityException вместо создания Win32Exception.

В целом, однако, я бы избегал вариантов 2 и 3, которые вы перечислили.

Второй вариант выдает общий тип Exception - я бы рекомендовал полностью избегать этого. Кажется неразумным превращать конкретное исключение в более общее.

Третий вариант кажется излишним - на самом деле нет никаких преимуществ перед вариантом 1.

person Reed Copsey    schedule 23.03.2009
comment
Спасибо за предложения. Я думаю, что в моей ситуации может быть лучше всего вызвать Win32Exception напрямую, поскольку класс, содержащий код, на самом деле является оболочкой для вещей, которые манипулируют другими процессами/потоками/разделяемой памятью. - person Noldorin; 23.03.2009
comment
(продолжение) Генерация исключений BCL (и обертка Win32-исключений?) также может быть уместной. Вариант 4 действительно кажется лучшим, когда кто-то хочет скрыть тот факт, что используется взаимодействие Win32 (то есть относительно высокоуровневый интерфейс). - person Noldorin; 23.03.2009

Лично я бы сделал № 2 или № 4 ... Предпочтительно № 4. Оберните Win32Exception внутри вашего исключения, которое зависит от контекста. Как это:

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

Таким образом, если кто-то поймает исключение, у него будет довольно хорошее представление о том, где возникла проблема. Я бы не стал делать # 1, потому что для интерпретации вашего текстового сообщения об ошибке требуется поймать. И № 3 действительно не дает никакой дополнительной информации.

person Keltex    schedule 23.03.2009
comment
Согласованный. Вариант 4, вероятно, я буду использовать, когда захочу скрыть исключения Win32 - возможно, используя исключения BCL, а не пользовательские, где это уместно. - person Noldorin; 23.03.2009