Windows程序运行机制

写在前面

首先理解下两个名词: API 和 SDK.

API(Application Programming Interface), 译为应用程序编程接口. 是Windows系统提供给开发人员的函数的简称.
SDK(Software Development Kit), 译为软件开发包. 是API库, 使用手册, 帮助说明, 其他辅助工具等资源, 即开发所需资源的一个集合.

一个Windows程序至少包括一个与用户交互的窗口(即主窗口), 窗口可以分为客户区和非客户区.

客户区是窗口的一部分, Windows应用程序一般在客户区显示和绘制.

标题, 菜单, 状态栏, 最大/小框等属于非客户区, 它们有Windows操作系统来管理.

窗口可以有一个父窗口, 有父窗口的窗口则为子窗口.

在Windows应用程序中, 窗口通过窗口句柄(HWND)来标识, 对窗口的操作也都需借由窗口句柄来进行.

Windows程序是一种基于消息的程序设计方式, 每个Windows程序都会有一个消息循环来捕获用户在窗口上操作, 然后交由窗口的 "窗口过程"处理.

一个Windows应用程序一般会有以下步骤:
①设计一个窗口类
②注册窗口类
③创建窗口
④显示/刷新窗口
⑤进入消息循环

设计

Windows操作系统会为每个窗口类维护一个WNDCLASS结构体, 如下:

//ANSI版本
typedef struct tagWNDCLASSA {
    UINT        style;			//窗口类型. 例CS_HREDRAW | CS_VREDRAW表示水平垂直尺寸发生变化时重绘窗口
    WNDPROC     lpfnWndProc;	//窗口过程, 原型:typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
    int         cbClsExtra;		//类附加内存,一般用于存储类的附加信息,该窗口类的所有窗口共享该内存;0 表示不使用
    
    //窗口附加内存,Windows系统为每一个窗口(实例对象)管理一个内部数据结构,在注册一个窗口类时,应用程序能够指定一定字节数的附加内存空间,称为窗口附加内存,注意区别于类附加内存
    //在创建该类窗口时,Windows系统就为窗口的结构(即Windows为窗口管理的内部数据结构)分配和追加指定书目的窗口附加内存空间,应用程序可用这部分内存存储窗口特有的数据。
    //Windows系统把该部分初始化为0,表示不使用。如果应用程序用WNDCLASS结构注册对话框,则必须给DLGWINDOWEXTRA设置这个成员。
    int         cbWndExtra;	
    HINSTANCE   hInstance;		//包含窗口过程的应用程序的实例句柄

	//指定窗口类的图标句柄, 为NULL则为系统默认图标
	//可使用LoadIcon函数来加载自定义图标资源, 原型如下:参数一为要加载的图标句柄,为NULL则加载标准图标;参数二为图标名称,LPCTSTR类型,可使用MAKEINTRESOURCE宏将ID转换成LPCTSTR
	//HICON LoadIcon(HINSTANCE hInstance, LPCTSTR lpInconName);	
    HICON       hIcon;
	
	//指定窗口类的光标句柄, 这个成员必须是一个关闭资源的句柄。如果为NULL,则无论何时鼠标进入应用程序窗口中,应用程序都必须明确地设置光标的形状
	//可使用LoadCursor函数来加载一个光标资源.函数原型如下:参数一为要加载的光标句柄,为NULL则加载标准光标;参数二为光标名称,可使用MAKEINTRESOURCE宏将ID转换成LPCTSTR
	//HCURSOR LoadCursor(HINSTANCE hInstance, LPCTSTR lpCursorName);
    HCURSOR     hCursor;

	//指定窗口类的背景画刷, 当窗口重绘时使用该画刷擦除窗口的背景
	//可使用GetStockObject函数获取画刷句柄,该函数还可用来获取画笔、字体和调色板的句柄,原型如下:
	//HGDIOBJ GetStockObject(int fnObject); 参数指定要获取的对象类型,例BLACK_BRUSH表示获取黑色的画刷; 还需注意GetStockObject可获取多种资源的句柄,因此返回值是一个通用的HGDIOBJ(void*),可强制类型转成所需类型
    HBRUSH      hbrBackground;
    LPCSTR      lpszMenuName;		//指定窗口类的菜单资源, 菜单的ID一般都是UINT,可使用MAKEINTRESOURCE宏转换成LPCTSTR
    LPCSTR      lpszClassName;		//指定窗口类的名称
} WNDCLASSA, *PWNDCLASSA, NEAR *NPWNDCLASSA, FAR *LPWNDCLASSA;

