Windows界面UI自绘编程(上)之上部



第一章 Win32程序基本框架

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

总结:
在这里插入图片描述

作业:
在这里插入图片描述

第二章 Win32对话框和文件的遍历(函数FindFirstFile、FindNextFile和SHGetFileInfo)

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
LVITEM代表上图列表中的一项,它不是一行。

目前的问题是我们遍历出来的名称,没有图标,区分不出来哪些是文件、哪些是目录,所以我们接下来给遍历出来的每一项加一个图标。

我们首先在对话框初始化消息那里给ListView设置一个图片列表:
在这里插入图片描述
在这里插入图片描述

cx和cy是图片的尺寸,我们用系统ICON的尺寸,flags我们给一个系统预置的颜色,初始有10张图片,cGrow是当系统需要为新图片腾出空间时,图片列表可以增加的映像数量,此参数表示调整大小的图像列表可以包含的新图像的数量。

在这里插入图片描述

在这里插入图片描述
出现这种错误是因为没有库文件,所以我们要把comctl32.lib文件给附加依赖项加进去:
在这里插入图片描述

在这里插入图片描述

在文件遍历函数那里我们来获取文件的图标,我们有一个非常强大的API函数:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

总结文件遍历的几个步骤:
  1. 文件的遍历
    FindFirstFile函数
    FindNextFile函数
    FindClose函数

  2. 给ListView项加图标
    a)创建HIMAGELIST 通过ImageList_Create函数
    b)要把IMAGELIST与ListView关联起来 通过ListView_SetImageList函数
    c)获取文件的图标 SHGetFileInfo函数
    d)把获取到的图标添加进HIMAGELIST里面,通过ImageList_AddIcon函数,同时该函数会返回图标在IMAGELIST里面的索引值;IMAGELIST里面可以只有2个图标,但是我们ListView里面可以有几十项,这几十项可以重复用这2个图标,可以多个项共用同一个图标;
    e)把图标在IMAGELIST里面的索引设置给ListView里面的项

在这里插入图片描述

在这里插入图片描述
我们发现遍历system32目录的时候多了两个点,代表当前目录和上一级目录,我们把这两个过滤掉,不让它们显示:
在这里插入图片描述

我们还可以增加条件过滤掉目录,只显示文件:
在这里插入图片描述

过滤掉.和…这两个目录,除此之外的都显示的话:
在这里插入图片描述

获取图标这个功能在我们平常的开发中非常有用:
在这里插入图片描述

MFC只是对win32 API的封装,我们可以看一下MFC底层的代码,如下图所示点击右上角的Go转到:
在这里插入图片描述
在这里插入图片描述
往下翻找CreateEx,点击Go:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到它还是进行了窗口的注册:
在这里插入图片描述
在这里插入图片描述
所以MFC是对win32 API的一个封装,我们学MFC和学win32 API,我们互相比较学习这两个的话对win32的学习很有帮助。

处理双击ListView控件中的项产生的消息WM_NOTIFY:
在这里插入图片描述

在这里插入图片描述

可以做鼠标右击弹出菜单,删除目录、删除文件的功能;打开文件的话有一种方法,就是调用ShellExecute函数打开文件。

第三章 MFC与GDI

复习上节课内容:

在这里插入图片描述

第一个和第二个参数就是图标的长度和宽度,第三个参数是图标的颜色,第4个参数是这个容器初始时候的大小;
最后一个参数是如下图所示的,每调用一次ImageList_AddIcon函数添加一个图标的时候,g_ImageList这个容器会自动扩容,我们这里设置的是1,就是容器每次扩容会增大1个。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
IMAGELIST就是一个数据结构。

在这里插入图片描述

在这里插入图片描述
因为我们这里定义的是SmallIcon显示方式,如果定义的是Report的话就会有列的概念:
在这里插入图片描述
所以,代码中的li.iSubItem的值我们设置的是0,因为只有一列没有多列,没有列的概念,所以iSubItem一直是0。

在这里插入图片描述

