您的位置:首页 > 其它

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

2016-01-10 22:37 453 查看
本文摘自: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的时候,就是释放的机会啦。看起来很复杂,没办法,因为这是在介绍原理。其实在我们写程序的时候到比较简单,请大家遵守几个原则:

启动组件得到一个接口指针(Interface)后,不要调用AddRef()。因为系统知道你得到了一个指针,所以它已经帮你调用了AddRef()函数;
通过QueryInterface()得到另一个接口指针后,不要调用AddRef()。因为......和上面的道理一样;
当你把接口指针赋值给(保存到)另一个变量中的时候,请调用AddRef();
当不需要再使用接口指针的时候,务必执行Release()释放;
当使用智能指针的时候,可以省略指针的维护工作;(注1)

三、内存分配和释放

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

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

CListBox::GetText(int,LPTSTR)
取得列表窗中子项目的标题。需要调用两个函数,先取得长度,然后分配内存,再实际取得标题内容。真烦!
说实在的,不但函数调用者感觉别扭,就连函数设计者心情也不会爽的,而这一切都是为了满足所谓“谁申请,谁释放”的原则。 解决这个问题最好的方式就是:函数内部根据实际需要动态申请内存,而调用者负责释放。这虽然违背了上述原则,但 COM 从方便性和效率出发,确实是这么设计的。

C语言C++语言Windows 平台COMIMalloc 接口BSTR
申请malloc()newGlobalAlloc()CoTaskMemAlloc()Alloc()SysAllocString()
重新申请realloc()GlobalReAlloc()CoTaskRealloc()Realloc()SysReAllocString()
释放free()deleteGlobalFree()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呀......
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: