您的位置:首页 > 编程语言 > C语言/C++

[VC++深入详解] 1. Windows程序内部运行机制

2013-11-18 22:08 1136 查看
本系列(VC++深入详解)为《VC++深入详解》(孙鑫 编著)读书笔记,很多例子都是仿照此书,很多概念也是来自此书,在对其做归纳总结的同时,也加入了自己的一些理解。

一、 最简单的Windows程序框架概览

#include <windows.h>
#include <stdio.h>

LRESULT CALLBACK WndProc(
_In_  HWND hwnd,
_In_  UINT uMsg,
_In_  WPARAM wParam,
_In_  LPARAM lParam
);

int WINAPI WinMain(
_In_  HINSTANCE hInstance,
_In_  HINSTANCE hPrevInstance,
_In_  LPSTR lpCmdLine,
_In_  int nCmdShow
)
{
WNDCLASS wnd;
wnd.cbClsExtra = 0;
wnd.cbWndExtra = 0;
wnd.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);
wnd.hCursor = LoadCursor(NULL, IDC_HAND);
wnd.hIcon = LoadIcon(NULL, IDI_INFORMATION);
wnd.hInstance = hInstance;
wnd.lpfnWndProc = WndProc;
wnd.lpszClassName = "uranux";
wnd.lpszMenuName = NULL;
wnd.style = CS_VREDRAW | CS_HREDRAW;

RegisterClass(&wnd);
HWND hWnd = CreateWindow(wnd.lpszClassName, "Hello Uranux!", WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX, 100, 100, 500, 400, NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);

