Вопрос

Недавно я написал много кода, который включает взаимодействие с 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 свойство объекта Исключения.Однако я подумал, что было бы целесообразно изменить / обернуть по крайней мере некоторые, если не все, из этих исключений, чтобы они выдавали немного более контекстно-ориентированное сообщение об ошибке, т. е.еще один значимый момент, в какой бы ситуации ни использовался машинный код.Я рассмотрел несколько альтернатив для этого:

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

    throw new Win32Exception(Marshal.GetLastWin32Error(), "My custom error message.");
    
  2. Обертывание Win32Exception в другом объекте исключения, чтобы были сохранены как исходный код ошибки, так и сообщение (the 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?Я вижу, что это делается в коде с открытым исходным кодом самыми разными способами, но мне было любопытно, есть ли какие-либо рекомендации по дизайну.Если нет, то мне было бы интересно узнать о ваших личных предпочтениях здесь.(Возможно, вы даже не используете ни один из вышеперечисленных методов?)

Это было полезно?

Решение

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

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

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top