//Unicode版本
typedef struct tagWNDCLASSW {
    UINT        style;
    WNDPROC     lpfnWndProc;
    int         cbClsExtra;
    int         cbWndExtra;
    HINSTANCE   hInstance;
    HICON       hIcon;
    HCURSOR     hCursor;
    HBRUSH      hbrBackground;
    LPCWSTR     lpszMenuName;
    LPCWSTR     lpszClassName;
} WNDCLASSW, *PWNDCLASSW, NEAR *NPWNDCLASSW, FAR *LPWNDCLASSW;

#ifdef UNICODE
typedef WNDCLASSW WNDCLASS;
#else
typedef WNDCLASSA WNDCLASS;

例这里设计一个窗口如下:

	WNDCLASS wndcls;	
	wndcls.style = CS_HREDRAW | CS_VREDRAW;						//水平垂直尺寸发生变化时重绘窗口
	wndcls.lpfnWndProc = WinProc;								//指定该窗口的窗口过程
	wndcls.cbClsExtra = 0;										//不使用类附加内存
	wndcls.cbWndExtra = 0;										//不使用窗口附加内存
	wndcls.hInstance = hInstance;								//使用WinMain中hInstance参数初始化
	wndcls.hIcon = LoadIcon(NULL, IDI_INFORMATION); 			//使用信息图标
	wndcls.hCursor = LoadCursor(NULL, IDC_CROSS);				//使用十字光标
	wndcls.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);	//BLACK_BRUSH表示获取黑色的画刷
	wndcls.lpszMenuName = NULL;									//这里不指定菜单
	wndcls.lpszClassName = "WinPro";							//将自己设计的窗口类命名为WinPro

窗口过程WinProc如下:

	//窗口过程
	//窗口过程函数
LRESULT CALLBACK WinProc(
	HWND hwnd,
	UINT msg,
	WPARAM wParam,
	LPARAM lParam
	)
{
	switch (msg)
	{
	case WM_CHAR:
		char szChar[20];
		sprintf_s(szChar, sizeof(szChar), "char code is %d", wParam);
		MessageBox(hwnd, szChar, "char", 0);
		break;

	case WM_LBUTTONDOWN:
		MessageBox(hwnd, _T("mouse clicked"), _T("message"), 0);
		HDC hdc;
		hdc = GetDC(hwnd);	//不能在WM_PAINT的响应中调用GetDC函数,在WM_PAINT响应中必须且只能使用BeginPaint(HWND, PAINTSTRUCT*)
		TextOut(hdc, 0, 50, _T("mouse clicked"), strlen("mouse clicked"));
		ReleaseDC(hwnd, hdc);
		break;

	case WM_PAINT:
		HDC hDC;
		PAINTSTRUCT ps;		//用于接收绘制的信息
		hDC = BeginPaint(hwnd, &ps);
		TextOut( hDC, 0, 0, _T("WM_PAINT.."), _tcslen(_T("WM_PAINT..")) );
		EndPaint(hwnd, &ps);
		break;

	case WM_CLOSE:
		if (IDYES == MessageBox(hwnd, "是否关闭窗口?", "message", MB_YESNO))
		{
			DestroyWindow(hwnd);
		}
		break;

	case WM_DESTROY:
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hwnd, msg, wParam, lParam);
	}

	return 0;
}

注册

使用RegisterClass注册窗口,原型如下:

typedef WORD                ATOM;   //BUGBUG - might want to remove this from minwin

//参数: WNDCLASS类的指针, 在将结构传递给函数之前,必须使用适当的类属性填充结构。
//返回值: 如果函数成功,则返回值是一个类原子,用于唯一标识正在注册的类。如果函数失败,则返回值为零。
ATOM RegisterClassA(
  [in] const WNDCLASSA *lpWndClass
);

例: 这里注册上面设计的WinPro窗口类

RegisterClass(&wndcls);	

创建

使用CreateWindow函数创建一个窗口示例, 原型如下:

