Win32编程,指的是用Window提供的API(Application Programming Interface)为Window编写应用程序。Windows编程,大家也通常称为win32编程,或win32 SDK编程,其含义是针对32位Windows操作系统。
在这一章节中,我们会带大家认识Windows应用程序,编写第一个Windows程序,带大家学习注册窗口和创建窗口。
文章目录
一.Windows应用程序分类
在Windows平台下,我们可以将应用程序大致分为一下几类:
- 控制台程序
- 窗口程序
- 库程序(静态库.lib,动态库.dll)
二.开发工具和库
开发工具我们现在大多数都是用visual studio,visual studio开发平台集成了很多工具,非常方便了我们使用,这里我来带领大家看看编译程序到底是如何实现的:
- 编译器cl.exi,可以将我们写好的.c或者.cpp编译成目标代码.obj文件
- 链接器LINK.EXE,将目标代码,库链接生成最终文件
- 资源编译器RC.EXE(编译.rc文件<脚本语言代码>),将资源(应用程序图标等)编译,最终通过链接器生成最终文件。
接下来带大家看看几个重要的库文件:
Windows库: - Kernel32.dll:提供核心API,例如进程,线程,内存管理
- user32.dll:提供了窗口,消息等API
- gdi32.dll:绘图相关API
库和头文件 - windows.h:所有Windows头文件的集合
- windef.h:Windows数据类型
- winbase.h:kernel32的API
- wingdi.h:gdi32的API
- winuser.h:user32的API
- winnt.h:UNICODE字符集支持
三.程序编译过程
- 环境准备:vcvars32.bat
- 编译程序:cl.exe xxxxxx.c xxxxxxx.cpp
- 链接程序:LINK.EXE xxxxxx,obj xxxxx.lib
- 编写资源.rc资源脚本文件
- 编译.rc文件:rc.exe xxx.res
- 将资源链接到程序中
这里给出一张图帮助大家理解:
四.第一个Windows程序
// Win32VS版本.cpp : 定义应用程序的入口点。
#include "framework.h"
#include "Win32VS版本.h"
#define MAX_LOADSTRING 100
LRESULT CALLBACK WindowProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam
);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
//窗口类名
TCHAR className[] = TEXT("My First Windows.");
//创建窗口对象
WNDCLASS wndclass = { 0 };
wndclass.hbrBackground = (HBRUSH)0; //窗口背景颜色
wndclass.lpfnWndProc = WindowProc; //窗口的过程函数
wndclass.lpszClassName = className; //窗口的类名字
wndclass.hInstance = hInstance; //定义窗口类的应用程序的实例句柄
//注册窗口
RegisterClass(&wndclass);
//创建窗口
HWND hwnd = CreateWindow(
className, //lpname类名
TEXT("我的第一个窗口。"), //窗口标题
WS_OVERLAPPEDWINDOW, //dwStyle
10,
10,
600,
300,
NULL,
NULL,
hInstance,
NULL);
//显示窗口
ShowWindow(hwnd, SW_SHOW);
// 主消息循环:
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd,NULL, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
LRESULT CALLBACK WindowProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam
)
{
switch (uMsg)
{
//窗口消息
case WM_CREATE:
{
CREATESTRUCT* createst = (CREATESTRUCT*)lParam;
return 0;
}
case WM_MOVE:
{
POINTS points = MAKEPOINTS(lParam);
return 0;
}
case WM_SIZE:
{
int newWidth = (int)(short)LOWORD(lParam);
int newHeight = (int)(short)HIWORD(lParam);
return 0;
}
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}
//键盘消息
case WM_KEYUP:
{
return 0;
}
case WM_KEYDOWN:
{
return 0;
}
//鼠标消息
case WM_LBUTTONDOWN:
{
POINTS points = MAKEPOINTS(lParam);
return 0;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
在这里向大家介绍一下编写一个Windows程序必要的步骤:
- 1.定义程序入口WinMain函数
- 2.定义窗口处理函数(可以自定义,作用:处理消息)
- 3.注册窗口类(向操作系统内核中写入一段数据)
- 4.创建窗口(在内存中创建窗口)
- 5.显示窗口(绘制窗口图像)
- 6.消息循环(获取,翻译,派发)
- 7.消息处理
五.注册窗口类
1.窗口类的概念:
<1>.窗口类包含了窗口各种参数信息的结构
<2>.每个窗口都有一个窗口类,基于窗口类创建窗口
<3>.每个窗口类都有一个名称,使用前必须注册到操作系统内核中
2.窗口的分类
<1>.系统窗口类
系统已经定义好的窗口类,所有应用程序都可以使用,例如Button(按钮),edit(编辑框)
<2>.应用程序全局窗口类
由用户自己定义,当前应用程序所有模块中都可以使用
<3>.应用程序局部窗口类
由用户自己定义,当前应用程序中只有本模块可以使用
3.注册窗口类函数
ATOM RegisterClassExW(
[in] const WNDCLASSEXW *unnamedParam1
);
在这里带大家看一下WNDCLASS结构体:
typedef struct tagWNDCLASSA {
UINT style; //窗口风格
WNDPROC lpfnWndProc; //窗口处理函数
int cbClsExtra; //窗口类的附加数据buff的大小
int cbWndExtra; //窗口类的附加数据buff的大小
HINSTANCE hInstance; //当前模块的实例句柄
HICON hIcon; //窗口图标句柄
HCURSOR hCursor; //鼠标句柄
HBRUSH hbrBackground; //绘制窗口背景的画刷句柄
LPCSTR lpszMenuName; //窗口菜单的资源id字符串
LPCSTR lpszClassName; //窗口类的名称
} WNDCLASSA, *PWNDCLASSA, *NPWNDCLASSA, *LPWNDCLASSA;
大家也可以在微软的MSDN官方文档中查看:WNDCLASS结构体
4.style窗口类风格
应用程序全局窗口类的注册,需要在窗口类的风格中增加CS_GLOBALECLASS
应用程序局部窗口类,在注册窗口类时,不添加CS_GLOBALECLASS
具体的窗口类风格,大家可以到MSDN官方文档中查看:窗口类风格style
六.创建窗口
我们来看看创建窗口函数:
HWND CreateWindowExW(
[in] DWORD dwExStyle, //窗口的拓展风格
[in, optional] LPCWSTR lpClassName, //已经注册的窗口类名称
[in, optional] LPCWSTR lpWindowName, //窗口标题栏名称
[in] DWORD dwStyle, //窗口的基本风格
[in] int X,
[in] int Y,
[in] int nWidth,
[in] int nHeight, //以上四个参数是决定窗口的位置
[in, optional] HWND hWndParent, //父窗口句柄
[in, optional] HMENU hMenu, //窗口的菜单句柄
[in, optional] HINSTANCE hInstance, //当前应用程序实例句柄
[in, optional] LPVOID lpParam //窗口创建时的附加参数
);
这里给出MSDN官方文档地址:CreateWindowExW函数
那么在CreateWindow函数中,具体是如何实现的?
这里先给出一张图,我们围绕这张图来讲解:
CreateWindow函数创建窗口过程:
- 1:CreateWindow函数内部根据传入的窗口类名称,在应用程序局部窗口类中查找,如果找到与之匹配的,执行2,如果没有找到,执行3
- 2:比较局部窗口类与创建窗口时传入的HINSTANCE变量,如果相等,说明创建和注册的窗口在同一个模块,创建窗口并返回
- 3:在应用程序全局窗口类中查找窗口类名称,如果找到,执行4,否则执行5(这时候已经不需要比对hins参数了,因为之前提到了,应用程序全局窗口类当前应用程序任何一个模块都可以使用
- 4:使用找到的窗口类信息,创建窗口并且返回
- 5:在系统窗口类中查找,如果找到,创建窗口并且返回(这时候已不需要比对hins参数,理由同上),如果未找到,创建创库失败,返回0。
这里给大家一段伪代码,表示创建窗口的过程:
CreateWindow(各种参数){
匹配查找窗口类,即上述过程
if(找到了){
申请一块内存,将窗口的数据存该内存(该数据包括两部分,CreateWindow函数的参数和找到的窗口类信息)
return 本内存句柄;
}else{
return 0;
}
七.无法正常关闭窗口问题
当我们写出第一个windows程序的时候,我们点击程序的关闭按钮,发现程序是不显示了,但是在任务管理器中仍然存在,说明我们的应用程序没有正常关闭。
我们来看看,为什么没有正常关闭?没有正常关闭的话,我们的程序停在了哪?
我们发现在代码的消息处理函数中有这样一段代码:
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd,NULL, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
我们可以发现我们的程序肯定停在了while循环中,我们分析发现,如果GetMessage
函数返回值为0,那么程序就可以正常关闭,那么我们如何让GetMessage
函数的返回值变为零呢?
我们可以将窗口处理函数这样写:
switch(msgID){
case WM_DESTORY:{
PostQuitMessage(0);
break;
}
}
这样我们写的应用程序就可以正常退出了,至于这其中的原理,我们会在后续章节中讲解到。
八.子窗口创建过程
子窗口的创建很简单,我们只需要满足两个条件;
- 1.创建窗口时设置父窗口句柄
- 2.创建窗口时,在style参数中加入
WS_WHILD|WS_VISIBLE
即可。