最近在忙COM的项目,中途遇到一些问题和自己的一些想法,先记录下来,以便以后再遇到可以查阅,先胡乱写一通,将来东西多了,再整理成篇吧。
1.关于ATL Merge ProxyStub的问题,具体的问题就不描述了,自己新建一个ATL Merge一下就明白了。由于使用的是VS2010做开发,VC6的方式已经行不通了
(附VC6方案InformIT: The Trusted Technology Source for IT Pros and Developers),经长时间的搜索终于在下面的网址找到了解决方案,但是悲剧的是我在写作这个记录的时候,下面的网址打不开了。必应,
难道就因为他暴露了微软自己的解决方案?微软这么小气?
方案是这样子:1.新建一个ATL的工程(不要选择merge,不需要定义接口),将其他所有需要散列集的Interface对应的idl文件包含进来,然后编译此工程的XXX_PS工程。之后这个编译出来的ProxyStub的dll就是你整个项目所有的散列集dll,按那个网址的说法,微软自己也是这么干的。以后就不需要单独每个COM对应一个PSdll了。
2.关于COM接口传递C++类对象的问题
目前考虑到两种方式,一种是直接声明为local(缺点不能进行远程调用,只能在本进程中用), 或者实现为COM对象,传递IUnknown指针,网上还有个国外的人,提供了另一种实现方案,就是利用MFC的CObject基类,实现Serialize序列化方式。仁者见仁智者见智,我们项目多用前两种方式。
3.COM的函数调用切记一定用SUCCEDED和FAILED宏做判断
由于项目是从MFC改过来的,以前的判断常常忘记修改,虽然不是什么导致崩溃的大问题,但是总给你莫名其奥妙的逻辑错误。
4.关于BSTR
这个只须切记,不要再给BSTR赋常量了!例如,BSTR bstr = L””,这样虽然可能不会导致崩溃,但是有些COM相关的函数需要BSTR被动态分配。项目中遇到一个问题,SysStringLen的调用一直返回0,明明bstr是有值的,最后发现是因为BSTR常量字符串导致。最好,最方便,最不容易出错的就是使用BSTR的包装类吧。CComBSTR和_bstr_t都可以,一个是ATL的封装,一个是C++的封装。 ATL开发中,对BSTR判空,可以简单的进行:
CString strFile = COLE2T(bstrFile);
if (strFile.IsEmpty())
{
return S_OK;
}
5.关于AddRef、Release和智能指针
其实做COM开发的都懂,但是还是会偶尔忘记的,项目中也遇到了一个函数调用传出COM接口忘记AddRef最后程序退出崩溃的问题。建议就是:能用智能指针的地方尽量用,毕竟人的脑子经常会忘记事情,不用的地方,一定要注意是否是属于那种局部使用的情况,如若不是,记得要AddRef。一般的智能指针都重载了!,==,*(),&等操作符,可以直接对指针判空(!p),(NULL == p),或者&p可以直接传给Fuc(IXXXXX** p)的接口,但是对于CComSafeArray,由于没有提供&操作符,因此对于接受(SAFEARRAY** punkArray)的函数,深入解析ATL建议可以通过类似以下方式使用:
CComSafeArray<IUnknown*> pArray;
SAFEARRAY *pSAFEARRAY = NULL;
spJLIColGroup->GetInterfacesFromIID(IID_IJLUISimulationSinkConnpoint, &pSAFEARRAY);
pArray.Attach(pSAFEARRAY);
关于智能指针的使用,还有一点需要记住的是,全局变量,需要在::CoUninitialize();之前,手动调用.Release()进行释放。
6.关于COM相关的数据结构定义,.idl还是.h?
由于IDL中也是能包含.h文件的,具体过程中,估计还是定义在idl中更好一些。在idl中如果要防止重定义,因为不能直接if define。因此需要用到idl专门的语法。
cpp_quote("#ifndef _STRUCT_")
cpp_quote("#define _STRUCT_")
//数据结构定义
typedef struct xxx{
USHORT xxx; //按钮ID
}COxxx, *LPxxx ;
cpp_quote("#endif")
这样在生成的.h中就会包含#ifndef
7.关于COM Singleton
深入解析ATL第二版里面说Singleton是邪恶的,DLL中只在每个进程中才是唯一的,进程外服务器中Singleton只在最好的情况下才是每个系统唯一的,通常,多数使用Singleton的更好方式是多个实例共享状态,而不是单个共享实例。
我们项目中有个COM组件就是使用的这个方式,多实例共享状态,状态用Mutex进行保护。
8.使用ProgID还是复杂没有意义的UUID?
项目中还是事先定义出规范,每个COM组件都有唯一,方便记忆的ProgID。使用的时候直接用ProgID,既有意义,又容易记忆。做过office自动化就清楚了,ProgID还是更好的选择。
9.关于接口继承(IA<-IB,ComI实现IB这样的接口继承方式)
在项目中遇到一个问题,CComQIPtr<IA> spXML; hr = spXML.CoCreateInstance(......之后hr提示COM接口没有注册。本来COM是多是介绍“横向继承的”(类似ComI同时实现IA和IB,不需要IB继承至IA),而这里想使用IA的抽象,直接让IB接口继承IA。这里就需要而外做一些导出接口的操作。主要需要修改两个地方。
1.在idl文件中coclass ComI
{
interface IA;
[default] interface IB;
};
添加interface IA;以便导出IA。
2.在对应的实现文件中
BEGIN_COM_MAP(CComI)
COM_INTERFACE_ENTRY(IA)
COM_INTERFACE_ENTRY(IB)
END_COM_MAP()
也添加IA的导出,这样就可以正常使用接口继承了。其实跟横向继承的导出是一样,只是这种竖向的继承往往会忘记做这些导出
10.COM数组
首先是[size_is]指定列集时的数组内存大小。size_is attribute - Win32 apps | Microsoft Learn
虽然这样做了,但是仍有人遇到exe之间调用COM接口传递数组不成功的问题,比如这位兄弟http://topic.csdn.net/t/20051021/17/4342421.html
因此可能会优先考虑VARIANT或者SAFEARRAY。
1)
SafeArray转为Varaint示例代码:
STDMETHODIMP CXXXXXXXX::GetInterfacesFromIID(REFIID iid, VARIANT* punkArray)
{
::VariantInit(punkArray);
punkArray->vt = VT_EMPTY;
......
{
CComSafeArray<IUnknown*> pSafeArray(nCount);
......
{
IUnknown *pUnKnown = NULL;
iter->second->QueryInterface(iid, (void**)&pUnKnown);
if (NULL != pUnKnown)
{
pSafeArray.SetAt(nIndex, pUnKnown, FALSE);
++nIndex;
}
}
CComVariant varArray = pSafeArray.m_psa;
return varArray.Detach(punkArray);
}
return S_OK;
}
CComSafeArray包装SAFEARRAY示例代码:
CComSafeArray<IUnknown*> pArray;
SAFEARRAY *pSArray = NULL;
spColGroup->GetInterfacesFromIID(IID_IRecordJLValidDeviceInfo, &pSArray);
pArray.Attach(pSArray);
UINT lLBound = 0, lUBound = 0;
lLBound = pArray.GetLowerBound();
lUBound = pArray.GetUpperBound();
IUnknown* pUnKnown = pArray.GetAt(0);
2)SAFEARRAY与CComSafeArray
使用从外面传递进来的SAFEARRAY的时候,采用下面的方法:
void SetArray(SAFEARRAY * psa)
CComSafeArray<long> sa;
sa.Attach(psa);
//操作
.....
sa.Detach();
返回SAFEARRAY的时候,用下面的方法:
void GetArray(SAFEARRAY ** ppsa)
{
CComSafeArray <long> sa(10);
......
*ppsa=sa.Detach();
}
11.CoInitialize || CoInitializeEx
项目中在做单元测试时,遇到一个问题。程序在运行期间一切正常,退出时,智能指针释放崩溃。最后查到是因为线程启动时没有初始化COM库导致。
关于初始化的重要性,网上有个高手总结了一下原因:COM高手总结的八个经验和教训http://www.yesky.com/98/1832098.shtml
12.关于COM 套间以及数据传递
网上有人总结得非常好:
关于com自定义参数的传递_zzffly9的博客-CSDN博客
[转]理解COM套间(第一部分) - Quincy - 博客园
http://www.vckbase.com/index.php/wv/1315
VC知识库中的COM部分系列文章
http://www.vckbase.com/index.php/wenku/index/fid/90
项目还在进行中,持续记录......