g_ImageList只是一个保存东西的地方,这里的LVSIL_SMALL才是跟我们ListView控件有关系,即设置ListView控件上显示的图标为小图标;
同时,通过ListView_SetImageList函数把HIMAGELIST与ListView关联起来。

我们的代码还有一个问题:
在这里插入图片描述
在这里插入图片描述
如上图所示没有图标,这种情况我们没有过滤掉。
在这里插入图片描述
这是因为SHGetFileInfo函数本身不检测路径的有效性,所以如果路径中你多加了一个反斜杠,这个函数是解析不出来,也就是说如果路径中有双斜杠的话,SHGetFileInfo这个函数就调用失败了。

上节课的回顾:
在这里插入图片描述

你用了一个API,你不知道需不需要包含lib库

例如下图我们把comctl32.lib删掉:
在这里插入图片描述
你用了一个API,编译的时候发现链接错误:
在这里插入图片描述
说明你需要手动指定一个lib库。

那么我们怎么知道一个API需要哪个lib库呢?

举个例子,我们在msdn里面搜这个API:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

我们现在比较多的是用GDI+进行界面的自绘。

MFC简介

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

把上面的控件都删除掉。

在这里插入图片描述

MFCApp.h文件:

在这里插入图片描述
CWinApp封装了WinMain函数,整个程序执行的入口点就在CMFCAppApp的InitInstance里面了,它一开始执行的话就进CMFCAppApp::InitInstance这里面了,它把WinMain隐藏起来了,同时把主消息循环也隐藏起来了。

MFCAppDlg.h文件:

这里面就是一个对话框,MFC把窗口用CWnd封装起来了,所有的窗口都是从CWnd继承过来的,它把窗口类的注册和窗口的创建在CWnd里面都封装起来了。

在这里插入图片描述
看上图对话框就是直接一个DoModal就创建出来了。

消息循环隐藏的比较深:
在这里插入图片描述

在这里插入图片描述

我们再看看Dialog:
在这里插入图片描述
我们看到CDialog也是从CWnd继承过来的,所以我们进CWnd里面看看:
在这里插入图片描述
我们的窗口句柄在这里了,它这里面有个创建,我们看看它做了什么事情:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

我们可以看到这个创建窗口的过程,我们再看看创建窗口之前它做了什么事情:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
MFC通过一套自己的机制,把这个隐藏起来了。

我们后面讲自绘窗口的时候,我们会自己把这些注册窗口类、创建窗口、消息泵这些封装起来。

我们win32里面有个窗口过程函数,MFC把这个窗口过程函数给隐藏起来了,它做了几个宏实现了消息映射机制:
在这里插入图片描述
我们再到MFCAppDlg.cpp里面看看:
在这里插入图片描述

它把所有的消息处理分支都做封装成了一个个函数:
在这里插入图片描述
到时候我们也要按照这种机制自己来做。

我们来找找窗口的绘制WM_PAINT:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
而我们从这个BEGIN_MESSAGE_MAP宏的定义中可以看到,一个消息处理分支函数对应_messageEntries数组中的一项:
在这里插入图片描述

它把WM_PAINT事件对应的函数OnPaint做了一个map保存在了_messageEntries数组里面,系统每产生一个消息,就会到_messageEntries这个map里面去找消息对应的函数然后调用,
在这里插入图片描述
我们从AFX_MSGMAP_ENTRY结构定义可以看到,_messageEntries就是一个结构体数组。

在这里插入图片描述
我们就拿OnPaint举例,看看宏展开后的样子:
在这里插入图片描述

MFC如果不需要深入研究的话,不建议去看侯捷的那本《深入浅出MFC》,MFC太庞大复杂了,它会局限你的思维,你把win32 API学好之后再去看侯捷的这本书;
不要舍本逐末把本质的win32 API给丢掉,win32 API学个基础就去搞MFC,去看侯捷这本书,会把你的思维给局限住,会被那本书给毒害了,你会被陷入CWnd窗口、CDialog、消息映射等那些MFC的繁杂细节里面去了,等你习惯了MFC再去学win32 API就会很不习惯。。。。。。
但是你把win32 API学好了再去看MFC,这个时候你的关注点会不一样的,它的框架思维会对你自己做软件架构还是有很大帮助的,开拓你的视野。

你现在只要知道MFC程序的入口InitInstance,知道消息是怎么映射的就可以了,例如下图所示位置我们添加一个OnCreate函数的声明:
在这里插入图片描述

然后在消息映射这里把我们这个自定义的消息映射一下:
在这里插入图片描述
之后我们再把OnCreate函数实现就可以了:
在这里插入图片描述

最主要的就是这个消息映射,你知道怎么去映射这个消息就可以了。

还有MFC的动态创建类对象的RUNTIMECLASS机制做的也很牛的,在程序运行时可以访问该结构体CRunTimeClass来获取对象及其基类的运行时信息。

在这里插入图片描述
我们来看看在MFC里面怎么来获取控件、操作控件,我们可以给这个CEdit控件定义一个成员变量:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
点击完成后,在MFCAppDlg.h里面就声明了一个CEdit控件变量m_Edit出来了:
在这里插入图片描述
在这里插入图片描述
而且DoDateExchange函数也有响应的变化(DDX_Control)。

我们还可以给IDC_EDIT1添加另外一个值类型的变量:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

我们再看看怎么给按钮添加消息处理函数,我们双击一下这个按钮会自动生成该函数代码:
在这里插入图片描述
消息映射也自动帮我们映射好了:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
此时这个MessageBox是CWnd类的窗口成员函数了,基本上关于窗口之类的界面的API函数MFC都帮我们封装过了。

在这里插入图片描述
还有一种获取方式,我们刚才还给CEdit关联了一个CString类型的变量m_strValue:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

当给成员函数UpdateData传入参数TRUE的话,就会把控件内容更新到绑定的变量;
参数传入FALSE的话,就是把变量的内容更新到控件。

这里只是为了演示一下,对于MFC的控件我们这里声明了两种类型的变量。

在这里插入图片描述
在这里插入图片描述
当你不记得可以用哪些消息的时候,你可以如上所示通过类向导查看消息的种类。

在这个类向导里我们还可以添加类:
在这里插入图片描述
在这里插入图片描述

子窗口

我们演示一下新增一个对话框模板资源:
在这里插入图片描述
然后我们来给对话框添加一个类:
在这里插入图片描述

快捷方式按下ctrl键+双击鼠标左键就会打开MFC添加类向导,给它生成一个类。

在这里插入图片描述
然后我们把它做成MFCAppDlg的一个子窗口,我们在MFCAppDlg.h里面包含SubDialog.h头文件:
在这里插入图片描述
在这里插入图片描述
然后我们在MFCAppDlg这个对话框初始化的时候,我们来创建SubDialog子窗口:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

然后我们映射一下WM_PAINT消息:
在这里插入图片描述
在这里插入图片描述
我们创建一个DC,换一下这个子对话框的背景,否则看不出效果:
在这里插入图片描述
最后我们还要把这个子窗口显示出来:
在这里插入图片描述
在这里插入图片描述

我们发现子窗口和主窗口是分离的,各是各的,这是因为子窗口的风格没有改变为Child类型:
在这里插入图片描述
先把Border设置为None,然后再设置Style为Child,并把该子窗口上的两个按钮删除:
在这里插入图片描述
在这里插入图片描述

我们添加的子窗口就到主窗口里面来了。

然后我们把主窗口上的Edit和Button的Visible属性设置为FALSE,先把它们隐藏起来:
在这里插入图片描述
我们把上图红色的子窗口在主窗口里面向右移动一些位置:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
我们尝试着用spy++抓取对话框窗口右上角的关闭按钮,发现抓不到,说明这个叉(关闭按钮)不是子窗口。

图形设备接口GDI

在这里插入图片描述

