C++使用代码创建一个Windows桌面应用程序

WinMain函数

Windows应用程序的唯一程序入口。

函数原型

1 int WINAPI WinMain
2 {
3     HINSTANCE hInstancem
4     HINSTANCE hPreInstance,
5     LPSTR lpCmdLine,
6     int nCmdShow 
7 }

WINAPI定义如下

#define WINAPI _stdcall

 

_stdcall是一个函数调用约定,除此之外,还有__cdeclfastcallthiscallnaked call等函数调用约定。

_stdcall调用约定又称Pascal调用约定,也是Pascal语言的调用约定。它使用的方式为:

1 int __stdcall sum(int a,int b);

__stdcall:函数的多个参数由调用者按从右到左的顺序压入堆栈,被调用函数获得参数的序列是从左到右的的;清理堆栈的工作由被调用函数负责。
在Visual C++中,常用宏WINAPICALLBACK来表示__stdcall调用约定。

更详细的说明可以查看

https://docs.microsoft.com/en-us/cpp/cpp/stdcall?view=vs-2019

 

__cdecl(也可写成_cdecl)调用约定又称C调用约定,是C函数默认的调用约定,也是C++全局函数的默认调用约定,通常省略。

1 int sum(int a,int b);
2 int __cdecl sum(int a,int b);

__cdecl:函数的多个参数由调用者按从右向左的顺序压入堆栈,被调函数获得参数的序列是从左到右的;清理堆栈的工作由调用者负责

更详细的说明可以查看
https://docs.microsoft.com/en-us/cpp/cpp/cdecl?view=vs-2019

 

WinMain函数的各参数说明

hInstance

应用程序当前运行的实例的句柄,该句柄由Windows系统生成。

 

hPrevInstance

当前实例的前一个实例的句柄,在Win32环境下,该参数总是NULL,不再起作用

 

lpCmdLine

一个以空终止的字符串,代表传递给程序的命令行参数。

 

nCmdShow

指定窗口的显示状态

常用值如下

nCmdShow = 0;不显示

nCmdShow = 1;正常显示(默认)

nCmdShow = 2;最小化显示

nCmdShow = 3;最大化显示

 

使用代码创建Windows程序的步骤

1、设计一个Windows类

2、在Windows系统中注册Windows类

3、用该Windows类创建一个窗口

4、显示窗口

5、创建一个消息循环

6、创建一个窗口过程函数WndProc

 

一、设计Windows类

在创建一个窗口前,必须对窗口进行设计,指定窗口的属性。系统已经定义了WNDCLASS结构用于描述待创建窗口的参数。

WNDCLASS声明如下

 1 typedef struct tagWNDCLASSA {
 2   UINT      style;
 3   WNDPROC   lpfnWndProc;
 4   int       cbClsExtra;
 5   int       cbWndExtra;
 6   HINSTANCE hInstance;
 7   HICON     hIcon;
 8   HCURSOR   hCursor;
 9   HBRUSH    hbrBackground;
10   LPCSTR    lpszMenuName;
11   LPCSTR    lpszClassName;
12 } WNDCLASSA, *PWNDCLASSA, *NPWNDCLASSA, *LPWNDCLASSA;

下面介绍各参数

style

窗口样式,可用值如下

窗口样式 说明
CS_VREDRAW 垂直重绘,当窗口垂直方向上的高度发生变化时,将重新绘制整个窗口。如果没有指定这一样式,在垂直方向上调整窗口高度时,将不会重绘窗口。
CS_HREDRAW 水平重绘,当窗口水平方向上的宽度发生变化时,将重新绘制整个窗口。如果没有指定这一样式,在水平方向上调整窗口高度时,将不会重绘窗口。
CS_OWNDC 独占设备描述表,为该类中的每个窗口分配一个单值的设备描述表。
CS_SAVEBITS 在一个窗口中保存用户图像,以便于在该窗口被遮住、移动时不必每次刷新屏幕。但是,这样会占用更多的内存,并且比人工进行同样操作时要慢得多。
CS_DBLCLKS 使窗口可以检测到鼠标双击事件,当用户在窗口中双击鼠标时,向窗口过程发送鼠标双击消息
CS_BYTEALLGNCLIENT 鼠标用户区域按字节对齐显示。
CS_BYTEALLGNWINDOW 鼠标用户窗口按字节对齐显示。
CS_PARENTDC 在父窗口中设定一个子窗口的剪切区,以便于子窗口能够画在父窗口中。
CS_NOCLOSE 系统菜单中没有CLOSE菜单项,窗口没有关闭按钮。

 

lpfnWndProc

指向窗口过程函数的函数指针。窗口过程函数是一个回调函数,针对Windows的消息处理机制,窗口过程函数被调用的过程如下:

1、在设计窗口类的时候,将窗口过程函数的地址赋给lpfnWndProc成员变量

