Как получить код ошибки c

You can use this to check the exception and the inner exception for a Win32Exception derived exception.

catch (Exception e) {  
    var w32ex = e as Win32Exception;
    if(w32ex == null) {
        w32ex = e.InnerException as Win32Exception;
    }    
    if(w32ex != null) {
        int code =  w32ex.ErrorCode;
        // do stuff
    }    
    // do other stuff   
}

Starting with C# 6, when can be used in a catch statement to specify a condition that must be true for the handler for a specific exception to execute.

catch (Win32Exception ex) when (ex.InnerException is Win32Exception) {
    var w32ex = (Win32Exception)ex.InnerException;
    var code =  w32ex.ErrorCode;
}

As in the comments, you really need to see what exception is actually being thrown to understand what you can do, and in which case a specific catch is preferred over just catching Exception. Something like:

  catch (BlahBlahException ex) {  
      // do stuff   
  }

Also System.Exception has a HRESULT

 catch (Exception ex) {  
     var code = ex.HResult;
 }

However, it’s only available from .NET 4.5 upwards.

Время на прочтение
13 мин

Количество просмотров 74K

Введение

Ошибки, увы, неизбежны, поэтому их обработка занимает очень важное место в программировании. И если алгоритмические ошибки можно выявить и исправить во время написания и тестирования программы, то ошибок времени выполнения избежать нельзя в принципе. Сегодня мы рассмотрим функции стандартной библиотеки (C Standard Library) и POSIX, используемые в обработке ошибок.

Переменная errno и коды ошибок

<errno.h>

errno – переменная, хранящая целочисленный код последней ошибки. В каждом потоке существует своя локальная версия errno, чем и обусловливается её безопасность в многопоточной среде. Обычно errno реализуется в виде макроса, разворачивающегося в вызов функции, возвращающей указатель на целочисленный буфер. При запуске программы значение errno равно нулю.

Все коды ошибок имеют положительные значения, и могут использоваться в директивах препроцессора #if. В целях удобства и переносимости заголовочный файл <errno.h> определяет макросы, соответствующие кодам ошибок.

Стандарт ISO C определяет следующие коды:

  • EDOM – (Error domain) ошибка области определения.
  • EILSEQ – (Error invalid sequence) ошибочная последовательность байтов.
  • ERANGE – (Error range) результат слишком велик.

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

Нехитрый скрипт печатает в консоль коды ошибок, их символические имена и описания:

#!/usr/bin/perl

use strict;
use warnings;

use Errno;

foreach my $err (sort keys (%!)) {
    $! = eval "Errno::$err";
    printf "%20s %4d   %sn", $err, $! + 0, $!
}

Если вызов функции завершился ошибкой, то она устанавливает переменную errno в ненулевое значение. Если же вызов прошёл успешно, функция обычно не проверяет и не меняет переменную errno. Поэтому перед вызовом функции её нужно установить в 0.

Пример:

/* convert from UTF16 to UTF8 */
errno = 0;	
n_ret = iconv(icd, (char **) &p_src, &n_src, &p_dst, &n_dst);   
	
if (n_ret == (size_t) -1) {
    VJ_PERROR();
    if (errno == E2BIG)  
        fprintf(stderr, " Error : input conversion stopped due to lack of space in the output buffern");
    else if (errno == EILSEQ)  
        fprintf(stderr, " Error : input conversion stopped due to an input byte that does not belong to the input codesetn");
    else if (errno == EINVAL)  
        fprintf(stderr, " Error : input conversion stopped due to an incomplete character or shift sequence at the end of the input buffern");
/* clean the memory */   
    free(p_out_buf);
    errno = 0;
    n_ret = iconv_close(icd);      
    if (n_ret == (size_t) -1)  
        VJ_PERROR();
    return (size_t) -1; 
}

Как видите, описания ошибок в спецификации функции iconv() более информативны, чем в <errno.h>.

Функции работы с errno

Получив код ошибки, хочется сразу получить по нему её описание. К счастью, ISO C предлагает целый набор полезных функций.

<stdio.h>

void perror(const char *s);

Печатает в stderr содержимое строки s, за которой следует двоеточие, пробел и сообщение об ошибке. После чего печатает символ новой строки 'n'.

Пример:

/*
//  main.c
//  perror example
//
//  Created by Ariel Feinerman on 23/03/17.
//  Copyright  2017 Feinerman Research, Inc. All rights reserved.
*/

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main(int argc, const char * argv[]) 
{
    // Generate unique filename.
    char *file_name = tmpnam((char[L_tmpnam]){0});
   
    errno = 0;
    FILE *file = fopen(file_name, "rb");

    if (file) {
        // Do something useful. 
        fclose(file);
    }
    else {
        perror("fopen() ");
    }
	
    return EXIT_SUCCESS;
}

<string.h>

char* strerror(int errnum);
Возвращает строку, содержащую описание ошибки errnum. Язык сообщения зависит от локали (немецкий, иврит и даже японский), но обычно поддерживается лишь английский.

/*
//  main.c
//  strerror example
//
//  Created by Ariel Feinerman on 23/03/17.
//  Copyright  2017 Feinerman Research, Inc. All rights reserved.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <errno.h>

int main(int argc, const char * argv[]) 
{
    // Generate unique filename.
    char *file_name = tmpnam((char[L_tmpnam]){0});

    errno = 0;
    FILE *file = fopen(file_name, "rb");
    // Save error number. 
    errno_t error_num = errno;
	
    if (file) {
        // Do something useful. 
        fclose(file);
    }
    else {
        char *errorbuf = strerror(error_num);
        fprintf(stderr, "Error message : %sn", errorbuf);
    }
    
    return EXIT_SUCCESS;
}

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

Поэтому в новом стандарте ISO C11 были предложены две очень полезные функции.

size_t strerrorlen_s(errno_t errnum);

Возвращает длину строки с описанием ошибки errnum.

errno_t strerror_s(char *buf, rsize_t buflen, errno_t errnum);

Копирует строку с описание ошибки errnum в буфер buf длиной buflen.

Пример:

/*
//  main.c
//  strerror_s example 
//
//  Created by Ariel Feinerman on 23/02/17.
//  Copyright  2017 Feinerman Research, Inc. All rights reserved.
*/

#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <errno.h>

int main(int argc, const char * argv[]) 
{
    // Generate unique filename.
    char *file_name = tmpnam((char[L_tmpnam]){0});
	
    errno = 0;
    FILE *file = fopen(file_name, "rb");
    // Save error number. 
    errno_t error_num = errno;

    if (file) {
        // Do something useful. 
        fclose(file);
    }
    else {
#ifdef __STDC_LIB_EXT1__
    size_t error_len = strerrorlen_s(errno) + 1;
    char error_buf[error_len];
    strerror_s(error_buf, error_len, errno);
    fprintf(stderr, "Error message : %sn", error_buf);
#endif
    }
	
    return EXIT_SUCCESS;
}

Функции входят в Annex K (Bounds-checking interfaces), вызвавший много споров. Он не обязателен к выполнению и целиком не реализован ни в одной из свободных библиотек. Open Watcom C/C++ (Windows), Slibc (GNU libc) и Safe C Library (POSIX), в последней, к сожалению, именно эти две функции не реализованы. Тем не менее, их можно найти в коммерческих средах разработки и системах реального времени, Embarcadero RAD Studio, INtime RTOS, QNX.

Стандарт POSIX.1-2008 определяет следующие функции:

char *strerror_l(int errnum, locale_t locale);

Возвращает строку, содержащую локализованное описание ошибки errnum, используя locale. Безопасна в многопоточной среде. Не реализована в Mac OS X, FreeBSD, NetBSD, OpenBSD, Solaris и прочих коммерческих UNIX. Реализована в Linux, MINIX 3 и Illumos (OpenSolaris).

Пример:

