COM/ATL项目开发小记

最近在忙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线程模型-套间 - 吴碧宇 - 博客园

关于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

项目还在进行中,持续记录......

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值