注意:我们创建的是桌面应用程序
1.WinMain
int WINAPI wWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PWSTR pCmdLine,
int nCmdShow);
解释参数:
<1>hInstance:
在win32中h开头的通常都是句柄,我们知道各种各样的句柄
HANDLE:指向一个内核模块的句柄
HWND:指向一个窗口的句柄
HDC:指向设备上下文的句柄
今天我们学的HINSTANCE:指向一个模块的句柄
大家在看到句柄的时候应该记住:
1.真正的对象在内核层,句柄只是个索引
2.句柄都是个DWORD(四个字节)储存一个索引
<2>hPrevInstance:它用于16位Windows,但现在始终为零。(填空就行)
<3>pCmdLine;nCmdShow:这两个放一起说
之前我们学习过创建进程的函数
BOOL CreateProcessA(
[in, optional] LPCSTR lpApplicationName,
[in, out, optional] LPSTR lpCommandLine,//我们知道进程都是由另一个进程创建的当A进程创建B进程时,通过A进程中的这个参数,把这个值传递给B进程入口函数(WinMain)的pCmdLine中
........
[in] LPSTARTUPINFOA lpStartupInfo,//创建你创建的进程由什么方式显现出来是最大化啊,还是最小化啊等等,这会传递给nCmdShow
........
);
2.调试信息的输出:
我们不能想用控制台程序直接用printf,我们可以用OutputDebugString(szOutBuff)不过它只能打印固定的字符串(szOutBuff).所以我们定义一个即可
代码如下
#include "framework.h"
#include "WindowsProject1.h"
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
char szOutBuff[0x80];
DWORD dwAddr = (DWORD)hInstance;
sprintf(szOutBuff, "模块路径: %x", dwAddr);
OutputDebugStringA(szOutBuff);
return 0;
}
我们用debugview++查看输出
3.创建窗口
<1>第一步:定义你的窗口是什么样的
在WindowsAPI中有一个(结构体)类这个类叫做WNDCLASS我们查询官方文档
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, *NPWNDCLASSW, *LPWNDCLASSW;
发现这个结构体中有很多成员。我们了解其中几个即可
1.hbrBackground:你所要创建窗口的背景色是什么.。它是一个类背景画笔的句柄。我们可以选择多种颜色值。不过在使用时我们必须要转型把颜色值转化为画刷也就是HBRUSH类型,因为背景色是画刷类型。
以下是可选择颜色值:
COLOR_ACTIVEBORDER
COLOR_ACTIVECAPTION
COLOR_APPWORKSPACE
COLOR_BACKGROUND
COLOR_BTNFACE
COLOR_BTNSHADOW
COLOR_BTNTEXT
COLOR_CAPTIONTEXT
COLOR_GRAYTEXT
COLOR_HIGHLIGHT
COLOR_HIGHLIGHTTEXT
COLOR_INACTIVEBORDER
COLOR_INACTIVECAPTION
COLOR_MENU
COLOR_MENUTEXT
COLOR_SCROLLBAR
COLOR_WINDOW
COLOR_WINDOWFRAME
COLOR_WINDOWTEXT
2.lpszClassName:我们必须要为WNDCLASS起名,这个名字是个具体的字符串。
3.hInstance:你当前的窗口是属于哪个程序的
4.lpfnWndProc:每个窗口对应一个窗口程序,窗口程序根据你消息的类型做出不同反应。我们知道当我们点击窗口的×号时,操作系统会生成消息发送给我们的程序,那么就是这个窗口程序来处理消息,lpfnWndProc这个就是指向窗口程序的指针。
那么我们怎么创建窗口程序?参考官方文档: For more information, see WindowProc.
这个WindowProc就是窗口处理函数,它是由操作系统调用的。WindowProc()在窗口对象中有描述
LRESULT CALLBACK WindowProc(
_In_ HWND hwnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
);
找到结构体:我们上面定义完了一个结构体,不过操作系统还找不到这个结构体,所以我们需
要调用RegisterClass(注册窗口类):就是告诉操作系统让操作系统找到它。
ATOM RegisterClassA(
[in] const WNDCLASSA *lpWndClass
);
<2>创建并显示窗口:
1.创建窗口:CreateWindow()
void CreateWindowW(
[in, optional] lpClassName,//通过上边创建的ClassName关联
[in, optional] lpWindowName,//窗口的名字
[in] dwStyle,//风格,具体的查阅官方文档
[in] x,//相对于父窗口的x坐标
[in] y,//相对于父窗口y坐标
[in] nWidth,//窗口宽度,单位是像素
[in] nHeight,//窗口宽度,单位事像素
[in, optional] hWndParent,//父窗口句柄
[in, optional] hMenu,//菜单句柄
[in, optional] hInstance,//当前应用程序句柄(属于哪个模块)
[in, optional] lpParam//附加数据
);
不过我们怎么知道是否创建窗口成功了?这个函数是有返回值的。我们查看官方文档:
If the function succeeds, the return value is a handle to the new window.
If the function fails, the return value is NULL. To get extended error information, call GetLastError.
翻译:
如果函数成功,则返回值是新窗口的句柄。
如果函数失败,则返回值为NULL。要获取扩展的错误信息,请致电GetLastError。
2.显示窗口:ShowWindow
BOOL ShowWindow(
[in] HWND hWnd,//要显示哪个窗口,把窗口句柄给它
[in] int nCmdShow//以什么形式显示,具体查阅官方文档
);
接下来我们通过图来说明操作系统做了哪些事
如图所示,我们知道操作系统通过窗口对象找到线程、再在消息队列中取出消息,根据消息类型决定要做什么。所以下面就要接收消息并处理
3.第三步:接受消息并处理
我们用到GetMessage()函数:从消息队列中取出消息,取什么消息、什么样的消息。如果没有消息就会阻塞
BOOL GetMessage(
[out] LPMSG lpMsg,//一个指针,指向MSG把取出的消息放在里面
[in, optional] HWND hWnd,//要检索其消息的窗口的句柄,如果填空那么就是所有消息都要
[in] UINT wMsgFilterMin,//先填0
[in] UINT wMsgFilterMax//先填0
);
后面三个参数全是限制条件,我们看上面的图片。一个线程中可能有很多窗口,所以我们用GetMessage()需要让操作系统找到要找的窗口。
这里我们可以使用官方文档中给出的示例代码
BOOL bRet;
while ((bRet = GetMessage(&msg, hWnd, 0, 0)) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
//转换消息
//TranslateMessage(&msg);
DispatchMessage(&msg);//分发消息
}
}
取出消息后我们应该去处理消息,调用DispatchMessage()函数 :分发消息。分发消息的目的就是调用消息处理函数:WinodwProc()
这里我们可能会觉得分发消息多此一举,因为我们已经把消息取出来了,那么直接调用消息处理函数不就行了。那么我们调用分发的原因是消息处理函数不是我们调用的是操作系统调用的。
如图所示,我们虽然使用GetMessage()把消息取出来,但是我们并不知道每个窗口对应的消息处理函数在哪。我们想要处理消息应该知道消息要由谁处理。我们取出来的消息可能是100个窗口中的消息,这些消息是由WindowProc1还是WindowProc2处理呢(见上图)?而且消息处理函数在窗口对象(内核层中)中有描述,这就需要在内核中处理,所以我们需要再次进到内核层。这时就通过DispatchMessage()再次进入内核,并拿着窗口的句柄找到底是哪个窗口。如果是A窗口就调用WindowProc1如果是B窗口就调用WindowProc2。
经过上述步骤窗口就创建好了,不过在WindowProc()中即使我们什么都不干也要调用默认的窗口处理函数
LRESULT CALLBACK WindowProc(
_In_ HWND hwnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)
{
return DefWindowProc(hwnd,uMsg,wParam,lParam);//这个就是默认的窗口处理函数,就是一些通用的功能Windows已经为我们处理好了只要调用就可以了
}
下面我们自己试着创建一个窗口,代码如下
#include "framework.h"
#include "WindowsProject1.h"
LRESULT CALLBACK WindowProc(
_In_ HWND hwnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)
{
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
char szOutBuff[0x80];
//1.第一步:定义你的窗口是什么样的
TCHAR className[] = TEXT("My First Window");
WNDCLASS wndclass = { 0 };
wndclass.hbrBackground = (HBRUSH)COLOR_BACKGROUND;
wndclass.lpszClassName = className;
wndclass.hInstance = hInstance;
wndclass.lpfnWndProc = WindowProc;//不是调用函数,只是告诉函数名操作系统会来调用
RegisterClass(&wndclass);
//第二部:创建并显示窗口
HWND hwnd = CreateWindow(
className,
TEXT("我的第一个窗口"),
WS_OVERLAPPEDWINDOW,
10,
10,
600,
300,
NULL,
NULL,
hInstance,
NULL
);
if (hwnd == NULL)
{
sprintf(szOutBuff, "Error:%d", GetLastError());
OutputDebugStringA(szOutBuff);
return 0;
}
ShowWindow(hwnd, SW_SHOW);
//3.第三步:接收消息并处理
MSG msg;
BOOL bRet;
while ((bRet = GetMessage(&msg, hwnd, 0, 0)) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
//转换消息
//TranslateMessage(&msg);
DispatchMessage(&msg);//分发消息
}
}
return 0;
}
注意:如果我们关闭窗口再打开会报错一个无法写入的错误。可能大家之前遇到过具体原因我们在下次消息类型中会介绍。 解决:在任务管理器中找到程序终止
生成:
我们的这个窗口可以放大缩小也可以关闭,我们自己虽然没有设计窗口处理函数但是我们调用了默认的窗口处理函数,就是一些通用的功能Windows已经替我们处理好了。
最后我们再用图来总结一下。