2、调用RegisterClass(&wndclass)注册窗口类,系统就有了用户编写的窗口过程函数的地址

3、当应用程序接收到某一窗口的信息时,调用DispatchMessage(&msg)将消息回传给系统。系统则利用先前注册窗口类时得到的函数指针,调用窗口过程函数对消息进行处理

cbClsExtra

Windows系统为窗口类结构分配追加的额外字节数。一般为0

cbWndExtra

Windows系统为窗口实例分配或追加的额外字节数,一般为0。如果应用程序使用资源文件里的CLASS指令创建对话框,并用WNDCLASS结构注册对话框框时,cbWndExtra必须设置成DLGWINDOWEXTRA

hInstance

包含窗口过程程序的实例句柄。一般直接赋WinMain()hInstance即可

hIcon

窗口类的图标资源。这个成员变量必须是一个图标资源的句柄。可以使用LoadIcon()函数加载图标,如果hIconNULL,窗口将使用系统提供的默认图标

hCursor

窗口类的光标句柄。这个成员变量必须是一个光标资源的句柄。可以使用LoadCursor()函数加载光标。如果hCursorNULL,应用程序必须在鼠标进入应用程序窗口时,明确设置光标的形状

hbrBackground

窗口类的背景画刷句柄。当窗口发生重绘时,系统使用这里指定的画刷来填充窗口的背景。该成员可以指定为用于绘制背景的物理画刷的句柄,也可以指定为标准的系统颜色值。如下:

BLACK_BRUSH 黑色

DKGRAY_BRUSH 深灰

GRAY_BRUSH 灰色

HOLLOW_BRUSH

LTGRAY_BRUSH 浅灰

NULL_BRUSH 等同于HOLLOW_BRUSH

WHITE_BRUSH 白色

BLACK_BRUSH 黑色

lpszMenuName

指向一个以空终止的字符串,该字符串描述菜单的资源名。若使用整数来标识菜单,需要用MAKEINTRESOURCE宏来进行转换。如果lpszMenuName设置为NULL,那么基于窗口类创建的窗口将没有默认菜单

lpszClassName

指向一个以空终止的字符串,该字符串描述窗口类的名字。这个类名可以是由RegisterClass或者RegisterClassEx注册的名字,或者是任何预定义的控件类名

WNDCLASS使用实例如下

 1 WNDCLASS wc;
 2 
 3     wc.style          = CS_HREDRAW | CS_VREDRAW;
 4     wc.lpfnWndProc    = WndProc;
 5     wc.cbClsExtra     = 0;
 6     wc.cbWndExtra     = 0;
 7     wc.hInstance      = hInstance;
 8     wc.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT1));
 9     wc.hCursor        = LoadCursor(nullptr, IDC_ARROW);
10     wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
11     wc.lpszMenuName   = MAKEINTRESOURCEW(IDC_WINDOWSPROJECT1);
12     wc.lpszClassName  = szWindowClass;

 

二、注册Windows类

Windows类设计完成时,需要调用RegisterClass()函数去注册这个类,才可以创建该类型的窗口

1 ATOM RegisterClass(
2   const WNDCLASSA *lpWndClass
3 );

注册代码如下

if(!RegisterClass(&wc))
{
    return 0;
}

 

三、创建窗口

 使用CreateWindow函数创建窗口,如果函数调用成功,返回值为新窗口的句柄;如果调用失败,返回值为NULL。可以使用GetLastError()函数获取错误信息

 1 HWND CreateWindow( 
 2   LPCTSTR lpClassName, 
 3   LPCTSTR lpWindowName, 
 4   DWORD dwStyle, 
 5   int x, 
 6   int y, 
 7   int nWidth, 
 8   int nHeight, 
 9   HWND hWndParent, 
10   HMENU hMenu, 
11   HANDLE hInstance, 
12   PVOID lpParam 
13 ); 

lpClassName

指定窗口类的名称,这个名称就是WNDCLASSA中的lpszClassName。如果在调用CreateWindow函数之前,没有调用RegisterClass函数注册这个类,系统无法得知窗口的相关信息,窗口创建就会失败。

lpWindowName

指定窗口名称,如果指定了标题栏,那么这里指向的字符串就会显示在标题栏上。

dwStyle

指定创建窗口的样式,可以组合不同的窗口样式

