Windows核心編程之核心總結(第一章 錯誤處理)(2018.5.26)

前沿

學習Windows核心編程是步入Windows編程殿堂的必經之路,2018年寒假重溫了計算機操作系統知識,前陣子又過學習Windows程序設計方面的基礎,正所謂打鐵要乘熱,所以我又入了Windows核心編程的坑啦,哈哈~

學習目標

每一章的學習都要明確一個目標,就是你學完這一章之後你能做些什麼?好的,我們一步步來學習第一章節錯誤處理。以下是這一章節的學習目標:
1.瞭解Windows函數的錯誤機制
2.瞭解GetLastError和SetLastError函數的使用
3.瞭解FormatMessage函數使用及參數說明
4.通過以上的學習,自行寫出將錯誤代碼轉換爲錯誤信息代碼例子。
5.結合Windows核心編程給出的ErrorShow示例程序,分析第4點自己寫的代碼。

Windows函數的錯誤機制

Windows核心編程這本書的第一章是最簡單的,雖然簡單,但我們不能驕傲,因 爲我們要征服這本書就要以虛心向學的態度學習和總結,再應用。
我們都知道調用Windows函數時,它會先驗證我們傳給它的參數,驗證通過後再開始執行任務。如果傳入的參數無效或者由於其他原因導致操作無法執行,則函數的返回值將指出函數因爲某些原因失敗了。例如:最經典的函數莫過於CreateFile函數,假如打開文件失敗,那麼函數的返回值會是INVALID_HANDLE_VALUE(-1)。

HANDLE hFile=CreateFile(TEXT("c:\\fish"),0,0,NULL,OPEN_EXISTING,0,NULL);

下面展示大多數Windows函數使用的返回值的數據類型:

> VOID:這個函數不可能失敗!
> BOOL:FALSE失敗;TRUE成功。
> HANDLE:失敗返回NULL,否則返回非零句柄。如果有特殊說明,則可能爲特殊值例如:INVALID_HANDLE_VALUE。
> PVOID:返回一個內存地址,失敗爲NULL
> LONG/DWORD:應該根據SDK說明來確定函數狀況。

可以發現,雖然函數的返回值能夠告訴我們函數的執行失敗,但是卻沒有告訴我們函數爲什麼會調用失敗。所以,Microsoft編輯了一個列表,其中列出了所有可能的錯誤代碼,併爲每個錯誤代碼都分配了一個32位的編號,其實錯誤代碼是一個DWORD(unsigned long)類型的值。然後每個錯誤代碼都定義了一個錯誤信息,這個錯誤列表是保存在WinError.h頭文件中。下面截個WinError.h頭文件的頭部分:

#define ERROR_SUCCESS                    0L

#define NO_ERROR 0L                                                 // dderror
#define SEC_E_OK                         ((HRESULT)0x00000000L)

//
// MessageId: ERROR_INVALID_FUNCTION
//
// MessageText:
//
// Incorrect function.
//
#define ERROR_INVALID_FUNCTION           1L    // dderror

//
// MessageId: ERROR_FILE_NOT_FOUND
//
// MessageText:
//
// The system cannot find the file specified.
//
#define ERROR_FILE_NOT_FOUND             2L

//
// MessageId: ERROR_PATH_NOT_FOUND
//
// MessageText:
//
// The system cannot find the path specified.
//
#define ERROR_PATH_NOT_FOUND             3L

//
// MessageId: ERROR_TOO_MANY_OPEN_FILES
//
// MessageText:
//
// The system cannot open the file.
//
#define ERROR_TOO_MANY_OPEN_FILES        4L

GetLastError和SetLastError函數

好了,到了這裏我們對Windows函數的錯誤機制有了一定的瞭解,那麼我們程序中就要想辦法獲取錯誤代碼和設置錯誤代碼。
GetLastError函數獲取調用線程的上一次錯誤代碼。
函數原型:DWORD WINAPI GetLastError(void);
返回值:返回該線程的上一次錯誤代碼。錯誤代碼是一個32位無符號長整型(DWORD).
備註:Windows函數失敗之後,應該馬上調用GetLastError,因爲有可能下一次的函數調用影響這次的錯誤代碼值。
SetLastError函數用於爲調用線程設置最近的錯誤代碼。
函數原型:void WINAPI SetLastError(In DWORD dwErrCode);
參數:DWORD類型的錯誤代碼。
備註:利用GetLastError函數返回線程的上一個錯誤代碼,而通過這SetLastError函數設置錯誤代碼。