/*
 //  main.c
 //  strerror_l example – works on Linux, MINIX 3, Illumos
 //
 //  Created by Ariel Feinerman on 23/03/17.
 //  Copyright  2017 Feinerman Research, Inc. All rights reserved.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <errno.h>

#include <locale.h>

int main(int argc, const char * argv[]) 
{
    locale_t locale = newlocale(LC_ALL_MASK, "fr_FR.UTF-8", (locale_t) 0);
    
    if (!locale) {
        fprintf(stderr, "Error: cannot create locale.");
        exit(EXIT_FAILURE);
    }

    // Generate unique filename.
    char *file_name = tmpnam((char[L_tmpnam]){0});
	
    errno = 0;
    FILE *file = fopen(tmpnam(file_name, "rb");
    // Save error number. 
    errno_t error_num = errno;

    if (file) {
        // Do something useful. 
        fclose(file);
    }
    else {
        char *error_buf = strerror_l(errno, locale);
        fprintf(stderr, "Error message : %sn", error_buf);
    }
	
    freelocale(locale);
	
    return EXIT_SUCCESS;
}

Вывод:

Error message : Aucun fichier ou dossier de ce type

int strerror_r(int errnum, char *buf, size_t buflen);

Копирует строку с описание ошибки errnum в буфер buf длиной buflen. Если buflen меньше длины строки, лишнее обрезается. Безопасна в многоготочной среде. Реализована во всех UNIX.

Пример:

/*
//  main.c
//  strerror_r POSIX example
//
//  Created by Ariel Feinerman on 25/02/17.
//  Copyright  2017 Feinerman Research, Inc. All rights reserved.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <errno.h>

#define MSG_LEN 1024 

int main(int argc, const char * argv[]) 
{
    // Generate unique filename.
    char *file_name = tmpnam((char[L_tmpnam]){0});
    
    errno = 0;
    FILE *file = fopen(file_name, "rb");
    // Save error number. 
    errno_t error_num = errno;	
	
    if (file) {
        // Do something useful.
        fclose(file);
    }
    else {
        char error_buf[MSG_LEN];
        errno_t error = strerror_r (error_num, error_buf, MSG_LEN);
		
        switch (error) {
            case EINVAL:
                    fprintf (stderr, "strerror_r() failed: invalid error code, %dn", error);
                    break;
            case ERANGE:
                    fprintf (stderr, "strerror_r() failed: buffer too small: %dn", MSG_LEN);
            case 0:
                    fprintf(stderr, "Error message : %sn", error_buf);
                    break;
            default: 
                    fprintf (stderr, "strerror_r() failed: unknown error, %dn", error);
                    break;
        }
    }
    
    return EXIT_SUCCESS;
}

Увы, никакого аналога strerrorlen_s() в POSIX не определили, поэтому длину строки можно выяснить лишь экспериментальным путём. Обычно 300 символов хватает за глаза. GNU C Library в реализации strerror() использует буфер длиной в 1024 символа. Но мало ли, а вдруг?

Пример:

/*
 //  main.c
 //  strerror_r safe POSIX example
 //
 //  Created by Ariel Feinerman on 23/03/17.
 //  Copyright  2017 Feinerman Research, Inc. All rights reserved.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <errno.h>

#define MSG_LEN 1024 
#define MUL_FACTOR 2

int main(int argc, const char * argv[]) 
{
    // Generate unique filename.
    char *file_name = tmpnam((char[L_tmpnam]){0});
	
    errno = 0;
    FILE *file = fopen(file_name, "rb");
    // Save error number. 
    errno_t error_num = errno;
	
    if (file) {
        // Do something useful.
        fclose(file);
    }
    else {
        errno_t error = 0;
        size_t error_len = MSG_LEN; 
		
        do {
            char error_buf[error_len];
            error = strerror_r (error_num, error_buf, error_len);
            switch (error) {
                    case 0:
                            fprintf(stderr, "File : %snLine : %dnCurrent function : %s()nFailed function : %s()nError message : %sn", __FILE__, __LINE__, __func__, "fopen", error_buf);
	                    break;
                    case ERANGE: 
                            error_len *= MUL_FACTOR;
                            break;
                    case EINVAL: 
                            fprintf (stderr, "strerror_r() failed: invalid error code, %dn", error_num);
                            break;
                    default:
                            fprintf (stderr, "strerror_r() failed: unknown error, %dn", error);
                            break;
            }
			
        } while (error == ERANGE);
    }
    
    return EXIT_SUCCESS;
}

Вывод:

File : /Users/ariel/main.c
Line : 47
Current function : main()
Failed function : fopen()
Error message : No such file or directory

Макрос assert()

<assert.h>

void assert(expression)

Макрос, проверяющий условие expression (его результат должен быть числом) во время выполнения. Если условие не выполняется (expression равно нулю), он печатает в stderr значения __FILE__, __LINE__, __func__ и expression в виде строки, после чего вызывает функцию abort().

/*
//  main.c
//  assert example
//
//  Created by Ariel Feinerman on 23/03/17.
//  Copyright  2017 Feinerman Research, Inc. All rights reserved.
*/

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#include <math.h>

int main(int argc, const char * argv[]) {
    double x = -1.0;
    assert(x >= 0.0);
    printf("sqrt(x) = %fn", sqrt(x));   
    
    return EXIT_SUCCESS;
}

Вывод:

Assertion failed: (x >= 0.0), function main, file /Users/ariel/main.c, line 17.

Если макрос NDEBUG определён перед включением <assert.h>, то assert() разворачивается в ((void) 0) и не делает ничего. Используется в отладочных целях.

Пример:

/*
//  main.c
//  assert_example
//
//  Created by Ariel Feinerman on 23/03/17.
//  Copyright  2017 Feinerman Research, Inc. All rights reserved.
*/

#NDEBUG

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#include <math.h>

int main(int argc, const char * argv[]) {
    double x = -1.0;
    assert(x >= 0.0);
    printf("sqrt(x) = %fn", sqrt(x));   
    
    return EXIT_SUCCESS;
}

Вывод:

sqrt(x) = nan

Функции atexit(), exit() и abort()

<stdlib.h>

int atexit(void (*func)(void));

Регистрирует функции, вызываемые при нормальном завершении работы программы в порядке, обратном их регистрации. Можно зарегистрировать до 32 функций.

_Noreturn void exit(int exit_code);

Вызывает нормальное завершение программы, возвращает в среду число exit_code. ISO C стандарт определяет всего три возможных значения: 0, EXIT_SUCCESS и EXIT_FAILURE. При этом вызываются функции, зарегистрированные через atexit(), сбрасываются и закрываются потоки ввода — вывода, уничтожаются временные файлы, после чего управление передаётся в среду. Функция exit() вызывается в main() при выполнении return или достижении конца программы.

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

Пример:

/*
//  main.c
//  exit example
//
//  Created by Ariel Feinerman on 17/03/17.
//  Copyright  2017 Feinerman Research, Inc. All rights reserved.
*/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

void third_2(void) 
{
    printf("third #2n");          // Does not print.
}

void third_1(void) 
{
    printf("third #1n");          // Does not print.
}

void second(double num) 
{
    printf("second : before exit()n");	// Prints.
    
    if ((num < 1.0f) && (num > -1.0f)) {
        printf("asin(%.1f) = %.3fn", num, asin(num));
        exit(EXIT_SUCCESS);
    }
    else {
        fprintf(stderr, "Error: %.1f is beyond the range [-1.0; 1.0]n", num);
        exit(EXIT_FAILURE);
    }
    
    printf("second : after exit()n");	// Does not print.
}

void first(double num) 
{
    printf("first : before second()n")
    second(num);
    printf("first : after second()n");          // Does not print.
}

int main(int argc, const char * argv[]) 
{
    atexit(third_1); // Register first handler. 
    atexit(third_2); // Register second handler.
    
    first(-3.0f);
    
    return EXIT_SUCCESS;
}

Вывод:

first : before second()
second : before exit()
Error: -3.0 is beyond the range [-1.0; 1.0]
third #2
third #1

_Noreturn void abort(void);

Вызывает аварийное завершение программы, если сигнал не был перехвачен обработчиком сигналов. Временные файлы не уничтожаются, закрытие потоков определяется реализацией. Самое главное отличие вызовов abort() и exit(EXIT_FAILURE) в том, что первый посылает программе сигнал SIGABRT, его можно перехватить и произвести нужные действия перед завершением программы. Записывается дамп памяти программы (core dump file), если они разрешены. При запуске в отладчике он перехватывает сигнал SIGABRT и останавливает выполнение программы, что очень удобно в отладке.

