COM技术介绍
一、COM介绍
1、
定义
(Component Object Model)
COM是微软公司的最高级的,包罗万象的二进制通讯规范(也就是说是大家都要遵守的合同)。用于软件组件间跨进程,跨机器,和操作系统进行交互操作。COM是透明位置的。它可以在EXE,DLL或者远程机器上使用。
OLE是一个主要与用户界面相关的高级功能的集合。COM和OLE的概念界限原本就不清晰,总是容易混淆。
2、
历史
OLE(Object Linking & Embedding )是1991年首次出现的(是WINDOWS3.1自带的)。OLE最初的含义是对象链接和嵌入。当时用DDE(动态数据交换)作为底层通讯协议。
1993,COM首次出现。微软推出OLE2.0,开始用COM代替DDE作为底层通讯协议。这也是COM第一个重要的用途。
1996年,大多数开发人员开始编写32位的WIN95应用程序。他们发现,OLE使用COM的方式是一种非常好的设计软件的方法。开发人员开始使用类似的方法编写自己的对象和界面。另外,操作系统也开始要求使用COM技术编程,如编写WIN95用户界面。这些即不是OLE,也不是AUTOMATION,那么他到底是什么呢?这个属于大多数人倾向于使用COM。
3、
发展
1996年,微软推出NT4.0,DCOM首次出现,作为NT的一部分。它实现了将COM在分布式系统中的应用。
1997年开始流行ATL。COM作为一种技术规范,最早是由C语言来实现的,但是实现起来比较复杂。出现VC以后,又对COM进行了预制和封装,大大简化COM应用的开发。这就是ATL(Active Template Library)。
4、
现状
我们经常见到的用途:
使用外来控件。特别是在网页上使用ACTIVEX控件。ADO
WORD/EXCEL的应用。(两者交叉使用,在应用程序中调用)
二、概念
接口:可以理解为一个抽象的类。
OBJCET(component),相当与组件。与VC和VB中理解的OBJECT是两个不同的概念。千万不要混淆。一个纯粹封装的OBJCET
它是一个封装好的黑匣子。是一个不能看到内部数据结构的东西。是一个抽象的东西。
最基本的OBJCET,给这个OBJECT 加上接口,用于访问这个黑匣子。那么这个OBJECT就是一个
COM OBJECT了。
Func1()
|
IFOO
Func2()
|
相当于一个手机充电器。用插销作为标准接口,但是内部实现被隐藏了起来。
在C++中,接口被表示为一个抽象的类。
例如: 程序清单:
class IFoo {
virtual void Func1();
virtual void Func2();
};
特点:
内部有多个纯虚函数。并且没有函数的实现。而且也不能包含任何内部数据成员。
把接口与实现隔离开来的目的:要把对象内部的工作细节隐藏起来,而这些实现都在类中实现。当实现类的数据成员发生变化时,客户程序是与已经编译好的二进制的接口通讯,所以它也无须重新编译。
例子:
手机是一个COM。它的芯片是接口。无论信息如何发生变化。我们都能接受。它的实现是个不不相同。而不同的实现就是不同的国产手机厂家。但是国内所有的厂家生产的手机全部都是使用的国外同一家的芯片产品,也就是说用同一个接口。
常见(有两个接口)
对象支持多接口。如果老版本的接口不方便,可以继承一个新的接口。但不能修改老的接口。接口一旦发布,就不能修改。
如果增加一个接口IFOO2,该接口仍然包含原接口IFOO的两个函数,而且新增加一个函数
IFOO
IFOO2
程序清单:
class IFoo2 : public IFoo {
// Inherited Func1, Func2
virtual void Func2Ex(double nCount) = 0;
};
如何在C++中使用该接口的方法?
实现接口的来首先要从接口继承下来。
实现该接口:
class CFoo : public IFoo {
void Func1() { /* ... */ }
void Func2(int nCount) { /* ... */ }
};
调用:
首先要引入接口的定义。
创建一个指向对象的指针!!这只是为了方便而说的。实际上是错误的。COM中没有对象的指针。只有指向接口的指针。由于VC++调用内部虚拟函数是用了VTBL技术。即建立一个指针表,表中的指针指向成员函数。所以,指向接口的指针实质就是一个指向指针的指针。
将指针指向对象,那么意思实际上就是说,将指针指向对象的接口的简称。
所以,我们可以将指针指向对象的任意一个接口。最后都达到了将指针指向一个对象的目的。
用DELPHI语法写,更清晰易懂。
var pFoo:IFoo;
pFoo:=CFoo.Create;
//实现接口的函数
pFoo.Func1()
COM接口的实现。
COM定义了大量的接口及相对应的IID。
CLSID (class identifier GUID) (identifier:标识)
IID (interface identifier GUID)
GUID:是一个16个字节长的结构。能有3.4的10的38次方的组合。相当与宇宙中原子总数的平方根。所以,永远都用不完的。尽量不要拷贝它。
使用 GUIDGEN.EXE 来生成。
例如 IUnknown
IID of "00000000-0000-0000-c000-000000000046"
任何一个COM对象都包含一个IUNKNOWN接口。所有的接口都是从它这里派生的。
IUnknown 接口有三个函数:
HRESULT QueryInterface(REFIID riid, void **ppvObject);
ULONG AddRef();
ULONG Release();
简要功能介绍:
QueryInterface:用来查询riid对应的接口是否存在。结果在输出参数中。
AddRef:在对象的使用期限内增加引用记数。当对象第一次被创建时或者其他的用户将一个指针指向该对象时,调用该函数,内部记数将增加1。
Release:当用户不再使用的时候,调用它。最后一次调用时,记数变为0,对象将释放自己。
所以,前面的
CLASS的定义
class IFoo {
virtual void Func1(void) = 0;
virtual void Func2(int nCount) = 0;
};
应该被改写为:COM对应的定义方式:
interface IFoo : IUnknown {
virtual HRESULT STDMETHODCALLTYPE Func1(void) = 0;
virtual HRESULT STDMETHODCALLTYPE Func2(int nCount) = 0;
};
用宏替换以后:
interface IFoo : IUnknown {
STDMETHOD Func1(void) PURE;
STDMETHOD Func2(int nCount) PURE;
};
创建OBJECT的实例,通过接口的指针引用对象(不能简单的说是:获取指向对象的指针)
IFoo *pFoo = NULL;
HRESULT hr = CoCreateInstance(CLSID_Foo, NULL, CLSCTX_ALL,
IID_IFoo, (void **)&pFoo);
if (SUCCEEDED(hr)) {
pFoo->Func1(); // Call methods.
pFoo->Func2(5);
pFoo->Release(); // MUST release interface when done.
}
一个实际例子:
IUnknown
|
.
IFoo
Ifoo2
(IUNKNOWN接口有三个函数)
上边的接口是公共保留接口,左边的是自己定义的接口,都派生自IUNKNOWN。
QueryInterface的作用,获取同一个对象的另外一个接口。
IFoo *pFoo = NULL;
HRESULT hr = CoCreateInstance(CLSID_Foo2, NULL, CLSCTX_ALL,
IID_IFoo, (void **)&pFoo);
if (SUCCEEDED(hr)) {
pFoo->Func1(); // call IFoo::Func1
IFoo2 *pFoo2 = NULL;
hr = pFoo->QueryInterface(IID_IFoo2, (void **)&pFoo2);
if (SUCCEEDED(hr)) {
int inoutval = 5;
pFoo2->Func3(&inoutval); // IFoo2::Func3
pFoo2->Release();
}
pFoo->Release();
}
IclassFactory的概念:
我们不能让COM对象自己创建自己的实例。要通过服务器。服务器则通过建立一个产生COM对象的工厂:类厂。
用
IclassFactory来产生很多新的OBJECT。但是,IclassFactory本身也是一个OBJECT。
IDL文件的目的:
达到跨语言实现。它以OSF(Open Software Foundation)的DCE RPC(Distributed Computing Environment Remote Procedure Call) IDL为基础,对它进行了扩展。
[object, uuid(3AB1D289-2145-4a33-9A98-9635C3518CD7)] //uuid和GUID是一个概念。
interface IFoo:Iunkonown
{
HRESULT Func2([in] long * p);
}
在VC中,实际上MFC也支持COM,但是,建议用ATL开发COM。应为它更简单,更纯净,更高级。ATL技术代表着COM技术的精华,提供了建立COM应用的核心构架,大大节省了手工代码量。所以我们从ATL实例开始了解COM技术。
三、COM应用
1、
OLE
2、
ATUOMATION
3、
ACTIVEX
4、 ATL
四、COM实际例子
一、建立一个服务器
1、
利用 MICROSOFT VISUALSTATIO 建立一个ATL PROJECT WIZARD 。选择默认的DLL。
2、
建立后,保存为mycomservice,REBUILD(编译)。
3、
右键调出 NEW ATL OBJECT 。
4、
选择默认的SIAMPLE,然后NEXT。
5、
输入名字为:First表示第一个接口。
6、
取默认值双接口。(DUAL)
7、
确定后,系统自动生成接口Ifirst和类Cfirst。
类实现,还没有代码。
8、
右键添加方法Methd1
[in] long mPara ,[out,retval] long newPara
9、
右键添加属性 Ppty1
10、
在类定义中的PUBLIC部分增加成员变量定义: long m_x; 注意要将变量放到与方法和属性在同一个代码段。
11、
增加一些功能。比如增1,代码是:
m_x=mPara;
*newPara=m_x+1;
12、
新定义一个自定义接口。(通过右键添加一个新的ATL OBJECT)
13、
在新的接口 Second上添加新的方法Methd2
14、
定义新的方法的内容:system("dir|more");
15、
服务器完成定义。
16、
所有的代码:
IDL文件:
// mycomservice.idl : IDL source for mycomservice.dll
//
// This file will be processed by the MIDL tool to
// produce the type library (mycomservice.tlb) and marshalling code.
import "oaidl.idl";
import "ocidl.idl";
[
object,
uuid(913EFDEE-3FD8-4F66-852E-57D95490A196),
dual,
helpstring("IFirst Interface"),
pointer_default(unique)
]
interface IFirst : IDispatch
{
[propget, id(2), helpstring("property Ppty1")] HRESULT Ppty1([out, retval] long *pVal);
[propput, id(2), helpstring("property Ppty1")] HRESULT Ppty1([in] long newVal);
[id(3), helpstring("method Methd1")] HRESULT Methd1([in] long mPara,[out ,retval] long * newPara);
};
[
object,
uuid(ADD28210-06E1-4FF7-BC76-FF559ED7254A),
helpstring("ISecond Interface"),
pointer_default(unique)
]
interface ISecond : IUnknown
{
[helpstring("method Methd2")] HRESULT Methd2();
};
[
uuid(7C025B97-09F8-4BB9-9951-2B8AFBB377EB),
version(1.0),
helpstring("mycomservice 1.0 Type Library")
]
library MYCOMSERVICELib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
[
uuid(2BF62644-5257-414C-911C-8DBFACEFDBCC),
helpstring("First Class")
]
coclass First
{
[default] interface IFirst;
};
[
uuid(7CBE299E-0965-4504-B4EB-06F841B7EF0F),
helpstring("Second Class")
]
coclass Second
{
[default] interface ISecond;
};
};
FIRST类定义:
// First.h : Declaration of the CFirst
#ifndef __FIRST_H_
#define __FIRST_H_
#include "resource.h"
// main symbols
/
// CFirst
class ATL_NO_VTABLE CFirst :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CFirst, &CLSID_First>,
public IDispatchImpl<IFirst, &IID_IFirst, &LIBID_MYCOMSERVICELib>
{
public:
CFirst()
{
}
DECLARE_REGISTRY_RESOURCEID(IDR_FIRST)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CFirst)
COM_INTERFACE_ENTRY(IFirst)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
// IFirst
public:
long m_x;
long m_p;
STDMETHOD(Methd1)(/*[in]*/ long mPara,/*[out ,retval]*/ long * newPara);
STDMETHOD(get_Ppty1)(/*[out, retval]*/ long *pVal);
STDMETHOD(put_Ppty1)(/*[in]*/ long newVal);
};
#endif //__FIRST_H_
FIRST类的实现:
// First.cpp : Implementation of CFirst
#include "stdafx.h"
#include "Mycomservice.h"
#include "First.h"
/
// CFirst
STDMETHODIMP CFirst::get_Ppty1(long *pVal)
{
// TODO: Add your implementation code here
*pVal =m_p;
return S_OK;
}
STDMETHODIMP CFirst::put_Ppty1(long newVal)
{
// TODO: Add your implementation code here
m_p=newVal;
return S_OK;
}
STDMETHODIMP CFirst::Methd1(long mPara, long *newPara)
{
// TODO: Add your implementation code here
m_x=mPara;
*newPara=m_x+1;
return S_OK;
}
SECOND类定义:
// Second.h : Declaration of the CSecond
#ifndef __SECOND_H_
#define __SECOND_H_
#include "resource.h"
// main symbols
/
// CSecond
class ATL_NO_VTABLE CSecond :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CSecond, &CLSID_Second>,
public ISecond
{
public:
CSecond()
{
}
DECLARE_REGISTRY_RESOURCEID(IDR_SECOND)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CSecond)
COM_INTERFACE_ENTRY(ISecond)
END_COM_MAP()
// ISecond
public:
STDMETHOD(Methd2)();
};
#endif //__SECOND_H_
Second类的实现
// Second.cpp : Implementation of CSecond
#include "stdafx.h"
#include "Mycomservice.h"
#include "Second.h"
/
// CSecond
STDMETHODIMP CSecond::Methd2()
{
// TODO: Add your implementation code here
//system("dir|more");
MessageBox(NULL,"???hello????","",0);
return S_OK;
}
二、建立一个客户
1、
建立一个基于对话框的EXE文件,名字是:mycomclient。
2、
在StdAfx.h头文件中加入引入代码:
#import "../mycomservice/mycomservice.tlb" no_namespace named_guids
编译,将自动增加新的文件(External Dependencies)。
3、
建立一个按钮和对应的函数。
4、
在函数中写代码。
5、
首先采用传统的方法。
6、
在对话框的公共部分:
IUnknown * pUnk;
HRESULT hr;
IFirst
*pFirst;
ISecond
*pSecond;
CString
s;
7、
在对话框初始化时:
HRESULT hr;
hr=CoInitialize(NULL);
if FAILED(hr) // SUCCEEDED
AfxMessageBox("INITE Failed!!");
8、
第一个BUTTON的事件。
/*
//方法1:直接将指针指到要使用的接口,创建COM对象
hr=CoCreateInstance
(CLSID_First,//或者clsid
NULL,
//CLSCTX_LOCAL_SERVER,
CLSCTX_INPROC_SERVER, //这是从MSDN中查到的唯一正确的用法
IID_IFirst,
(LPVOID* ) &pFirst //首先指向该接口,这是一个基本接口,可以代表该对象
) ;
if (S_OK!=hr)
AfxMessageBox("创建并初始化COM接口失败!!");
else
AfxMessageBox("创建并初始化COM接口成功!!");
*/
/*
//调通!!
pFirst->put_Ppty1(7);
long p ;
pFirst->get_Ppty1(&p);
s.Format("the car's fuel is %d ",p);
AfxMessageBox(s);
*/
//方法2:
接口间查询!
hr=CoCreateInstance
(CLSID_Second,//或者clsid
NULL,
CLSCTX_INPROC_SERVER,
IID_IUnknown,
(LPVOID* ) &pFirst
//首先指向该接口,这是一个基本接口,可以代表该对象
) ;
if (S_OK!=hr)
AfxMessageBox("获取IKNOWN接口失败!!!");
else
AfxMessageBox("获取IKNOWN接口成功!!");
hr=pFirst->QueryInterface(IID_ISecond, (LPVOID *) &pSecond);
if SUCCEEDED(hr)
AfxMessageBox("query seond interface succefully!!");
else
AfxMessageBox("query seond interface Failed!!");
pSecond->Methd2();
//CoUninitialize();
9、
第二个BUTTON准备
//用到了灵敏指针
IFirstPtr pFirst;
pFirst.CreateInstance(CLSID_First);
pFirst->put_Ppty1(15);
long p;
pFirst->get_Ppty1(&p);
s.Format("the car fuel level is %d",p);
MessageBox(s);
通过DELPHI实现这些在VC下编写的接口
首先引入库。
Project/import library
implementation
uses MYCOMSERVICELib_TLB,comobj;
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
var x:ISecond;
begin
x:=CoSecond.Create;
x.Methd2;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
y:variant;
z:IFirst;
L:Longint;
S:shortint;
begin
y:=createoleobject('MYCOMSERVICE.First');
//y:=createoleobject('TSecond.Second');
//自动化不支持这个接口
y.ppty1:=5;
L:=y.ppty1;
S:=shortint(L);
showmessage(inttostr(S));
(*
z:=CoFirst.Create;
//z.Ppty1:=5;
z.Set_Ppty1(5);
showmessage(inttostr(z.Get_Ppty1));
*)
end;