常量 说明
WS_CAPTION(0x00C00000L) 创建一个有标题栏的窗口
WS_SYSMENU(0x00080000L) 创建一个在标题栏上带有系统菜单的窗口(需要和WS_CAPTION一起使用)
WS_MINIMIZEBOX(0x00020000L) 创建一个具有最小化按钮的窗口(需要和WS_SYSMENU一起使用)
WS_MAXIMIZEBOX(0x00010000L) 创建一个具有最大化按钮的窗口(需要和WS_SYSMENU一起使用)
WS_TILED(0x00000000L) 创建一个层叠的窗口,层叠的窗口有一个标题栏和一个边框
WS_TILEDWINDOW 创建一个使用(WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX)样式的层叠的窗口
WS_CHILD(0x40000000L) 创建窗口为子窗口,不能应用于弹出式窗口样式
WS_OVERLAPPED 与WS_TILED样式相同
WS_OVERLAPPEDWINDOW 创建一个使用(WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX)样式的层叠的窗口
WS_EX_TOPMOST 创建一个始终置顶的窗口(不管窗口是否已经激活)  
WS_POPUP(0x80000000L) 创建一个弹出式窗口(不能与WS_CHILD一起使用)
WS_VISIBLE(0x10000000L) 创建一个初始状态为可见的窗口(可以使用ShowWindow函数来控制显示或隐藏窗口)
完整窗口样式可以访问:https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles

x

指定窗口左上角的x坐标

y

指定窗口左上角的y坐标

nWidth

以设备单元指定窗口的宽度 

nHeight

以设备单元指定窗口的高度

hWndParent

指定被创建窗口的父窗口的句柄。如果要创建一个子窗口,这里就需要提供父窗口的句柄。

hMenu

菜单句柄,指向附属于该窗口的菜单

hInstance

WinMain函数中传入的应用程序实例的句柄

lpParam

作为WM_CREATE消息的附加参数lParam传入的数据指针。在创建多文档界面的客户窗口时,lpParam必须指向CLIENTCREATESTRUCT结构体。多数窗口将这个参数设置为NULL

 

CreateWindow示例代码如下

HWND hwnd; 
 
    hwnd = CreateWindow( 
        "MainWClass",        
        "Test Window",           
        WS_OVERLAPPEDWINDOW, 
        0,      
        0,    
        CW_USEDEFAULT,       // 默认宽度
        CW_USEDEFAULT,       // 默认高度
        NULL,         // 没有父窗体
        NULL,        // 没有菜单
        hinstance,           
        NULL);      //没有附加数据

 

四、显示窗口

执行CreateWindow函数,窗体创建成功之后,需要调用ShowWindow函数把窗口显示在桌面上

BOOL ShowWindow(HWND hWnd,int nCmdShow);

hWnd

CreateWindow创建窗口成功后返回的窗口句柄

nCmdShow

指示窗口显示的状态

常用的窗口显示状态如下

窗口状态 说明
SW_HIDE 隐藏窗口并激活其它窗口
SW_SHOW 在窗口原来的位置以原来的尺寸激活并显示窗口
SW_SHOWMAXIMIZED 激活并以最大化显示窗口
SW_SHOWMINIMIZED 激活并最小化显示窗口
SW_SHOWNORMAL 激活并显示窗口。如果窗口是最大化或最小化的状态,系统将其恢复到原来的尺寸和大小。应用程序在第一次显示窗口时,应该使用这种状态

 

调用ShowWindow()函数之后,需要调用UpdateWindow()函数来更新窗口。

1 BOOL UpdateWindow(HWND hwnd);

hwnd:调用CreateWindow()成功创建窗口后返回的窗口句柄

UpdateWindow()函数通过发送一个WM_PAINT消息来刷新窗口。如果窗口更新的区域不为空,UpdateWindow()函数绕过应用程序的消息队列,直接发送WM_PAINT消息给指定窗口的窗口过程函数进行处理。如果更新区域为空,则不发送消息。

 

至此,我们就完成了Window类的设计和注册,窗口的创建、显示及更新,接下来开始处理窗体的消息。

 

五、创建消息循环

在窗口创建成功之后,需要编写一个消息循环来不断地从消息队列中取出消息,并进行响应。

调用GetMessage()函数从消息队列中取出消息

1 BOOL GetMessage(
2   LPMSG lpMsg,
3   HWND  hWnd,
4   UINT  wMsgFilterMin,
5   UINT  wMsgFilterMax
6 );

lpMsg:指向一个消息结构体(MSG),GetMessage从线程的消息队列中取出的消息将保存在该结构体对象中。

hWnd:指向被接收消息的窗口句柄(指定接收属于哪一个窗口的消息),设置为NULL时,函数接收属于调用线程的所有窗口的窗口消息。

wMsgFilterMin:指定要获取的消息的最小值,通常设置为0

wMsgFilterMax:指定要获取的消息的最大值,如果wMsgFilterMinwMsgFilterMax都设置为0,则接收所有消息。

 

关于消息的介绍,可以参考:

https://www.cnblogs.com/zhaotianff/p/11285312.html

 

取出消息后,需要对消息进行转换。这个时候就需要调用TranslateMessage()函数,该函数将虚拟消息转换为字符消息。字符消息被送到调用线程的消息队列里,当下一次线程调用函数GetMessage()PeekMessage()时被读出。