Пример:

/*
//  main.c
//  abort example
//
//  Created by Ariel Feinerman on 17/02/17.
//  Copyright  2017 Feinerman Research, Inc. All rights reserved.
*/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

void third_2(void) 
{
    printf("third #2n");          // Does not print.
}

void third_1(void) 
{
    printf("third #1n");          // Does not print.
}

void second(double num) 
{
    printf("second : before exit()n");	// Prints.
    
    if ((num < 1.0f) && (num > -1.0f)) {
        printf("asin(%.1f) = %.3fn", num, asin(num));
        exit(EXIT_SUCCESS);
    }
    else {
        fprintf(stderr, "Error: %.1f is beyond the range [-1.0; 1.0]n", num);
        abort();
    }
    
    printf("second : after exit()n");	// Does not print.
}

void first(double num) 
{
    printf("first : before second()n");
    second(num);
    printf("first : after second()n");          // Does not print.
}

int main(int argc, const char * argv[]) 
{
    atexit(third_1); // register first handler 
    atexit(third_2); // register second handler
    
    first(-3.0f);
    
    return EXIT_SUCCESS;
}

Вывод:

first : before second()
second : before exit()
Error: -3.0 is beyond the range [-1.0; 1.0]
Abort trap: 6

Вывод в отладчике:

$ lldb abort_example 
(lldb) target create "abort_example"
Current executable set to 'abort_example' (x86_64).
(lldb) run
Process 22570 launched: '/Users/ariel/abort_example' (x86_64)
first : before second()
second : before exit()
Error: -3.0 is beyond the range [-1.0; 1.0]
Process 22570 stopped
* thread #1: tid = 0x113a8, 0x00007fff89c01286 libsystem_kernel.dylib`__pthread_kill + 10, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
    frame #0: 0x00007fff89c01286 libsystem_kernel.dylib`__pthread_kill + 10
