首先我们用VS2013, 或者VC6.0等工具创建一个WIN32项目,我这里用的是VS2013, 由于工具的不同,代码会有一些差异,但是本质上还是一样的。
创建一个简单的WIN32程序生成了如图所示的代码,我们打开 Win32Generic.cpp ,可以看到自动生成的代码接近两百行。
程序的入口:
在编写C/C++ 的时候,我们的程序入口叫做 main
而在WIN32的程序中,入口程序变成了:
int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine,
_In_ int nCmdShow)
{
......
}
接下来我们看到了两行代码:
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
UNREFERENCED_PARAMETER 是一个宏, 表示 对没有使用的 已经声明的变量不发出警告。
就是 当你定义了一个变量,但是在接下来的代码中,你却不去使用它,编译器标会发出警告,有变量未使用,如果使用这个宏,就不会生成这些警告
再往下 发现调用了一个函数:LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
int LoadString(
HINSTANCE hInstance,
UINT uID,
LPTSTR lpBuffer,
int nBufferMax
);
函数的作用:将字符串资源加载到制定的进程中。
第一个参数:hInstance ,代表一个进程的句柄,句柄的可以理解为代表,进程是实际存在于系统的事物, 而我们要去找到这个进程的就必须通过它的句柄。这个类似:人是存在于世界中的具体事物,如果我们要找到这个人的话,我们就得知道这个人的一个唯一代表性的东西,例如人的相貌, 或者身份证号,相貌和身份号就可以成为人的句柄。 你会奇怪那我们怎么会事先拥有了句柄这个东西呢,我们可看我们的入口函数,第一个参数便是 hInstance,这是程序在调用开始运行的时候,系统传递给我们的。
第二个参数:uID, 这个表示资源ID, ID和句柄类似, 唯一的代表着一个资源 。如果不大理解的话我们可以看一下 Resource.h在头文件中
#define IDS_APP_TITLE103#define IDR_MAINFRAME128#define IDD_WIN32GENERIC_DIALOG102#define IDD_ABOUTBOX103#define IDM_ABOUT104#define IDM_EXIT105#define IDI_WIN32GENERIC107#define IDI_SMALL108#define IDC_WIN32GENERIC109#define IDC_MYICON2#ifndef IDC_STATIC#define IDC_STATIC-1
我们可以看到 资源文件中又许多宏 , 那他们 代表什么意思呢, 我们需要在打开一个Win32Generic.rc 文件(在资源文件夹中)
可以看到:
在String table 中 又 如上图: IDS_APP_TITLE 值 Win32Generic 意思为 103号资源 是字符串 “Win32Generic”
所以函数 LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); 的功能是 将103号资源的字符串 复制到stTitle 中。
第三个参数:lpbuffer , 用来存放这些字符串的字符数组指针nBufferMax
第四个参数:nBufferMax , 需要加载的字符串的最大长度
窗口类注册:
接下来我们看到的是 MyRegisterClass(HINSTANCE hInstance)
我们看看这个函数
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32GENERIC));(窗口左上角的图标)
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_WIN32GENERIC);
wcex.lpszClassName = szWindowClass;
<div style="text-indent: 28px;"></div> wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));//窗口的小图标(任务栏上的图标),如果为NULL,就和hIcon使用同一个图标
return RegisterClassEx(&wcex);
}
LoadXXX函数意义和之前的LoadString 意义相同只是加载的资源不一样
它首先定义了一个结构体:WNDCLASSEX 有些是 WINDCLASS 这不大重要 都可以理解为WINDCLASS , EX的意思为扩展 extend 新版本的MFC对旧版本做的一些扩展,但是这些功能暂时还不需要讲到。
我们可以看到这里对结构体的各个属性进行赋值,而这些属性基本都可以 望文知意
<span style="white-space:pre"> </span>typedef struct tagWNDCLASSW {
<span style="white-space:pre"> </span> UINT style; //窗口的显示风格
<span style="white-space:pre"> </span> WNDPROC lpfnWndProc;<span style="white-space:pre"> </span>//窗口的处理函数, 通过回调来执行消息的处理程序。(如果理解的话可以当做是窗口中发生了鼠标点击,或者<span style="white-space:pre"> </span>菜单被点击时,你需要做什么处理,你就把代码写到这个函数中去,当对应的事件发生,系统自动帮你执行对应的代码)
<span style="white-space:pre"> </span> int cbClsExtra;
<span style="white-space:pre"> </span> int cbWndExtra;
<span style="white-space:pre"> </span> HINSTANCE hInstance;//窗口属于哪一个进程的,一般为本进程
<span style="white-space:pre"> </span> HICON hIcon;//窗口的图标
<span style="white-space:pre"> </span> HCURSOR hCursor;//窗口使用的光标
<span style="white-space:pre"> </span> HBRUSH hbrBackground;//背景颜色
<span style="white-space:pre"> </span> LPCWSTR lpszMenuName;//菜单的名字
<span style="white-space:pre"> </span> LPCWSTR lpszClassName;//窗口的名称,后面会用到
<span style="white-space:pre"> </span>} WNDCLASSW;
当所有的属性设置好以后,
<span style="white-space:pre"> </span>RegisterClassEx
将窗口注册到系统中(窗口类都需要注册才可以使用)。
生成/显示窗口 :
InitInstance (hInstance, nCmdShow)接着就是 InitInstance
<span style="white-space:pre"> </span>BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
<span style="white-space:pre"> </span>{
<span style="white-space:pre"> </span> HWND hWnd;
<span style="white-space:pre"> </span> hInst = hInstance; // 将实例句柄存储在全局变量中
<span style="white-space:pre"> </span> hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
<span style="white-space:pre"> </span> CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
<span style="white-space:pre"> </span> if (!hWnd)
<span style="white-space:pre"> </span> {
<span style="white-space:pre"> </span> return FALSE;
<span style="white-space:pre"> </span> }
<span style="white-space:pre"> </span> ShowWindow(hWnd, nCmdShow);
<span style="white-space:pre"> </span> UpdateWindow(hWnd);
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span>return TRUE;
<span style="white-space:pre"> </span>}
这里我们主要分析 CreateWindow函数:
HWND CreateWindow(
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam
);
第一个参数: 便是之前注册窗口时填写的窗口类名称,只需要填入对应的名称就可以生成对应的窗口。
第二个参数: 窗口的标题栏名称 第三个参数:窗口的风格, WS_OVERAPPEDWINDW, 代表创建的窗口有菜单栏, 细边框, 可以最小化,也可以最大化 。如图
x,y 表示窗口的位置。
width,height 窗口的宽高
hWndParent 这个窗口的父窗口。 hInstance 和这个窗口有关联的进程, 注册的时候写的 hInstance , 因为窗口注册进入了你指定的进程句柄,当需要创建的时该窗口的时候,就需要去对应的进程中去寻找, 所这里需要填入 窗口注册时填写的进程句柄
lpParam 需要传递给窗口的额外信息。这里暂时填空NULL
如果函数成功就会返回窗口的句柄,之后你要对这个窗口进行操作的话,就需要通过这个句柄才行。
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
显示窗口和更新窗口。
hWnd 为窗口句柄
这里 nCmdShow的值为 SW_SHOW , 表示 需要显示窗口 nCmdShow 的更多选项可以查看MSDN
消息循环设置:
我们先来看大致的消息传递流程:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}