新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     计算机科学论坛     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论高级C/C++编程、代码重构(Refactoring)、极限编程(XP)、泛型编程等话题
    [返回] 中文XML论坛 - 专业的XML技术讨论区计算机技术与应用『 C/C++编程思想 』 → COM 组件设计与应用(三、四)——数据类型 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 12426 个阅读者浏览上一篇主题  刷新本主题   平板显示贴子 浏览下一篇主题
     * 贴子主题: COM 组件设计与应用(三、四)——数据类型 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     卷积内核 帅哥哟,离线,有人找我吗?
      
      
      威望:8
      头衔:总统
      等级:博士二年级(版主)
      文章:3942
      积分:27590
      门派:XML.ORG.CN
      注册:2004/7/21

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给卷积内核发送一个短消息 把卷积内核加入好友 查看卷积内核的个人资料 搜索卷积内核在『 C/C++编程思想 』的所有贴子 访问卷积内核的主页 引用回复这个贴子 回复这个贴子 查看卷积内核的博客楼主
    发贴心情 

    COM 组件设计与应用(四)——简单调用组件
    二、组件的启动和释放
      在第三回中,大家用“小本本”记录了一个原则:COM 组件是运行在分布式环境中的 。于是,如何启动组件立刻就遇到了严重的问题,大家看这段代码:

    p = new 对象;
       p->对象函数();
       delete p;
      这样的代码再熟悉不过了,在本地进程中运行是不会有问题的。但是你想想,如果这个对象是在“地球另一边”的计算机上,结果会如何?嘿嘿,C++ 在设计 new 的时候,可没有考虑远程的实现呀(计算机语言当然不会,也没必要去设计)。因此启动组件、调用接口的功能,当然就由 COM 系统来实现了。

    按此在新窗口浏览图片
      图一 组件调用机制

      由上图可以看出,当调用组件的时候,其实是依靠代理(运行在本地)和存根(运行在远端)之间的通讯完成的。具体来说,当客户程序通过 CoCreateInstance() 函数启动组件,则代理接管该调用,它和存根通讯,存根则它所在的本地(相对于客户程序来说就是远程了)执行 new 操作加载对象。对于初学者,你可以不用理它,代理和存根对我们来说是透明的。只要大约知道是怎么一回事就一切OK了。

      问题又来了,这个远程的对象什么时候消灭呢?在第二回介绍接口概念的时候,当时我们特意忽略了两个函数,就是IUnknown::AddRef()和IUnknown::Release(),从函数名就能猜到了,一个是对内部引用记数器(Ref)加1,一个是释放(减1),当记数器减为0的时候,就是释放的机会啦。看起来很复杂,没办法,因为这是在介绍原理。其实在我们写程序的时候到比较简单,请大家遵守几个原则:

      1、启动组件得到一个接口指针(Interface)后,不要调用AddRef()。因为系统知道你得到了一个指针,所以它已经帮你调用了AddRef()函数;

      2、通过QueryInterface()得到另一个接口指针后,不要调用AddRef()。因为......和上面的道理一样;

      3、当你把接口指针赋值给(保存到)另一个变量中的时候,请调用AddRef();

      4、当不需要再使用接口指针的时候,务必执行Release()释放;

      5、当使用智能指针的时候,可以省略指针的维护工作;(注1)

      三、内存分配和释放

      自从学习了C语言,老师就教导我们说:对于动态内存的申请和释放,一定要遵守“谁申请,谁释放”的原则。在此原则的指导下,不仅是我、不仅是你,就连特级大师都设计了这样怪怪的函数:

       函数 说明 评论
    GetWindowText(HWND,LPTSTR,int) 取得窗口标题。需要在参数中给出保存标题所使用的内存指针,和这块内存的尺寸。 晕!我又不知道窗口标题的长度,居然还要我提供尺寸?!没办法,只能估摸着给一个大一些的尺寸吧。
    sprintf(char *,const char *,...) 格式化一个字符串。这个函数不用给出缓冲区的长度啦。 恩,虽然不用给出长度了,但你敢给个小尺寸吗?哼!
    int CListBox::GetTextLen(int)

      CListBox::GetText(int,LPTSTR)
    取得列表窗中子项目的标题。需要调用两个函数,先取得长度,然后分配内存,再实际取得标题内容。 真烦!


      说实在的,不但函数调用者感觉别扭,就连函数设计者心情也不会爽的,而这一切都是为了满足所谓“谁申请,谁释放”的原则。 解决这个问题最好的方式就是:函数内部根据实际需要动态申请内存,而调用者负责释放。这虽然违背了上述原则,但 COM 从方便性和效率出发,确实是这么设计的。

         C语言 C++语言 Windows 平台 COM IMalloc 接口 BSTR
    申请 malloc() new GlobalAlloc() CoTaskMemAlloc() Alloc() SysAllocString()
    重新申请 realloc()   GlobalReAlloc() CoTaskRealloc() Realloc() SysReAllocString()
    释放 free() delete GlobalFree() CoTaskMemFree() Free() SysFreeString()


      以上这些函数必须要按类型配合使用(比如:new 申请的内存,则必须用 delete 释放)。在 COM 内部,当然你可以随便使用任何类型的内存分配释放函数,但组件如果需要与客户进行内存的交互,则必须使用上表中的后三类函数族。

      1、BSTR 内存在上回书中,已经有比较丰富的介绍了,不再重复;

      2、CoTaskXXX()函数族,其本质上就是调用C语言的函数(malloc...);

      3、IMalloc 接口又是对 CoTaskXXX() 函数族的一个包装。包装后,同时增强了一些功能,比如:IMalloc::GetSize()可以取得尺寸,使用 IMallocSpy 可以监视内存的使用;

      四、参数传递方向

      在C语言的函数声明中,尤其当参数为指针的时候,你是看不出它传递方向的。比如:

      void fun(char * p1, int * p2); 请问,p1、p2 哪个是入参?哪个是出参?甚或都是入参或都是出参?由于牵扯到内存分配和释放等问题,COM 需要明确标注参数方向。以后我们写程序,就类似下面的样子:

    HRESULT Add([in] long n1, [in] long n2, [out] long *pnSum); // IDL文件(注2)
       STDMETHOD(Add)(/*[in]*/ long n1, /*[in]*/ long n2, /*[out]*/ long *pnSum); // .h文件
      如果参数是动态分配的内存指针,那么遵守如下的规定:

       方向 申请人 释放人 提示
    [in] 调用者 调用者 组件接收指针后,不能重新分配内存
    [out] 组件 调用者 组件返回指针后,调用者“爱咋咋地”(注3)
    [in,out] 调用者 调用者 组件可以重新分配内存


      五、示例程序

      示例一、由 CLSID 得到 ProgID。(程序以 word 为例子。如果运行不正确,嘿嘿,你没有安装 word 吧?)

    ::CoInitialize( NULL );
      HRESULT hr;
      // {000209FF-0000-0000-C000-000000000046} = word.application.9
      CLSID clsid = {0x209ff,0,0,{0xc0,0,0,0,0,0,0,0x46}};
      LPOLESTR lpwProgID = NULL;
      
      hr = ::ProgIDFromCLSID( clsid, &lpwProgID );
      if ( SUCCEEDED(hr) )
      {
        ::MessageBoxW( NULL, lpwProgID, L"ProgID", MB_OK );
        IMalloc * pMalloc = NULL;
        hr = ::CoGetMalloc( 1, &pMalloc ); // 取得 IMalloc
        if ( SUCCEEDED(hr) )
        {
          pMalloc->Free( lpwProgID ); // 释放ProgID内存
          pMalloc->Release();     // 释放IMalloc
        }
      }
      ::CoUninitialize();
      示例二、如何使用“浏览文件夹”选择对话窗。

    CString BrowseFolder(HWND hWnd, LPCTSTR lpTitle)
    {
      // 调用 SHBrowseForFolder 取得目录(文件夹)名称
      // 参数 hWnd: 父窗口句柄
      // 参数 lpTitle: 窗口标题
      
      char szPath[MAX_PATH]={0};
      BROWSEINFO m_bi;
      m_bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT;
      m_bi.hwndOwner = hWnd;
      m_bi.pidlRoot = NULL;
      m_bi.lpszTitle = lpTitle;
      m_bi.lpfn = NULL;
      m_bi.lParam = NULL;
      m_bi.pszDisplayName = szPath;
      LPITEMIDLIST pidl = ::SHBrowseForFolder( &m_bi );
      if ( pidl )
      {
        if( !::SHGetPathFromIDList ( pidl, szPath ) ) szPath[0]=0;
        IMalloc * pMalloc = NULL;
        if ( SUCCEEDED ( ::SHGetMalloc( &pMalloc ) ) ) // 取得IMalloc分配器接口
        {
          pMalloc->Free( pidl );  // 释放内存
          pMalloc->Release();    // 释放接口
        }
      }
      return szPath;
    }
      示例三、在窗口中显示一幅 JPG 图象。

    void CxxxView::OnDraw(CDC* pDC)
    {
      ::CoInitialize(NULL); // COM 初始化
      HRESULT hr;
      CFile file;
      
      file.Open( "c:\\aa.jpg", CFile::modeRead | CFile::shareDenyNone ); // 读入文件内容
      DWORD dwSize = file.GetLength();
      HGLOBAL hMem = ::GlobalAlloc( GMEM_MOVEABLE, dwSize );
      LPVOID lpBuf = ::GlobalLock( hMem );
      file.ReadHuge( lpBuf, dwSize );
      file.Close();
      ::GlobalUnlock( hMem );
      IStream * pStream = NULL;
      IPicture * pPicture = NULL;
      
      // 由 HGLOBAL 得到 IStream,参数 TRUE 表示释放 IStream 的同时,释放内存
      hr = ::CreateStreamOnHGlobal( hMem, TRUE, &pStream );
      ASSERT ( SUCCEEDED(hr) );
      
      hr = ::OleLoadPicture( pStream, dwSize, TRUE, IID_IP

    ----------------------------------------------
    事业是国家的,荣誉是单位的,成绩是领导的,工资是老婆的,财产是孩子的,错误是自己的。

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/19 8:35:00
     
     GoogleAdSense
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/6/23 3:07:09

    本主题贴数4,分页: [1]

     *树形目录 (最近20个回帖) 顶端 
    主题:  COM 组件设计与应用(三、四)——数据类型(13318字) - 卷积内核,2007年10月19日
        回复:  二三楼之间调用OleLoadPicture中,参数有遗缺。查MSDN,大概应为 hr = ::O..(148字) - oldnwind,2009年2月1日
        回复:  ASSERT(hr==S_OK);long nWidth,nHeight; // 宽高,MM..(4416字) - 卷积内核,2007年10月22日
        回复:  COM 组件设计与应用(四)——简单调用组件二、组件的启动和释放   在第三回中,大家用“小本..(6817字) - 卷积内核,2007年10月19日

    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    6,722.656ms