1 BOOL TranslateMessage(
2   const MSG *lpMsg
3 );

lpMsg:指向MSG结构的指针,该结构用于存放调用函数GetMessage()PeekMessage()从消息队列里取出的消息

返回值:如果消息可以得到,返回非零值;如果没有消息,返回值是0

 

当消息转换后,需要将消息分发到窗口过程,由窗口过程函数对消息进行处理。

此时就需要调用DispatchMessage()函数

3 DispatchMessage(
4     _In_ CONST MSG *lpMsg);

lpMsg:指向含有消息的MSG结构的指针。

返回值:返回值是窗口过程的返回值。

 

六、窗口过程函数

在前面注册窗口类的时候,有这样一行代码

1  wcex.lpfnWndProc    = WndProc;

这里就是注册窗口过程函数。

窗口过程函数的作用是处理发送给窗口的消息,根据不同的消息,作出不同的响应。

一个Windows应用程序的主要代码部分都集中在窗口过程函数中,WndProc()函数的原型声明如下所示。

1 typedef LRESULT (CALLBACK* WNDPROC)
(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam);

LRESULT指定函数返回值类型,这个值实际上是一个整数,表示调用结果是否成功。

CALLBACK是一个声明说明符,表明函数是由操作系统调用的,必须指定这个符号。

函数名可依据实际情况自定。

hWnd:指向窗口的句柄。·

message:指定消息类型。

wParam:指定其余的、消息特定的信息。该参数的内容与message参数值有关。

lParam:指定其余的、消息特定的信息。该参数的内容与message参数值有关。

在窗口过程处理函数的最后,还要调用默认的窗口过程函数DefWindowProc(),为应用程序没有处理的窗口消息提供默认的处理。

 

我们看一下WndProc函数的一个示例

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
   ...case WM_KEYDOWN:
        {
           if(wParam == VK_ESCAPE)  //当ESC键按下的时候
DestroyWindow(hWnd); //销毁窗口
} break; ... default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }

下面对上述代码进行一个简单的说明:

通过swtich对消息进行对应的处理。WM_KEYDOWN(键盘按键)是系统定义的窗口消息之一。

WM_KEYDOWN消息的处理逻辑中,我们判断ESC键是否按下,如果按下,就销毁窗口。

 

七、一个完整的Windows应用程序

 

 1 #ifndef UNICODE
 2 #define UNICODE
 3 #endif
 4 
 5 #include <windows.h>
 6 
 7 LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
 8 
 9 int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
10 {
11 
12     // 注册窗口类
13     const wchar_t CLASS_NAME[] = L"Sample Window Class";
14 
15     WNDCLASS wc = { };
16 
17     wc.lpfnWndProc = WindowProc;
18     wc.hInstance = hInstance;
19     wc.lpszClassName = CLASS_NAME;
20 
21     RegisterClass(&wc);
22 
23     // 创建窗口.
24     HWND hwnd = CreateWindow(
25         CLASS_NAME,           //窗口类名
26         L"Demo Window",       //窗口标题
27         WS_OVERLAPPEDWINDOW,  //窗口样式
28         CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,  //窗口位置和大小
29         NULL, //父窗口
30         NULL, //菜单 
31         hInstance,  //Instance句柄
32         NULL  //额外的程序数据
33     );
34 
35     if (hwnd == NULL)
36     {
37         return 0;
38     }
39 
40     ShowWindow(hwnd, nCmdShow);
41 
42     // 消息循环.
43     MSG msg = { };
44     while (GetMessage(&msg, NULL, 0, 0))
45     {
46         TranslateMessage(&msg);
47         DispatchMessage(&msg);
48     }
49 
50     return 0;
51 }
52 
53 LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
54 {
55     switch (uMsg)
56     {
57     case WM_DESTROY:
58         PostQuitMessage(0);
59         return 0;
60 
61     case WM_PAINT:
62     {
63         PAINTSTRUCT ps;
64         HDC hdc = BeginPaint(hwnd, &ps);
65 
66         //在此处添加使用 hdc 的任何绘图代码...
67         FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
68         EndPaint(hwnd, &ps);
69     }
70     return 0;
71     }
72 
73     return DefWindowProc(hwnd, uMsg, wParam, lParam);
74 }

 

 

在上面的代码中,我们创建了一个最基本的Windows窗口应用程序,运行效果如下

 

示例代码

 

 

参考资料

https://learn.microsoft.com/zh-cn/windows/win32/learnwin32/learn-to-program-for-windows

https://learn.microsoft.com/zh-cn/windows/win32/LearnWin32/your-first-windows-program

 

posted @ 2019-08-04 09:59  zhaotianff  阅读(12334)  评论(0编辑  收藏  举报