libsystem_kernel.dylib`__pthread_kill:
->  0x7fff89c01286 <+10>: jae    0x7fff89c01290            ; <+20>
    0x7fff89c01288 <+12>: movq   %rax, %rdi
    0x7fff89c0128b <+15>: jmp    0x7fff89bfcc53            ; cerror_nocancel
    0x7fff89c01290 <+20>: retq   
(lldb) 

В случае критической ошибки нужно использовать функцию abort(). К примеру, если при выделении памяти или записи файла произошла ошибка. Любые дальнейшие действия могут усугубить ситуацию. Если завершить выполнение обычным способом, при котором производится сброс потоков ввода — вывода, можно потерять ещё неповрежденные данные и временные файлы, поэтому самым лучшим решением будет записать дамп и мгновенно завершить программу.

В случае же некритической ошибки, например, вы не смогли открыть файл, можно безопасно выйти через exit().

Функции setjmp() и longjmp()

Вот мы и подошли к самому интересному – функциям нелокальных переходов. setjmp() и longjmp() работают по принципу goto, но в отличие от него позволяют перепрыгивать из одного места в другое в пределах всей программы, а не одной функции.

<setjmp.h>

int setjmp(jmp_buf env);

Сохраняет информацию о контексте выполнения программы (регистры микропроцессора и прочее) в env. Возвращает 0, если была вызвана напрямую или value, если из longjmp().

void longjmp(jmp_buf env, int value);

Восстанавливает контекст выполнения программы из env, возвращает управление setjmp() и передаёт ей value.

Пример:

/*
//  main.c
//  setjmp simple
//
//  Created by Ariel Feinerman on 18/02/17.
//  Copyright  2017 Feinerman Research, Inc. All rights reserved.
*/

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>

static jmp_buf buf;

void second(void) 
{
    printf("second : before longjmp()n");	// prints
    longjmp(buf, 1);						// jumps back to where setjmp was called – making setjmp now return 1
    printf("second : after longjmp()n");	// does not prints
	
    // <- Here is the point that is never reached. All impossible cases like your own house in Miami, your million dollars, your nice girl, etc.
}

void first(void) 
{
    printf("first : before second()n");
    second();
    printf("first : after second()n");          // does not print
}

int main(int argc, const char * argv[]) 
{
    if (!setjmp(buf))
        first();                // when executed, setjmp returned 0
    else                        // when longjmp jumps back, setjmp returns 1
        printf("mainn");       // prints
    
    return EXIT_SUCCESS;
}

Вывод:

first : before second()
second : before longjmp()
main

Используя setjmp() и longjmp() можно реализовать механизм исключений. Во многих языках высокого уровня (например, в Perl) исключения реализованы через них.

Пример:

/*
//  main.c
//  exception simple
//
//  Created by Ariel Feinerman on 18/02/17.
//  Copyright  2017 Feinerman Research, Inc. All rights reserved.
*/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include <setjmp.h>

#define str(s) #s

static jmp_buf buf;

typedef enum {
    NO_EXCEPTION    = 0,
    RANGE_EXCEPTION = 1,
    NUM_EXCEPTIONS
} exception_t;

static char *exception_name[NUM_EXCEPTIONS] = {
	
    str(NO_EXCEPTION),
    str(RANGE_EXCEPTION)
};

float asin_e(float num) 
{
    if ((num < 1.0f) && (num > -1.0f)) {
        return asinf(num);
    }	
    else {
        longjmp(buf, RANGE_EXCEPTION);        // | @throw  
    }
}

void do_work(float num) 
{
    float res = asin_e(num);
    printf("asin(%f) = %fn", num, res);         
}

int main(int argc, const char * argv[]) 
{
    exception_t exc = NO_EXCEPTION;
    if (!(exc = setjmp(buf))) {        // |	
        do_work(-3.0f);                // | @try
    }                                  // |
    else {                                                                               // | 
        fprintf(stderr, "%s was hadled in %s()n", exception_name[exc], __func__);       // | @catch
    }                                                                                    // | 
	
    return EXIT_SUCCESS;
}

Вывод:

RANGE_EXCEPTION was hadled in main()

Внимание! Функции setjmp() и longjmp() в первую очередь применяются в системном программировании, и их использование в клиентском коде не рекомендуется. Их применение ухудшает читаемость программы и может привести к непредсказуемым ошибкам. Например, что произойдёт, если вы прыгните не вверх по стеку – в вызывающую функцию, а в параллельную, уже завершившую выполнение?

Информация

  • стандарт ISO/IEC C (89/99/11)
  • Single UNIX Specifcation, Version 4, 2016 Edition
  • The Open Group Base Specifcations Issue 7, 2016 Edition (POSIX.1-2008)
  • SEI CERT C Coding Standard
  • cправочная информация среды программирования
  • справочная информация операционной системы (man pages)
  • заголовочные файлы (/usr/include)
  • исходные тексты библиотеки (C Standard Library)

Is there a way that i can get the corresponding error code of an Exceptions ?
I need the thrown exceptions error code instead of its message , so that i based on the error code i show the right message to the user.

asked Mar 17, 2013 at 15:42

Hossein's user avatar

3

If you’re looking for the win32 error code, that’s available on the Win32Exception class

catch (Win32Exception e)
{  
    Console.WriteLine("ErrorCode: {0}", e.ErrorCode);
}

For plain old CLR exception, there is no integer error code.

Given the problem you describe, I’d go with millimoose‘s solution for getting resource strings for each type of exception.

answered Mar 17, 2013 at 15:45

p.s.w.g's user avatar

p.s.w.gp.s.w.g

146k30 gold badges289 silver badges327 bronze badges

The whole point of exceptions is that they provide richer information than just an error code. By default, they don’t have one, and don’t really need one. If you like using error codes you can just use your own exception base class that you derive all your exceptions from:

public abstract class MyExceptionBase : Exception 
{
    public int ErrorCode { get; set; }
    // ...
}

That said, I wouldn’t bother. Personally I map exceptions to error messages using their type name:

ResourceManager errorMessages = ...;
errorMessages.GetString(ex.GetType().FullName);

(You can also create more flexible schemes, like make the resources format strings and interpolate exception properties into them.)

answered Mar 17, 2013 at 15:47

millimoose's user avatar

millimoosemillimoose

38.9k9 gold badges81 silver badges134 bronze badges

7

For a COM exception that is upgraded to a Managed exception, you will be able to retrieve the «error code» from the HResult property as such:

try {
    // code goes here
} catch(System.IO.FileNotFoundException ex) {
    Console.WriteLine(
        String.Format("(HRESULT:0x{1:X8}) {0}",
                      ex.Message,
                      ex.HResult)
    );
}

Not all exceptions however will have a meaningful HResult set however.


For .NET 3.0, 3.5 and 4.0 you will have to use reflection to get the value of the HResult property as it is marked protected.

answered Mar 17, 2013 at 15:47

Andrew Moore's user avatar

Andrew MooreAndrew Moore

93.1k30 gold badges163 silver badges175 bronze badges

5

Исключения, ориетированные на коды ошибок

Доброго времени суток! В этой статье я хочу показать на практике, как в C# можно создать класс-исключение ориентированный на использование кодов ошибок. Сейчас я чуть подробнее расскажу что имею ввиду и зачем это нужно, а потом, приступим к делу.

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

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

//Класс, для описания некого человека
class Person
{
    //Конструктор (принимает имя, возраст и рост)
    public Person(string aName, int anAge, int aHeight)
    {
        name = aName;

        //Если указан отрицательный возраст
        if (anAge < 0)
            throw new PersonException(PersonException.NEGATIVE_AGE); 

        age = anAge;

        //Если указан отрицательный рост
        if (aHeight < 0)
            throw new PersonException(PersonException.NEGATIVE_HEIGHT);

        height = aHeight;
    }

    //Возвращает, или устанавливает имя
    public string Name { get { return name; } set { name = value; } }
 
    //Возвращает, или устанавливает возраст
    public int Age 
    { 
        get { return age; } 
        set 
        {
            //Если указан отрицательный возраст
            if (value < 0)
                throw new PersonException(PersonException.NEGATIVE_AGE); 

            age = value; 
        } 
    }

    //Возвращает, или устанавливает рост
    public int Height 
    { 
        get { return height; } 
        set 
        {
            //Если указан отрицательный рост
            if (value < 0)
                throw new PersonException(PersonException.NEGATIVE_HEIGHT);

            height = value; 
        } 
    }
 
    private string name; //Имя
    private int age;     //Возраст 
    private int height;  //Рост
}

//Класс, для описания пользовательского типа ошибок
class PersonException : Exception //Используем наследование
{
    //Массив описаний ошибок
    private static string[] descriptions = new string[] 
    {
        "",
        "Указано отрицательное число в качестве возраста",
        "Указано отрицательное число в качестве роста"
    };

    //Константы-коды ошибок
    public const int NO_CODE = 0; //Не указан код ошибки 
    public const int NEGATIVE_AGE = 1; //Отрицательный возраст
    public const int NEGATIVE_HEIGHT = 2; //Отрицательный рост

    //Конструктор. Принимает код ошибки в качестве аргумента
    public PersonException(int anErrorCode) 
    {
        errorCode = anErrorCode;
    }

    //Возвращает текстовое описание ошибки
    public override string Message
    {
        get
        {
            //Если указан некорректный код ошибки, или вообще не указан
            if (errorCode < 1 || errorCode > descriptions.Length - 1)
                return descriptions[0];

           return descriptions[errorCode];
        }
    }

    //Возвращает код ошибки
    public int ErrorCode { get { return errorCode; } }

    //Код ошибки
    private int errorCode;
}

В примере описаны два класса, один («Person») отвечает за бизнес логику, а второй («PersonException») служебный — для представления ошибок.  В классе «PersonException» есть поле, представляющее код ошибки, значение которому, задается при создании объекта (используется класса «Person» в конструкторе и двух свойствах). Причем, код ошибки, является не только числовым кодом, но и индексом, для получения строкового описания ошибки из статического массива «descriptions», объявленного в классе. Это строковое описание возвращает пользователю класса, переопределенное, виртуальное свойство «Message». Но это свойство вернет описание только в том случае, если код ошибки указан корректно, т.е. не «взят с потолка» а является одним из допустимых. Набор допустимых кодов ошибок представлен открытыми константами класса.

Обратите внимание, чтобы не получить исключительную ситуацию в свойстве «Message», сначала проверяется корректность кода ошибки, и если он не выходит за пределы массива описаний, тогда возвращается описание по коду. В противном случае, или если код ошибки равен «0» возвращается пустая строка.

Такой подход позволяет не создавать снова и снова новые классы для представления исключительных ситуаций, а просто расширять список кодов ошибок и их описаний в рамках одного класса. А точно идентифицировать тип обрабатываемой в блоке catch ошибки, можно будет по её коду.

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

5 ответов

попробуйте это, чтобы проверить исключение и внутреннее для исключения Win32Exception.

catch (Exception e){  
    var w32ex = e as Win32Exception;
    if(w32ex == null) {
        w32ex = e.InnerException as Win32Exception;
    }    
    if(w32ex != null) {
        int code =  w32ex.ErrorCode;
        // do stuff
    }    
    // do other stuff   
}

Как и в комментариях, вам действительно нужно увидеть, какое исключение действительно бросается, чтобы понять, что вы можете сделать, и в этом случае предпочтение отдается предпочтению только для исключения Exception. Что-то вроде:

  catch (BlahBlahException ex){  
      // do stuff   
  }

Также System.Exception имеет HRESULT

 catch (Exception ex){  
     int code = ex.HResult;
 }

Preet Sangha
01 авг. 2011, в 01:10

Поделиться

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

Я бы также посмотрел, указывает ли документация InvokeMethod на то, выбрал ли он более специализированный класс исключений, чем Exception, — например, исключение Win32Exception, предлагаемое @Preet. Захват и просто просмотр базового класса Exception может оказаться не особенно полезным.

iandotkelly
01 авг. 2011, в 01:10

Поделиться

Основываясь на решении Preet Sangha, следующее должно безопасно охватить сценарий, в котором вы работаете с большим решением с возможностью нескольких внутренних исключений.

 try
 {
     object result = processClass.InvokeMethod("Create", methodArgs);
 }
 catch (Exception e)
 {
     // Here I was hoping to get an error code.
     if (ExceptionContainsErrorCode(e, 10004))
     {
         // Execute desired actions
     }
 }

private bool ExceptionContainsErrorCode(Exception e, int ErrorCode)
{
    Win32Exception winEx = e as Win32Exception;
    if (winEx != null && ErrorCode == winEx.ErrorCode) 
        return true;

    if (e.InnerException != null) 
        return ExceptionContainsErrorCode(e.InnerException, ErrorCode);

    return false;
}

Этот код был протестирован модулем.

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

Thomas N.
01 фев. 2017, в 16:14

Поделиться

Я предлагаю вам использовать Message Properte из объекта исключения. Как ниже код

try
{
 object result = processClass.InvokeMethod("Create", methodArgs);
}
catch (Exception e)
{  
    //use Console.Write(e.Message); from Console Application 
    //and use MessageBox.Show(e.Message); from WindowsForm and WPF Application
}

user4509170
29 янв. 2015, в 23:38

Поделиться

Другой метод — получить код ошибки из класса исключений напрямую. Например:

catch (Exception ex)
{
    if (ex.InnerException is ServiceResponseException)
       {
        ServiceResponseException srex = ex.InnerException as ServiceResponseException;
        string ErrorCode = srex.ErrorCode.ToString();
       }
}

GadgetNC
03 апр. 2017, в 13:31

Поделиться

Ещё вопросы

  • 0Почему значение файла cookie, который я устанавливаю (строка), увеличивается на единицу при установке из $ _GET?
  • 1в Websphere я не могу получить переменные JVM
  • 1Проблемы при построении временных рядов против пользовательских логинов?
  • 0Проверка DOB проверки JQuery работает в Chrome, но не работает в Safari
  • 1Доступ к Android Входящие / Сообщения из Активности?
  • 0Модульное тестирование отсутствия элемента dom
  • 0Получить строку, где строка содержит php pdo
  • 1Позвоните классу через минуту после инициализации Eclipse
  • 1Как исправить «java.io.IOException: чтение не удалось, сокет может быть закрыт или истекло время ожидания» при попытке подключения к сопряженному устройству?
  • 1Использование экземпляра одноэлементного класса в качестве переменной уровня класса приемлемо?
  • 0ложный HTTP-ответ с обещаниями и возвращением фиктивных данных
  • 0CakePHP генерирует ссылку, параметр аргумента которой определяется входом select
  • 1Как извлечь данные из CDATA в Python и BeautifulSoup?
  • 0Ошибка входа в Java
  • 1Как динамически получать изображения подушек в Tkinter
  • 0Должен ли я избегать перенаправления в классах реализации при переносе API
  • 0Как очистить ng-repeat перед фильтрацией
  • 0Когда письмо отправляется с живого письма с китайским содержимым, тело сообщения отображается с некоторыми специальными символами
  • 0Как использовать угловой повтор с ng-grid и щелкнуть мышью?
  • 1Клиент обычно отключается от сервера через несколько десятков минут
  • 1Модульное тестирование AlertDialog — java.lang.VerifyError: Неверный тип в стеке операндов
  • 1Озадаченный чем-то в методе .reduce () (Javascript)
  • 1Почему я получаю ошибку значения в моей функции определения местоположения?
  • 0Удаление различий между объектами и массивами из разных массивов
  • 0Считать значения списка радиокнопок
  • 1HTML 5 Геолокация не работает в Safari
  • 1Python OpenCV предупреждение libpng: iCCP: известен неправильный профиль sRGB
  • 0Динамическое отображение .NET 4.5 и атрибут ошибки
  • 0как помешать yii2 выполнить запрос select при вызове Yii :: $ app-> user-> id
  • 1Tomcat 8 — виртуальный хост не перекомпилирует JSP или не распознает обновленные классы
  • 1Как: создать GridSplitter, который настраивает размер DockPanel (C #, WPF)
  • 1Кнопка Windows Forms: изображение или ImageList + ImageIndex?
  • 0Обработчик событий Jquery не работает при вызове со страницы
  • 0Установка переменной в число на основе результата производной таблицы
  • 0QDialog закрашивает виджеты черным при добавлении новых
  • 0JPA вернул объект, выбрасывающий внутреннюю ошибку сервера
  • 0Алгоритм, шаблон или лучшие практики для маршалинга и ожидания во множестве взаимозависимых потоков?
  • 1Java — проблемы с подстрокой
  • 0MySQL JSON заменить строку в целое число
  • 1Групер и ось должны быть одинаковой длины в Python
  • 0холст изображение на php
  • 0Magento получить значение атрибута выпадающего с php
  • 0COM-функция возвращает E_POINTER
  • 1Ошибка Play Console 403 после принятия приглашения
  • 1Изменение семейства шрифтов в OpenCV Python с использованием PIL
  • 1Migradoc динамический размер страницы
  • 0координаты мыши только с круглыми числами — функция jquery
  • 0Альтернатива нулевой структуре размера
  • 1Какая стратегия используется для демаршалинга XML-файлов с помощью JAXB?

Исключения

В нашем разговоре о потоке исполнения команд различными подсистемами пришло время поговорить про исключения или, скорее, исключительные ситуации. И прежде чем продолжить стоит совсем немного остановиться именно на самом определении. Что такое исключительная ситуация?

Исключительной называют такую ситуацию, которая делает исполнение дальнейшего кода абсолютно не корректным. Не таким как задумывалось, проектировалось. Переводит состояние приложения в целом или же его отдельной части (например, объекта) в состояние нарушенной целостности. Т.е. что-то экстраординарное, исключительное.

Почему же это так важно — определить терминологию? Работа с терминологией очень важна, т.к. она держит нас в рамках, а работу кода — предсказуемой для стороннего разработчика. Если не следовать терминологии можно уйти далеко от созданного проектировщиками концепта и получить множество неоднозначных ситуаций как для себя так и для других. А чтобы закрепить понимание вопроса давайте обратимся к примеру:

 struct Number
 {
     // Переводит строковое представление в численное
     public static Number Parse(string source)
     {
         // ...
         if(!parsed)
         {
             throw new ParsingException();
         }
         // ...
     }

     // Переводит строковое представление в численное
     public static bool TryParse(string source, out Number result)
     {
        // ..
        return parsed;
     }
 }

Этот пример кажется немного странным: и это не просто так. Для того чтобы показать исключительность проблем, возникающих в данном коде я сделал его несколько утрированным. Для начала посмотрим на метод Parse. Почему он должен выбрасывать исключение?

  • Он принимает в качестве параметра строку, а в качестве результата — некоторое число, которое является значимым типом. По этому числу мы никак не можем определить, является ли оно результатом корректных вычислений или же нет: оно просто есть. Другими словами, в интерфейсе метода отсутствует возможность сообщить о проблеме;
  • С другой стороны метод, принимая строку подразумевает что её для него корректно подготовили: там нет лишних символов и строка содержит некоторое число. Если это не так, то возникает проблема предусловий к методу: тот код, который вызвал наш метод отдал ему не корректные данные.

Получается, что для данного метода ситуация получения строки с не корректными данными является исключительной: метод не может вернуть корректного значения, но и вернуть абы что он не может. А потому единственный выход для него — бросить исключение.

Второй вариант метода обладает каналом сигнализации о наличии проблем с входными данными: возвращаемое значение тут boolean и является признаком успешности выполнения метода. Сигнализировать о каких-либо проблемах при помощи механизма исключений данный метод не имеет ни малейшего повода: все виды проблем легко уместятся в возвращаемое значение false.

Общая картина

Обработка исключительных ситуаций может показаться вопросом достаточно элементарным: ведь все что нам необходимо сделать — это установить try-catch блоки и ждать соответствующего события. Однако вопрос кажется элементарным только благодаря огромной работе, проделанной командами CLR и CoreCLR чтобы унифицировать все ошибки, которые лезут в CLR со всех щелей — из самых разных источников. Чтобы иметь представление, о чем мы будем далее вести беседу, давайте взглянем на диаграмму:

устарело: нет Linux

На этой схеме мы видим, что в .NET существует по сути два мира: все, что относится к CLR и все, что находится за ней: все возможные ошибки, возникающие в Windows / Linux и прочем unsafe мире:

  • Structured Exception Handling (SEH) — структурированная обработка исключений — стандарт платформы Windows для обработки исключений. Во время вызовов unsafe методов и последующем выбросе исключений происходит конвертация исключений unsafe <-> CLR в обе стороны: из unsafe в CLR и обратно, т.к. CLR может вызвать unsafe метод, а тот в свою очередь — CLR метод.
  • Vectored Exception Handling (VEH) — по своей сути является корнем SEH, позволяя вставлять свои обработчики в точку выброса исключения. Используется в частности для установки FirstChanceException.
  • COM+ исключения — когда источником проблемы является некоторый COM компонент, то прослойка между COM и .NET методом должна сконвертировать COM ошибку в исключение .NET
  • И, наконец, обёртки для HRESULT. Введены для конвертации модели WinAPI (код ошибки — в возвращаемом значении, а возвращаемые значения — через параметры метода) в модель исключений: для .NET в отличии от операционных систем стандартом является именно исключительная ситуация

С другой стороны, поверх CLI располагаются языки программирования, каждый из которых частично или же полностью — предлагает функционал по обработке исключений конечному пользователю языка. Так, например, языки VB.NET и F# до недавнего времени обладали более богатым функционалом по части обработки исключительных ситуаций, предлагая функционал фильтров, которых не существовало в языке C#.

Коды возврата vs. исключение

Стоит отдельно отметить, что можно вообще обойтись без исключений. Не выбрасывать их никогда, всегда работая на кодах возврата. Эта идея может показаться странной: мы ведь все привыкли к тому, что исключения есть, их много и их выбрасывают практически все классы нашей необъятной платформы. Однако, если не отворачиваться, а порассуждать, то можно прийти к выводам, что работа без исключений, возможно даже, более удобная и безопасная чем с ними.

Сами посудите: в случае исключения код будет разорван, прерван и эту поломку сможет перехватить только некий другой код выше во стеку. Причём влетая туда, в этот catch блок вы можете получить приложение, находящееся в не консистентном состоянии (например, если выброшено было не предусмотренное исключение, а блок перехватывал по типу базового класса). Плюс, если логика написана с использованием кодов ошибки, которые оформлены в виде enum, пользователь метода видит и возможность самой ошибки, а также может сразу понять, какие ошибки возможны (перейдя на определение enum).

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

Блоки Try-Catch-Finally коротко

Блок try создаёт секцию, от которой программист ожидает возникновения критических ситуаций, которые с точки зрения внешнего кода являются нормой поведения. Т.е. другими словами, если мы работаем с некоторым кодом, который в рамках своих правил считает внутреннее состояние более не консистентным и в связи с этим выбрасывает исключение, то внешняя система, которая обладает более широким видением той же ситуации возникшее исключение может перехватить блоком catch тем самым нормализовав исполнение кода приложения. А потому, перехватом исключений вы легализуете их наличие на данном участке кода. Это, на мой взгляд, очень важная мысль, которая обосновывает запрет на перехват всех типов исключений try-catch(Exception ex){ ... } на всякий случай.

Это вовсе не означает, что перехватывать исключения идеологически плохо: я всего лишь хочу сказать о необходимости перехватывать то и только то, что вы ожидаете от конкретного участка кода и ничего больше. Например, вы не можете ожидать все типы исключений, которые наследуется от ArgumentException или же получение NullReferenceException поскольку это означает что проблема чаще всего не в вызываемом коде, а в вашем. Зато вполне корректно ожидать, что желаемый файл открыть вы не сможете. Даже если на 200% уверены, что сможете, не забудьте сделать проверку.

Третий блок — finally — также не должен нуждаться в представлении. Этот блок срабатывает для всех случаев работы блоков try-catch. Кроме некоторых достаточно редких особых ситуаций, этот блок отрабатывает всегда. Для чего введена такая гарантия исполнения? Для зачистки тех ресурсов и тех групп объектов, которые были выделены или же захвачены в блоке try и при этом являются зоной его ответственности.

Этот блок очень часто используется без блока catch, когда нам не важно, какая ошибка уронила алгоритм, но важно очистить все выделенные для этого конкретно алгоритма ресурсы. Простой пример: для алгоритма копирования файла необходимо: два открытых файла и участок памяти под кэш-буфер копирования. Память мы выделить смогли, один файл открыть смогли, а вот со вторым возникли какие-то проблемы. Чтобы запечатать все в одну «транзакцию», мы помещаем все три операции в единый try блок (как вариант реализации), с очисткой ресурсов — в finally. Пример может показаться упрощённым, но тут главное — показать суть.

Чего не хватает в языке программирования C#, так это блока fault, суть которого — срабатывать всегда, когда произошла любая ошибка. Т.е. тот же finally, только на стероидах. Если бы такое было, мы бы смогли, как классический пример делать единую точку входа в логирование исключительных ситуаций:

try {
    //...
} fault exception
{
    _logger.Warn(exception);
}

Также, о чем хотелось бы упомянуть во вводной части — это фильтры исключительных ситуаций. Для платформы .NET это новшеством не является, однако является таковым для разработчиков на языке программирования C#: фильтрация исключительных ситуаций появилась у нас только в шестой версии языка. Фильтры призваны нормализовать ситуацию, когда есть единый тип исключения, который объединяет в себе несколько видов ошибок. И в то время как мы хотим отрабатывать на конкретный сценарий, вынуждены перехватывать всю группу и фильтровать её — уже после перехвата. Я, конечно же, имею в виду код следующего вида:

try {
    //...
}
catch (ParserException exception)
{
    switch(exception.ErrorCode)
    {
        case ErrorCode.MissingModifier:
            // ...
            break;
        case ErrorCode.MissingBracket:
            // ...
            break;
        default:
            throw;
    }
}

Так вот теперь мы можем переписать этот код нормально:

try {
    //...
}
catch (ParserException exception) when (exception.ErrorCode == ErrorCode.MissingModifier)
{
    // ...
}
catch (ParserException exception) when (exception.ErrorCode == ErrorCode.MissingBracket)
{
    // ...
}

И вопрос улучшения тут вовсе не в отсутствии конструкции switch. Новая конструкция как по мне лучше по нескольким пунктам:

  • фильтруя по when мы перехватываем ровно то что хотим поймать и не более того. Это правильно идеологически;
  • в новом виде код стал более читаем. Просматривая взглядом, мозг более легко находит определения ошибок, т.к. изначально он их ищет не в switch-case, а в catch;
  • и менее явное, но также очень важное: предварительное сравнение идёт ДО входа в catch блок. А это значит, что работа такой конструкции для случая промаха мимо всех условий будет идти намного быстрее чем switch с перевыбросом исключения.

Особенностью исполнения кода является то, что код фильтрации происходит до того как произойдёт развёртка стека. Это можно наблюдать в ситуациях, когда между местом выброса исключения и местом проверки на фильтрацию нет никаких других вызовов кроме обычных:

static void Main()
{
    try
    {
        Foo();
    }
    catch (Exception ex) when (Check(ex))
    {
        ;
    }
}

static void Foo()
{
    Boo();
}

static void Boo()
{
    throw new Exception("1");
}

static bool Check(Exception ex)
{
    return ex.Message == "1";
}

Stack without unrolling

Как видно на изображении трассировка стека содержит не только первый вызов Main как место отлова исключительной ситуации, но и весь стек до точки выброса исключения плюс повторный вход в Main через некоторый неуправляемый код. Можно предположить, что этот код и есть код выброса исключений, который просто находится в стадии фильтрации и выбора конечного обработчика. Однако стоит отметить что не все вызовы позволяют работать без раскрутки стека. На мой скромный взгляд, внешняя унифицированность платформы порождает излишнее к ней доверие. Например, вызов методов между доменами с точки зрения кода выглядит абсолютно прозрачно. Тем не менее, работа вызовов методов происходит совсем по другим законам. О них мы и поговорим в следующей части.

Сериализация

Давайте начнём несколько издалека и посмотрим на результаты работы следующего кода (я добавил проброс вызова через границу между доменами приложения):

    class Program
    {
        static void Main()
        {
            try
            {
                ProxyRunner.Go();
            }
            catch (Exception ex) when (Check(ex))
            {
                ;
            }
        }

        static bool Check(Exception ex)
        {
            var domain = AppDomain.CurrentDomain.FriendlyName; // -> TestApp.exe
            return ex.Message == "1";
        }

        public class ProxyRunner : MarshalByRefObject
        {
            private void MethodInsideAppDomain()
            {
                throw new Exception("1");
            }

            public static void Go()
            {
                var dom = AppDomain.CreateDomain("PseudoIsolated", null, new AppDomainSetup
                {
                    ApplicationBase = AppDomain.CurrentDomain.BaseDirectory
                });
                var proxy = (ProxyRunner) dom.CreateInstanceAndUnwrap(typeof(ProxyRunner).Assembly.FullName, typeof(ProxyRunner).FullName);
                proxy.MethodInsideAppDomain();
            }
        }
    }

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

StackUnroll

А второй — после:

StackUnroll2

Изучим трассировку вызовов до и после попадания в фильтр исключений. Что же здесь происходит? Здесь мы видим, что разработчики платформы сделали некоторую с первого взгляда защиту дочернего домена. Трассировка обрезана по крайний метод в цепочке вызовов, после которого идёт переход в другой домен. Но на самом деле, как по мне так это выглядит несколько странно. Чтобы понять, почему так происходит, вспомним основное правило для типов, организующих взаимодействие между доменами. Эти типы должны наследовать MarshalByRefObject плюс — быть сериализуемыми. Однако, как бы ни был строг C#, типы исключений могут быть какими угодно. А что это значит? Это значит, что могут быть ситуации, когда исключительная ситуация внутри дочернего домена может привести к её перехвату в родительском домене. И если у объекта данных исключительной ситуации есть какие-либо опасные методы с точки зрения безопасности, они могут быть вызваны в родительском домене. Чтобы такого избежать, исключение сериализуется, проходит через границу доменов приложений и возникает вновь — с новым стеком. Давайте проверим эту стройную теорию:

[StructLayout(LayoutKind.Explicit)]
class Cast
{
    [FieldOffset(0)]
    public Exception Exception;

    [FieldOffset(0)]
    public object obj;
}

static void Main()
{
    try
    {
        ProxyRunner.Go();
        Console.ReadKey();
    }
    catch (RuntimeWrappedException ex) when (ex.WrappedException is Program)
    {
        ;
    }
}

static bool Check(Exception ex)
{
    var domain = AppDomain.CurrentDomain.FriendlyName; // -> TestApp.exe
    return ex.Message == "1";
}

public class ProxyRunner : MarshalByRefObject
{
    private void MethodInsideAppDomain()
    {
        var x = new Cast {obj = new Program()};
        throw x.Exception;
    }

    public static void Go()
    {
        var dom = AppDomain.CreateDomain("PseudoIsolated", null, new AppDomainSetup
        {
            ApplicationBase = AppDomain.CurrentDomain.BaseDirectory
        });
        var proxy = (ProxyRunner)dom.CreateInstanceAndUnwrap(typeof(ProxyRunner).Assembly.FullName, typeof(ProxyRunner).FullName);
        proxy.MethodInsideAppDomain();
    }
}

В данном примере, для того чтобы выбросить исключение любого типа из C# кода (я не хочу никого мучить вставками на MSIL) был проделан трюк с приведением типа к не сопоставимому: чтобы мы бросили исключение любого типа, а транслятор C# думал бы, что мы используем тип Exception. Мы создаём экземпляр типа Program — гарантированно не сериализуемого и бросаем исключение с ним в виде полезной нагрузки. Хорошие новости заключаются в том, что вы получите обёртку над не-Exception исключениями RuntimeWrappedException, который внутри себя сохранит экземпляр нашего объекта типа Program и в C# перехватить такое исключение мы сможем. Однако есть и плохая новость, которая подтверждает наше предположение: вызов proxy.MethodInsideAppDomain(); приведёт к исключению SerializationException:

Т.е. проброс между доменами такого исключения не возможен, т.к. его нет возможности сериализовать. А это в свою очередь значит, что оборачивание вызовов методов, находящихся в других доменах фильтрами исключений все равно приведёт к развёртке стека несмотря на то что при FullTrust настройках дочернего домена сериализация казалось бы не нужна.

Стоит дополнительно обратить внимание на причину, по которой сериализация между доменами так необходима. В нашем синтетическом примере мы создаём дочерний домен, который не имеет никаких настроек. А это значит, что он работает в FullTrust. Т.е. CLR полностью доверяет его содержимому как себе и никаких дополнительных проверок делать не будет. Но как только вы выставите хоть одну настройку безопасности, полная доверенность пропадёт и CLR начнёт контролировать все что происходит внутри этого дочернего домена. Так вот когда домен полностью доверенный, сериализация по идее не нужна. Нам нет необходимости как-то защищаться, согласитесь. Но сериализация создана не только для защиты. Каждый домен грузит в себя все необходимые сборки по второму разу, создавая их копии. Тем самым создавая копии всех типов и всех таблиц виртуальных методов. Передавая объект по ссылке из домена в домен, вы получите, конечно, тот же объект. Но у него будут чужие таблицы виртуальных методов и как результат — этот объект не сможет быть приведён к другому типу. Другими словами, если вы создали экземпляр типа Boo, то получив его в другом домене приведение типа (Boo)boo не сработает. А сериализация и десериализация решает проблему: объект будет существовать одновременно в двух доменах. Там где он был создан — со всеми своими данными и в доменах использования — в виде прокси-объекта, обеспечивающего вызов методов оригинального объекта.

Передавая сериализуемый объект между доменами, вы получите в другом домене полную копию объекта из первого сохранив некоторую разграниченность по памяти. Разграниченность тоже мнимая. Она — только для тех типов, которые не находятся в Shared AppDomain. Т.е., например, если в качестве исключения бросить что-нибудь несериализуемое, но из Shared AppDomain, то ошибки сериализации не будет (можно попробовать вместо Program бросить Action). Однако раскрутка стека при этом все равно произойдёт: оба случая должны работать стандартно. Чтобы никого не путать.


Форум программистов Vingrad

Модераторы: Partizan, gambit

Поиск:

Ответ в темуСоздание новой темы
Создание опроса
> C# — получение кода системной ошибки, Как C#-коду получить код системн. ошибки 

V

   

Опции темы

valent_N
Дата 3.1.2006, 15:18 (ссылка)
| (нет голосов)
Загрузка ... Загрузка …




Быстрая цитата

Цитата

Шустрый
*

Профиль
Группа: Участник
Сообщений: 64
Регистрация: 9.8.2003
Где: Рига, Латвия

Репутация: нет
Всего: 1

Здравствуйте!
C#-программа должна обрабатывать ошибки с учетом их кода. Какой культурный способ существует для получения кода системной ошибки и сброса ее, аналогично тому, как это позволяют сделать Win32 API-функции GetLastError() и SetLastError(…)? Или, все же, надо вызывать именно их, обеспечив для них соответствующие прототипы? Или же надо прибегнуть к исключению? Как тогда получить из экземпляра исключения код ошибки? — я не смог найти в MSDN такую информацию.
Большое Спасибо

PM MAIL   Вверх
Void
Дата 3.1.2006, 15:23 (ссылка)
| (нет голосов)
Загрузка ... Загрузка …




Быстрая цитата

Цитата

λcat.lolcat
****

Профиль
Группа: Участник Клуба
Сообщений: 2206
Регистрация: 16.11.2004
Где: Zürich

Репутация: 25
Всего: 173

Посмотрите на метод System.Runtime.InteropServices.Marshal.GetLastWin32Error и класс исключения System.ComponentMode.Win32Exception.

———————

“Coming back to where you started is not the same as never leaving.” — Terry Pratchett

PM MAIL WWW GTalk   Вверх
WTF
Дата 3.1.2006, 15:30 (ссылка)
| (нет голосов)
Загрузка ... Загрузка …




Быстрая цитата

Цитата

Новичок

Профиль
Группа: Участник
Сообщений: 5
Регистрация: 30.12.2005

Репутация: нет
Всего: нет

Не совсем понятно о каких системных ошибках идет речь. Наиболее распространенные ошибки реализованы в виде исключений (exceptions). Например:

Код

try
{
    // этот код генерирует ошибку
    DoSomthing();
}
catch(OutOfMemoryException ex)
{
    // обработка ошибки
}

Если стандартные классы исключений по како-то причине тебе не подходят — у класса System.Exception есть свойсво HRESULT который содержит код ошибки.

Сбрасывать ошибку не надо.

PM MAIL WWW   Вверх
Exception
Дата 3.1.2006, 15:53 (ссылка)
| (нет голосов)
Загрузка ... Загрузка …




Быстрая цитата

Цитата

Эксперт
****

Профиль
Группа: Участник Клуба
Сообщений: 4525
Регистрация: 26.12.2004

Репутация: 29
Всего: 186

Цитата(WTF @ 3.1.2006, 16:30)
Сбрасывать ошибку не надо.

Это он говорит о стандартных ошибках Win32.
Полностью согласен с
Void.
А вообще не пойму, что мешает пользоваться

Цитата(valent_N @ 3.1.2006, 16:18)
GetLastError() и SetLastError(…)

?

PM   Вверх
Void
Дата 3.1.2006, 15:55 (ссылка)
| (нет голосов)
Загрузка ... Загрузка …




Быстрая цитата

Цитата

λcat.lolcat
****

Профиль
Группа: Участник Клуба
Сообщений: 2206
Регистрация: 16.11.2004
Где: Zürich

Репутация: 25
Всего: 173

Цитата(Exception @ 3.1.2006, 17:53)
А вообще не пойму, что мешает пользоваться

Читай документацию на Marshal.GetLastWin32Error smile Там ясно сказано:

Цитата
GetLastWin32Error exposes the Win32 GetLastError API method from Kernel32.DLL. This method exists because it is not safe to make a direct platform invoke call to GetLastError to obtain this information. If you want to access this error code, you must call GetLastWin32Error rather than writing your own platform invoke definition for GetLastError and calling it. The common language runtime can make internal calls to APIs that overwrite the operating system maintained GetLastError.

———————

“Coming back to where you started is not the same as never leaving.” — Terry Pratchett

PM MAIL WWW GTalk   Вверх
valent_N
Дата 3.1.2006, 16:04 (ссылка)
| (нет голосов)
Загрузка ... Загрузка …




Быстрая цитата

Цитата

Шустрый
*

Профиль
Группа: Участник
Сообщений: 64
Регистрация: 9.8.2003
Где: Рига, Латвия

Репутация: нет
Всего: 1

Очень всем признателен за внимание к моему вопросу. Я с Вашей помощью смог разобраться и получить полезную информацию, которую сам не смог найти.

Цитата(Exception @ 3.1.2006, 15:53)
А вообще не пойму, что мешает пользоваться

Хотелось узнать, какой метод более рационален.

Большущее ВСЕМ СПАСИБО!!!

PM MAIL   Вверх
Exception
Дата 3.1.2006, 16:05 (ссылка)
| (нет голосов)
Загрузка ... Загрузка …




Быстрая цитата

Цитата

Эксперт
****

Профиль
Группа: Участник Клуба
Сообщений: 4525
Регистрация: 26.12.2004

Репутация: 29
Всего: 186

А SetLastError тама разве есть?

PM   Вверх
arilou
Дата 4.1.2006, 13:33 (ссылка)
| (нет голосов)
Загрузка ... Загрузка …




Быстрая цитата

Цитата

Великий МунаБудвин
****

Профиль
Группа: Экс. модератор
Сообщений: 2646
Регистрация: 15.7.2004
Где: город-герой Минск

Репутация: 21
Всего: 61

Цитата(Exception @ 3.1.2006, 16:05 Найти цитируемый пост)
А SetLastError тама разве есть?

А SetLastError нужен для апишных ф-ций. Для .NET приложений не нужен.

———————

user posted imageuser posted image

PM WWW ICQ   Вверх
redig
Дата 14.2.2007, 06:29 (ссылка)
| (нет голосов)
Загрузка ... Загрузка …




Быстрая цитата

Цитата

Новичок

Профиль
Группа: Участник
Сообщений: 20
Регистрация: 19.1.2007

Репутация: нет
Всего: нет

Цитата(valent_N @ 3.1.2006,  15:18)
Здравствуйте!
Какой культурный способ существует для получения кода системной ошибки?

Я так и не понял из этого топика как получить код ошибки. у меня например такая ситуация:
Вываливаются ошибки:
14.02.2007 12:05:48|Ошибка OutSocket() : Object reference not set to an instance of an object.
14.02.2007 12:06:46|Ошибка OutSocket() : Удаленный хост принудительно разорвал существующее подключение

на одном Exception-e:

Код

        public static void OutSocket()
        {
            try
            {
                Data msgToSend = new Data();

                msgToSend.cmdCommand = Command.Logout;
                msgToSend.strName = CLName;
                msgToSend.strMessage = null;

                byte[] byteData = msgToSend.ToByte();

                //Send it to the server
                clientSocket.Send(byteData, 0, byteData.Length, SocketFlags.None);
                clientSocket.Close();

                                           }
            catch (Exception ee)
            {
                LogingClass.SaveLoging(DateTime.Now.ToString() + "|Ошибка OutSocket() : " + ee.Message);
                //MessageBox.Show("Ошибка OutSocket() : " + ee.Message);
            }
        }

Тут говорили про HResult, в MSDN-е не понятно написано, если кто знает, покажите пожалуйста примером как вытащить из Exception-а код ошибки.

Это сообщение отредактировал(а) redig — 14.2.2007, 06:48

PM MAIL   Вверх



















Ответ в темуСоздание новой темы
Создание опроса
Прежде чем создать тему, посмотрите сюда:

mr.DUDA

THandle

  • Что же такое .NET? Краткое описание, изучаем.
  • Какой язык программирования выбрать? выбираем.
  • C#. С чего начать? начинаем.
  • Защита исходного кода .NET приложений, защищаем.
  • Литература по .NET, обращаемся.

  • FAQ раздела, ищем здесь.
  • Архиполезные ссылки: www.connectionstrings.com, www.pinvoke.net, www.codeproject.com

Используйте теги [code=csharp][/code] для подсветки кода. Используйтe чекбокс «транслит» если у Вас нет русских шрифтов.

Что делать если Вам помогли, но отблагодарить помощника плюсом в репутацию Вы не можете(не хватает сообщений)?
Пишите сюда, или отправляйте репорт. Поставим :)

Так же не забывайте отмечать свой вопрос решенным, если он таковым является :)


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, mr.DUDA, THandle.

 

0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Общие вопросы по .NET и C# | Следующая тема »
try
{
     object result = processClass.InvokeMethod("Create", methodArgs);
}
catch (Exception e)
{  
    // Here I was hoping to get an error code.
}

когда я вызываю вышеуказанный метод WMI, я должен получить отказ в доступе. В моем блоке catch я хочу убедиться, что исключения действительно отказано в доступе. Есть ли способ получить код ошибки для него ? Win32 код ошибки для Acceess Denied — 5.
Я не хочу искать сообщение об ошибке для запрещенной строки или что-то в этом роде.

спасибо

5 ответов


попробуйте это, чтобы проверить исключение и внутренний для Win32Exception исключение производных.

catch (Exception e){  
    var w32ex = e as Win32Exception;
    if(w32ex == null) {
        w32ex = e.InnerException as Win32Exception;
    }    
    if(w32ex != null) {
        int code =  w32ex.ErrorCode;
        // do stuff
    }    
    // do other stuff   
}

как и в комментариях, вам действительно нужно увидеть, какое исключение на самом деле бросается, чтобы понять, что вы можете сделать, и в этом случае конкретный улов предпочтительнее, чем просто поймать исключение. Что-то вроде:

  catch (BlahBlahException ex){  
      // do stuff   
  }

и


вы должны посмотреть на членов брошенного исключения, особенно .Message и .InnerException.

Я также хотел бы посмотреть, сообщает ли документация для InvokeMethod, создает ли он более специализированный класс исключений, чем исключение, например Win32Exception, предложенное @Preet. Ловить и просто смотреть на базовый класс исключений может быть не особенно полезно.


Я предлагаю вам использовать Message Properte из объекта исключения, как показано ниже code

try
{
 object result = processClass.InvokeMethod("Create", methodArgs);
}
catch (Exception e)
{  
    //use Console.Write(e.Message); from Console Application 
    //and use MessageBox.Show(e.Message); from WindowsForm and WPF Application
}

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

 try
 {
     object result = processClass.InvokeMethod("Create", methodArgs);
 }
 catch (Exception e)
 {
     // Here I was hoping to get an error code.
     if (ExceptionContainsErrorCode(e, 10004))
     {
         // Execute desired actions
     }
 }

private bool ExceptionContainsErrorCode(Exception e, int ErrorCode)
{
    Win32Exception winEx = e as Win32Exception;
    if (winEx != null && ErrorCode == winEx.ErrorCode) 
        return true;

    if (e.InnerException != null) 
        return ExceptionContainsErrorCode(e.InnerException, ErrorCode);

    return false;
}

этот код был протестирован.

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


другим методом было бы получить код ошибки непосредственно из класса исключений. Например:

catch (Exception ex)
{
    if (ex.InnerException is ServiceResponseException)
       {
        ServiceResponseException srex = ex.InnerException as ServiceResponseException;
        string ErrorCode = srex.ErrorCode.ToString();
       }
}

Понравилась статья? Поделить с друзьями:
  • Как посмотреть ошибки на компе
  • Как получить имя ошибки python
  • Как посмотреть ошибки на киа рио
  • Как получить 502 ошибку
  • Как посмотреть ошибки на бортовом компьютере штат