(1)對於上面GetLastError和SetLastError函數的使用方法後面我會舉例。現在,介紹一個Visual Studio的Watch窗口,以前學習C語言和C++我很少使用這個功能,一般都是看局部變量窗口,然後調試進行下一過程來查看各變量的變化情況。現在舉個例子來說明Visual Studio的Watch窗口的使用方法。圖片中監視1窗口的左側名稱是我自己輸入進去的,例如:count、hmodule、$err,hr。
Windows核心編程之核心總結(第一章 錯誤處理)(2018.5.26)

(2)我們可以看到我將斷點放在定義count的時候,當我輸入1000時就進入調試階段,其實WinError.h頭文件並沒有1000這個代碼值的定義,那麼這個FormatMessage函數的調用就會報錯。然後點擊逐過程進入下一條語句。
Windows核心編程之核心總結(第一章 錯誤處理)(2018.5.26)

(3)可以發現count變量成功被初始化成0了。而hmodule由於是條件語句內部還未執行到那,所以顯示未定義。這時候我再點擊逐過程進入下一條語句,這時候FormatMessage函數已經執行完成了,看下$err.hr的值變爲錯誤代碼加上錯誤代碼描述信息。可以總結$err是顯示函數調用失敗的錯誤代碼,而hr是錯誤代碼的描述信息,你也可以只寫$err,那麼就不是錯誤代碼信息了,只有錯誤代碼。Windows核心編程之核心總結(第一章 錯誤處理)(2018.5.26)

(4)假如我們寫一個函數給其他人調用,這個函數有可能會出錯,那麼我們需要向調用者指出錯誤,那麼我們就可以自己在函數中調用SetLastError函數,然後我們的函數返回FALSE或者NULL或者其他合適的值。注意,SetLastError參數是一個DWORD類型值。而錯誤代碼由幾個字段組成,下面貼圖:
Windows核心編程之核心總結(第一章 錯誤處理)(2018.5.26)

FormatMessage函數

下面是我對 FormatMessage函數的各參數一部分總結:

FormatMessage函數就是將GetLastError函數得到的錯誤信息(這個錯誤信息是數字代號)轉化成字符串信息的函數。

函數原型:

DWORD WINAPI FormatMessage(
In DWORD dwFlags,
_Inopt LPCVOID lpSource,
In DWORD dwMessageId,
In DWORD dwLanguageId,
Out LPTSTR lpBuffer,
In DWORD nSize,
_Inopt va_list *Arguments
);

參數1:標誌位
FORMAT_MESSAGE_ALLOCATE_BUFFER 0x00000100
如果設置了這個標誌位,那麼參數lpBuffer(經過初始化的指針變量)必須按(LPTSTR)&lpBuffer形式作爲參數來進行函數調用,當函數執行成功後,函數會自動分配一塊內存塊,該內存塊含有我們想要的字符串,然後這個指針會指向該內存塊,那麼我們就可以引用該字符串了。

FORMAT_MESSAGE_ARGUMENT_ARRAY 0x00002000
這個標誌位代表參數Arguments只是一個數組,不是va_list這種類型的參數。

FORMAT_MESSAGE_FROM_HMODULE 0x00000800
這個標誌說明,這個函數接收一個DLL模塊,從DLL模塊中查找字符串。

FORMAT_MESSAGE_FROM_STRING 0x00000400
從一個字符串中,查找消息字符串。

FORMAT_MESSAGE_FROM_SYSTEM 0x00001000
從系統中獲取消息字符串,不是從某個指定的字符串或者DLL中獲取消息字符串。

FORMAT_MESSAGE_IGNORE_INSERTS 0x00000200
這個表示說明Argument是否有用,如果設置了這個標誌,那麼Argument就會被函數忽略,如果沒有設置這個標誌位,那麼就必須在*Argument參數中提供這些佔位符的值。

參數2:lpSource:
從哪裏獲取消息字符串?如果是從系統獲取消息字符串的話,這個參數爲NULL。
參數3:dwMessageId:
消息索引
參數4:dwLanguageId:
消息的語言種類。
參數5:lpBuffer:
接受消息的內存塊指針。
參數6:nSize:
接受消息的內存塊大小,以字節爲大小。
參數7:*Arguments:
消息中有些變量的值。
返回值:如果函數調用成功,返回字符消息的字符數。否則返回0.

自己編寫的代碼例子(附上註釋說明)

我這個例子使用MFC簡單對話框,利用FormatMessage函數實現將錯誤代碼轉化爲錯誤代碼信息,並將錯誤代碼信息黏貼在靜態文本控件。下面是Ok按鈕的點擊事件處理,重要的還是這個內部代碼的實現,對於MFC不作過多講解,其實我對MFC也只是小白+1的存在,我主要學習Qt界面的開發。

