COM组件设计与应用(四)——简单调用组件

目录(?)[-]

  1. 一、前言
  2. 二、组件的启动和释放
  3. 三、内存分配和释放
  4. 四、参数传递方向
  5. 五、示例程序
  6. 六、小结

本文摘自:http://www.vckbase.net/index.php/wv/1211

一、前言

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

 

  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 吧?)

01.::CoInitialize(
NULL );

02.

03.HRESULT hr;

04.//
{000209FF-0000-0000-C000-000000000046} = word.application.9

05.CLSID
clsid = {0x209ff,0,0,{0xc0,0,0,0,0,0,0,0x46}};

06.LPOLESTR
lpwProgID = NULL;

07.

08.hr
= ::ProgIDFromCLSID( clsid, &lpwProgID );

09.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();

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

01.CString
BrowseFolder(
HWND hWnd, LPCTSTR lpTitle)

02.{

03.//
调用 SHBrowseForFolder 取得目录(文件夹)名称

04.//
参数 hWnd: 父窗口句柄

05.//
参数 lpTitle: 窗口标题

06.

07.char szPath[MAX_PATH]={0};

08.BROWSEINFO
m_bi;

09.

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 图象。

01.void CxxxView::OnDraw(CDC*
pDC)

02.{

03.::CoInitialize(NULL);  //
COM 初始化

04.HRESULT hr;

05.CFile
file;

06.

07.file.Open( "c:\\aa.jpg",
CFile::modeRead | CFile::shareDenyNone );  
//
读入文件内容

08.DWORD dwSize
= file.GetLength();

09.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)

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

01.#include
< atlconv.h >

02.void CreateShortcut(LPCTSTR lpszExe, LPCTSTR lpszLnk)

03.{

04.//
建立块捷方式

05.//
参数 lpszExe: EXE 文件全路径名

06.//
参数 lpszLnk: 快捷方式文件全路径名

07.

08.::CoInitialize(
NULL );

09.

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呀......

时间: 2024-10-04 23:39:42

COM组件设计与应用(四)——简单调用组件的相关文章

【转载】COM 组件设计与应用(四)——简单调用组件

原文:http://vckbase.com/index.php/wv/1211.html 一.前言 同志们.朋友们.各位领导,大家好. VCKBASE 不得了, 网友众多文章好. 组件设计怎么学? 知识库里闷头找! 摘自---杨老师打油集录 在 VCKBASE 的顶力支持下,在各位网友回帖的鼓励下,我才能顺利完成系列论文的前三回.书到本回,我们终于开始写代码啦.写点啥那?恩,有了!咱们先从如何调用现成的简单的组件开始吧,同时也顺便介绍一些相关的知识. 二.组件的启动和释放 在第三回中,大家用“小

COM组件设计与应用(三)——数据类型

本文摘自:http://www.vckbase.com/index.php/wv/1206 一.前言 上回书介绍了GUID.CLSID.IID和接口的概念.本回的重点是介绍 COM 中的数据类型.咋还不介绍组件程序的设计步骤呀?咳......别着急,别着急!孔子曰:"饭要一口一口地吃":老子语:"心急吃不了热豆腐",孙子云:"走一步看一步吧" ...... 先掌握必要的知识,将来写起程序来才会得心应手也:-) 走入正题之前,请大家牢牢记住一条原则

Unity3d 组件设计的思考

在使用unity3d之前,我已经知道组件设计的概念,我们某个项目实际上也是基于组件的,虽然底层引擎只是设计了一个最简单的组件框架,遗憾的是其他部分,并没有按照多少组件的意思来组织代码.这个组件失败的地方在于,没有提供一个很好的组件之间通信的方法.我们的组件系统使用一个interface类作为组件提供内在功能的手段.好处在于,使用该interface类你无需包含特定组件的细节(不用包含组件头文件).坏处是,该interface类本身很庞大,因为他使用仿函数(boost function)作为与组件

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

原文:http://vckbase.com/index.php/wv/1236.html 一.前言 前段时间,由于工作比较忙,没有能及时地写作.其间收到了很多网友的来信询问和鼓励,在此一并表示感谢.咳......我也需要工作来养家糊口呀...... 上回书介绍了两种方法来写自动化(IDispatch)接口的组件程序,一是用 MFC 方式编写“纯粹”的IDispatch 接口:二是用 ATL 方式编写“双接口”的组件. 二.IDispatch 接口和双接口 使用者要想调用普通的 COM 组件功能,

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

原文:http://vckbase.com/index.php/wv/1218.html 一.前言 上两回中,咱们用 ATL 写了第一个 COM 组件程序,这回中,主要介绍编译.册和调用方法.示例程序你已经下载了吗?如果还没有下载,vc6.0 的用户点,vc.net 的用户点这里. 二.关于编译 2-1 最小依赖 “最小依赖”,表示编译器会把 ATL 中必须使用的一些函数静态连接到目标程序中.这样目标文件尺寸会稍大,但独立性更强,安装方便:反之系统执行的时候需要有 ATL.DLL 文件的支持.如

组件设计原则之概念篇(四)

http://blog.csdn.net/lovelion/article/details/8539543   稳定抽象原则SAP是六个组件设计原则中的最后一个,它通常与稳定依赖原则SDP结合在一起,用于创建具有较高质量的组件依赖结构.终于是最后一个了,. 稳定抽象原则(The Stable-Abstractions Principle, SAP) A component should be as abstract as it is stable.组件的抽象程度应该与其稳定程度一致. SAP将组

HT图形组件设计之道(四)

在<HT图形组件设计之道(二)>我们展示了HT在2D图形矢量的数据绑定功能,这种机制不仅可用于2D图形,HT的通用组件甚至3D引擎都具备这种数据绑定机制,此篇我们将构建一个3D飞机模型,展示如果将数据绑定机制运用于3D模型,同时会运用到HT的动画机制,以及OBJ 3D模型加载等技术细节,正巧赶上刚发布的iOS8我们终于能将基于HT for Web开发的HTML5 3D应用跑在iOS系统了. 首选我们需要一个飞机模型,采用HT for Web构建3D模型可采用API组合各种基础模型的方式,但今天

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

原文:http://vckbase.com/index.php/wv/1264.html 一.前言 我们写程序,经常需要实现这样的需求: 例一.程序运行产生一个窗口,用户关闭的时候需要记录窗口的位置,以便下次运行时保持位置不变: 例二.由于程序运行时间很长,今天执行一部分,明天继续执行.那么在下次运行前要恢复前次的状态: ... ... ... ... 智慧的老师:以上这些需求,如何实现呢? 懵懂的学生:这个简单,只要在程序退出前提取必要的信息保存到文件中,下次运行时再从文件中读出来,设置一下就

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

原文: http://vckbase.com/index.php/wv/1224.html 一.前言 终于写到了第九回,我也一直期盼着写这回的内容耶,为啥呢?因为自动化(automation)是非常常用.非常有用.非常精彩的一个 COM 功能.由于 WORD.EXCEL 等 OFFICE 软件提供了“宏”的功能,就连我们使用的VC开发环境也提供了“宏”功能,更由于 HTML.ASP.JSP 等都要依靠脚本(Script)的支持,更体现出了自动化接口的重要性. 如果你使用 vc6.0 的开发环境,