【易大师网络工具箱】项目解析

在我几年前上大学那会,我就想开发一个小软件,里面集成更多的功能,方便更多的人使用,但因为各种原因,软件始终没做成。到了现在,我觉得我可以重新建立好这个软件。本着学习交流的目的,我将软件开源,开源协议GPL3.0,并将里面的工具类授权为MIT,方便需要找代码的朋友们直接使用。


下面我来大致分析一下这个软件的架构。这个软件包括两个项目,一个是DuiLib_Faw,地址:https://github.com/fawdlstty/DuiLib_Faw。这个项目我就不说太多了,一个分支,主要是对DuiLib项目的改进。下面主要集中在NetToolBox这个项目上面。

这个项目只有三个cpp文件,除了StdAfx.cpp外,main.cpp的作用是程序入口点,实现判断软件版本以及加载窗口的功能;NetToolboxWnd.cpp为主窗口类实现,里面的操作都是为了处理主窗口的消息及事件。通过主窗口标签页可以切换子Tab页,所有子Tab页的处理全部放在pages目录下,文件名格式为page_xxx.hpp。因为子Tab比较多,如果全部放NetToolboxWnd里面将导致文件体积太大不方便开发与维护,所以单独提炼出来。

接下来是工具类,子Tab里面调用的所有实现,以及部分main.cpp/NetToolboxWnd.cpp的部分公共操作,均放在tools文件夹下,文件名格式为tool_xxx.hpp。这些工具库都是为实现单一的功能而设计,可以根据文件名猜出来这个工具类是做什么的。不过需要注意的是,工具类有很多互相引用,使用前请注意。

再说说Debug和Release的主要区别吧。Debug模式是读取res目录下的文件资源,方便改了之后直接运行;Release是通过7z将res目录打包,然后作为资源嵌入exe中。

xml文件在此跳过,下面详细说说C++实现的方式吧。

首先是main.cpp文件,引入各种头文件及lib文件,然后是一个ProgramGuard类,这个类严格来说不算单例,但它只有一个实例化。这段代码这样写的作用是,确保各种初始化API能正确调用,并在析构时调用释放代码。这个类的实例在WinMain中,类在函数中实例化,因为类的特性,函数结束后会自动调用对象的析构函数,这样就能保证释放代码能正确执行。

接下来是判断版本,这段代码主要做自动更新用,由于Debug模式不需要自动更新功能(有这功能就不方便调试了),所以通过#ifdef _DEBUG控制编译。然后就是我说的资源的加载。Debug模式直接设置资源加载路径,Release模式找到ZIPRES资源文件夹,然后从解压的文件里面找。

后面的代码就是根据资源版本号来设置窗口标题,并创建窗口。代码在此解析完毕,接下来是NetToolboxWnd类,这个类我原本是打算直接一个.hpp文件解决,但后面发现,一个文件解决,将很难再将子Tab页的功能再拆开成其他文件,否则源码就太乱了。所以经过研究,我还是觉得一个.h一个.cpp实现。

这个窗口类包括两大类函数,第一类是duilib所需要的重载函数,比如OnClick事件、OnLButtonDown消息等处理函数;第二类是窗口所提供的功能函数,这类函数主要供拆出去的子Tab页面的代码调用。这类函数比较简单,这儿重点说说两个函数,invoke和async_invoke。如果用C#的WinForm就比较了解了,等同于Invoke和BeginInvoke。我大致说说,这两个均是工作线程与窗口线程同步的函数,也就是说,工作线程中调用这两个函数,传入一个函数闭包,将使得函数闭包控制权转移到窗口线程,然后由窗口线程执行。其中invoke是同步,也就是窗口线程执行完毕后返回结果;async_invoke是异步,也就是转移完控制权就返回,然后窗口线程处理消息时才会执行函数闭包。

然而这两个函数有什么作用呢?其实这两者是为了避免Bug产生而不得不使用的一种中间写法。原因是DuiLib界面库与其他几乎所有界面库一样,界面只能一个线程进行处理,否则很容易出问题。需要工作线程访问主线程的地方,也就需要使用同步的机制。其实完全可以将这一块完美封装:任何操作比如设置一个控件显示的内容,指定一个函数SetText,函数内部判断是否是窗口线程,如果是,那么直接设置,如果不是,那么内部调用invoke将控制权交给窗口线程进行设置。这样的好处更大,可以在多线程环境可以直接写想要处理的代码,不会有任何不适。这儿没有这么做的原因是,一方面开销太大,每个函数都得改,并且一个CControlUI类甚至可以从一个窗口转移到另一个窗口上(比较麻烦,但完全可以实现),其次,这种方式会带来开销,每次都得判断线程ID,不同窗口可能有不同线程ID也可能有相同线程ID,这个开销的处理也不轻松。假如以后有哪个界面库能实现这个完整的处理,那估计会是一个体积挺大,并且速度没那么快的界面库了。

OK,主窗口类介绍完毕。下面介绍一下每个子Tab页。这儿每个Tab页都由一个pages/page_xxx.hpp来进行处理消息事件。我对控件的命名规则是,只要控制权属于一个子Tab页,那么控件名称的前缀全部相同。比如正则工具,控件名称统一“regex_”开头,然后处理事件的代码在pages/page_Regex.hpp,看起来还是挺好找的吧?找到消息处理页后,再看调用的方法,几乎都是通过tools工具库实现的操作。

发布者

fawdlstty

又一只萌萌哒程序猿~~

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注