Widows核心编程第一章:错误处理
Windows API同linux一樣, 在返回值的基礎上可以通過錯誤碼確認具體的程序錯誤原因。當一個 Windows 函數檢測到錯誤時,它會使用一個名為“線程本地存儲區” (thread-local storage)的機制將恰當的錯誤碼與“主調線程”(或者說發出調用的線程,即 calling thread)關聯到一起(windows核心編程)。在vs調試時刻可以監視(watch)$err,hr變量的值查看系統調用具體錯誤原因。
?
這個錯誤碼也可以在代碼中通過DWORD GetLastError() 函數獲取。
獲取到的錯誤碼可以通過
DWORD FormatMessage( DWORD dwFlags,
LPCVOID pSource,
DWORD dwMessageId,
DWORD dwLanguageId,
PTSTR pszBuffer,
DWORD nSize,
va_list *Arguments);
函數轉換為具體的文本信息。具體參數如下:
dwFlags :?格式化選項
低位值指定了函數如何處理輸出緩沖區處理行轉換,也可以指定格式化輸出字符串輸出行的最大寬度。
| Value | Meaning |
| FORMAT_MESSAGE_ALLOCATE_BUFFER | lpBuffer參數是一個PVOID指針,nSize參數指定按TCHARs為單位的分配給輸出消息緩沖區的最小值。當你不適用這個緩沖區的時候也就是lpBuffer的時候需要用LocalFree將其釋放 ? |
| FORMAT_MESSAGE_ARGUMENT_ARRAY | Arguments參數不是一個va_list結構,但是它表示一個數組指針。這個標識符不能在64位整數值時使用,你如果要使用64位整數值,那么你必須使用va_list結構體 ? |
| FORMAT_MESSAGE_FROM_HMODULE | lpSource參數是一個包含了消息表資源(Message-table resources)模塊(dll)句柄。如果lpSource句柄為NULL,系統會自動搜索當前進程文件的消息資源。 這個標示符不可以和FORMAT_MESSAGE_FROM_STRING共用。 如果模塊中沒有資源表,這個函數執行失敗并且返回ERROR_RESOURCE_TYPE_NOT_FOUND錯誤值。 ? |
| FORMAT_MESSAGE_FROM_STRING | lpSource參數指向一個包含了消息定義的字符串.這個消息定義里面可能包含了插入序列(insert sequence),像消息表資源中包含消息文本一樣.和這個標示符不和FORMAT_MESSAGE_FROM_HMODULE或者FORMAT_MESSAGE_FROM_SYSTEM一起使用. ? |
| FORMAT_MESSAGE_FROM_SYSTEM | 函數將會搜索系統消息表資源來尋找所需消息資源。如果這個標示符同時定義了FORMAT_MESSAGE_FROM_HMODULE,那么如果函數在模塊中沒有搜索到所需消息的話將會在系統中搜索。這個標示符不能和FORMAT_MESSAGE_FROM_STRING一起使用. 當這個標示符設置的時候,可以使用GetLastError函數返回值來搜索這個錯誤碼在系統定義錯誤中相應的消息文本。 ? |
| FORMAT_MESSAGE_IGNORE_INSERTS | 在消息定義中的插入序列將會被忽略,這個標示符在獲取一個格式化好的消息十分有用,如果這個標示符設置好了,那么Arguments參數將被忽略。 ? |
lpSource:
這個值是消息表資源來自哪里,這個值依靠dwFlags,詳細請看FORMAT_MESSAGE_FROM_HMODULE和FORMAT_MESSAGE_FROM_STRING,如果這兩個標示符都沒設置,那么lpSource將會被忽略。
dwMessageId :
所需格式化消息的標識符。當dwFlags設置了FORMAT_MESSAGE_FROM_STRING,這個參數將會被忽略
dwLanguageId:
格式化消息語言標識符。
lpBuffer:
?一個緩沖區指針來接受格式化后的消息。當dwFlags包括了FORMAT_MESSAGE_ALLOCATE_BUFFER標志符,這個函數將會使用LocalAlloc函數分配一塊緩沖區,lpBuffer需要接受一個地址來使用這個緩沖區。(這里要注意傳參一定要傳地址)。
nSize:
?如果FORMAT_MESSAGE_ALLOCATE_BUFFER沒有設置,那么這個參數指定了輸出緩沖區的消息,以TCHARs為單位。如果FORMAT_MESSAGE_ALLOCATE_BUFFER設置了,這個參數設置以TCHARs為單位的輸出緩沖區的最小值。這個輸出緩沖區不能大于64KB。
Arguments:
一個數組中的值在格式化消息中作為插入值,根據消息文本的格式里面的內容(詳見mc.exe使用),可以知道%n[!format_specifier!]為這個參數的指定形式。
比如說在格式字符串中的%1為數組中的第一個值,%2為第二個值。n就代表數組第幾個值。
那么[!format_specifier!]如何解釋呢?這個格式化指定具體解釋在 Format Specification Fields,也就是printf的格式形式安排。
?
錯誤碼被定義在WinError.h頭文件中, 是一個32位數。
?
?
用戶可以通過VOID SetLastError(DWORD dwErrCode); 函數定義自己的錯誤碼。
線程本地存儲(thread-local storage):
https://www.cnblogs.com/zhoug2020/p/6497709.html
在同一進程中的線程,全局或靜態的局部變量通常時共有的。其中一個線程進行了修改, 就會影響到其他線程。
TLS技術將數據和執行的特定線程聯系起來。分為靜態和動態。
靜態TLS:
__declspec(thread) DWORD myTLSData=0;
__declspec(thread)的前綴是Microsoft添加給Visual C++編譯器的一個修改符。它告訴編譯器,對應的變量應該放入可執行文件或DLL文件中它的自己的.tls節中。__declspec(thread)后面的變量必須聲明為函數中(或函數外)的一個全局變量或靜態變量。
然后,為了使含有靜態TLS的程序能夠運行,操作系統必須參與其操作。當TLS應用程序加載到內存中時,系統要尋找可執行文件中的.tls節,并且動態地分配一個足夠大的內存塊,以便存放所有的靜態TLS變量。應用程序中的代碼每次引用其中的一個變量時,就要轉換為已分配內存塊中包含的一個內存位置。因此,編譯器必須生成一些輔助代碼來引用該靜態TLS變量,這將使你的應用程序變得比較大而且運行的速度比較慢。在x86 CPU上,將為每次引用的靜態TLS變量生成3個輔助機器指令。如果在進程中創建了另一個線程,那么系統就要將它捕獲并且自動分配另一個內存塊,以便存放新線程的靜態TLS變量。新線程只擁有對它自己的靜態TLS變量的訪問權,不能訪問屬于其他線程的TLS變量。
如果隱式鏈接包含靜態TLS變量的DLL, ,當系統加載該應用程序時,它首先要確定應用程序的.tls節的大小,并將這個值與應用程序鏈接的DLL中的所有.tls節的大小相加。當在你的進程中創建線程時,系統自動分配足夠大的內存塊來存放所有應用程序聲明的和所有隱含鏈接的DLL包含的TLS變量。
如果顯式鏈接包含靜態TLS變量的DLL,系統必須查看該進程中已經存在的所有線程,并擴大它們的TLS內存塊,以便適應新DLL對內存的需求。另外,如果調用FreeLibrary來釋放包含靜態TLS變量的DLL,那么與進程中的每個線程相關的的TLS內存塊又都應該被壓縮。 對于操作系統來說,這樣的管理任務太重了。所以,雖然系統允許包含靜態TLS變量的庫在運行期進行顯式加載,但是其包含TLS數據卻沒有進行相應的初始化。如果試圖訪問這些數據,就可能導致訪問違規!
動態TLS:
1、使用動態TLS
?
動態TLS在程序實現中比靜態TLS要稍微麻煩一些,需要通過一組函數來實現:
?
DWORD TlsAlloc();//返回TLS數組可用位置的索引
?
BOOL TlsSetValue(DWORD dwTlsIndex, LPVOID lpTlsValue); //將調用線程的TLS數組索引dwTlsIndex處設為值lpTlsValue
?
LPVOID TlsGetValue(DWORD dwTlsIndex); //返回調用線程的TLS數組dwTlsIndex索引處的值
?
BOOL TlsFree(DWORD dwTlsIndex); //釋放所有線程的TLS數組位置索引dwTlsIndex,將該位置標記為未使用。
?
有了以上四個函數,我們可以發現使用動態TLS其實還是很容易很方便的。
?
2、動態TLS原理
windows用來管理TLS的內部數據結構如下:
?
線程本地存儲器的位標志顯示了該進程中所有運行的線程正在使用的一組標志。每個標志均可設置為FREE或者INUSE,表示TLS插槽(slot)是否正在使用。Microsoft保證至少TLS_MINIMUM_AVAILABLE位標志是可供使用的。另外,TLS_MINIMUM_AVAILABLE在WinNT.h中被定義為64。Windows2000將這個標志數組擴展為允許有1000個以上的TLS插槽。
?
而每一個線程擁有一個自己獨立的TLS slot數組,用于存儲TLS數據。
為了使用動態TLS,我們首先調用TlsAlloc()來命令系統對進程的位標志進行掃描,找到一個可用的位置,并返回該索引;如果找不到,就返回TLS_OUT_OF_INDEXES。事實上,除此之外,TlsAlloc函數還會自動清空所有線程的TLS數組的對應索引的值。這避免以前遺留的值可能引起的問題。
然后,我們就可以調用TlsSetValue函數將對應的索引位保存一個特定的值,可以調用TlsGetValue()來返回該索引位的值。注意,這兩個函數并不執行任何測試和錯誤檢查,我們必須要保證索引是通過TlsAlloc正確分配的。
當所有線程都不需要保留TLS數組某個索引位的時候,應該調用TlsFree。該函數告知系統將進程的位標志數組的index位置為FREE狀態。如果運行成功,函數返回TRUE。注意,如果試圖釋放一個沒有分配的索引位,將產生一個錯誤。
?
動態TLS的使用相對靜態TLS稍微麻煩一點,但是無論是將其用在可執行文件中還是DLL中,都還是很簡單的。而且當用在DLL中時,沒有由于DLL鏈接方式而可能產生的問題,所以,如果要在DLL中用TLS,又不能保證客戶始終采用隱式鏈接方式,那么請采用動態TLS的實現。
總結
以上是生活随笔為你收集整理的Widows核心编程第一章:错误处理的全部內容,希望文章能夠幫你解決所遇到的問題。