COM -- 跨平台跨语言的解决方案。
这篇笔记是我学习过程中为了加深理解而写的,难免有误,如果你想学习COM,仅供参考,原文地址我贴出来,我也只是将原文照抄并整理一下条理性而已。
http://www.vckbase.com/document/viewdoc/?id=212
1. COM是什么
COM是Component Object Model的缩写,直译为中文就是组件对象模型,这句话是废话。
COM的作用是: 提供一种独立于平台与编程语言之外的,共享二进制代码的方法。
对,COM的诞生是为了功能模块的重用,可能有些人问为什么不使用STL,而使用COM呢?STL是C++源码级重用,你可以实现用VB来调用STL吗?不行,可VB可以调用C++写的COM。 因为COM是共享的二进制代码,也就是说,COM是一种二进制代码结构规范,无论C,C++,VB,DELPHI等等编程语言及开发平台,通过编译器编译出来的COM组件的二进制代码都拥有相同的结构以及内存布局,能够被互相理解,互相调用。
2. COM中的基本元素(一些COM术语)
(1)接口:一个名字以大写的I开头的抽象基类,包含一组虚方法,接口可以从其他接口继承,但不能继承于多个接口,只能从一个接口继承。
(2)coclass(component object class,组件对象类,通常被称为COM类): COM类通常就是一个C++类,这个类继承自一个或者多个接口,并实现它们,COM对象在内存中的表现就是这个COM类的一个实例。
(3)COM服务器: 包含了一个或者多个coclass的二进制DLLs或者EXE执行体。
(4)注册(Registration): 创建注册表入口的一个过程,告诉Windows操作系统COM服务器放在什么位置。
(5)取消注册(Unregistration):从注册表删除这些注册入口。
(6)GUID(谐音fluid,意思是全球唯一标示符Globally unique identifier): 是一个128位的数字,其实它和COM无关,在其他地方我们也经常看到它,只不过COM中的接口和coclass都拥有一个GUID,因为是全球唯一的,所以避免了名称冲突。
(7)UUID(universally unique identifier): 同GUID。
(8)CLSID(类ID): 是coclass的GUID。
(9)IID(接口ID): 是interface的GUID。
(10)HRESULT: 是一个代表着成功或错误代码的整型或长整型的数字,对COM对象的调用经常会返回一个HRESULT,虽然用H开头,但并不是句柄的意思。
(11)COM库:操作系统的一部分,调用COM组件时就是COM库在协助你完成调用。
3. 使用和处理COM对象
对象是类在内存中的一个实例,类决定了对象在内存中的布局,这是一句废话。
每一种编程语言都有自己创建与管理对象的方式,比如C++使用new来开辟一块以类为模板布局的内存空间,COM既然要实现跨语言,就必须由COM库来提供COM对象的管理方式。
下面是COM对象管理和C++对象管理所做的一个比较:
(1) 创建一个新对象:
C++中使用new操作符,或者在栈中创建一个对象。
COM中使用COM库的API创建一个对象 比如 CreateObject()
(2) 删除一个对象
C++中使用delete操作符,或者将栈对象踢出。
COM中,所有的COM对象保持它们自己的引用计数,调用者必须通知对象什么时候用完这个对象。当引用计数为0时,COM对象自己从内存中释放。
创建和销毁缺一不可,创建COM对象的时候要通知COM库使用哪个接口,如果对象创建成功,COM返回一个所请求接口的指针,然后用指针指向创建的对象,以后通过该指针调用创建的对象的方法,就像使用常规C++对象一个样子。
下面是COM对象的创建和删除的方式。
(1)COM对象的创建:COM对象的创建需要调用COM库的API函数CoCreateInstance():
REFCLSID rclsid, //coclass的CLSID,例如可以传递CLSID_ShellLink创建一个COM对象来建立快捷方式。
LPUNKNOWN pUnkOuter,//只用于COM对象的聚合,利用它向现有的coclass添加新方法,参数null表示不使用聚合
DWORD dwClsContext,//表示使用COM服务器的种类,比如进程内DLL:CLSCTX_INPROC_SERVER
REFIID riid,// 表示请求接口的IID,例如可以传递IID_IShellLink获得IShellLink接口指针。
LPVOID* ppv );//接口指针的地址,COM库通过这个参数返回请求的接口
在调用CoCreateInstance()的时候,他负责在注册表查找COM服务器的位置,将服务器加载到内存,并且创建你请求的coclass的实例,然后是一个例子,创建一个CLSID_ShellLink对象的实例并请求指向这个对象IShellLink接口指针。
IShellLink* pISL;
hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void **)&pISL);
if (SUCCEEDED(hr))
{
cout << "succeeded!" << endl;
}
else
{
cout << "failed" << endl;
}
(2)删除一个COM对象:我们不需要自己去手动释放一个COM对象,当使用完的时候我们只需要告诉他你已经用完了就可以了,IUnknown是一个接口,每个COM对象都必须实现这个接口,IUnknown有一个Release()方法,可以调用这个方法通知COM对象你已经不需要使用他了,一旦调用了这个方法,就不能再次使用这个指向对象的接口了,因为COM对象已经从内存消失了。
HRESULT hr;
IShellLink* pISL;
hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void **)&pISL);
if (SUCCEEDED(hr))
{
cout << "succeeded!" << endl;
pISL->Release();
}
else
{
cout << "failed" << endl;
}
::CoUninitialize();
如果你的应用程序中使用了许多不同的COM对象,再不使用某个接口的时候调用Release()方法就很重要,可以及时释放内存,以免造成不必要的开销,如果程序长时间运行,就应该在空闲时间调用CoFreeUnusedLibraries()API。这个API将检查内存中没有任何引用的COM服务器,并卸载他们,降低了应用程序的开销。
4. 基本接口 - IUnknown
每个COM对象都派生自IUnknown接口。Unknown的意思并不是说这个指针是一个未知的指针,而是说这个指针可以指向任何一个COM对象,你可以不知道他究竟指向的时什么COM对象。
IUnknown有三个方法:
(1) AddRef() -- 通知COM对象增加它的应用计数,如果进行了一次接口指针的拷贝,就必须调用一次这个方法,并且原始的值和拷贝的值都要用到。
(2) Release() -- 通知COM对象减少他的引用计数。
(3) QueryInterface() -- 从COM对象请求一个接口指针,当coclass实现一个以上的接口的时候,就要用到这个方法。
前面的例子中示例了Release()的使用方式,如果一个COM对象实现了一个以上的接口的时,我们想使用这个COM对象另外一个接口的方法,就要使用QueryInterface()方法来获取另外的指针了。QueryInterface()的原型声明:
REFIID iid, //所请求接口的IID
void** ppv ); //接口指针的地址,QueryInterface()通过这个参数在成功时返回接口
继续看下如何使用QueryInterface()这个方法,就拿刚才的ShellLink的例子,ShellLink实现了IShellLink和IPersistFile接口,如果已经有一个IShellLink指针,就可以调用QueryInterface()方法请求IPersistFile接口。
下面是一个完整的示例
//
#include "stdafx.h"
#include "iostream"
#include "icrsint.h"
#include "shobjidl.h"
#include "shlguid.h"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
::CoInitialize(NULL);
HRESULT hrCreateShellLink;
IShellLink* pISL;
//创建一个CLSID为CLSID_ShellLink的COM对象,并使用pISL接口引用该COM对象
hrCreateShellLink = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void **)&pISL);
if (SUCCEEDED(hrCreateShellLink))
{
cout << "CoCreateInstance ShellLink Succeeded" << endl;
IPersistFile* pIPF;
HRESULT hrQIPersistFile;
//获取ShellLink对象的IPersistFile接口
hrQIPersistFile = pISL->QueryInterface(IID_IPersistFile, (void**)&pIPF);
if (SUCCEEDED(hrQIPersistFile))
{
cout << "Queryinterface IID_IPersistFile Succeeded" << endl;
}
else
{
cout << "QUeryInterface IID_IPersistFile Failed" << endl;
}
//通知ShellLink对象 以后不再使用他
pISL->Release();
}
else
{
cout << "CoCreateInstance ShellLink Failed" << endl;
}
cin.get();
::CoUninitialize();
return 0;
}
5. 仔细做好串处理
因为COM的DLLs和EXE要独立于语言和开发平台,而不同的开发语言与开发平台对字符串的定义是不同的,比如MFC的CString和stl库中的string,C中的以0为结尾标识的char*,VB的前导位数的String等等,所以COM要定义自己的串结构来实现跨语言跨开发平台。
任何时候,COM方法返回一个串,都会是一个UNICODE串,可以很方便的把这个串转换成wchar类型串,如果想转换成char类型串,可以看这篇笔记。
http://www.cnblogs.com/coderlee/archive/2008/01/25/1053311.html
6. 用示例代码做总结
1) 使用单接口COM对象
//
#include "stdafx.h"
#include "learncom.h"
#include <atlconv.h> // ATL string conversion macros
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// 唯一的应用程序对象
CWinApp theApp;
using namespace std;
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
// 初始化 MFC 并在失败时显示错误
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: 更改错误代码以符合您的需要
_tprintf(_T("错误: MFC 初始化失败\n"));
nRetCode = 1;
}
else
{
WCHAR wszWallpaper[MAX_PATH];
string strPath;
HRESULT hr;
IActiveDesktop* pIAD;
//初始化COM库
CoInitialize(NULL);
//创建一个COM对象,CLSID为CLSID_ActiveDesktop,接口为IID_IActiveDesktop,使用pIAD接收
hr = CoCreateInstance(CLSID_ActiveDesktop,
NULL,
CLSCTX_INPROC_SERVER,
IID_IActiveDesktop,
(void**)&pIAD
);
if (SUCCEEDED(hr))
{
hr = pIAD->GetWallpaper ( wszWallpaper, MAX_PATH, 0 );
if (SUCCEEDED(hr))
{
char cWallpaper[MAX_PATH];
//将Unicode串转换成ANSI类型串
wcstombs(cWallpaper, wszWallpaper, MAX_PATH);
cout << cWallpaper << endl;
}
else
{
cout << "GetWallpaper Fail!" << endl;
}
//释放接口
pIAD->Release();
}
else
{
cout << "CreateCOMObject Fail!" << endl;
}
//卸载COM库
CoUninitialize();
cin.get();
}
return nRetCode;
}
要注意必须引用wininet.h库 而且必须在stdafx.h中的include "afx.h"后面接着引用wininet.h 位置不能放错
2) 使用多接口的COM对象(通过QueryInterface()查询其它接口)
简述代码流程:
1 CoInitialize(NULL) 初始化COM环境
2 使用CoCreateInstance创建COM对象ActiveDesktop(活动桌面对象),GUID为宏:CLSID_ActiveDesktop 返回给接口IActiveDesktop
3 调用IActiveDesktop->GetWallpaper()方法获取当前墙纸路径
4 创建标识为CLSID_ShellLink的COM对象(快捷方式对象) 返回给接口IShellLink
5 调用IShellLink->SetPath()设置快捷方式的路径
6 调用IShellLink->QueryInterface()获取IID_IPersistFile接口
7 调用IPersistFile->Save()方法 保存快捷方式
代码如下
//
#include "stdafx.h"
#include "learncom.h"
#include <atlconv.h> // ATL string conversion macros
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// 唯一的应用程序对象
CWinApp theApp;
using namespace std;
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
// 初始化 MFC 并在失败时显示错误
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: 更改错误代码以符合您的需要
_tprintf(_T("错误: MFC 初始化失败\n"));
nRetCode = 1;
}
else
{
WCHAR wszWallpaper[MAX_PATH];
string strPath;
HRESULT hr;
IActiveDesktop* pIAD;
IShellLink* pISL;
IPersistFile* pIPF;
//初始化COM库
CoInitialize(NULL);
//创建一个COM对象,CLSID为CLSID_ActiveDesktop,接口为IID_IActiveDesktop,使用pIAD接收
hr = CoCreateInstance(CLSID_ActiveDesktop,
NULL,
CLSCTX_INPROC_SERVER,
IID_IActiveDesktop,
(void**)&pIAD
);
if (SUCCEEDED(hr))
{
hr = pIAD->GetWallpaper ( wszWallpaper, MAX_PATH, 0 );
if (SUCCEEDED(hr))
{
char cWallpaper[MAX_PATH];
//将Unicode串转换成ANSI类型串
wcstombs(cWallpaper, wszWallpaper, MAX_PATH);
cout << cWallpaper << endl;
//创建Shell Link组件对象 并使用IShellLink类型的接口pISL指向新创建的对象
hr = CoCreateInstance(CLSID_ShellLink,
NULL,
CLSCTX_INPROC_SERVER,
IID_IShellLink,
(void **) &pISL);
if (SUCCEEDED(hr))
{
hr = pISL->SetPath(wszWallpaper);
if (SUCCEEDED(hr))
{
//获取IPersistFile接口
hr = pISL->QueryInterface(IID_IPersistFile, (void **)&pIPF);
if (SUCCEEDED(hr))
{
pIPF->Save(TEXT("C:\\quick.lnk"), FALSE);
cout << "save success!" << endl;
//释放IPersistFile接口
pIPF->Release();
}
else
{
cout << "persistfaile save fail!" << endl;
}
}
else
{
cout << "shell link setpath fail!" << endl;
}
//释放IShellLink接口
pISL->Release();
}
else
{
cout << "create shell link fail!" << endl;
}
}
else
{
cout << "GetWallpaper Fail!" << endl;
}
//释放IActiveDesktop接口
pIAD->Release();
}
else
{
cout << "CreateCOMObject Fail!" << endl;
}
//卸载COM库
CoUninitialize();
cin.get();
}
return nRetCode;
}