作者:陳曦
日期:2012-9-17 18:33:23
環境:[win7 32位作業系統 Intel i3 支援64位指令 VS2012]
轉載請註明出處
Q1: HINSTANCE到底表示什嗎?
A: 如果從資料類型來解釋,它僅僅是指標的一種形式;對於作業系統來說,一個啟動並執行程式就可以被看成一個這種類型的控制代碼。
typedef void __RPC_FAR *HINSTANCE;
類似地,還有其它一些H開頭的類型:
typedef void __RPC_FAR *HANDLE;typedef void __RPC_FAR *HMODULE;typedef void __RPC_FAR *HRGN;typedef void __RPC_FAR *HTASK;typedef void __RPC_FAR *HKEY;typedef void __RPC_FAR *HDESK;typedef void __RPC_FAR *HMF;typedef void __RPC_FAR *HEMF;typedef void __RPC_FAR *HPEN;typedef void __RPC_FAR *HRSRC;typedef void __RPC_FAR *HSTR;typedef void __RPC_FAR *HWINSTA;typedef void __RPC_FAR *HKL;typedef void __RPC_FAR *HGDIOBJ;
可以看出,void *就是它們的本質。
Q2: 作業系統操作一個核心對象為什麼使用控制代碼,而不使用真實的指標?
A: 這個問題其實就像,為什麼去銀行取錢需要通過ATM取款機而不是把一大把鈔票放在你面前讓你去取。
作業系統如果暴露指標,程式員可能很容易傾向於對於指標內部資料的存取,而這很可能破壞整個結構。所以,抽象出一個控制代碼來,更安全。
另外,核心對象一般需要採用引用計數來避免因為意外釋放導致其它使用它的程式崩潰,所以這也要求需要對核心對象進行封裝,控制代碼應運而生。
NT核心內部調用ob模組的ObReferenceObjectByHandle函數來實現控制代碼到指標的轉換:
NTSTATUSObReferenceObjectByHandle ( //從控制代碼找到地址 __in HANDLE Handle, __in ACCESS_MASK DesiredAccess, __in_opt POBJECT_TYPE ObjectType, __in KPROCESSOR_MODE AccessMode, __out PVOID *Object, __out_opt POBJECT_HANDLE_INFORMATION HandleInformation )
具體內部的轉換過程這裡不詳細介紹,詳情請參考深入windows作業系統。
Q3: wsprintf和swprintf函數怎麼這麼像?
A: 我認為這是ms設計不當的一個執行個體。如此相近的函數只會讓程式員更容易犯錯,如下是兩者的原型。
WINUSERAPI int WINAPIV wsprintfA(LPSTR, LPCSTR, ...);WINUSERAPI int WINAPIV wsprintfW(LPWSTR, LPCWSTR, ...);#ifdef UNICODE#define wsprintf wsprintfW#else#define wsprintf wsprintfA#endif // !UNICODE_CRTIMP int __cdecl swprintf(wchar_t *, const wchar_t *, ...);
由上,wsprintf改為sprintf_t或者tsprintf更好點。
Q4: 為什麼在預設情況下wprintf(L"%s", L"你好"); 會輸出亂碼?
A: 這都是locale惹的禍,如果沒有它,這個函數或許可以保證正常輸出。載入c執行階段程式庫將設定預設的Locale為"C", wprintf函數內部會調用WideCharToMultiByte函數將寬字元轉換成多位元組字元,而這個過程,不是使用作業系統母語的locale,而是使用locale "C"的(它是由TLS資料得到的).
如果是簡體中文作業系統,添加如下語句,輸出即可正常:
setlocale(LC_ALL, "chs");
對於setlocale參數中語言可選擇的值,可以參考:http://msdn.microsoft.com/zh-cn/library/39cwe7zf(v=vs.110).aspx
Q5: SelectObject和DeleteObject函數是做什麼的?
A: 它們不是一個多麼通用的函數,只是對於GDI繪製過程中的繪圖對象進行選擇和刪除的函數。
__gdi_entry WINGDIAPI HGDIOBJ WINAPI SelectObject(__in HDC hdc, __in HGDIOBJ h);
__gdi_entry WINGDIAPI BOOL WINAPI DeleteObject( __in HGDIOBJ ho);
這兩個函數名都不好,我認為改為SelectGDIObj和DeleteGDIObj更合適。
命名同樣不太好的函數還有:GetObject, GetStockObject, SetWindowLong等函數.
Q6: AfxMessageBox和MessageBox有什麼區別?
A: 先看看原始碼:
int AFXAPI AfxMessageBox(LPCTSTR lpszText, UINT nType, UINT nIDHelp){CWinApp* pApp = AfxGetApp();if (pApp != NULL) {return pApp->DoMessageBox(lpszText, nType, nIDHelp); }else {return CWinApp::ShowAppMessageBox(NULL, lpszText, nType, nIDHelp); }}
CWinApp的ShowAppMessageBox的內部實現如下:
// Helper for message boxes; can work when no CWinApp can be foundint CWinApp::ShowAppMessageBox(CWinApp *pApp, LPCTSTR lpszPrompt, UINT nType, UINT nIDPrompt){// disable windows for modal dialogDoEnableModeless(FALSE);HWND hWndTop;HWND hWnd = CWnd::GetSafeOwner_(NULL, &hWndTop);// re-enable the parent window, so that focus is restored // correctly when the dialog is dismissed.if (hWnd != hWndTop)EnableWindow(hWnd, TRUE);// set help context if possibleDWORD* pdwContext = NULL;DWORD dwWndPid=0;GetWindowThreadProcessId(hWnd,&dwWndPid);if (hWnd != NULL && dwWndPid==GetCurrentProcessId() ){// use app-level context or frame level contextLRESULT lResult = ::SendMessage(hWnd, WM_HELPPROMPTADDR, 0, 0);if (lResult != 0)pdwContext = (DWORD*)lResult;}// for backward compatibility use app context if possibleif (pdwContext == NULL && pApp != NULL)pdwContext = &pApp->m_dwPromptContext;DWORD dwOldPromptContext = 0;if (pdwContext != NULL){// save old prompt context for restoration laterdwOldPromptContext = *pdwContext;if (nIDPrompt != 0)*pdwContext = HID_BASE_PROMPT+nIDPrompt;}// determine icon based on type specifiedif ((nType & MB_ICONMASK) == 0){switch (nType & MB_TYPEMASK){case MB_OK:case MB_OKCANCEL:nType |= MB_ICONEXCLAMATION;break;case MB_YESNO:case MB_YESNOCANCEL:nType |= MB_ICONQUESTION;break;case MB_ABORTRETRYIGNORE:case MB_RETRYCANCEL:// No default icon for these types, since they are rarely used.// The caller should specify the icon.break;}}#ifdef _DEBUGif ((nType & MB_ICONMASK) == 0)TRACE(traceAppMsg, 0, "Warning: no icon specified for message box.\n");#endifTCHAR szAppName[_MAX_PATH];szAppName[0] = '\0';LPCTSTR pszAppName;if (pApp != NULL)pszAppName = pApp->m_pszAppName;else{pszAppName = szAppName;DWORD dwLen = GetModuleFileName(NULL, szAppName, _MAX_PATH);if (dwLen == _MAX_PATH)szAppName[_MAX_PATH - 1] = '\0';}int nResult = MessageBox(hWnd, lpszPrompt, pszAppName, nType);// restore prompt context if possibleif (pdwContext != NULL)*pdwContext = dwOldPromptContext;// re-enable windowsif (hWndTop != NULL)::EnableWindow(hWndTop, TRUE);DoEnableModeless(TRUE);return nResult;}
函數主要對於parent window以及message box的type、caption進行計算,後面調用windows api的MessageBox函數彈出提示框。
可見,AfxMessageBox是一個封裝函數,它最初調用了CWnd::GetSafeOwner_, 所以它屬於MFC類庫中的一個函數。至於何時該調用什麼函數,這就得根據函數參數、使用的SDK(連結的庫)來決定了。
Q7: 使用者進程都是運行在使用者態下,如何獲得虛擬位址的資訊?
A: VirtualQuery是個擷取虛擬位址資訊的函數。如下例子,將擷取使用者進程使用者空間模組的資訊:
int main(){ MEMORY_BASIC_INFORMATION mbi; size_t ret; LPSTR p = NULL; while(1) {ret = VirtualQuery(p, &mbi, sizeof(MEMORY_BASIC_INFORMATION));if(ret == 0){ printf("query addr:%p failed...\n", p); break;}else{ printf("%p AllocationProtect:%x Protect:%x size:%x\n", p, mbi.AllocationProtect, mbi.Type, mbi.RegionSize); p += mbi.RegionSize;} } while(1); return 0;}
調試運行:
在vs中的模組視窗,得到如下:
可以對比下,ntdll.dll, kernel32.dll, KernelBase.dll這3個dll被載入的地址和命令列控制台中輸出的地址,能夠發現後者正好在前面的範圍之內。
Q8: SEH異常處理和c語言的setjmp,longjump以及c++的try,catch有什麼區別?
A: SEH是windows系統層級的異常處理架構,它能夠處理包括浮點數異常、指令異常、堆疊溢位等系統層級的異常;而c和c++標準中的異常處理是語言層級的,像c++中throw語句才會拋出c++異常,它們無法也沒有能力捕獲系統層級異常。如下是使用SEH捕獲堆棧異常的執行個體:
#define PRINT_S(s) _tprintf("%s\n", (s)); double fac(int n){ if(n <= 1)return 1; return n * fac(n - 1);}int main(){ EXCEPTION_RECORD exceptRec; CONTEXT context; __try {//double ret = fac(10000000);//double ret = fac(100000);//double ret = fac(50000);//double ret = fac(25000);//double ret = fac(10000);double ret = fac(5000);//double ret = fac(4600);// 不會出現堆疊溢位的大概臨界點 } __except(exceptRec = *(GetExceptionInformation())->ExceptionRecord,context = *(GetExceptionInformation())->ContextRecord,EXCEPTION_EXECUTE_HANDLER) {switch (exceptRec.ExceptionCode){case EXCEPTION_STACK_OVERFLOW: PRINT_S("EXCEPTION_STACK_OVERFLOW happens...")break;default: break;} } return 0;}
運行結果:
如果改為c++代碼,使用try, catch來捕獲堆疊溢位異常:
int main(){ try {//double ret = fac(10000000);//double ret = fac(100000);//double ret = fac(50000);//double ret = fac(25000);//double ret = fac(10000);double ret = fac(5000);//double ret = fac(4600);// 不會出現堆疊溢位的臨界點 } catch(...) {PRINT_S("EXCEPTION_STACK_OVERFLOW happens...") } return 0;}
執行後:
可以看出,它沒有捕獲堆疊溢位的異常。
作者:陳曦
日期:2012-9-17 18:33:23
環境:[win7 32位作業系統 Intel i3 支援64位指令 VS2012]
轉載請註明出處