void CreateWindowA(
  [in, optional]  lpClassName,		//设计窗口时WNDCLASS中的lpszClassName成员
  [in, optional]  lpWindowName,		//窗口名称。即窗口标题
  [in]            dwStyle,			//正在创建的窗口的样式。注意区别于WNDCLASS中的style, 以IPhone为例, WNDCLASS中style指定的像是苹果手机的模具(刘海屏), 而创建时的dwStyle则可以指定使用该模具实际生产时的颜色
  [in]            x,				//窗口的初始水平位置。以父窗口为基准的坐标
  [in]            y,				//窗口的初始垂直位置。同样以父窗口为基准的坐标
  [in]            nWidth,			//窗口的宽度
  [in]            nHeight,			//窗口的高度
  [in, optional]  hWndParent,		//正在创建的窗口的父窗口或所有者窗口的句柄。
  [in, optional]  hMenu,			//菜单的句柄,或根据窗口样式指定子窗口标识符。
  [in, optional]  hInstance,		//要与窗口关联的模块实例的句柄。
  [in, optional]  lpParam			//指向要通过WM_CREATE消息的参数所指向的创建结构结构(lp创建参数成员)传递到窗口的值的指针, 一般设置为NULL。
);

例这里创建上面注册的WinPro窗口:

HWND hwnd;
hwnd = CreateWindow(_T("WinPro"), _T("这是一个标题"), WS_OVERLAPPEDWINDOW, 300, 200, 600, 400, NULL, NULL, hInstance, NULL);

显示/刷新

使用ShowWindow显示, 使用UpdateWindow刷新, 原型如下:

//返回值: 如果窗口以前可见,则返回值为非零值。如果窗口以前是隐藏的,则返回值为零。
BOOL ShowWindow(
  [in] HWND hWnd,		//要显示的窗口句柄
  [in] int  nCmdShow	//显示方式. 注意: 第一次显示窗口时应指定SW_SHOWNORMAL
);


//返回值: 如果函数成功,则返回值为非零值。如果函数失败,则返回值为零。
BOOL UpdateWindow(
  [in] HWND hWnd		//要刷新的窗口句柄
);

例这里显示和刷新上面创建的窗口:

	ShowWindow(hwnd, SW_SHOWNORMAL);	//第一次显示窗口时应制定SW_SHOWNORMAL
	UpdateWindow(hwnd);

消息循环

作为一个窗口应用程序, 必定会有与用户的交互, 而对用户的操作的相应处理, 放在应用程序的消息循环中处理.

可使用GetMessage函数从应用程序的消息队列中取出消息,函数原型如下:

//lpMsg: 保存从消息队列中取出的消息,hwnd制定接收哪个窗口的消息,为NULL则接收所有窗口的消息, wMsgFilterMin指定要获取消息的最小值, wMsgFilterMax指定要获取消息的最大值
//wMsgFilterMin和wMsgFilterMax均指定为0, 则接收所有消息
//返回值: GetMessage接收除WM_QUIT外的消息均返回非零值,对于WM_QUIT消息,返回0。如果出现了错误,则返回-1,例当hwnd为无效窗口句柄时或lpMsg为无效指针时,返回-1
BOOL GetMessage(LPMSG lpMsg, HWND hwnd, UINT wMsgFilterMin, UINT wMsgFilterMax);	

消息结构体MSG定义如下:

typedef struct tagMSG {
  HWND   hwnd;			//窗口过程的句柄,其窗口过程接收消息。当消息是线程消息时,此成员为 NULL。
  UINT   message;		//消息标识符。应用程序只能使用低字;高音由系统保留。
  WPARAM wParam;		//有关消息的其他信息。确切的含义取决于消息成员的值。
  LPARAM lParam;		//同wParam
  DWORD  time;			//发布消息的时间。
  POINT  pt;			//发布消息时光标位置(以屏幕坐标表示)。
  DWORD  lPrivate;		//
} MSG, *PMSG, *NPMSG, *LPMSG;

然后使用TranslateMessage将虚拟键消息转换成字符消息.

注意: TranslateMessage用于将虚拟键消息转换成字符消息,然后再使用投递到msg.hwnd窗口的消息队列中。TranslateMessage并不会修改原有的消息,它只产生新的消息并投递到对应窗口的消息队列中.

原型如下:

BOOL TranslateMessage(
  [in] const MSG *lpMsg		//指向 MSG 结构的指针,该结构包含使用“获取消息”或“扫视消息”函数从调用线程的消息队列中检索到的消息信息。
);

最后使用DispatchMessage分派消息到对应窗口的窗口过程. DispatchMessage实际上是将消息回传给操作系统,由操作系统调用对应窗口过程进行处理(响应).
原型如下:

//lpMsg: 指向包含消息的结构的指针。
//返回值: 指定窗口过程返回的值。尽管其含义取决于要调度的消息,但返回值通常会被忽略。
LRESULT DispatchMessage(
  [in] const MSG *lpMsg
);

