Windows MFC 工程应用开发与框架原理完全剖析教程(中)之上部



第3章 原理篇二 MFC核心框架完全实现

3.1 MFC顶层类结构设计与RTTI、CRunTimeClass设计思想剖析

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

//动态确定类
class CAge:public CObject
BOOL IsAge(CObject* pO)
{
    return pO->IsKindOf( RUNTIME_CLASS( CAge ) );
}
BOOL IsAge2(CAge* pO)
{
    return pO->IsKindOf( RUNTIME_CLASS( CAge ) );
}
void main(void)
{
    CObject a;
    CAge b;
    IsAge(&a);//return FALSE
    IsAge(&b);//return TRUE
    IsAge2((CAge*)&a);//return FALSE,避免强制转换带来的错误
}

CObject CRuntimeClass::CreateObject(void)可以产生一个类变量。作用和new类似,但在某些特殊场合有独特的作用。

//生成类
class CWndA: public CWnd
class CWndB: public CWnd
function1()
{
    CRuntimeClass* pC=RUNTIME_CLASS( CWndA );
    CreateWnd(pC);
}
CWnd* CreateWnd(CRuntimeClass* pClass)
{
    return (CWnd*)pClass->CreateObject();
    //相当于classCWndA->CreateObject();
}

在 上面例子中,CreateWnd返回的是CWnd* 其实它是一个CWndA*。你可以进行由父类到子类的强制转换而不必要担心出错。
使用CRuntimeClass可以代替使用switch生产类实例的一 些繁琐。(请好好想想它的用途,当你发现它的好处时,你一定会大吃一惊,M$使用宏来实现类的动态检测,如果谁有兴趣可以去看看MFC的源代码。)
注意:在类的定义中使用IMPLEMENT_DYNCREATE后方可生效。

MFC中经常只需要构建类,程序就可以运行了,并没有看到创建对象的过程。那么对象是如何创建的呢?
这就是动态创建机制。
动态创建允许,MFC自动为你编写的类创建对象,因此,在编写代码过程中只需进行类的编辑。

为了实现自动创建对象必须在每个类中建立一个结构体CRuntimeClass,这个结构体主要记录了类的几个信息:类名,几个指针,以及构建函数CreateObject。
指针用于把程序中所有的类(包括你创建的和MFC自动创建的类)串联起来,在整个程序中建立一张类的链表。需要指出,这种串联既是横向也是纵向的。横向是指,每一个类都会按照构建次序连起来(pfirst 和pnext的串联)(通过m_pNextClass,将所有CRuntimeClass对象用简单链表连在一起)。纵向是指,结构体中还有一个指针m_pBaseClass,指向该类的父类。这是为了方便查询出类的派生关系。

构建函数,用于构造类的对象。能动态创建的类中有CreateObject()函数。
能动态创建的类的CRuntimeClass结构中的m_pfnCreateObject字段初赋值为CreateObjec,如果为NULL,则不具备动态创建的能力。
上述构建函数可以参考:https://www.cnblogs.com/tinaluo/p/7826678.html

有了这些准备,动态创建就很容易了。例如要求在主程序中,提供一个类名,创建该类的对象。那么只需要编写一个搜索函数,有了类的链表,搜索函数很容易搜索出所对应的类,搜索成功后,调用该类中的构建函数,就可以产生对象。

上述搜索函数可以参考:https://blog.youkuaiyun.com/armman/article/details/1527409


3.2 RTTI设计与验证

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

上图return pClassThis->IsDerivedFrom函数的参数错了,应由pClassThis改为pClass。

在这里插入图片描述

上图RUNTIME_CLASS的宏定义,其中的CRuntimClass应改为CRuntimeClass。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


3.3 动态类型识别技术:DyNamic的设计与实现

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


3.4 动态创建技术——Declare宏

在这里插入图片描述

上图提示错误,我们通过下图的设置输出预处理文件,来检查宏展开后的代码语句。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

可以看到上图最后面有一个转义的反斜杠,应该把它去掉。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


3.5 CWinThread的深入剖析与实现(1):CSimpleList的实现

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


3.6 CWinThread的深入剖析与实现(2):MFC工作者线程的设计-CTypedSimpleList与CSimpleList的泛型化实现

