原文:http://vckbase.com/index.php/wv/1211.html

一、前言

同志们、朋友们、各位领导,大家好。
 

  VCKBASE 不得了,  
  网友众多文章好。  
  组件设计怎么学?  
  知识库里闷头找!  
    摘自---杨老师打油集录

在 VCKBASE 的顶力支持下,在各位网友回帖的鼓励下,我才能顺利完成系列论文的前三回。书到本回,我们终于开始写代码啦。写点啥那?恩,有了!咱们先从如何调用现成的简单的组件开始吧,同时也顺便介绍一些相关的知识。

二、组件的启动和释放

在第三回中,大家用“小本本”记录了一个原则:COM 组件是运行在分布式环境中的 。于是,如何启动组件立刻就遇到了严重的问题,大家看这段代码:

1.p = new 对象;
2.p->对象函数();
3.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 需要明确标注参数方向。以后我们写程序,就类似下面的样子:

1.HRESULT Add([in] long n1, [in] long n2, [out] long *pnSum);  // IDL文件(注2)
2.STDMETHOD(Add)(/*[in]*/ long n1, /*[in]*/ long n2, /*[out]*/ long *pnSum);  // .h文件

如果参数是动态分配的内存指针,那么遵守如下的规定:

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

五、示例程序

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

  1. ::CoInitialize( NULL );
  2.  
  3. HRESULT hr;
  4. // {000209FF-0000-0000-C000-000000000046} = word.application.9
  5. CLSID clsid = {0x209ff,0,0,{0xc0,0,0,0,0,0,0,0x46}};
  6. LPOLESTR lpwProgID = NULL;
  7.  
  8. hr = ::ProgIDFromCLSID( clsid, &lpwProgID );
  9. if ( SUCCEEDED(hr) )
  10. {
  11. ::MessageBoxW( NULL, lpwProgID, L"ProgID", MB_OK );
  12.  
  13. IMalloc * pMalloc = NULL;
  14. hr = ::CoGetMalloc( 1, &pMalloc ); // 取得 IMalloc
  15. if ( SUCCEEDED(hr) )
  16. {
  17. pMalloc->Free( lpwProgID ); // 释放ProgID内存
  18. pMalloc->Release(); // 释放IMalloc
  19. }
  20. }
  21.  
  22. ::CoUninitialize();

  

示例二、如何使用“浏览文件夹”选择对话窗。

  1. CString BrowseFolder(HWND hWnd, LPCTSTR lpTitle)
  2. {
  3. // 调用 SHBrowseForFolder 取得目录(文件夹)名称
  4. // 参数 hWnd: 父窗口句柄
  5. // 参数 lpTitle: 窗口标题
  6.  
  7. char szPath[MAX_PATH]={0};
  8. BROWSEINFO m_bi;
  9.  
  10. m_bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT;
  11. m_bi.hwndOwner = hWnd;
  12. m_bi.pidlRoot = NULL;
  13. m_bi.lpszTitle = lpTitle;
  14. m_bi.lpfn = NULL;
  15. m_bi.lParam = NULL;
  16. m_bi.pszDisplayName = szPath;
  17.  
  18. LPITEMIDLIST pidl = ::SHBrowseForFolder( &m_bi );
  19. if ( pidl )
  20. {
  21. if( !::SHGetPathFromIDList ( pidl, szPath ) ) szPath[0]=0;
  22.  
  23. IMalloc * pMalloc = NULL;
  24. if ( SUCCEEDED ( ::SHGetMalloc( &pMalloc ) ) ) // 取得IMalloc分配器接口
  25. {
  26. pMalloc->Free( pidl ); // 释放内存
  27. pMalloc->Release(); // 释放接口
  28. }
  29. }
  30. return szPath;
  31. }

  