void CMFCApplication1Dlg::OnBnClickedOk()
{
    // TODO:  在此添加控件通知處理程序代碼
    TCHAR *str=NULL;//消息字符串緩衝區
    this->UpdateData(1);

    /*
    FormatMessage函數進行參數的設置後,作用是爲這個函數傳入錯誤代碼,該函數就會返回對應系統或者DLL文件或者字符串中錯誤代碼信息字符串。
    該函數的返回值是一個DWORD類型的值,如果函數調用成功則返回字符串的字符數目,若失敗則返回0.
    所以這裏定義了一個count存儲FormatMessage函數的返回值。
    */
    DWORD count = 0;
    count=FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL, m_value, NULL, (LPTSTR)&str, 0, NULL);
    //函數調用成功
    if (count)
    {
        this->SetDlgItemTextW(IDC_STATIC1, (LPCTSTR)LocalLock(str));//在靜態文本控件的文本設置爲所獲取的錯誤代碼信息
        LocalFree(str);//如果FormatMessage函數是通過函數自動分配內存塊來存儲錯誤代碼信息,那麼就要調用LocalFree函數釋放掉這個內存。
    }
    //函數調用失敗,因爲上方調用失敗的原因可能是輸入的錯誤代碼在系統定義中沒有,那麼就查找網絡錯誤代碼信息,這裏加載網絡錯誤代碼信息的DLL文件
    else
    {
        //加載網絡錯誤代碼DLL文件,名字要記住
        HMODULE hmodule = LoadLibrary(TEXT("netmsg.dll"));
        //加載成功
        if (hmodule)
        {
            //再次調用FormatMessage函數,但參數1的標誌不同,第一個標誌變爲FORMAT_MESSAGE_FROM_HMODULE
            count = FormatMessage(FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
                hmodule, m_value, NULL, (LPTSTR)&str, 0, NULL);
            //函數調用成功
            if (count)
            {
                //在靜態文本控件的文本設置爲所獲取的錯誤代碼信息
                this->SetDlgItemTextW(IDC_STATIC1, (LPCTSTR)LocalLock(str));
                LocalFree(str);//釋放函數自動分配的內存塊
            }
            FreeLibrary(hmodule);//釋放動態鏈接庫文件的內存塊
        }
    }
    //如果上面通過兩種不同源路徑都獲取不到錯誤代碼對應的信息,那麼就輸出一行報錯文字
    if (count == 0)
    {
        this->SetDlgItemTextW(IDC_STATIC1, (LPCTSTR)L"沒有找到錯誤代碼信息");
    }
}

ErrorShow示例程序

DWORD dwError = GetDlgItemInt(hwnd, IDC_ERRORCODE, NULL, FALSE);//獲取輸入框的值,即錯誤代碼值

      HLOCAL hlocal = NULL;   // Buffer that gets the error message string
      //HLOCAL=HANDLE=void *,這三個都是一樣的

      // Use the default system locale since we look for Windows messages.
      // Note: this MAKELANGID combination has 0 as value
      DWORD systemLocale = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL);//語言

      // Get the error code's textual description
      BOOL fOk = FormatMessage(
         FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS |
         FORMAT_MESSAGE_ALLOCATE_BUFFER, 
         NULL, dwError, systemLocale, 
         (PTSTR) &hlocal, 0, NULL);
                 //如果函數調用失敗則採取另一種方法來獲取,即動態鏈接庫文件
      if (!fOk) {
         // Is it a network-related error?
         HMODULE hDll = LoadLibraryEx(TEXT("netmsg.dll"), NULL, 
            DONT_RESOLVE_DLL_REFERENCES);

         if (hDll != NULL) {
            fOk = FormatMessage(
               FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS |
               FORMAT_MESSAGE_ALLOCATE_BUFFER,
               hDll, dwError, systemLocale,
               (PTSTR) &hlocal, 0, NULL);
            FreeLibrary(hDll);
         }
      }

      if (fOk && (hlocal != NULL)) {
         SetDlgItemText(hwnd, IDC_ERRORTEXT, (PCTSTR) LocalLock(hlocal));
         LocalFree(hlocal);
      } else {
         SetDlgItemText(hwnd, IDC_ERRORTEXT, 
            TEXT("No text found for this error number."));
      }

總結

經過以上的學習,Windows核心編程的第一章就講完了,也就那麼多。我們分析自己寫的例子和ErrorShow示例程序之間的區別其實不是很大,但FormatMessage示例程序的邏輯更加嚴謹。還有,如果也有童鞋正在學習Windows核心編程,我們可以相互交流學習,本人不才但好學研究,還需要不斷學習來提升自己。可以通過QQ來聯繫我,我們一起不斷進步!
QQ:764238383

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章