这个DC的意思就是Device Context,即设备上下文;
我们画画的时候,需要有画布、画笔、颜料、字体等,这个dc就相当于这套东西的集合,dc的那些函数就相当于执行用画笔和颜料在画布上作画,你可以画直线、矩形、曲线等。
就像上图我们刚才写的程序,我们定义了一块画布(dc),我们定义了一个刷子并在这个画布(客户区域)上刷(填充)满颜色。

我们创建的CBrush刷子用完后要记得删除,而CClientDC不用删除,它自己会自动释放:
在这里插入图片描述

那么我们换一种dc定义的方式:
在这里插入图片描述
这种DC就要记得删除。

在这里插入图片描述

在这里插入图片描述
这个文字是DC默认的字体,比较难看,我们创建一个自己的字体:
在这里插入图片描述

我们再把文字背景的颜色改变下:
在这里插入图片描述
在这里插入图片描述
这个TRANSPARENT是透明的,设置成透明背景:
在这里插入图片描述

在这里插入图片描述

我们画个矩形:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
GDI画图就是以窗口为画布(CDC),用画笔(CPen)、画刷(CBrush)、字体(CFont)在窗口客户区域上面进行各种图形的绘制(基本的矢量图和位图)。
我们数学里面的那些图形都叫矢量图,它是由点线坐标组成的;位图是由像素组成的,一个一个颜色像素点组成的。

PostMessage/SendMessage

都是给窗口发消息的。
PostMessage:是非阻塞式的(同步),它不保证消息的准确到达,发送完直接返回;它的消息是要进消息队列的,它是直接把消息投递到消息队列后就返回;
SendMessage:是阻塞式的(异步),等待消息处理完了才返回;它的消息直接进窗口过程函数;它是直接把消息发送给对应的窗口过程函数。

回顾:

在这里插入图片描述

作业:
1)用MFC创建一个对话框程序,给它创建一个子窗口;
2)GDI画矢量图,至少要画五种矢量图(背景、画刷、字体)。

第四章 GDI+

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
找两张图片放在工程的Debug目录里面:
在这里插入图片描述

GDI+的环境配置

1)添加 gdiplus.lib 库

在这里插入图片描述
在这里插入图片描述
当然也可以用#pragma comment()也可以添加这个库。

2)包含gdiplus.h头文件

在这里插入图片描述

3)初始化gdi+环境

一般有两个地方可以进行初始化,要么在WinMain函数开始的地方初始化,在WinMain函数最后记得把环境给清除了。
在这里插入图片描述
该函数需要头两个参数,第3个参数不需要。
在这里插入图片描述
注意,GdiplusStartupInput有一个命名空间需要添加上才可以使用。

在程序退出之前(WinMain函数最后)我们利用g_Gditoken把GDI+的资源释放掉:
在这里插入图片描述

在这里插入图片描述
编译后出现问题,找了半天问题,在stdafx.h头文件中,把WIN32_LEAN_ADN_MEAN这个宏定义注释掉就可以了:
在这里插入图片描述
在这里插入图片描述

发现是添加库文件的时候写错gdiplus.lib名字,和头文件gdiplus.h不匹配导致的。

我们把风格改变为没有菜单、标题栏:
在这里插入图片描述
我们把菜单去掉:
#### 4)
我们把边框也去掉:
在这里插入图片描述

我们绘制窗口是在WM_PAINT消息里面

在这里插入图片描述
在这里插入图片描述
我们可以从上图看到Gdiplus::Graphics对象的创建需要一个hdc作为参数,这里的grx就相当于MFC中的CDC,相当于一块画布。

在这里插入图片描述
我们看到这个DrawImage有16个重载的函数,可以根据你的实际情况进行多种的绘制。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
我们看到上图这4个角有一点点白色的,我们要把这4个角变成圆角,我们先获取到窗口的大小,然后创建一个不规则的区域:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们发现只有左上角绘制圆角成功,剩下3个角还不行,这是怎么回事呢?

