Подтвердить что ты не робот

Выбрасывание исключения Win32Exception

Недавно я писал много кода, который включает взаимодействие с API Win32 и начал задаваться вопросом, как лучше всего справляться с собственными (неуправляемыми) ошибками, вызванными вызовами функций Windows API.

В настоящее время вызовы функций native выглядят примерно так:

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

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

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

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

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

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

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

Ответ 3

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

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

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