示例三、在窗口中显示一幅 JPG 图象。

  1. void CxxxView::OnDraw(CDC* pDC)
  2. {
  3. ::CoInitialize(NULL); // COM 初始化
  4. HRESULT hr;
  5. CFile file;
  6.  
  7. file.Open( "c:\\aa.jpg", CFile::modeRead | CFile::shareDenyNone ); // 读入文件内容
  8. DWORD dwSize = file.GetLength();
  9. HGLOBAL hMem = ::GlobalAlloc( GMEM_MOVEABLE, dwSize );
  10. LPVOID lpBuf = ::GlobalLock( hMem );
  11. file.ReadHuge( lpBuf, dwSize );
  12. file.Close();
  13. ::GlobalUnlock( hMem );
  14.  
  15. IStream * pStream = NULL;
  16. IPicture * pPicture = NULL;
  17.  
  18. // 由 HGLOBAL 得到 IStream,参数 TRUE 表示释放 IStream 的同时,释放内存
  19. hr = ::CreateStreamOnHGlobal( hMem, TRUE, &pStream );
  20. ASSERT ( SUCCEEDED(hr) );
  21.  
  22. hr = ::OleLoadPicture( pStream, dwSize, TRUE, IID_IPicture, ( LPVOID * )&pPicture );
  23. ASSERT(hr==S_OK);
  24.  
  25. long nWidth,nHeight; // 宽高,MM_HIMETRIC 模式,单位是0.01毫米
  26. pPicture->get_Width( &nWidth ); // 宽
  27. pPicture->get_Height( &nHeight ); // 高
  28.  
  29. ////////原大显示//////
  30. CSize sz( nWidth, nHeight );
  31. pDC->HIMETRICtoDP( &sz ); // 转换 MM_HIMETRIC 模式单位为 MM_TEXT 像素单位
  32. pPicture->Render(pDC->m_hDC,0,0,sz.cx,sz.cy,
  33. 0,nHeight,nWidth,-nHeight,NULL);
  34.  
  35. ////////按窗口尺寸显示////////
  36. // CRect rect; GetClientRect(&rect);
  37. // pPicture->Render(pDC->m_hDC,0,0,rect.Width(),rect.Height(),
  38. // 0,nHeight,nWidth,-nHeight,NULL);
  39.  
  40. if ( pPicture ) pPicture->Release();// 释放 IPicture 指针
  41. if ( pStream ) pStream->Release(); // 释放 IStream 指针,同时释放了 hMem
  42.  
  43. ::CoUninitialize();
  44. }

  

示例四、在桌面建立快捷方式

在阅读代码之前,先看一下关于“快捷方式”组件的结构示意图。

图二、快捷方式组件的接口结构示意图

从结构图中可以看出,“快捷方式”组件(CLSID_ShellLink),有3个(其实不止)接口,每个接口完成一组相关功能的函数。IShellLink 接口(IID_IShellLink)提供快捷方式的参数读写功能(见图三),IPersistFile 接口(IID_IPersistFile)提供快捷方式持续性文件的读写功能。对象的持续性(注5),是一个非常常用,并且功能强大的接口家族。但今天,我们只要了解其中两函数,就可以了:IPersistFile::Save()和IPersistFile:Load()。(注6)

图三、快捷方式中的各种属性

  1. #include < atlconv.h >
  2. void CreateShortcut(LPCTSTR lpszExe, LPCTSTR lpszLnk)
  3. {
  4. // 建立块捷方式
  5. // 参数 lpszExe: EXE 文件全路径名
  6. // 参数 lpszLnk: 快捷方式文件全路径名
  7.  
  8. ::CoInitialize( NULL );
  9.  
  10. IShellLink * psl = NULL;
  11. IPersistFile * ppf = NULL;
  12.  
  13. HRESULT hr = ::CoCreateInstance( // 启动组件
  14. CLSID_ShellLink, // 快捷方式 CLSID
  15. NULL, // 聚合用(注4)
  16. CLSCTX_INPROC_SERVER, // 进程内(Shell32.dll)服务
  17. IID_IShellLink, // IShellLink 的 IID
  18. (LPVOID *)&psl ); // 得到接口指针
  19.  
  20. if ( SUCCEEDED(hr) )
  21. {
  22. psl->SetPath( lpszExe ); // 全路径程序名
  23. // psl->SetArguments(); // 命令行参数
  24. // psl->SetDescription(); // 备注
  25. // psl->SetHotkey(); // 快捷键
  26. // psl->SetIconLocation(); // 图标
  27. // psl->SetShowCmd(); // 窗口尺寸
  28.  
  29. // 根据 EXE 的文件名,得到目录名
  30. TCHAR szWorkPath[ MAX_PATH ];
  31. ::lstrcpy( szWorkPath, lpszExe );
  32. LPTSTR lp = szWorkPath;
  33. while( *lp ) lp++;
  34. while( ''\\'' != *lp ) lp--;
  35. *lp=0;
  36.  
  37. // 设置 EXE 程序的默认工作目录
  38. psl->SetWorkingDirectory( szWorkPath );
  39.  
  40. hr = psl->QueryInterface( // 查找持续性文件接口指针
  41. IID_IPersistFile, // 持续性接口 IID
  42. (LPVOID *)&ppf ); // 得到接口指针
  43.  
  44. if ( SUCCEEDED(hr) )
  45. {
  46. USES_CONVERSION; // 转换为 UNICODE 字符串
  47. ppf->Save( T2COLE( lpszLnk ), TRUE ); // 保存
  48. }
  49. }
  50. if ( ppf ) ppf->Release();
  51. if ( psl ) psl->Release();
  52.  
  53. ::CoUninitialize();
  54. }
  55.  
  56. void OnXXX()
  57. {
  58. CreateShortcut(
  59. _T("c:\\winnt\\notepad.exe"), // 记事本程序。注意,你的系统是否也是这个目录?
  60. _T("c:\\Documents and Settings\\Administrator\\桌面\\我的记事本.lnk")
  61. );
  62. // 桌面上建立快捷方式(lnk)文件的全路径名。注意,你的系统是否也是这个目录?
  63. // 如果用程序实现寻找桌面的路径,则可以查注册表
  64. // HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders
  65. }

  

七、小结

本回介绍的内容比较实用。大家不要只抄袭代码,而一定要理解它。结合 MSDN 的说明去思索代码、理解其含义。好了,想方设法把代码忘掉!三天后(如过你还没有忘记,那就再过三天),你在不参考示例代码,但可以随便翻阅 MSDN 的情况下,自己能独立地再次完成这四个例程,那么恭喜你,你已经入门了:0) 从下回开始,我们要用 ATL 做 COM 的开发工作啦,您老人家准备好了吗?

作业,留作业啦......

1、你已经学会如何建立快捷方式了,那么你知道怎么读取它的属性吗?(如果写不出这个程序,那么你就不用继续学习了。因为......动点脑筋呀!我还没有见过象你这么笨的学生呢!)

2、示例程序三中使用了 IPicture 接口显示一个 JPG 图象。那么你现在去完成一个功能,把 JPG 文件转换为 BMP 文件。

注1:智能指针的概念和用法,后续介绍。

注2:IDL 文件,下回就要介绍啦。

注3:东北话,想干什么都可以,反正我不管啦。

注4:聚合,也许在第30回中介绍吧:-)

注5:持续性,IPersistXXXXXX是一个非常强大的接口家族,后续介绍。

注6:想知道 IShellLink、IPersistFile接口的所有函数吗?别愣着,快去看MSDN呀......