在这里插入图片描述
我们把网格线勾选上,用放大镜把左上角放大:
在这里插入图片描述
我们的不规则区域取这个点的规则是怎么取得呢?
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
我们拿到这种位图作为背景图的时候我们要去圆这个角,就可以用画图这个工具去看这些点;
第一个点{0, 5},我们取多了,按照上图所示取{0, 4}就足够了。
在这里插入图片描述
一般取纯色的几个点就可以了,如上图右下角有3个纯色点的话,我们一般就要取4个点(上图4个蓝点,下图左上角4个红点同理):
在这里插入图片描述
这样子就围成了一个区域,这种圆角矩形肯定是有顺序要求,要形成一个环的。

在这里插入图片描述
比如你要创建一个心形的窗口,那要取的点就多了,你从第一个点开始,顺序连每一个点,直到最后一个点它又和第一个点相连,组成一个闭合的图形。

继续回到刚才的问题,只有左上角绘制圆角成功,剩下3个角怎么不行呢?
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
编译运行后发现窗口不出来了。。。。。。
我们这里和MFC的东西混淆了起来,这里的坐标不是屏幕坐标,应该是窗口里面的相对坐标(客户区域坐标),用GetClientRect函数也应该是可以的:
在这里插入图片描述
在这里插入图片描述
这明显好多了,这里我们只取了8个点,应该取16个点就更圆了;
虽然你的背景图4个角纯白色部分是透明的,但是你所创建的窗口也是有背景(白色画刷);
png四个角和透不透明不是一回事,即使4个角是透明的,但是窗口还有背景色。

你也可以把窗口做成透明的试一试看看是什么效果;如果你把窗口做成透明的,那你绘制出来的png背景图也都没有了,你可以试一下,在透明窗口上绘制的东西看看有没有);
窗口透明了的话是响应不到鼠标消息的,窗口的透明区域正常情况下它是响应不到鼠标消息的。

做圆角就是这么做的,异形窗口就是这么创建的。

基本矢量图绘制

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们看到Color这个类有3参数构造函数,也有4参数构造函数,GDI+里面的支持多了一种颜色,ARGB里面的A就是透明度的意思,a有一个取值范围1~255,就是透明到不透明之间。

我们先画个不透明的红色线:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

画贝塞尔曲线:
在这里插入图片描述
在这里插入图片描述
画完图后,最后要记得释放hdc:
在这里插入图片描述
因为开始的时候我们把这个hdc放到Graphics里面去了:
在这里插入图片描述

渐变画刷

在这里插入图片描述
在这里插入图片描述

我们看下文本画刷怎么画

我们先看一下TextureBrush它的构造函数:
在这里插入图片描述
这个Image我们前面用过了,可以用来加载位图。
在这里插入图片描述

位图画刷

我们再来找一副jpg图片来做文本画刷,我们来看一下用这个来画文字怎么画:
在这里插入图片描述

在这里插入图片描述
我们可以看到在Gdi+里面画文字也有画刷这个参数。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
用Gdi+做这种效果比用Gdi很容易,所以现在做界面皮肤库的基本上都是用Gdi+画的。

渐变画刷

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
这个渐变效果太突兀不自然,我们修改Point的数值为Point(200, 200):
在这里插入图片描述
在这里插入图片描述

这是Gdi+里面画刷的一个很大的特色。

这个不同的渐变效果是怎么使用的呢?我们修改Point为(0, 0)到(100, 100):
在这里插入图片描述
在这里插入图片描述
我们可以看到它从100开始又重新开始渐变;
正常情况下我们会根据要填充区域的大小来设定Point的数值,这个就要看你想要实现的效果了,如果我们填个150:
在这里插入图片描述
在这里插入图片描述
如果你想要的这个填充区域从左上角到右下角一直渐变下来的话,那么Point就要尽量调大一点:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

你可以看到,它这个渐变到了极限之后,又重新开始从红色到蓝色这样开始渐变;所以这个就要根据你需要的效果来调整了。