编写上面创建窗口所属示例的消息循环:

	while (bRet = GetMessage(&msg, NULL, 0, 0))
	{
		if (bRet == -1)
		{
			return -1;
		}
		else
		{
			if (TranslateMessage(&msg))
			{
				OutputDebugString(_T("Translate"));
			}
			DispatchMessage(&msg);
		}
	}

代码

完整代码如下:

#include 
#include 
#include 

LRESULT CALLBACK WinProc(
	HWND hwnd,
	UINT msg,
	WPARAM wParam,
	LPARAM lParam
	);

int WINAPI WinMain(
	HINSTANCE hInstance,
	HINSTANCE hPreInstance,
	LPSTR lpCmdLine,
	int nCmdShow
	)
{
	//设计
	//操作系统为每个窗口类维护一个WNDCLASS结构
	WNDCLASS wndcls;	

	//水平垂直尺寸发生变化时重绘窗口
	wndcls.style = CS_HREDRAW | CS_VREDRAW;	

	//指定该窗口的窗口过程
	wndcls.lpfnWndProc = WinProc;	

	//类附加内存,一般用于存储类的附加信息,该窗口类的所有窗口共享该内存(理解成类的静态成员);0 表示不使用
	wndcls.cbClsExtra = 0;

	//窗口附加内存,Windows系统为每一个窗口(实例对象)管理一个内部数据结构,在注册一个窗口类时,应用程序能够指定一定字节数的附加内存空间,称为窗口附加内存,注意区别于类附加内存
	//在创建该类窗口时,Windows系统就为窗口的结构(即Windows为窗口管理的内部数据结构)分配和追加指定书目的窗口附加内存空间,应用程序可用这部分内存存储窗口特有的数据。
	//Windows系统把该部分初始化为0,表示不使用。如果应用程序用WNDCLASS结构注册对话框,则必须给DLGWINDOWEXTRA设置这个成员。
	wndcls.cbWndExtra = 0;

	//包含窗口过程的应用程序的实例句柄
	wndcls.hInstance = hInstance;

	//指定窗口类的图标句柄, 为NULL则为系统默认图标
	//可使用LoadIcon函数来加载自定义图标资源, 原型如下:参数一为要加载的图标句柄,为NULL则加载标准图标;参数二为图标名称,LPCTSTR类型,可使用MAKEINTRESOURCE宏将ID转换成LPCTSTR
	//HICON LoadIcon(HINSTANCE hInstance, LPCTSTR lpInconName);	
	wndcls.hIcon = LoadIcon(NULL, IDI_INFORMATION); //IDI_INFORMATION   IDI_ERROR

	//指定窗口类的光标句柄, 这个成员必须是一个关闭资源的句柄。如果为NULL,则无论何时鼠标进入应用程序窗口中,应用程序都必须明确地设置光标的形状
	//可使用LoadCursor函数来加载一个光标资源.函数原型如下:参数一为要加载的光标句柄,为NULL则加载标准光标;参数二为光标名称,可使用MAKEINTRESOURCE宏将ID转换成LPCTSTR
	//HCURSOR LoadCursor(HINSTANCE hInstance, LPCTSTR lpCursorName);
	wndcls.hCursor = LoadCursor(NULL, IDC_CROSS);

	//指定窗口类的背景画刷, 当窗口重绘时使用该画刷擦除窗口的背景
	//可使用GetStockObject函数获取画刷句柄,该函数还可用来获取画笔、字体和调色板的句柄,原型如下:
	//HGDIOBJ GetStockObject(int fnObject); 参数指定要获取的对象类型,例BLACK_BRUSH表示获取黑色的画刷; 还需注意GetStockObject可获取多种资源的句柄,因此返回值是一个通用的HGDIOBJ(void*),可强制类型转成所需类型
	wndcls.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);

	//指定窗口类的菜单资源, 菜单的ID一般都是UINT,可使用MAKEINTRESOURCE宏转换成LPCTSTR
	wndcls.lpszMenuName = NULL;	//这里不指定菜单

	//指定窗口类的名称
	wndcls.lpszClassName = "WinPro";	//将自己设计的窗口类命名为WinPro

	//注册, 使用RegisterClass注册窗口,原型如下:
	//ATOM RegisterClass(CONST WNDCLASS* lpWndClass);	ATOM 为WORD, 即unsigned short
	RegisterClass(&wndcls);

	//创建,使用CreateWindow函数创建一个窗口示例
	HWND hwnd = CreateWindow(_T("WinPro"), _T("这是一个标题"), WS_OVERLAPPEDWINDOW, 300, 200, 600, 400, NULL, NULL, hInstance, NULL);

	//显示及刷新
	ShowWindow(hwnd, SW_SHOWNORMAL);	//第一次显示窗口时应制定SW_SHOWNORMAL
	UpdateWindow(hwnd);

	//定义消息结构体,开始消息循环
	MSG msg;
	BOOL bRet = TRUE;

	//可使用GetMessage函数从应用程序的消息队列中取出消息,函数原型如下:
	//BOOL GetMessage(LPMSG lpMsg, HWND hwnd, UINT wMsgFilterMin, UINT wMsgFilterMax);	
	//lpMsg保存从消息队列中取出的消息,hwnd制定接收哪个窗口的消息,为NULL则接收所有窗口的消息, wMsgFilterMin指定要获取消息的最小值, wMsgFilterMax指定要获取消息的最大值
	//wMsgFilterMin和wMsgFilterMax均指定为0, 则接收所有消息
	//GetMessage接收除WM_QUIT外的消息均返回非零值,对于WM_QUIT消息,返回0。如果出现了错误,则返回-1,例当hwnd为无效窗口句柄时或lpMsg为无效指针时,返回-1
	while (bRet = GetMessage(&msg, NULL, 0, 0))
	{
		if (bRet == -1)
		{
			return -1;
		}
		else
		{
			//TranslateMessage用于将虚拟键消息转换成字符消息,然后再使用投递到msg.hwnd窗口的消息队列中。TranslateMessage并不会修改原有的消息,它只产生新的消息并投递到对应窗口的消息队列中
			if (TranslateMessage(&msg))
			{
				OutputDebugString(_T("Translate"));
			}

			//DispatchMessage函数分派一个消息到窗口过程,DispatchMessage实际上是将消息回传给操作系统,由操作系统调用对应窗口过程进行处理(响应)
			DispatchMessage(&msg);
		}
	}

	return msg.wParam;
}

