首先理解下两个名词: 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可以得知该消息来自哪个窗口, 然后分派给操作系统, 让其调用对应窗口的窗口过程.