因为LinearGradientBrush渐变画刷的构造函数只能定义良好总颜色,那么我们定义一个颜色数组,设置4种颜色(红、黄、蓝、绿):
在这里插入图片描述
在这里插入图片描述

这个函数就是设置很多种颜色混合之后的一种渐变,我们看下用这种颜色画出来的效果,我们发现没效果。。。
修改代码pos[0]的值:
在这里插入图片描述
在这里插入图片描述
这是4种颜色的,当然可以搞很多种颜色进行这种渐变效果这个pos是什么意思呢,我们再来加一种颜色:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们把Point调小一点(200, 200):
在这里插入图片描述
在这里插入图片描述
改成600呢?
在这里插入图片描述
在这里插入图片描述
这个太大了,看不出效果了,改成500:
在这里插入图片描述
还有点大,有一种颜色没出来,再改小点:
在这里插入图片描述
右下角的绿色效果出来了(红、黄、蓝、青、绿)。

所以说Gdi+里面画刷的效果特别强大,它还有做纹理的画刷也很强大的。

我们再用HatchBrush画刷来试一下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
它就是前景、背景的意思。
我们把前面定义的文本画刷来过来试试:
在这里插入图片描述
在这里插入图片描述
效果如上,不过它这个是平铺的,如果你要拉伸效果的话就要用Graphics::DrawImag来画,你用位图做画刷来填充,如果要填充的区域大于这个位图的话就是这种平铺效果,你看上图下面还空出来一块没有填充满,我们把填充的高度调高一点效果可能更明显:
在这里插入图片描述
在这里插入图片描述

我们可以去Gdi+里面看那些类的构造函数,你看构造函数就可以知道它需要什么参数能够达到什么样的效果。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

第五章 动态库

回顾上节课内容

在这里插入图片描述
子窗口风格和可见风格这两个是最基本的风格。

在这里插入图片描述
这就是我们创建的ListBox。
在这里插入图片描述
在这里插入图片描述
这些都是windows已经注册好的窗口类,都通过创建按钮的方式来创建。

动态库和静态库

动态库就是功能的封装、重用;
动态库是一个独立出来的二进制文件,编译的时候是不编译进exe文件里面去的;
外面要想调用动态库里面的函数、类、变量的话,这些函数是要导出的;现在def文件的时代已经过时了,我们现在写动态库都不写def文件了。

静态库编译的时候会打包进exe文件里面去,里面所有的函数、类、变量,是不需要进行任何处理,只要包含头文件就可以直接使用的;就和写自己的程序一样。

在这里插入图片描述
在这里插入图片描述

DLL始终只被加载一次,它的导出函数会有内存地址(始终不会变);DLL其实就是内存中的一段代码;
DLL第一次被LoadLibrary函数加载到系统中的时候,只会触发一次DLL_PROCESS_ATTACH事件,说明此时DLL是真正被加载进系统里面去;
后面多次调用LoadLibrary函数加载这个DLL的时候,会多次触发DLL_THREAD_ATTACH,每调用一次LoadLibrary函数加载该DLL,那么该DLL的引用计数加1;
当调用FreeLibrary函数卸载该DLL的时候,该DLL的引用计数减1,并触发DLL_THREAD_DETACH;
当引用计数减到0的时候,触发DLL_PROCESS_DETATCH,真正的把该DLL从系统中释放。

当调用LoadLibrary函数加载DLL的时候,系统会把该DLL中导出的函数入口地址映射到调用进程的内存地址空间。

DLL里面也是可以有资源的,也是可以有窗口的、有控件的,此时要注意资源ID号是否会发生重复:
在这里插入图片描述
DLL中的资源默认是从原进程里面搜资源ID号的,最后才会搜到DLL资源里面来,就是如果原进程里面有重复ID号的话,就会拿到那个ID号了;
所以我们可以在DLL_THREAD_ATTACH事件里面处理这个事情,通过AfxSetResourceHandle之类的函数设置成优先从我们DLL模块实例句柄hModule里面去搜资源。
AfxSetResourceHandle此函数设置 HINSTANCE 句柄,该句柄确定应用程序的默认资源的加载位置。
解决DLL和主调程序的资源冲突及如何使用Dll的资源
https://blog.csdn.net/hgy413/article/details/6019552

