当前位置导航:炫浪网>>网络学院>>编程开发>>C++教程>>Visual C++教程

Visual C++ 中的结构异常处理

    本文编译自Jeffrey Richter先生的“Advanced Windows”部分章节。
    1、引言
    在“C++中例外的处理”一文中(见计算机世界网2001年12月20日),我们讨论了C++中的例外(或异常)处理。本文将进一步探讨Visual C++中的结构异常处理。
    想象一下,如果在编程过程中你不需要考虑任何错误,你的程序永远不会出错,有足够的内存,你需要的文件永远存在,这将是一件多么愉快的事。这时你的程序不需要太多的if语句转来转去,非常容易写,容易读,也容易理解。如果你认为这样的编程环境是一种梦想,那么你就会喜欢结构异常处理(structu reed exception handling)。
    结构异常处理的本质就是让你专心于如何去完成你的任务。如果在程序运行过程中出现任何错误,系统会接收(catch)并通知(notify)你。虽然利用结构异常处理你不可能完全忽略你的程序出错的可能性,但是结构异常处理确确实实允许你将你的主要任务与错误处理分离开来。这种分离使得你可以集中精力于你的工作,而在以后在考虑可能的错误。
    结构异常处理的主要工作是由编译器来完成的,而不是由操作系统。编译器在遇到例外程序段时需要产生额外的特殊代码来支持结构异常处理。所以,每一个编译器产品供应商可能使用自己的语法和规定。这里我们采用微软的Visual C++编译器来进行讨论。
    注意不要将这里讨论的结构异常处理与C++中的异常处理混为一谈。C++中的异常处理是另一种形式的异常处理,它使用了C++的关键词catch和throw。
    微软最早在Visual C++版本2.0引进结构异常处理。结构异常处理主要由两部分组成:中断处理(termination handling)和例外处理(exception handling)。
    2、中断处理句柄(termination handler)
    2.1、中断处理句柄定义
    中断处理句柄保证了,不论进程如何离开另一程序段--这里称之为守卫体(guarded body),该句柄内的程序段永远会被调用和执行。微软的Visual C++编译器的中断处理句柄语法为
    __try {
    // Guarded body
    .
    .
    .
    }
    __finally {
    // Termination handler
    .
    .
    .
    }
    这里的__try和__finally勾画出了中断处理句柄的两个部分。在上面的例子中,操作系统和编译器一起保证了不论包含在__try内的程序段出现何种情况,包含在__finally内的程序段永远会被运行。不论你在__try内的程序段中调用return、goto或longjump,__finally内的中断处理句柄永远会被调用。其流程为
    // 1、执行try程序段前的代码
    __try {
    // 2、执行try程序段内的代码
    }
    __finally {
    // 3、执行finally程序段内的代码
    }
    // 4、执行finally程序段后的代码
    2.2、几个例子
    下面我们通过几个具体例子来讨论中断处理句柄是如何工作的。
    2.2.1、例1--Funcenstein1
    清单一给出了我们的第一个例子。
    DWORD Funcenstein1(void) {
    DWORD dwTemp;
    // 1. Do any processing here.
    .
    .
    .
    __try {
    // 2. request permission to access protected data, and then use it.
    WaitForSingleObject(g_hSem, INFINITE);
    g_dwProtectedData = 5;
    dwTemp = g_dwProtectedData;
    }
    __finally {
    // 3. Allow others to use protected data.
    ReleaseSemaphore(g_hSem, 1, NULL);
    }
    // 4. Continue processing.
    return (dwTemp);
    }
    例1 Funcenstein1函数代码
    在函数Funcenstein1中,我们使用了try-finally程序块。但是它们并没有为我们做多少工作:等待一个指示灯信号,改变保护数据的内容,将新的数据指定给一个局域变量dwTemp,释放指示灯信号,返回新的数据给调用函数。
    2.2.2、例2--Funcenstein2
    现在让我们对Funcenstein1稍稍做一些改动,看看会出现什么情况(见清单二)。
    DWORD Funcenstein2(void) {
    DWORD dwTemp;
    // 1. Do any processing here.
    .
    .
    .
    __try {
    // 2. request permission to access protected data, and then use it.
    WaitForSingleObject(g_hSem, INFINITE);
    g_dwProtectedData = 5;
    dwTemp = g_dwProtectedData;
    // Return the new value.
    return (dwTemp);
    }
    __finally {
    // 3. Allow others to use protected data.
    ReleaseSemaphore(g_hSem, 1, NULL);
    }
    // 4. Continue processing--this code will never execute in this version.
    dwTemp = 9;
    return (dwTemp);
    }
    例2 Funcenstein2函数代码
    在函数Funcenstein2中,我们在try程序段里加入了一个return返回语句。该返回语句告诉编译器,你想离开函数Funcenstein2并返回dwTemp内的内容5给调用函数。然而,如果此返回语句被执行,本线程永远不会释放指示灯信号,其它线程也就永远不会得到该指示灯信号。你可以想象,在多线程程序中这是一个多么严重的问题。
    但是,使用了中断处理句柄避免了这种情况发生。当返回语句试图离开try程序段时,编译器保证了在finally程序段内的代码得到执行。所以,finally程序段内的代码保证会在try程序段中的返回语句前执行。在函数Funcenstein2中,将调用ReleaseSemaphore放在finally程序段内保证了指示灯信号会得到释放。
    在finally程序段内的代码被执行后,函数Funcenstein2立即返回。这样,因为try程序段内的return返回语句,任何finally程序段后的代码都不会被执行。因而Funcenstein2返回值是5,而不是9。
    必须指出的是,当遇到例2中这种过早返回语句时,编译器需要产生额外的代码以保证finally程序段内的代码的执行。此过程称作为局域展开。当然,这必然会降低整个程序的效率。所以,你应该尽量避免使用这类代码。在后面我们会讨论关键词__leave,它可以帮助我们避免编写出现局域展开一类的代码。
    2.2.3、例3--Funcenstein3
    现在让我们对Funcenstein2做进一步改动,看看会出现什么情况(见例3)。
    DWORD Funcenstein3(void) {
    DWORD dwTemp;
    // 1. Do any processing here.
    .
    .
    .
    __try {
    // 2. request permission to access protected data, and then use it.
    WaitForSingleObject(g_hSem, INFINITE);
    g_dwProtectedData = 5;
    dwTemp = g_dwProtectedData;
    // Try to jump over the finally block.
    goto ReturnValue;
    }
    __finally {
    // 3. Allow others to use protected data.
    ReleaseSemaphore(g_hSem, 1, NULL);
    }
    dwTemp = 9;
    // 4. Continue processing.
    ReturnValue:
    return (dwTemp);
    }
    例3 Funcenstein3函数代码
    在函数Funcenstein3中,当遇到goto语句时编译器会产生额外的代码以保证finally程序段内的代码得到执行。但是,这一次finally程序段后ReturnValue标签后面的代码会被执行,因为try或finally程序段内没有返回语句。函数的返回值是5。同样,由于goto语句打断了从try程序段到finally程序段的自然流程,程序的效率会降低。
    2.2.4、例4--Funcfurter1
    现在让我们来看中断处理真正展现其功能的一个例子。(见例4)。
    DWORD Funcfurter1(void) {
    DWORD dwTemp;
    // 1. Do any processing here.
    .
    .
    .
    __try {
    // 2. request permission to access protected data, and then use it.
    WaitForSingleObject(g_hSem, INFINITE);
    dwTemp = Funcinator(g_dwProtectedData);
    }
    __finally {
    // 3. Allow others to use protected data.
    ReleaseSemaphore(g_hSem, 1, NULL);
    }
    // 4. Continue processing.
    return (dwTemp);
    }
    例4 Funcfurter1函数代码
    设想try程序段内调用的Funcinator函数具有某种缺陷而造成无效内存读写。在16位视窗应用程序中,这会导致一个已定义好的错误信息对话框出现。在用户关闭对话框的同时该应用程序也终止运行。在不具有try-finally的Win32应用程序中,这会导致程序终止运行,指示灯信号永远不会得到释放。这就造成了等待该指示灯信号的其它线程会永远等待下去。而将ReleaseSemaphore放在finally程序段内则从根本上保证了不论何种情况出现指示灯信号都会得到释放。
    如果中断处理句柄能够处理由于无效内存读写而造成的程序中断,我们就完全有理由相信它能够处理诸如setjump/longjump、break和continue这类的中断转移。事实也正是这样。
    2.3、小测试
    下面一个例子(见清单五)请读者猜测一下函数FuncaDoodleDoo的返回值。(答案为14)
    DWORD FuncaDoodleDoo(void) {
    DWORD dwTemp = 0;
    while (dwTemp 〈 10) {
    __try {
    if (dwTemp == 2)
    continue;
    if (dwTemp == 3)
    break;
    }
    __finally {
    dwTemp++;
    }
    dwTemp++;
    }
    dwTemp += 10;
    return (dwTemp);
    }
    FuncaDoodleDoo函数代码
    虽然中断处理句柄能够接收出现在try程序段内的绝大部分异常情况,但是如果线程或进程中断执行的话,则finally程序段内的代码不会被执行。调用ExitThread或ExitProcess就会立即造成线程或进程的中断,而不会执行finally程序段。另外,如果其它的应用程序调用ExitThread或ExitProcess而造成你的线程或进程中断,你程序中的finally程序段也不会被执行。一些C函数如abort会调用ExitProcess,也会导致你的finally程序段不被执行。对此你无能为力。但你可以防止你自己提早调用ExitThread或ExitProcess。
    2.4、应用例子
    我们已经讨论了中断处理句柄的句法及语法。现在我们进一步讨论如何利用中断处理句柄来简化一个比较复杂的编程问题。
    首先让我们来看一个没有使用中断处理句柄的例子,程序源代码见例6。
    BOOL Funcarama1 (void) {
    HANDLE hFile = INVALID_HANDLE_VALUE;
    LPVOID lpBuf = NULL;
    DWORD dwNumBytesRead;
    BOOL fOk;
    hFile = CreateFile("SOMEDATA.DAT", GENERIC_READ, FILE_SHARE_READ,
    NULL, OPEN_EXISTING, 0, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
    return (FALSE);
    }
    lpBuf = VitualAlloc(NULL, 1024, MEM_COMMIT, PAGE_READWRITE);
    if (lpBuf == NULL) {
    CloseHandle(hFile);
    return (FALSE);
    }
    fOk = ReadFile(hFile, lpBuf, 1024, &dwNumBytesRead, NULL);
    if (!fOk || (dwNumBytesRead == 0)) {
    VirtualFree(lpBuf, MEM_RELEASE | MEM_DECOMMIT);
    CloseHandle(hFile);
    return (FALSE);
    }
    // Do some calculation on the data.
    .
    .
    .

  

共2页 首页 上一页 1 2 下一页 尾页 跳转到
相关内容
赞助商链接