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函数来实现句柄到指针的转换:
NTSTATUS
ObReferenceObjectByHandle ( //从句柄找到地址
__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参数中语言可选择的值,可以参考:语言字符串 | Microsoft Learn
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 found
int CWinApp::ShowAppMessageBox(CWinApp *pApp, LPCTSTR lpszPrompt, UINT nType, UINT nIDPrompt)
{
// disable windows for modal dialog
DoEnableModeless(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 possible
DWORD* pdwContext = NULL;
DWORD dwWndPid=0;
GetWindowThreadProcessId(hWnd,&dwWndPid);
if (hWnd != NULL && dwWndPid==GetCurrentProcessId() )
{
// use app-level context or frame level context
LRESULT lResult = ::SendMessage(hWnd, WM_HELPPROMPTADDR, 0, 0);
if (lResult != 0)
pdwContext = (DWORD*)lResult;
}
// for backward compatibility use app context if possible
if (pdwContext == NULL && pApp != NULL)
pdwContext = &pApp->m_dwPromptContext;
DWORD dwOldPromptContext = 0;
if (pdwContext != NULL)
{
// save old prompt context for restoration later
dwOldPromptContext = *pdwContext;
if (nIDPrompt != 0)
*pdwContext = HID_BASE_PROMPT+nIDPrompt;
}
// determine icon based on type specified
if ((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 _DEBUG
if ((nType & MB_ICONMASK) == 0)
TRACE(traceAppMsg, 0, "Warning: no icon specified for message box.\n");
#endif
TCHAR 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 possible
if (pdwContext != NULL)
*pdwContext = dwOldPromptContext;
// re-enable windows
if (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;
}
执行后:
可以看出,它没有捕获堆栈溢出的异常。
微风不燥,阳光正好,你就像风一样经过这里,愿你停留的片刻温暖舒心。
我是程序员小迷(致力于C、C++、Java、Kotlin、Android、Shell、JavaScript、TypeScript、Python等编程技术的技巧经验分享),若作品对您有帮助,请关注、分享、点赞、收藏、在看、喜欢,您的支持是我们为您提供帮助的最大动力。
欢迎关注。助您在编程路上越走越好!