【转载】COM 组件设计与应用(四)——简单调用组件的更多相关文章

  1. 【转载】COM 组件设计与应用(三)——数据类型

    原文:http://vckbase.com/index.php/wv/1206.html COM 组件设计与应用 系列文章:http://vckbase.com/index.php/piwz?& ...

  2. xmlplus 组件设计系列之零 - xmlplus 简介

    xmlplus 是什么 xmlplus 是博主写的一个 JavaScript 框架,用于快速开发前后端项目. xmlplus 基于组件设计,组件是基本的构造块.评价组件设计好坏的一个重要标准是封装度. ...

  3. 微信小程序——页面中调用组件方法

    我现在有一个弹层的组件(popup),组件里面定义了显示组件(showPopup)和隐藏组件(hidePopup)的方法. 我们如何在调用组件的页面中调用组件里面的方法呢? 在调用组件的页面写如下代码 ...

  4. 【转载】COM 组件设计与应用(十七)——持续性

    原文:http://vckbase.com/index.php/wv/1264.html 一.前言 我们写程序,经常需要实现这样的需求: 例一.程序运行产生一个窗口,用户关闭的时候需要记录窗口的位置, ...

  5. 【转载】COM 组件设计与应用(十一)—— IDispatch 及双接口的调用

    原文:http://vckbase.com/index.php/wv/1236.html 一.前言 前段时间,由于工作比较忙,没有能及时地写作.其间收到了很多网友的来信询问和鼓励,在此一并表示感谢.咳 ...

  6. 【转载】COM 组件设计与应用(十)——IDispatch 接口 for VC.NET

    原文:http://vckbase.com/index.php/wv/1225.html 一.前言 终于写到了第十回,我也一直期盼着写这回的内容耶,为啥呢?因为自动化(automation)是非常常用 ...

  7. 【转载】COM 组件设计与应用(九)——IDispatch 接口 for VC6.0

    原文: http://vckbase.com/index.php/wv/1224.html 一.前言 终于写到了第九回,我也一直期盼着写这回的内容耶,为啥呢?因为自动化(automation)是非常常 ...

  8. 【转载】COM 组件设计与应用(八)——实现多接口

    原文:http://vckbase.com/index.php/wv/1219.html 一.前言 从第五回开始到第七回,咱们用 ATL 写了一个简单的 COM 组件,之所以说简单,是因为在组件中,只 ...

  9. 【转载】COM 组件设计与应用(七)——编译、注册、调用

    原文:http://vckbase.com/index.php/wv/1218.html 一.前言 上两回中,咱们用 ATL 写了第一个 COM 组件程序,这回中,主要介绍编译.册和调用方法.示例程序 ...

随机推荐

  1. Elasticsearch入坑指南之RESTful API

    Elasticsearch入坑指南之RESTful API Tags:Elasticsearch ES为开发者提供了非常丰富的基于Http协议的Rest API,通过简单的Rest请求,就可以实现非常 ...

  2. .net Cookie的操作

    using System; using System.Collections.Generic; using System.Web; namespace Zhong.Core { /// <sum ...

  3. 使用NSOperation以及NSOperationQueue

    使用NSOperation以及NSOperationQueue NSOperation vs. Grand Central Dispatch (GCD) 在Mac OS X v10.6和iOS4之前, ...

  4. ELK搭建实时日志分析平台之二Logstash和Kibana搭建

    本文书接前回<ELK搭建实时日志分析平台之一ElasticSearch> 文:铁乐与猫 四.安装Logstash logstash是一个数据分析软件,主要目的是分析log日志. 1)下载和 ...

  5. ms17-010漏洞利用教程

    ms17-010 漏洞利用并拿下服务器教程 攻击环境: 攻击机win2003 ip:192.168.150.129 Window2003 Python环境及工具 攻击机kali: ip:192.168 ...

  6. Asp.net Core 2.0+EntityFrameWorkCore 2.0添加数据迁移

    Asp.net Core 由于依赖注入的广泛使用,配置数据迁移,与Asp.net大不相同,本篇介绍一下Asp.net Core添加数据迁移的过程 添加Nuget包 Install-Package Mi ...

  7. 前段js初学总结

    常用的js整理 confirm("此次修改操作会清空所有基础数据!!!您确定要修改吗?") <a onclick="delBasisData('${data['_i ...

  8. Substring Search

    查找子字符串 Introduction 在长度为 N 的文本里寻找长度为 M 的模式(子串),典型情况是 N >> M. 这个应用就很广泛啦,在文本中寻找特定的模式(子串)是很常见的需求. ...

  9. 使用ubuntu desktop是可能会用到的配置

    1.ubuntu desktop12.04 接双显示器 想要12.04版本在接上双显示器时能很好的工作,则需要进行如下设置: (1).编辑/etc/X11/xorg.conf文件: /etc/X11/ ...

  10. SOJ4453 Excel列数 进制转换

    描述 我们都知道Excel的列数是用字母表示的,比如第1列对应A,第27列对应AA. 假设给定一个正整数n,你能给出它所对应的字母表示么? 输入格式 程序需要读入多个测试样例,每个测试样例中: 一个正 ...