在这里插入图片描述

在这里插入图片描述

上图有误,应改为:

TYPE GetNext(TYPE p){
		return (TYPE)CSimpleList::GetNext(p);
	}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


3.7 CWinThread的深入剖析与实现(3):MFC工作者线程的设计-CNoTrackObject的实现

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

如果我们的数据节点不从CNoTrackObject继承的话:
在这里插入图片描述

在这里插入图片描述

它调用的是通用的new和delete操作符。


3.8 CWinThread的深入剖析与实现(4):MFC工作者线程的设计-MFC对线程推进的封装基石-Windows的TLS机制详解

在这里插入图片描述

在这里插入图片描述

这是我们原先的一个非常简单的工作者线程的使用,我们在main函数当中直接用AfxBeginThread就推进出来了,根据微软的讲法,在MFC当中我们不需要去CloseHandle,也不需要去做一系列的线程的关闭,我们直接调这样的参数就可以使用起来了,不需要我们去删除对象,不需要我们去管理句柄。

在这里插入图片描述

对于线程局部存储,我们得注重两个概念:
(1)线程局部存储的位数组;
(2)线程与位数组的对应。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


3.9 CWinThread的深入剖析与实现(5):MFC工作者线程的设计-MFC封装TLS的原理剖析

在这里插入图片描述

我们希望线程和线程之间的数据也可以有条理的访问:
在这里插入图片描述

MFC利用线程局部存储进行一种扩展,使得我们原先有固定数量的线程局部存储,变得可以无限扩展了。

在这里插入图片描述


3.10 MFC工作者线程的设计——MFC封装TLS的头文件编码实现

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们定义一个CThreadLocalObject类,这个是我们为了完成槽的分配而定义的一个辅助数据结构,即槽中数据所指的真正的用户数据。
在这里插入图片描述

这个实际上是我们线程局部存储当中那个槽里面的对象,所以肯定有一个槽号。

我们仿照MFC里面的内容,对CThreadLocalObject进行模板化:
在这里插入图片描述

在这里插入图片描述

在我们开发过程当中,为什么MFC在做这个工作者线程的时候,它不允许非MFC对象跨线程来访问,因为一旦跨线程访问的话就脱离我们这个TLS的管理,使得你的这样一个实现超越了我们链表的管理,使得你的内存管理不可控,因为你也知道它的内存都已经不在C++运行时了,实际上是向操作系统要的,所以它不能跨线程的访问。


3.11 MFC工作者线程的设计——MFC封装TLS 1

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


3.12 MFC工作者线程的设计——MFC封装TLS2

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


3.13 CThreadSlotData的析构、DeletValues与CThreadLocalObject函数的编码实现

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


3.14 MFC工作者线程的设计——MFC封装TLS的单元测试与总结

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在我们调用的过程当中,已经非常像MFC的CWinThread的入口点函数AfxThreadEntry了,换句话说,我们只要把上图78行到81行这几行给去掉,变成一个AfxThreadEntry函数调用就OK了,但是我们现在还没有对_bgeinthreadex和WaitForMultipleObjects,以及CloseHandle来做一些事情(要把它们封装起来),我们还不能自动关闭这里面的内容(线程句柄、同步事件、线程局部对象等),所以接下来我们就开始编码CWinThread工作者线程。


3.15 MFC工作者线程的设计——CWinThread的完整实现

新建定义CWinThread类的头文件_AFXWIN.h:
在这里插入图片描述

在这里插入图片描述

这个m_hThread是说,既然CWinThread是一个C++的语法,它里头应该能保存一个句柄,这个句柄才是真正调用Windows的SDK帮我们把这个thread创建出来;
重载一个小括号运算符,它的实现就是return一个m_hThread。

在这里插入图片描述

在我们这一个进程当中可能会有多个线程,每个线程当中呢会有各种各样的模块放在里面(线程私有数据),在这些模块当中对于我们这一个线程来讲它是私有的,比如说线程1有自己的模块,线程2也有自己的模块,这些模块我们必须作为线程得维护它的状态,因为这些可执行体(模块)它们也会被推进,推进到哪里也会被记录,那这个时候我们得再设计一个类来记录这样的状态,我们把这样的状态叫AFX_MODULE_THREAD_STATE,很明显这个线程状态必须依赖于TLS。

在这里插入图片描述

换句话说,我们的MFC内部就维护了每一个线程独立的每一个模块;
我们还得把它进行维护,让它能够用链表把它串起来,以便管理各线程的状态,使用THREAD_LOCAL宏把表示线程状态的全局变量_afxModuleThreadState改写成线程局部变量(通过我们自己的线程局部存储),即CThreadLocal<AFX_MODULE_THREAD_STATE> _afxModuleThreadState

在这里插入图片描述

在这里插入图片描述

我们除了AFX_MODULE_THREAD_STATE的定义,我们还得想一想,我们用户使用CWinThread创建线程的时候啊,除了设置线程的状态,进入消息循环,我们还要把线程的函数地址进行适当地设置,但是我们又不能直接的暴露给用户,那这个时候呢我们就想了,我们要有一个设计来帮助我们把Windows SDK里面的C++函数进行一个设计。

在这里插入图片描述

AFX_THREADPROC这个类型的函数将用来初始化我们的线程参数,帮助我们线程的运行。

在这里插入图片描述

在这里插入图片描述

我们回想一下MFC的使用过程,需要我们提供一个AfxThreadEntry来帮助我们进入函数的入口点,那接下来我们实现AfxThreadEntry,新建CWinThread类的实现文件ThreadCore.cpp:

在这里插入图片描述

也就是说,我需要为子线程创造了一定的环境以后(例如同步机制),才能把这个子线程进行推进,所以这个时候我们还得有一个封装,叫AFX_THREAD_STARTUP。

在这里插入图片描述

我们通过这样一个结构,把它放进Win32的CreateThread函数里面去,可以这么讲,我们是把CWinThread组合进入CreateThread,来完成CWinThread的入口点函数AfxThreadEntry的。

这两个线程(父线程和子线程)的交互用的是事件对象:
在这里插入图片描述

第1个hEvent告诉你在子线程创建了以后触发,告诉父线程我们已经创建好了,告诉父线程你可以推进了;
第2个hEvent2是说,接下来当我这个调用线程所有的分量全部准备完毕以后再触发,换句话说,就是告诉子线程你可以推进了。

实现线程的回调函数_AfxThreadEntry:
在这里插入图片描述

这个时候呢,既然有了AFX_THREAD_STARTUP,我们就可以为CWinThread对象组合进来提供帮助了。
通过AFX_THREAD_STARTUP获得我们将要推进的CWinThread对象,即pStatup->pThread

接下来我们要对线程的状态进行设置,看看里面的内容:
在这里插入图片描述

看看我们封装出来的这个对象能否获得当前的这个模块的状态,同时我们把当前的状态进行一个指定,指向当前的pThread这个CWinThread对象;
通过try…catch语句看看这个转换是否OK,有没有异常,因为线程当中有可能会被其他打断,这个时候我们就不能往下执行了,设置bError为TRUE,告诉父线程新创建的这个子线程初始化出错。

在这里插入图片描述

在这里插入图片描述

CommonConstruct函数它的任务是要构建我们的对象。

除此之外,我们还要给用户提供两个全局函数:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们回到ThreadCore.cpp里面,上图这个地方,当父线程作为一个入口点去推进的时候,我们会考虑,这个pStartup指向的内存空间是咱们的新线程的空间,当我们启用了新线程的初始化之后啊,有可能当新线程的堆栈如果构造不成功的话,可能会被侵蚀掉这个线程的空间(pStartup指向的内存空间),所以我们要在CreateThread能够正常初始化之前呢,我们先保存下pStartup->hEvent2的值,以保证线程初始化是完备的(避免子线程死锁)。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

要返回当前线程CWinThread对象的指针,我们就得拿它的状态。

在这里插入图片描述

也就是说AFX_MODULE_THREAD_STATE这个对象始终封装了我们CWinThread对象。

在这里插入图片描述