我们来看看怎么导出DLL里面的函数

在这里插入图片描述
导入是告诉编译器,这个函数的定义是在外部DLL里面。

在这里插入图片描述
在这里插入图片描述

而这个宏在另外一个程序GDIEx里面肯定没有定义,所以对于另外一个程序GDIEx来说,所要调用的HelloWord()函数是导入的,毕竟GDIEx这个程序要调用的外部函数HelloWord函数是在某一个DLL里面的;
这就是Win32Dll程序里面这个宏WIN32DLL_EXPORTS的妙用,它就相当于一个开关;导出是给DLL用的,导入是给宿主应用程序用的。

在宿主程序中不写导入也可以(直接用GetProcAddress函数),但是它有两个方面的局限性:
1)不写导入的话,即使你导出了变量类的静态成员变量和静态成员函数,在外部你仍然不能使用;
2)不写导入的话,编译出来的代码效率没有写了导入的高。

使用GetProcAddress函数配合LoadLibrary函数这种方式的优点:
没有使用这个DLL导出的内容的话这个DLL不会被加载;

在这里插入图片描述

第六章 动态库的编写

在这里插入图片描述
我们新建一个头文件:
在这里插入图片描述
在这里插入图片描述
配置属性中的这个宏是vs为我们的DLL自动生成的。

在这里插入图片描述
我们先导出一个函数:
在这里插入图片描述
在这里插入图片描述

动态库常用工具Depends.exe

在这里插入图片描述
在这里插入图片描述

我们通过这个工具发现我们生成的dll里面没有导出的函数HelloWord,这是怎么回事呢?
这是由于我们没有添加头文件导致的。

在这里插入图片描述通过工具Depend.exe可以看到导出的函数名称前后都被加上了一些特殊字符,这是编译器编译C++源文件的时候,会把函数名变成编译器内部的名字(名字规则由编译器定义);因为C++有重载函数,为了避免函数名冲突,所以编译出来的函数名字都会按照C++编译器的规则,在函数名前后添加一些特殊字符。

如果给函数声明添加extern "C"的话,就会按照C语言的规则进行编译,你定义的是什么名字,编译生成出来的就是什么名字;
通过工具Depend.exe可以看到导出的函数名称的变化:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

我们再添加一个工程来测试下动态库的加载

在这里插入图片描述
在这里插入图片描述

静态加载有两种方式:
1)通过预处理指令#pragma comment(lib, “Lesson_06.lib”)

2)在工程配置属性中的链接器、输入,附加依赖项
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
然后我们还要指定一下dll的头文件目录:
在这里插入图片描述
在这里插入图片描述
我们在测试程序的菜单资源里面添加一项:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们先用动态加载的方式,所以先把dll头文件和#pragma指令注释掉:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

我们来试一下不加extern "C"的情况:
在这里插入图片描述
运行程序后点击菜单没有任何反应,我们来调试看看:
在这里插入图片描述
可以看到GetProcAdderss找不到HelloWord这个函数,我们复制用Depend.exe程序看到的函数名:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这样就能找到了,但是这种方式的函数名可读性非常差,日常开发中导出函数前面都要加extern "C"这个。

在这里插入图片描述
这种不加extern "C"的方式,我们用静态加载的话是可以的:
在这里插入图片描述
静态加载必须要加DLL头文件和#pragma指令,然后我们直接调用导出的函数就可以了:
在这里插入图片描述
在这里插入图片描述

但是一般在日常开发中,我们要给函数签名前面加上extern “C”。

.lib,这是函数导出符号表;
.dll,这个才是真正的实现;
.lib和.dll是成对的,我们一般要把这两个文件都提供给别人,这样子的话不管客户要动态加载还是静态加载,它都可以实现。

在这里插入图片描述
在这里插入图片描述

