COM组件技术在Linux C++下的使用例子COM的
接口一. 接口概念的出现承接COM的目的,现在需要将重用的COM
对象相互关联在一起,那么有什么好方法呢?(1)OO中使用public成员来让外界和内部对象进行数据交互。COM中更进一步,只能使用public的成员函数。因为直接访问对象内存不利于实行低耦合的模型,所以数据的交互都应该使用函数调用。(2)一个COM对象可以提供多个函数供外部调用,这是很自然的。(3}类似于OO中的多态,一个函数可以被多个COM对象实现,这样调用方可以方便统一的实现所需的功能。在C++中,我们用public成员函数来提供对外接口,用虚函数来实现多态。因此,对COM的要求,实际上就是需要一个虚基类,其定义了一组函数,然后COM类来继承这个基类,从而也拥有了这组函数。而当COM类要有多个供外界调用的函数时,可以把这些函数分别定义在一些虚基类中,然后再用多重继承的方法使COM类拥有这些函数。尽管对每个函数都去定义一个虚基类也毫无不可,但很多时候这样分散并不利于管理这些函数。因此常常把一组功能有关联的函数合并在一个虚基类里面。这个只拥有一组虚函数的基类就是COM中的接口,其目的是定义COM对象被访问的方法。每个接口都被一个GUID标识,称为IID。二. 接口的本质如果把C++描述转化成二进制代码的话,就会发现接口本质上就是vtable,位于COM对象的开头,指向一组函数
指针。那么为什么是接口而不是单个函数被GUID表识,从而能够被准确定位呢?还是参考C++编写的COM对象在内存中的二进制表示吧。此时其开头是一列指针,分别对应于多重继承而来的各个接口,然后每个指针指向一个函数指针数组,就是对应于各个接口的成员函数。因此,接口和对象在内存中是平级的!C++实现多重继承时只不过是罗列了多个虚函数表,然后调用函数时再根据具体使用的指针类型,给指向对象的指针加上某个偏移量得到该类型对应的虚函数表,再找到具体的函数。显然指针在接口这一层时可以很自如的通过偏移得到其它的接口指针以及对象指针——而一旦得到具体函数指针后,就很难回头了。COM规定了一个函数QueryInterface(),用来得到接口的指针。并把QueryInterface()放入接口类IUnknown中,而且规定所有的接口都要从IUnknown继承,换句话说,所有的接口都要实现IUnknown类定义的那几个函数。QueryInterface()的引入可以让调用方在使用COM对象时,能够在COM对象提供的接口之间自由的来回切换。当然,正如上面所述,其本质只是指向接口的指针做了偏移而已。三. 接口的使用方法首先,所有COM对象的接口都继承自IUnknown,而IUnknown中是有QueryInterface()函数的。再次,COM对象的开头就是第一个接口的vtable,所以指向COM对象的指针同时也是指向第一个接口的指针。并且,由于接口都是继承自IUnknown,因此这个指针也一定是指向IUnknown的指针。这样一来的话就能够顺利调用QueryInterface(),得到某个接口的指针了。然后,不管任何时候,只要有某接口的指针,就可以接着用QueryInterface()来得到该COM对象拥有的其它接口指针。类厂以及COM对象的构造二. 类厂概念的引入类厂(ClassFactory)这个名词其实有点迷惑性,因为这个东西实际上应该叫对象工厂。类厂也是一个普通的COM对象,它有一个特殊的接口IClassFactory,这个接口的一个函数CreateInstance()能够生成COM对象,并返回其需要的接口。如果把C++中的概念平移过来,就会发现类厂的作用本质上就是那个被C++编译器隐藏了的new。在COM中没有类定义,自然也没有new,要想生成COM对象,只能靠COM类的规范。类厂就实现了从COM类规范到COM对象的过程。当用C++实现COM的时候,往往在类厂也就是new出来一个对象,然后做一个QueryInterface()得到接口指针。表面上看,中间多了类厂这么一层有点多此一举,实际上这里隐含了根据抽象的COM类在内存中生成COM对象的步骤,绝非可有可无的。三. 类厂的返回值在前面说过COM中以COM对象为单位实行重用,COM对象通过接口和外界交互,COM对象的接口之间可以通过偏移来实现跳转。并且,从二进制上看,指向COM对象的指针就是指向COM对象继承的第一个接口的指针。所以,在COM中并不需要一个指向COM对象的指针,而只需要指向该COM对象的某一个接口的指针。因此类厂最后是返回COM对象的一个接口指针来告诉用户,这个COM对象已经生成了。当然,这个接口指针的表识(IID)需要用户提供。COM对象的调用AddRef和Release一. IUnknown接口按照COM标准,所有的COM接口的前三个函数都必须是IUnknown接口的那三个函数:QueryInterface(),AddRef()和Release()。如果用C++表述的话,就是所有的COM接口都必须从IUnknown这个虚基类继承而来。QueryInterface()的作用前面已经说过了,是根据IID查询当前COM对象是否有此接口,并返回接口指针。那么AddRef()和Release()呢?按照字面的意思,AddRef()的意思就是说增加当此接口被引用的次数,而Release()则是释放。实际上也差不多就是这么回事……虽然Release()表面上看起来起一个SubRef()的名字能够更加和AddRef()匹配一点。二. COM对象的创建过程和引用计数的需求如果按照一般的思想,COM对象被创建后,大家自由使用就是了,为了什么非要引入AddRef()和Release()函数?其实这里涉及到的问题主要是COM对象的生存期问题。一个COM对象何时被谁创建?何时又被谁释放呢?最自然的回答肯定是需要时创建以实现应用,不需要时释放以节约系统资源。但是这里实现就有很多问题:首先,按照前面所述,客户并不真正的了解COM对象,它只能提供CLSID来定位COM对象,提供IID来查询接口,然后能做的就是利用接口实现功能。在Windows的COM库中,用CoCreateInstance()函数来封装客户端的调用,然后CoCreateInstance()根据CLSID在注册表中找到实现该对象保存的文件,再根据调用方式的不同(进程内/进程外)将该文件装载入内存,创建类厂,然后用类厂的CreateInstance()接口创建COM对象并返回IID指定的接口。这一连串的工作分的很细,主要的目的就是用中间层,比如COM库函数和标准IClassFactory接口等隔开用户和具体COM对象,实现更好地封装。既然如此,具体生成COM对象的并不是客户端而是COM组件中和COM对象对应的类厂对象。因此,释放或者说从内存中卸载COM对象的任务也不能是客户端完成。而在COM组件中,类厂只管生成,那么释放的任务就只能交给COM对象自己完成了。所以,最后的要求就变成了COM对象自己需要知道什么时候能够释放自身,那么就需要有一个量来表示现在到底又多少用户在使用此COM对象,这就是引用计数了。三. 引用计数的实现实现引用计数的方法很简单,用一个全局的变量来保存计数,多一个引用时加一,少一个引用时减一。COM规定当创建COM对象时先把计数从0加到1,然后加加减减,直到计数变到0,说明已经没有用户使用该COM对象,那么这个就可以释放资源了。由于客户端只能对接口 *** 作,因此AddRef()和Release()需要保证能够在任何接口下都能调用,包括IUnknown。这样一来,这两个函数和QueryInterface()并列成为IUnknown的三个成员也就顺理成章了。这里还有一些小问题。比如说是针对COM对象整体计数呢,还是针对各个接口计数?COM标准没有硬性规定,但是作为COM对象的使用者,客户端必须考虑到不同情况,所以必须是调用增加或减少引用的那个接口的AddRef()和Release()。COM实现的技术,主要是C++的虚函数、多继承以及动态链接库(DLL)技术。COM组件的实现:项目代码如下类厂头文件
链表类厂 头文件 ListClassFactory.h/*************************************************************************>File Name: ListClassFactory.h>Created Time: 2016年09月12日 23时53分23秒 CST************************************************************************/#ifndef _LIST_CLASS_FACTORY_H #define _LIST_CLASS_FACTORY_H#include "../../ibasecom/IUnknown.h" #include "../../ibasecom/IClassFactory.h"class ListClassFactory : public IClassFactory { private:ULONG m_cRefpublic:ListClassFactory(){LogD("ListClassFactory: ","===ListClassFactory()===")}~ListClassFactory(){LogD("ListClassFactory: ","====~ListClassFactory()=======")}private:virtual LONG QueryInterface(const IID&iid, void** ppv)virtual ULONG AddRef()virtual ULONG Release()virtual IUnknown* CreateInstance(const IID&iid,void**ppv)}#ifdef __cplusplus extern "C" { #endifIUnknown* DllGetClassObject(const CLSID &clsid, const IID &iid, void **ppv)IUnknown* (*g_CoCreate)(const IID&iid,void** ppv) = NULL#ifdef __cplusplus } #endif#endifVector类厂 头文件 VectorClassFactory.h/*************************************************************************>File Name: VectorClassFactory.h>Created Time: 2016年08月26日 23时50分23秒 CST************************************************************************/#ifndef _VECTOR_CLASS_FACTORY_H #define _VECTOR_CLASS_FACTORY_H#include "../../ibasecom/IUnknown.h" #include "../../ibasecom/IClassFactory.h"class VectorClassFactory : public IClassFactory { private:ULONG m_cRefpublic:VectorClassFactory(){LogD("VectorClassFactory: ","===VectorClassFactory()===")}~VectorClassFactory(){LogD("VectorClassFactory: ","====~VectorClassFactory()=======")}private:virtual LONG QueryInterface(const IID&iid, void** ppv)virtual ULONG AddRef()virtual ULONG Release()virtual IUnknown* CreateInstance(const IID&iid,void**ppv)}#ifdef __cplusplus extern "C" { #endifIUnknown* DllGetClassObject(const CLSID &clsid, const IID &iid, void **ppv)IUnknown* (*g_CoCreate)(const IID&iid,void** ppv) = NULL#ifdef __cplusplus } #endif#endif类厂实现文件 ListClassFactory.cpp
从上面的第一行输出可以看出我已经链接了一个FT232的设备,这个就是我的连接线。
但是这还不行,还不知道他的USB会话接口号是多少,那么在设备列表里查一下就行了。
输出结果显示我有一个USB设备是可以支持tty的,这里多说一下TTY的来源(百度上找的):
大概就是如上所述的意思了,现在可以确定我们的设备名称是 ttyUSB0 了,下面开始连接。
当最下面出现 Terminal ready 的提示就证明已经通过数据线接入到了调试设备,按下回车就可以开心的调试了。
进去后推出的方法是先按Ctrl+a,然后再按Ctrl+q就可以了。(我之前都是直接关会话,后来才知道原来是这么关)
最后说一下比他功能强大的 minicom ,之前看百度上各种的 minicom 使用方法什么的,但是那些东西一看我就烦了,还是picocom这种软件更适合我用,因为用起来比较简单。
第一次写点儿正经的东西,的Markdown看着好舒服!
评论列表(0条)