【转载】COM 组件设计与应用(四)——简单调用组件
原文:http://vckbase.com/index.php/wv/1211.html
?
一、前言
同志們、朋友們、各位領(lǐng)導(dǎo),大家好。
| VCKBASE 不得了, | ||
| 網(wǎng)友眾多文章好。 | ||
| 組件設(shè)計(jì)怎么學(xué)? | ||
| 知識庫里悶頭找! | ||
| 摘自---楊老師打油集錄 |
在 VCKBASE 的頂力支持下,在各位網(wǎng)友回帖的鼓勵(lì)下,我才能順利完成系列論文的前三回。書到本回,我們終于開始寫代碼啦。寫點(diǎn)啥那?恩,有了!咱們先從如何調(diào)用現(xiàn)成的簡單的組件開始吧,同時(shí)也順便介紹一些相關(guān)的知識。
二、組件的啟動(dòng)和釋放
在第三回中,大家用“小本本”記錄了一個(gè)原則:COM 組件是運(yùn)行在分布式環(huán)境中的 。于是,如何啟動(dòng)組件立刻就遇到了嚴(yán)重的問題,大家看這段代碼:
1.p =?new?對象; 2.p->對象函數(shù)(); 3.delete?p;這樣的代碼再熟悉不過了,在本地進(jìn)程中運(yùn)行是不會有問題的。但是你想想,如果這個(gè)對象是在“地球另一邊”的計(jì)算機(jī)上,結(jié)果會如何?嘿嘿,C++ 在設(shè)計(jì) new 的時(shí)候,可沒有考慮遠(yuǎn)程的實(shí)現(xiàn)呀(計(jì)算機(jī)語言當(dāng)然不會,也沒必要去設(shè)計(jì))。因此啟動(dòng)組件、調(diào)用接口的功能,當(dāng)然就由 COM 系統(tǒng)來實(shí)現(xiàn)了。
圖一 組件調(diào)用機(jī)制
由上圖可以看出,當(dāng)調(diào)用組件的時(shí)候,其實(shí)是依靠代理(運(yùn)行在本地)和存根(運(yùn)行在遠(yuǎn)端)之間的通訊完成的。具體來說,當(dāng)客戶程序通過 CoCreateInstance() 函數(shù)啟動(dòng)組件,則代理接管該調(diào)用,它和存根通訊,存根則它所在的本地(相對于客戶程序來說就是遠(yuǎn)程了)執(zhí)行 new 操作加載對象。對于初學(xué)者,你可以不用理它,代理和存根對我們來說是透明的。只要大約知道是怎么一回事就一切OK了。
問題又來了,這個(gè)遠(yuǎn)程的對象什么時(shí)候消滅呢?在第二回介紹接口概念的時(shí)候,當(dāng)時(shí)我們特意忽略了兩個(gè)函數(shù),就是IUnknown::AddRef()和IUnknown::Release(),從函數(shù)名就能猜到了,一個(gè)是對內(nèi)部引用記數(shù)器(Ref)加1,一個(gè)是釋放(減1),當(dāng)記數(shù)器減為0的時(shí)候,就是釋放的機(jī)會啦。看起來很復(fù)雜,沒辦法,因?yàn)檫@是在介紹原理。其實(shí)在我們寫程序的時(shí)候到比較簡單,請大家遵守幾個(gè)原則:
1、啟動(dòng)組件得到一個(gè)接口指針(Interface)后,不要調(diào)用AddRef()。因?yàn)橄到y(tǒng)知道你得到了一個(gè)指針,所以它已經(jīng)幫你調(diào)用了AddRef()函數(shù);
2、通過QueryInterface()得到另一個(gè)接口指針后,不要調(diào)用AddRef()。因?yàn)?.....和上面的道理一樣;
3、當(dāng)你把接口指針賦值給(保存到)另一個(gè)變量中的時(shí)候,請調(diào)用AddRef();
4、當(dāng)不需要再使用接口指針的時(shí)候,務(wù)必執(zhí)行Release()釋放;
5、當(dāng)使用智能指針的時(shí)候,可以省略指針的維護(hù)工作;(注1)
三、內(nèi)存分配和釋放
自從學(xué)習(xí)了C語言,老師就教導(dǎo)我們說:對于動(dòng)態(tài)內(nèi)存的申請和釋放,一定要遵守“誰申請,誰釋放”的原則。在此原則的指導(dǎo)下,不僅是我、不僅是你,就連特級大師都設(shè)計(jì)了這樣怪怪的函數(shù):
| 函數(shù) | 說明 | 評論 |
| GetWindowText(HWND,LPTSTR,int) | 取得窗口標(biāo)題。需要在參數(shù)中給出保存標(biāo)題所使用的內(nèi)存指針,和這塊內(nèi)存的尺寸。 | 暈!我又不知道窗口標(biāo)題的長度,居然還要我提供尺寸?!沒辦法,只能估摸著給一個(gè)大一些的尺寸吧。 |
| sprintf(char *,const char *,...) | 格式化一個(gè)字符串。這個(gè)函數(shù)不用給出緩沖區(qū)的長度啦。 | 恩,雖然不用給出長度了,但你敢給個(gè)小尺寸嗎?哼! |
| int CListBox::GetTextLen(int) CListBox::GetText(int,LPTSTR) | 取得列表窗中子項(xiàng)目的標(biāo)題。需要調(diào)用兩個(gè)函數(shù),先取得長度,然后分配內(nèi)存,再實(shí)際取得標(biāo)題內(nèi)容。 | 真煩! |
說實(shí)在的,不但函數(shù)調(diào)用者感覺別扭,就連函數(shù)設(shè)計(jì)者心情也不會爽的,而這一切都是為了滿足所謂“誰申請,誰釋放”的原則。 解決這個(gè)問題最好的方式就是:函數(shù)內(nèi)部根據(jù)實(shí)際需要?jiǎng)討B(tài)申請內(nèi)存,而調(diào)用者負(fù)責(zé)釋放。這雖然違背了上述原則,但 COM 從方便性和效率出發(fā),確實(shí)是這么設(shè)計(jì)的。
| C語言 | C++語言 | Windows 平臺 | COM | IMalloc 接口 | BSTR | |
| 申請 | malloc() | new | GlobalAlloc() | CoTaskMemAlloc() | Alloc() | SysAllocString() |
| 重新申請 | realloc() | GlobalReAlloc() | CoTaskRealloc() | Realloc() | SysReAllocString() | |
| 釋放 | free() | delete | GlobalFree() | CoTaskMemFree() | Free() | SysFreeString() |
以上這些函數(shù)必須要按類型配合使用(比如:new 申請的內(nèi)存,則必須用 delete 釋放)。在 COM 內(nèi)部,當(dāng)然你可以隨便使用任何類型的內(nèi)存分配釋放函數(shù),但組件如果需要與客戶進(jìn)行內(nèi)存的交互,則必須使用上表中的后三類函數(shù)族。
1、BSTR 內(nèi)存在上回書中,已經(jīng)有比較豐富的介紹了,不再重復(fù);
2、CoTaskXXX()函數(shù)族,其本質(zhì)上就是調(diào)用C語言的函數(shù)(malloc...);
3、IMalloc 接口又是對 CoTaskXXX() 函數(shù)族的一個(gè)包裝。包裝后,同時(shí)增強(qiáng)了一些功能,比如:IMalloc::GetSize()可以取得尺寸,使用 IMallocSpy 可以監(jiān)視內(nèi)存的使用;
四、參數(shù)傳遞方向
在C語言的函數(shù)聲明中,尤其當(dāng)參數(shù)為指針的時(shí)候,你是看不出它傳遞方向的。比如:
void fun(char * p1, int * p2); 請問,p1、p2 哪個(gè)是入?yún)?#xff1f;哪個(gè)是出參?甚或都是入?yún)⒒蚨际浅鰠?#xff1f;由于牽扯到內(nèi)存分配和釋放等問題,COM 需要明確標(biāo)注參數(shù)方向。以后我們寫程序,就類似下面的樣子:
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文件如果參數(shù)是動(dòng)態(tài)分配的內(nèi)存指針,那么遵守如下的規(guī)定:
| 方向 | 申請人 | 釋放人 | 提示 |
| [in] | 調(diào)用者 | 調(diào)用者 | 組件接收指針后,不能重新分配內(nèi)存 |
| [out] | 組件 | 調(diào)用者 | 組件返回指針后,調(diào)用者“愛咋咋地”(注3) |
| [in,out] | 調(diào)用者 | 調(diào)用者 | 組件可以重新分配內(nèi)存 |
五、示例程序
示例一、由 CLSID 得到 ProgID。(程序以 word 為例子。如果運(yùn)行不正確,嘿嘿,你沒有安裝 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 ); // 取得 IMallocif ( SUCCEEDED(hr) ){pMalloc->Free( lpwProgID ); // 釋放ProgID內(nèi)存pMalloc->Release(); // 釋放IMalloc} }::CoUninitialize();
示例二、如何使用“瀏覽文件夾”選擇對話窗。
CString BrowseFolder(HWND hWnd, LPCTSTR lpTitle) {// 調(diào)用 SHBrowseForFolder 取得目錄(文件夾)名稱// 參數(shù) hWnd: 父窗口句柄// 參數(shù) lpTitle: 窗口標(biāo)題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 ); // 釋放內(nèi)存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 ); // 讀入文件內(nèi)容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,參數(shù) TRUE 表示釋放 IStream 的同時(shí),釋放內(nèi)存hr = ::CreateStreamOnHGlobal( hMem, TRUE, &pStream );ASSERT ( SUCCEEDED(hr) );hr = ::OleLoadPicture( pStream, dwSize, TRUE, IID_IPicture, ( LPVOID * )&pPicture );ASSERT(hr==S_OK);long nWidth,nHeight; // 寬高,MM_HIMETRIC 模式,單位是0.01毫米pPicture->get_Width( &nWidth ); // 寬pPicture->get_Height( &nHeight ); // 高原大顯示//CSize sz( nWidth, nHeight );pDC->HIMETRICtoDP( &sz ); // 轉(zhuǎn)換 MM_HIMETRIC 模式單位為 MM_TEXT 像素單位pPicture->Render(pDC->m_hDC,0,0,sz.cx,sz.cy,0,nHeight,nWidth,-nHeight,NULL);按窗口尺寸顯示 // CRect rect; GetClientRect(&rect); // pPicture->Render(pDC->m_hDC,0,0,rect.Width(),rect.Height(), // 0,nHeight,nWidth,-nHeight,NULL);if ( pPicture ) pPicture->Release();// 釋放 IPicture 指針if ( pStream ) pStream->Release(); // 釋放 IStream 指針,同時(shí)釋放了 hMem::CoUninitialize(); }
示例四、在桌面建立快捷方式
在閱讀代碼之前,先看一下關(guān)于“快捷方式”組件的結(jié)構(gòu)示意圖。
?
圖二、快捷方式組件的接口結(jié)構(gòu)示意圖
從結(jié)構(gòu)圖中可以看出,“快捷方式”組件(CLSID_ShellLink),有3個(gè)(其實(shí)不止)接口,每個(gè)接口完成一組相關(guān)功能的函數(shù)。IShellLink 接口(IID_IShellLink)提供快捷方式的參數(shù)讀寫功能(見圖三),IPersistFile 接口(IID_IPersistFile)提供快捷方式持續(xù)性文件的讀寫功能。對象的持續(xù)性(注5),是一個(gè)非常常用,并且功能強(qiáng)大的接口家族。但今天,我們只要了解其中兩函數(shù),就可以了:IPersistFile::Save()和IPersistFile:Load()。(注6)
?
圖三、快捷方式中的各種屬性
#include < atlconv.h > void CreateShortcut(LPCTSTR lpszExe, LPCTSTR lpszLnk) {// 建立塊捷方式// 參數(shù) lpszExe: EXE 文件全路徑名// 參數(shù) lpszLnk: 快捷方式文件全路徑名::CoInitialize( NULL );IShellLink * psl = NULL;IPersistFile * ppf = NULL;HRESULT hr = ::CoCreateInstance( // 啟動(dòng)組件CLSID_ShellLink, // 快捷方式 CLSIDNULL, // 聚合用(注4)CLSCTX_INPROC_SERVER, // 進(jìn)程內(nèi)(Shell32.dll)服務(wù)IID_IShellLink, // IShellLink 的 IID(LPVOID *)&psl ); // 得到接口指針if ( SUCCEEDED(hr) ){psl->SetPath( lpszExe ); // 全路徑程序名 // psl->SetArguments(); // 命令行參數(shù) // psl->SetDescription(); // 備注 // psl->SetHotkey(); // 快捷鍵 // psl->SetIconLocation(); // 圖標(biāo) // psl->SetShowCmd(); // 窗口尺寸// 根據(jù) EXE 的文件名,得到目錄名TCHAR szWorkPath[ MAX_PATH ];::lstrcpy( szWorkPath, lpszExe );LPTSTR lp = szWorkPath;while( *lp ) lp++;while( ''\\'' != *lp ) lp--;*lp=0;// 設(shè)置 EXE 程序的默認(rèn)工作目錄psl->SetWorkingDirectory( szWorkPath );hr = psl->QueryInterface( // 查找持續(xù)性文件接口指針I(yè)ID_IPersistFile, // 持續(xù)性接口 IID(LPVOID *)&ppf ); // 得到接口指針if ( SUCCEEDED(hr) ){USES_CONVERSION; // 轉(zhuǎn)換為 UNICODE 字符串ppf->Save( T2COLE( lpszLnk ), TRUE ); // 保存}}if ( ppf ) ppf->Release();if ( psl ) psl->Release();::CoUninitialize(); }void OnXXX() {CreateShortcut(_T("c:\\winnt\\notepad.exe"), // 記事本程序。注意,你的系統(tǒng)是否也是這個(gè)目錄?_T("c:\\Documents and Settings\\Administrator\\桌面\\我的記事本.lnk"));// 桌面上建立快捷方式(lnk)文件的全路徑名。注意,你的系統(tǒng)是否也是這個(gè)目錄?// 如果用程序?qū)崿F(xiàn)尋找桌面的路徑,則可以查注冊表// HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders }
七、小結(jié)
本回介紹的內(nèi)容比較實(shí)用。大家不要只抄襲代碼,而一定要理解它。結(jié)合 MSDN 的說明去思索代碼、理解其含義。好了,想方設(shè)法把代碼忘掉!三天后(如過你還沒有忘記,那就再過三天),你在不參考示例代碼,但可以隨便翻閱 MSDN 的情況下,自己能獨(dú)立地再次完成這四個(gè)例程,那么恭喜你,你已經(jīng)入門了:0) 從下回開始,我們要用 ATL 做 COM 的開發(fā)工作啦,您老人家準(zhǔn)備好了嗎?
作業(yè),留作業(yè)啦......
1、你已經(jīng)學(xué)會如何建立快捷方式了,那么你知道怎么讀取它的屬性嗎?(如果寫不出這個(gè)程序,那么你就不用繼續(xù)學(xué)習(xí)了。因?yàn)?.....動(dòng)點(diǎn)腦筋呀!我還沒有見過象你這么笨的學(xué)生呢!)
2、示例程序三中使用了 IPicture 接口顯示一個(gè) JPG 圖象。那么你現(xiàn)在去完成一個(gè)功能,把 JPG 文件轉(zhuǎn)換為 BMP 文件。
注1:智能指針的概念和用法,后續(xù)介紹。
注2:IDL 文件,下回就要介紹啦。
注3:東北話,想干什么都可以,反正我不管啦。
注4:聚合,也許在第30回中介紹吧:-)
注5:持續(xù)性,IPersistXXXXXX是一個(gè)非常強(qiáng)大的接口家族,后續(xù)介紹。
注6:想知道 IShellLink、IPersistFile接口的所有函數(shù)嗎?別愣著,快去看MSDN呀......
轉(zhuǎn)載于:https://www.cnblogs.com/zhehan54/p/4993064.html
總結(jié)
以上是生活随笔為你收集整理的【转载】COM 组件设计与应用(四)——简单调用组件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于EGE图形库在CodeBlocks下
- 下一篇: 【读书笔记】iOS-iCloud编程