MSG msg;
BOOL bRet;
while (bRet = GetMessage(&msg, hWnd, 0, 0))
{
if (bRet == -1)
{
DWORD dwErr = GetLastError();
char info[10];
sprintf_s(info, "%d", dwErr);
MessageBox(hWnd, info, "Error", 0);
return -1;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

return 0;
}

LRESULT CALLBACK WndProc(
_In_  HWND hwnd,
_In_  UINT uMsg,
_In_  WPARAM wParam,
_In_  LPARAM lParam
)
{
HDC hDC;
PAINTSTRUCT ps;
static int charCount = 0;
int x, y;
switch (uMsg)
{
case WM_CHAR:
hDC = GetDC(hwnd);
TextOut(hDC, 100 + charCount * 8, 100, (LPCSTR)&wParam, 1);
charCount++;
ReleaseDC(hwnd, hDC);
break;
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
hDC = GetDC(hwnd);
char info[100];
char keyType[10];
switch (wParam)
{
case MK_LBUTTON:
strcpy_s(keyType, "Left");
break;
case MK_MBUTTON:
strcpy_s(keyType, "Middle");
break;
case MK_RBUTTON:
strcpy_s(keyType, "Right");
break;
default:
break;
}
x = lParam & 0x0000FFFF;
y = lParam >> 16;
sprintf_s(info, "%s Key down, At (%d, %d)", keyType, x, y);
TextOut(hDC, 200, 0, info, strlen(info));

ReleaseDC(hwnd, hDC);
break;
case WM_PAINT:
hDC = BeginPaint(hwnd, &ps);
TextOut(hDC, 0, 0, "Hi, I'm Uranux!", strlen("Hi, I'm Uranux!"));
EndPaint(hwnd, &ps);
break;
case WM_CLOSE:
if (IDYES == MessageBox(hwnd, "Are you sure?", "Quit", MB_YESNO))
{
DestroyWindow(hwnd);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
break;
}

return NULL;
}


下面我来层层剖析这段代码。

二、 头文件

windows.h这个文件包含了Windows API的声明,比如代码中的CreateWindow,PostQuitMessage等等,都是Windows API,是操作系统提供给程序员的函数接口。

三、 程序入口

Windows程序的入口函数是WinMain,其函数原型如下:

int WINAPI WinMain(
_In_  HINSTANCE hInstance,
_In_  HINSTANCE hPrevInstance,
_In_  LPSTR lpCmdLine,
_In_  int nCmdShow
)
1. WINAPI是一个宏,值是__stdcall,它是一种函数调用约定,约定函数参数从右向左入站,由调用方清栈,所以该调用约定不能修饰变参函数。

2. 至于_In_,它也是一个宏,它的定义比较复杂,我目前还不理解。它的定义位于sal.h:

// Input parameters --------------------------

//   _In_ - Annotations for parameters where data is passed into the function, but not modified.
//          _In_ by itself can be used with non-pointer types (although it is redundant).

// e.g. void SetPoint( _In_ const POINT* pPT );
#define _In_                            _SAL2_Source_(_In_, (), _Pre1_impl_(__notnull_impl_notref) _Pre_valid_impl_ _Deref_pre1_impl_(__readaccess_impl_notref))
其中_SAL2_Source_也是一个宏,位于sal.h:

#define _SAL2_Source_(Name, args, annotes) _SA_annotes3(SAL_name, #Name, "", "2") _Group_(annotes _SAL_nop_impl_)
其中_SA_annotes3也是一个宏,位于sal.h:

#define _SA_annotes3(n,pp1,pp2,pp3)    __declspec(#n "(" _SA_SPECSTRIZE(pp1) "," _SA_SPECSTRIZE(pp2) "," _SA_SPECSTRIZE(pp3) ")")
此处不再展开分析,后续会专门开篇说明。值得注意的是__declspec,它是另外一种函数调用约定,用于dll函数的导入导出,通常与extern "C"联合使用。

3. hInstance是一个程序实例句柄(Handle of instance),它代表当前程序的实例。

4. hPrevInstance也是一个程序实例句柄,它代表之前一个相同程序的实例。比如A.exe是一个Windows程序,第一次打开它时,hPrevInstance为NULL,然后不关掉它,再打开,新进程的hPrevInstance就是第一个进程的hInstance。

5. lpCmdLine是传递给该程序的命令行参数,类型是LPSTR(Long pointer of string),相当于标准C程序中的argv。

6. nCmdShow指定程序窗口的显示方式,例如最大化、最小化、隐藏等。

四、 窗口

1. WNDCLASS是窗口类,其定义如下:

typedef struct tagWNDCLASS {
UINT      style;
WNDPROC   lpfnWndProc;
int       cbClsExtra;
int       cbWndExtra;
HINSTANCE hInstance;
HICON     hIcon;
HCURSOR   hCursor;
HBRUSH    hbrBackground;
LPCTSTR   lpszMenuName;
LPCTSTR   lpszClassName;
} WNDCLASS, *PWNDCLASS;
要创建窗口,首先要定义一个窗口类,并完成其各个成员的初始化。

WNDCLASS wnd;


style是窗口的样式

wnd.style = CS_VREDRAW | CS_HREDRAW;
CS_VREDRAW和CS_HREDRAW分别表示垂直重绘(Vertical redraw)和水平重绘(Horizontal redraw),CS表示Class style。

lpfnWndProc是窗口过程,所谓窗口过程(Windows process)就是指窗口接收到消息时的处理函数,这个函数由操作系统调用。

wnd.lpfnWndProc = WndProc;
其类型为:

typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
其中CALLBACK是一个宏,表示__stdcall。

cbClsExtra表示类附加内存的大小,一般为0;cbWndExtra表示窗口附加内存的大小,一般为0。

wnd.cbClsExtra = 0;
wnd.cbWndExtra = 0;


hInstance表示该窗口过程所属的窗口的句柄。

wnd.hInstance = hInstance;


hIcon表示窗口图标的句柄(Handle of icon),可以使用LoadIcon获得一个窗口图标的句柄。

wnd.hIcon = LoadIcon(NULL, IDI_INFORMATION);
hCursor表示窗口光标的句柄(Handle of cursor),可以使用LoadCursor获得一个窗口光标的句柄。

wnd.hCursor = LoadCursor(NULL, IDC_HAND);
hbrBackground指定窗口类的背景画刷句柄(Handle of brush)。

wnd.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);
GetStockObject的返回类型是HGDIOBJ,而hbrBackground的类型是HBRUSH,所以需要强制类型转换。

lpszMenuName是菜单的名称,没有菜单的话就为NULL。

wnd.lpszMenuName = NULL;


wnd.lpszClassName是窗口类的名称,用于CreateWindow。

wnd.lpszClassName = "uranux";


2. 当窗口类初始化完毕时,需要进行注册,调用RegisterClass。

RegisterClass(&wnd);


3. 然后就可以显示窗口了。

HWND hWnd = CreateWindow(wnd.lpszClassName, "Hello Uranux!", WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX, 100, 100, 500, 400, NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);


CreateWindow函数的原型如下:

HWND WINAPI CreateWindow(
_In_opt_  LPCTSTR lpClassName,
_In_opt_  LPCTSTR lpWindowName,
_In_      DWORD dwStyle,
_In_      int x,
_In_      int y,
_In_      int nWidth,
_In_      int nHeight,
_In_opt_  HWND hWndParent,
_In_opt_  HMENU hMenu,
_In_opt_  HINSTANCE hInstance,
_In_opt_  LPVOID lpParam
);


lpClassName是需要创建的窗体类的名称,必须是注册过的。lpWindowsName是窗口标题栏的内容。

值得注意的是dwStyle,它表示窗口的样式,我在这里取值是WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX,其定义如下,位于WinUser.h:

/*
* Window Styles
*/
#define WS_OVERLAPPED       0x00000000L
#define WS_POPUP            0x80000000L
#define WS_CHILD            0x40000000L
#define WS_MINIMIZE         0x20000000L
#define WS_VISIBLE          0x10000000L
#define WS_DISABLED         0x08000000L
#define WS_CLIPSIBLINGS     0x04000000L
#define WS_CLIPCHILDREN     0x02000000L
#define WS_MAXIMIZE         0x01000000L
#define WS_CAPTION          0x00C00000L     /* WS_BORDER | WS_DLGFRAME  */
#define WS_BORDER           0x00800000L
#define WS_DLGFRAME         0x00400000L
#define WS_VSCROLL          0x00200000L
#define WS_HSCROLL          0x00100000L
#define WS_SYSMENU          0x00080000L
#define WS_THICKFRAME       0x00040000L
#define WS_GROUP            0x00020000L
#define WS_TABSTOP          0x00010000L

#define WS_MINIMIZEBOX      0x00020000L
#define WS_MAXIMIZEBOX      0x00010000L

#define WS_TILED            WS_OVERLAPPED
#define WS_ICONIC           WS_MINIMIZE
#define WS_SIZEBOX          WS_THICKFRAME
#define WS_TILEDWINDOW      WS_OVERLAPPEDWINDOW

/*
* Common Window Styles
*/
#define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED     | \
WS_CAPTION        | \
WS_SYSMENU        | \
WS_THICKFRAME     | \
WS_MINIMIZEBOX    | \
WS_MAXIMIZEBOX)

#define WS_POPUPWINDOW      (WS_POPUP          | \
WS_BORDER         | \
WS_SYSMENU)

#define WS_CHILDWINDOW      (WS_CHILD)


可以看出每个基本的宏(非Common Window Styles)都只有一个二进制位为1,所以需要多种特性的时候可以进行位或操作。WS_OVERLAPPEDWINDOW 是一个封装的宏,封装了6种特性,在这里我不需要其中的WS_MAXIMIZEBOX特性(支持最大化),所以我把WS_OVERLAPPEDWINDOW和~WS_MAXIMIZEBOX做了位与操作。

ShowWindow的原型如下:

BOOL WINAPI ShowWindow(
_In_  HWND hWnd,
_In_  int nCmdShow
);


nCmdShow表示显示参数(最小化显示、最大化显示、隐藏显示、正常显示等)。

值得注意的是它返回类型为BOOL,它并不是C++内置类型bool,而是int:

typedef int                 BOOL;
位与minwindef.h。

UpdateWindow函数负责立即向窗口发送一个WM_PAINT消息。

五、

未完待续

---------------------------------------

2014.5.4更新

由于工作原因,此文不再更新
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: