Qt开发Active控件:如何使用ActiveQt Server开发大型软件的主框架(2)
Qt开发Active控件:如何使用ActiveQt Server开发大型软件的主框架
注:本文更多地是带着如何去思考答案,而不是纯粹的放一个答案上来,如果你需要直接看到完整的答案,请直接看实例和最后的柳暗花明部分,里面由详细的注释可以解答你的问题。
前情提要:
Qt的进程间通信,以服务器的形式,手把手教你VS上进行Qt的COM、ActivedQt Server的开发,比保姆还保姆
本文提供两个实例:
ActiveQt Server COM服务器端:Qt_ActiveServer_Main
ActiveQt Active控件调用端:Client_ActiveServer_Demo
上文提到如何创建并调用一个Active控件,但是并不能完全满足我们的要求:现在我有一个主框架或者说主程序,会有很多别的模块需要在外部开发,这些模块可能会需要用到主框架中的信息,或者说一些接口来调用一些特定的功能。比如说我一个课堂教学模块,可能需要实现屏幕广播,语音广播,资料下发等功能。
如果你根据前文的实例进行我们根据上文的实例开发中会发现一个很明显的问题,那就是我们可以从调用方向主框架调用方法,但是却没法从主框架中发送消息给调用方。或者说如果这个COM服务器是有ui的,如果多个调用方可能就会调起多个界面,而且关闭调用方的同时会把这些界面都关闭,这又是怎么回事呢?为什么我们的ActiveServer不是像一个服务器一样简单地向其他的调用方提供服务?
关于这点,我翻遍了国内外几乎所有论坛和文章,以及Qt官方论坛和文章,发现并没有什么讨论的,或者零星有两个提问的人,但是都没有获得相关开发人员的回应。在这里我将会讨论上面的问题,并给出一套可行的方案。
一、当我们在创建一个ActiveQt Server的时候,我们在做什么。
由前文我们可以知道,我们建立一个Qt的COM组件,其实很简单,通过
这样一个宏就可以将一个类暴露在外,并且建立连接。然后我们可以拆分成如下形式:
--------------与以下形式---------------
我们在main函数内这么写了之后,我们就可以来调用了。这是我们在Qt的官方文档上看到的,也是我们能在几乎所有资料上看到的。
现在我们是不是建立了一个Active Server了呢?是的,你是建立了一个ActiveServer,但是现在这个ActiveServer只能单方面地提供服务,就像我上面说的那样:,那就是我们可以从调用方向主框架调用方法,但是却没法从主框架中发送消息给调用方。
这是为什么?我们要从创建开始说起。我们知道ActiveQt Server上是通过三个模块来实现ActiveServer的,我们可以从官方文档上看到:
第一个 第二个类你稍微看一下内容你就会发现,第一个类是为了提供一些额外接口,第二个类是用来提供与COM事件和绑定函数、参数的。真正用于运作整个ActiveServer的其实是QAxFactory
但是我们走进来看这个类的介绍,我们发现这个类里面全是提供的虚函数,我们应该会意识到这个类其实是一个基类,通过虚函数提供了一大堆的方法供其他组件去调用
聪明的你肯定想到了,那既然如此肯定是需要我们去继承这个QAxFactory类,然后来执行一些操作的吧!
等等,我们真的需要继承一个QAxFactory类(且不论能不能继承)吗?我们好像并没有见到一个QAxFactory的实例吧,那我们继承它又有什么用呢?(当然了,其实我是尝试继承了的,但是继承不了,这个单例会直接无法初始化了,还有些别的问题,这里就不一一分析了)
文档中没有提到,但是我在某个不记得的帖子上看到有人在说QAxFactory类提供了一个全局的单例qAxFactory(),于是我开始研究起了这个单例
经过一段时间对QAxFactory类的函数的研究,我发现这些函数其实没有多大用,并没有想到能解决什么问题的,于是问题又继续了下去。
有什么,不一样?
我们这个时候想到去比对一下,我们生成的ActiveServer和普通的Qt应用程序有什么不一样?聪明的你发现了,除了引用了QAxFactory.h之外,就是引用了这个宏:
----------------分割线-----------------
在中文
通过查找资料,你会发现Q_CLASSINFO宏只是给类提供了一个标识,并不是影响工作的关键。于是QAXFACTORY_BEGIN就理所应当的是我们查找的关键,让我们走到官方文档:(英文的就不放了,看着费劲,这里放个百度翻译吧)
这个好像和我之前讲的也没有什么关系,只是影响了声明导出和注册的类等信息,为了探寻真相,我们需要进一步走到QAxFactory.h内部去看发生了什么
这个宏好像就覆写一堆函数...有点意义不明,继续看下去,然后就想了一段时间....
想一下发生了什么
我们发现,每次一个外部进程用一个QAxObject来setControl一下我们的服务器,就会多一个窗口对吧。等等,多一个窗口?
为什么会多一个窗口?难道是创建了一个新的进程吗?不会啊,还记得我们的Main函数是怎么写的吗?
在我们的程序设计之初就考虑了不会让外部进程启动一个新进程的,所以不可能是新的进程导致的窗口启动的。
那就只有一个可能了,是一个新的类实例化了。我们将断点放到窗口类的构造函数上,我们发现断点被击中了!说明猜测是对的,类确实是被实例化了。
这样一切的疑问就解决了!为什么我们没法通信,因为信号根本就不是从这个实例发出的,而是从另外一个被外部调用绑定的实例啊!都不是一个对象而且没有进行绑定那肯定是没法通信的啊!
也就是说,每个QAxObject 绑定了与ActiveQtServer之间的连接的时候,都会实例化一个新的实例来专门处理这这两个进程之间的服务与连接。
现在的问题就很简单了,我们该如何找到这个被新建的实例?
我们可以根据调用堆栈找到,是通过这个QAxFactory类的某个实例调用了createObject。那么问题就从怎么建立连接变成了怎么找到这个QAxFactory的某个实例建立的,于是我又回到了对qAxFactory()的研究
介绍文档中是这么介绍QAxFactory类的:
我真的被这个文档疯狂误导,因为确实是调用了这个createObject 方法生成了一个实例(但是不是通过qAxObject进行的,后面会说),但是我硬是横竖看不懂怎么找到已经调用生成的实例我要去哪里找,唯一有点关系的就是这个featurList() (其实一点关系没有),搞了好久我觉得哪里有问题,就又回到qaxfactory.h里面去看,到底做了什么
这次不一样了,我看到一点不一样的东西,我们看到这个宏,初始化了一个QAxClass,这个类是什么?在Qt的官方文档上我居然没有找到,定睛一看,哦原来是这个.h文件里面声明的类,来看看内容
原来是提供了一个QAxFactory的模板子类,到这里我才明白,原来这是通过这个QAxClass的createObject来生成实例的。于是我开始尝试继承重写这个类
(过程不表了,反正搞了一个多小时我发现这个类其实是写在lib里面的改不了 囧)
ok重写是不行的,那肯定也和这个qAxFactory()的实例没有关系了,现在貌似又进了一个死胡同。
最后柳暗花明的是,我抱着试一试的态度问了下AI,它居然告诉我,你既然要记录实例化的对象,那你为什么不通过一个静态的QList,在每次这个类的对象实例化的时候将其记录在案。当你需要发送消息的时候,就可以从这个QList中读取想要的QObject,然后发送对应的信号,不就行了吗?
听罢,我惊为天人。于是就开始写
柳暗花明-总结
于是总的开发就这样结束了,把大概最后的框架描述一下就是
首先我们需要一个导出类A,这个类是暴露给外部的,提供接口给外部进程调用,这个类A需要继承QObject和QAxBindable,前者可以保证signals和slots的使用,后者可以保证signals和slots和COM函数和事件对接上。
然后我们要知道,外部每次调用setControl,其实都是实例化了一个导出类A,与这个实例之间进行信号交换。我们需要在这个类中声明一个静态数组,用于每次实例化这样一个对象的时候将所有的对象记录在案,以供调用。
然后我们可以通过一个总的COM管理类Main_Activerserver_Demo来管理这些实例化对象,比如让所有的对象发送消息
具体你想怎么操作都行,这里就不表了。
这里你可能会问,那我怎么让这个管理类Main_Activerserver_Demo接收外部进程的消息?它怎么知道信号什么时候来,怎么知道什么时候有新的实例会来?当然了,我们不知道,于是只能借助外部的一个信号中转站SignalCenter,并建立一个单例,以类似
的形式来广播通知让所有能看到SignalCenter的实例接收到消息...目前的想法是这样,也许后面会有更好的方案,但这个不是重点。
more?剩下的只有愉快的交流就完事了