//窗口过程函数
LRESULT CALLBACK WinProc(
	HWND hwnd,
	UINT msg,
	WPARAM wParam,
	LPARAM lParam
	)
{
	switch (msg)
	{
	case WM_CHAR:
		char szChar[20];
		sprintf_s(szChar, sizeof(szChar), "char code is %d", wParam);
		MessageBox(hwnd, szChar, "char", 0);
		break;

	case WM_LBUTTONDOWN:
		MessageBox(hwnd, _T("mouse clicked"), _T("message"), 0);
		HDC hdc;
		hdc = GetDC(hwnd);	//不能在WM_PAINT的响应中调用GetDC函数,在WM_PAINT响应中必须且只能使用BeginPaint(HWND, PAINTSTRUCT*)
		TextOut(hdc, 0, 50, _T("mouse clicked"), strlen("mouse clicked"));
		ReleaseDC(hwnd, hdc);
		break;

	case WM_PAINT:
		HDC hDC;
		PAINTSTRUCT ps;		//用于接收绘制的信息
		hDC = BeginPaint(hwnd, &ps);
		TextOut( hDC, 0, 0, _T("WM_PAINT.."), _tcslen(_T("WM_PAINT..")) );
		EndPaint(hwnd, &ps);
		break;

	case WM_CLOSE:
		if (IDYES == MessageBox(hwnd, "是否关闭窗口?", "message", MB_YESNO))
		{
			DestroyWindow(hwnd);
		}
		break;

	case WM_DESTROY:
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hwnd, msg, wParam, lParam);
	}

	return 0;
}

总结

通过该示例了解Windows应用程序的内部运行机制, 即:
①设计一个窗口类
②注册窗口类
③创建窗口
④显示/刷新窗口
⑤进入消息循环

需要注意的是:
①设计窗口类是必须明确的指定光标
②必须注册已设计的窗口类, Windows系统会为每个窗口类维护一个WNDCLASS结构体
③必须创建已注册的窗口类
④第一次显示窗口时应该指定SW_NORMAL

Windows系统会为每个应用程序创建一个消息队列, 因此需要一个消息循环不断的读取消息队列中的消息, 通过消息结构体MSG中的窗口句柄成员hwnd可以得知该消息来自哪个窗口, 然后分派给操作系统, 让其调用对应窗口的窗口过程.

你可能感兴趣的:(Windows程序设计,windows,c++,开发语言)