类的导出

类的导出就是把__declspec(dllexport)加载class关键字和类名中间:
在这里插入图片描述

Win32窗口的封装

在这里插入图片描述
在这里插入图片描述
窗口的创建:
1)注册窗口类;
2)创建窗口;
3)显示窗口。

注册窗口类

在这里插入图片描述
这个回调函数只能是静态的。

在这里插入图片描述
大家知道为什么要声明成虚拟函数么?
因为这个CXUIWindow类是我们写的一个基类,别人可以继承我们的,让别人可以重写这些函数。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

创建窗口

在这里插入图片描述
在这里插入图片描述
给Create函数传的这个参数this是做什么用的呢?

实现窗口过程(重点)

为什么这个窗口过程是静态的呢?
它是静态的话,那所有的窗口都共享这一个窗口过程,这其实是肯定没法满足我们需求的,我们每一个不同的窗口类的窗口过程可能需要处理消息是不一样的;
我们给Create函数传了一个this参数进去,然后我们看一下这个窗口过程怎么来写,来实现我们每一个不同的窗口怎么来处理自己的,这个就是接下来的精彩时刻。

在这里插入图片描述

this参数的精彩妙用

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
这个结构体中的lpCreateParams成员就是我们刚刚传给CreateWindow函数的最后一个参数this。

在这里插入图片描述
SetWindowLongPtr这个函数就是设置用户数据的,我们给这个窗口设置一个用户数据,我们就把窗口的指针this设置到系统里面,因为只有WM_NCCREATE这一个消息的时候lParam里面才保存了这个this;
在这里插入图片描述
在这里插入图片描述

其他消息的时候我们就从系统里面获取这个this:
在这里插入图片描述
如果this是空的话,我们就返回一个默认窗口过程;
如果this不为空的话,就调用我们自己的窗口消息处理函数:
在这里插入图片描述
在这里插入图片描述
这样子,我们每一个窗口就可以来处理自己的消息,在这个函数里面我们自己来处理自己的消息,以后每一个从CXUIWindows类继承的窗口类,重写HandleMessage这一个函数就可以了。

在这里插入图片描述

MFC里面有一个消息映射,我们也可以自己来模仿着写,我们可以不用跟MFC一样用宏来实现,我们可以把所有的消息都在这里做成消息函数
在这里插入图片描述
我们基类里面什么都不做,都交由继承类来重写即可。

这里为了简单讲解我们先把这些消息函数注释掉:
在这里插入图片描述

在这里插入图片描述

实现ShowWindow和UpdateWindows这两个函数:
在这里插入图片描述
在这里插入图片描述

在DLL中使用窗口类CXUIWindow来创建窗口

我们把DllDemo.cpp里面系统自动生成的创建窗口的代码都删掉(就留下最基本的东西),用我们自己写的窗口类CXUIWindow来实现:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
给DllDemo添加一个类:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

实现我们自己的窗口过程(重写基类函数)

在这里插入图片描述

在这里插入图片描述

这个__super代表父类,我们直接用CXUIWindow::HandleMessage也是可以的(动态调用类)。
在这里插入图片描述

这里我们就实现一个消息WM_PAINT:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

像这样子的话,以后我们可以把每一个窗口根据自己窗口的逻辑都写在自己的类里面,而不需要像C语言那样写很多单个的函数,这样就便于我们代码的可维护性,可读性会提高很多。

在这里插入图片描述
我们导出类就如上图这样就可以了,然后使用的时候,添加#pragma comment(lib, "Lesson_06.lib"这一句就可以了。
在这里插入图片描述
动态库的静态加载用#pragma指令这个就可以。

在这里插入图片描述

暂时先不要学MFC用宏来定义消息函数,你用宏来做这些事你还做不好,先不要用宏来封装消息函数,我们现在还没达到那种程度。

其实窗口的封装主要都在CXUIWindow::WndProc窗口过程函数这里,就是怎么来让每一个窗口实例都能够处理自己的消息。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值