这是我们自己写的线程局部存储,既然线程CWinThread对象都不在了,那么我们也要把我们自己分配的内存空间给释放,不然的话会占用内存资源的;
参数FALSE表明仅仅删除当前线程的线程局部存储 占用的空间。

在这里插入图片描述

vs智能感知不到_afxThreadData变量,它是放在AFXTLS.cpp文件当中的,所以需要调整一下位置,放到_AFXSTAT.h里面:
在这里插入图片描述

在这里插入图片描述

我们之所以创建线程的时候让它先挂起,是要考虑父线程和子线程的交互,并且让子线程有足够的时间,充分的去加载它里面的资源。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

如果要是创建成功,这个线程句柄存在了,那我们要恢复线程的执行。
讲白了,就是告诉你父线程把你这个子线程创建出来了以后,先让你把里面的空间全部加载好,然后接下来再推进你。

这个时候在ResumeThread之后,我们就要开始让子线程去把相应的空间初始化完毕,所以要等待你(子线程)告诉我初始化完毕好了,告诉我什么时候能够推进,由你子线程完成告诉我。
在这里插入图片描述

如果用户一上来并不是要求你直接推进,你创建的是一个挂起的线程,我就先让你暂时停止推进,等什么时候需要推进的时候由你来推进。
用户是可以创建一个挂起的线程,这是一个常见的动作。

线程创建是有可能失败的,因为资源不充分。
在这里插入图片描述

在这里插入图片描述


3.16 MFC工作者线程的设计——CWinThread的单元测试与Bug解除

在这里插入图片描述

我们来看看期待已久的MFC工作者线程是怎么实现的。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

你看,这个AfxBeginThread就跟MFC的工作者线程非常相近了,至此我们CWinThread基本工作内容就到这里了。

在这里插入图片描述

注意刚才我们贴错了,应该贴到_AFXTLS_.h文件中:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这个时候就会出现上图这种重定义的问题,这个原因是什么呢?
这个是因为重复包含导致的,我们为了解决这种重复包含,我们会对头文件进行修订,因为在C/C++当中再做混合编程的时候,特别要注意这个问题,因为C语言是没有所谓的命名空间的概念的,因此它会以文件为单位,而在我们的ThreadCore.cpp里面用到的_afxThreadData,和AFXTLS.cpp里面的_afxThreadData,就会有这种重复冲突的现象,所以在obj文件里面就没有办法有唯一的生成。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

同样的,_AFXSTAT.h和_AFXWIN.h这两个头文件里面也要做同样的修正:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

剪切上图选中的两行内容,放到AFXTLS.cpp文件中:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


3.17 MFC工作者线程的设计——CWinThread流程运行总结

我们来看看工作者线程究竟做了什么事情。
我们从父线程创建开始来看:

在这里插入图片描述

新线程创建,那么这个时候新线程创建在我们操作系统来看啊,它实际上是把一个线程的堆栈和一些空间给你,但是它并没有立即执行,为什么呢,因为你要知道我们可能会把一些句柄啊,一些模块啊,还有一些相应的能够被Windows推进的执行体给放进来,在放进来的当中它没有完全初始化好的话,它是不能够被执行的,所以怎么办呢,它先挂起,这是子线程做的事情。

在这里插入图片描述

上图左侧是MFC在做的事情,MFC帮助我们推进这个新线程(右侧),所以这个新线程开始执行AfxThreadEntry。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这个就是我们CWinThread的流程,可以这么讲,这个CWinThread我们虽然用起来很方便,但是MFC为了推进我们这个应用程序做了大量的工作,大家一定要明白这里面的过程,所以我们的新线程并不是一上来就会被推进的。

现在基本上可以知道两点,第一个,CWinThread之所以能够封装WinMain这个函数,也是因为可以把它当成一个函数执行体,作为线程这样一个指令序列的聚合体进行推进,所以我们的MFC借助CWinThread把WinMain函数封装起来;
第二个,我们MFC在做这个线程推进的时候,它考虑到了这种线程交互的状态,所以做了一系列的内容,并且能够自己管理内存,可以管理线程自有数据,并且同时通过了TLS这样的技术,使得线程的交互变得更加的统一和协调,在这个过程当中,可以说它是一个比较复杂的运用了Windows操作系统底层的机理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值