com组件开发:原理与基础知识汇总(一)


com组件的原理:

前置知识简单总结:

+知道dll的导出原理和调用方法

+纯虚函数是什么?纯虚类是什么?抽象类是什么?   纯虚函数就是只有定义,把它声明为虚函数,让派生类去做实现,纯虚类也叫抽象类,也可以叫接口

+在c++导出类的时候,内部的原理实际上是导出了类的函数(方法),

+在c++导出类的时候,如果该类为纯虚类,那么导出的实际上就是一个指针数组,数组的每一个元素存放了函数的指针

什么是.IDL文件?

        IDL是(interface definition language)接口定义语言的英文首字母简称

由于dll导出的接口是个数组指针,而且数组里还是指针,其他语言并不知道怎么用,而且导出的函数的名字在c++编译时会被修改,并且函数的参数和数据类型也不统一,所以要想让任意语言都很方便的使用com组件,那么还需要一个统一的"函数说明文件"这个文件的后缀就是IDL。我个人的理解就是对指针的一些描述

什么是GUID?

        GUID是指全局唯一标识符(Globally Unique Identifier)是一种由算法生成的二进制长度为128位的数字标识符,用来代表一个接口

        由于每个com组件可能导出多个类。并且系统中那么多com,为了防止接口的名字冲突所以每个类都指定了一个GUID,

        这个GUID可以使用visual Studio点击工具>创建GUID 的小工具来生成, 也可以使用CoCreateGuid函数生成

       

.IDL文件的格式:

//.IDL文件的格式:
[属性1,属性2,属性3......]
interface 接口名
{
  [属性1,属性2] 返回类型 函数名(参数列表)
}

注意:

方括号里"属性"指的是"补充说明"  并不是我们类里面向对象的那个"属性"

方括号后面跟的是接口名 那么就是对该接口的补充说明  如果是函数的定义,则是该函数的补充说明  如果后面跟的是参数,那么就是对该参数的补充说明

//IDL属性的种类

接口属性:uuid,object,pointer_default等 // 其中pointer_default指的是指针的类型 属性可以有三个参数 ref,unique,ptr

函数属性:propget,propput,proprofref  // (获取变量的值,设置变量的值,通过引用方式来使用变量的值)

函数的参数属性: in,out,retval  //in为传入的,out为传出的,一般是个指针


//.IDL文件例子:(单独创建一个这样的文件 点生成会自动生成一些.c和.h的文件)
[
	object,//声明这是一个com组件的接口  (因为接口不仅有com组件接口)
	uuid(9352ECB1-82EF-4EA3-81C8-E7E7374E68E7),//声明uuid,  此属性和上面的object 是com组件必须要有的两个属性
	version(1.0),
]
interface ICalculator :IUnknown{
	import"unknwn.idl"; //相当于include了父类的文件  可以在上面点右键 打开文件,再定位里面的unknwnbase.idl查看文件内容 
	HRESULT Clear(void); //凡是com组件的函数  都必须返回 HRESULT类型 ,ULONG AddRef(); ULONG Release();除外
	HRESULT Add([in]int a,[in]int b);
	HRESULT Sum(void);
};

COM组件的最基本接口IUnknown

由于使用com组件的程序可能有多个,所以需要接口内部来统计当前使用此接口的程序数量。来决定啥时候可以销毁自己。当com组件的引用计数为零时,可以考虑销毁com组件

而且还需要实现通过a接口查询b接口的功能,所以这些功能统一放到了IUnknown接口里了,所有的com接口,都必须直接或间接的继承IUnknown接口,且com里的一个接口只能继承一个接口

IUnknown接口的定义:

//它们的排列顺序也是不可改变的
interface IUnKnown{
virtual HRESULT __stdcall QueryInterface(const IID& iid,void **ppv)=0; //查询接口
virtual ULONG __stdcall AddRef()=0; //通常用来增加引用计数,本意是通知com组件,先不要释放掉了
virtual ULONG __stdcall Release()=0;//通常用来减少引用计数,本意是通知com组件"本程序不再用你了"
}

这样,在使用com组件的时候,就必须遵循以下规则:否则就可能导致com组件过早释放,或者一直不释放自己的内存。

    1.当一个有效接口指针从一个内存块拷贝到另一个内存块的时候,应该调用AddRef,增加com组件的引用计数

    2.当一个有效接口指针被改写前,应该调用这个接口的Release函数,减少com组件的引用计数

    3.如果程序员能够确认,前两种情况,则可以不使用Addref和Release,前提是程序员必须确保COM组件的引用计数器不会乱

以上只是常规的方法 并不是必须要实现的   也可以不使用计数器,接口指针就偏偏一直不释放,但是显然大多数情况这样做不太合理


应该何时调用AddRef?

    1.把一个有效的接口指针保存到本地变量

    2.把一个有效接口指针作为函数的一个输出参数的时候(函数参数属性中有out属性的参数)

    3.把一个有效接口指针作为函数的返回值

    4.把一个有效接口指针,保存到某个对象的成员变量中

应该何时调用Release?

    1.改写一个有效的接口指针变量前(包括对象的成员变量)

    2.一个本地变量离开自己的有效作用域前 //有效作用域一般是指{}大括号

    3.一个有效的接口指针,作为[in,out]类型的参数传递给函数后,在函数体内如果对这个参数进行改写,在改写前

    4.如果一个对象中有接口指针作为其成员变量,在对象销毁前