原文地址:http://www.cnblogs.com/hoodlum1980/archive/2009/12/30/1635702.html
COM 是 WIN32 系統中最複雜和晦澀,最重要的技術。
【備註:以下是個人看法】COM 是比傳統的 API 提供方式(*.h, *.lib, *.dll) 更”高“層次的服務標準, 從某種意義上說,COM也是一種”API“,但它的使用和實現都要比傳統API複雜的多。COM 的宗旨是提供這樣的一種標準,使操作系統,獨立軟件開發商之間以一種標準方式提供交互性服務。COM 相比傳統API合作方式相比:兩者本質上都提供的二進制代碼(編譯後產品),前者是基於有組織的,語言中立的 具有面向對象特性的標準。後者是零散的,語言相關,面向過程的方式。前者的顯著優點是,它的組件是共享式的安裝在系統上的一種服務,組件位置對用戶是透明的,因此它僅需要在每個客戶端部署一份(甚至部署在一臺遠程計算機)。而後者位置不透明因此未必能夠做到這一點。COM 技術使用推出新接口實現升級,以降低對客戶端的影響。而傳統 API 提供方式有可能在升級時導致DLL版本混亂。
由於對語言中立性的追求以及COM的標準,COM 的代碼相比普通的 C/C++ 可讀性更低,但也具有一定規律性。ATL中提供了一些輔助類來降低 COM 代碼的使用難度。
這裏是兩個例子。
【例子1】: 在 Windows Mobile 上獲取收件箱中的短信內容。
(1)每個帳戶對應的是一個MsgStore。每個MsgStore可包含一組文件夾。
(2)提供EntryID,用 OpenEntry 方法打開MAPI對象。
(3)MAPI容器 使用 GetContentTable 獲取容器內內容的描述表。
(4)MAPI Table 使用SetColumns 設定要查詢的字段。
(5)使用 MAPI 提供的函數釋放相關查詢信息分配的內存。
更多細節參考 相關SDK 文檔。
下面是代碼。當打開收件箱以後,短信是按照時間升序排列的,因此使用 SeekRow 方法把遊標遊動到最後一行讀出既是最近收到的短信。
bool GetLastMessage(TCHAR *buffer, int bufferSize)
{
HRESULT hr = S_OK;
ICEMAPISession *pSession = NULL;
IMsgStore *pMsgStore = NULL;
IMAPIFolder *pFolder = NULL;
IMessage *pMsg = NULL;
//和Table有關的MAPI對象
IMAPITable *pTable = NULL;
SRowSet *pRows = NULL;
SPropValue *pSProps = NULL;
ULONG ulObjType; //對象類型
ULONG cCount = 0;
int remainLength, subjectLength; //字符串長度
bool result = false;
//設置MsgStore表的要查詢字段
SizedSPropTagArray(2 , Columns1) =
{
2,
PR_ENTRYID, //Store的Entry ID
PR_DISPLAY_NAME //Store的Display Name
};
//設置收件箱內容表要查詢的字段
SizedSPropTagArray(1, Columns2) =
{
1,
PR_ENTRYID //獲取每個消息的EntryID
};
CoInitializeEx(NULL, COINIT_MULTITHREADED);
hr = MAPIInitialize(NULL);
if(FAILED(hr))
{
wcscpy_s(buffer, bufferSize, _T("failed at: MAPIInitialize"));
goto TAG_CLEAR;
}
hr = MAPILogonEx(0, NULL, NULL, 0, (LPMAPISESSION *)&pSession);
if(FAILED(hr))
{
wcscpy_s(buffer, bufferSize, _T("failed at: MAPILogonEx"));
goto TAG_CLEAR;
}
hr = pSession->GetMsgStoresTable(0, &pTable);
if(FAILED(hr))
{
wcscpy_s(buffer, bufferSize, _T("failed at: GetMsgStoresTable"));
goto TAG_CLEAR;
}
pTable->SetColumns((LPSPropTagArray)&Columns1, 0);
while(SUCCEEDED(pTable->QueryRows(1, 0, &pRows)))
{
if (pRows == NULL || pRows->cRows != 1)
break;
//開始一條條記錄查詢,
//pRows->aRow[0].lpProps[0]代表了第一個查詢屬性,即Entry ID,
//pRows->aRow[0].lpProps[1]則表示Display Name。
if (_tcsicmp(pRows->aRow[0].lpProps[1].Value.lpszW, _T("SMS")) == 0)
{
//如果是SMS,則獲取Store對象
pSession->OpenEntry(
pRows->aRow[0].lpProps[0].Value.bin.cb, (LPENTRYID)pRows->aRow[0].lpProps[0].Value.bin.lpb,
NULL, MAPI_BEST_ACCESS, &ulObjType,(LPUNKNOWN*)&pMsgStore
);
break;
}
FreeProws(pRows);
pRows = NULL;
}
if(pRows != NULL)
{
FreeProws(pRows);
}
if(pTable != NULL)
{
pTable->Release();
}
if(pMsgStore == NULL)
{
wcscpy_s(buffer, bufferSize, _T("Cannot Open MsgStore of SMS."));
goto TAG_CLEAR;
}
//查詢“收件箱”的ENTRYID
ULONG STags[] = { 1, PR_CE_IPM_INBOX_ENTRYID };
hr = pMsgStore->GetProps((SPropTagArray*)&STags, 0, &cCount, &pSProps);
if(FAILED(hr))
{
wcscpy_s(buffer, bufferSize, _T("Cannot Get Inbox's EntryID."));
goto TAG_CLEAR;
}
//打開收件箱文件夾
hr = pMsgStore->OpenEntry(pSProps[0].Value.bin.cb, (LPENTRYID)pSProps[0].Value.bin.lpb,NULL,0, &ulObjType, (IUnknown **)&pFolder);
MAPIFreeBuffer(pSProps);
if (FAILED(hr))
{
wcscpy_s(buffer, bufferSize, _T("Cannot Open Folder Of Inbox."));
goto TAG_CLEAR;
}
//獲取收件箱的內容表。
pFolder->GetContentsTable(0, &pTable);
//嘗試移動到最後一行(最近收到的短信)
ULONG rowCount;
hr = pTable->GetRowCount(0, &rowCount);
if(FAILED(hr) || rowCount <= 0)
{
pTable->Release();
pTable = NULL;
wcscpy_s(buffer, bufferSize, _T("收件箱:沒有新的消息。"));
goto TAG_CLEAR;
}
hr = pTable->SeekRow(BOOKMARK_BEGINNING, rowCount - 1, NULL);
hr = pTable->SetColumns((LPSPropTagArray)&Columns2, 0);
//要查詢的消息屬性:發件人,主題(短信內容)
ULONG pMsgPropTags[] = { 2, PR_SENDER_EMAIL_ADDRESS, PR_SUBJECT };
while(SUCCEEDED(pTable->QueryRows(1, 0, &pRows)))
{
//通過OpenEntry獲取IMessage對象
pFolder->OpenEntry(
pRows->aRow[0].lpProps[0].Value.bin.cb,
(LPENTRYID)pRows->aRow[0].lpProps[0].Value.bin.lpb, //Message's EntryID
NULL,
MAPI_BEST_ACCESS,
&ulObjType,
(LPUNKNOWN*)&pMsg
);
//這裏拿到每個IMessage對象就代表了Folder裏面的每一條Message,可以通過對它的操作來獲取想要的信息。
hr = pMsg->GetProps((SPropTagArray*)&pMsgPropTags, MAPI_UNICODE, &cCount, &pSProps);
if(SUCCEEDED(hr))
{
if(pSProps[0].ulPropTag == PR_SENDER_EMAIL_ADDRESS)
{
wcscpy_s(buffer, bufferSize, pSProps[0].Value.lpszW);
wcscat_s(buffer, bufferSize, _T(": "));
}
else
{
buffer[0] = '/0';
}
if(pSProps[1].ulPropTag == PR_SUBJECT)
{
remainLength = bufferSize - wcslen(buffer) - 1;
subjectLength = wcslen(pSProps[1].Value.lpszW);
if(remainLength >= subjectLength)
wcscat_s(buffer, bufferSize, pSProps[1].Value.lpszW);
else
{
//截斷字符串,並用三個點表示省略。
wcsncpy_s(buffer + wcslen(buffer), bufferSize, pSProps[1].Value.lpszW, remainLength - 3);
wcscat_s(buffer, bufferSize, _T("..."));
}
}
result = true;
}
else
{
wcscpy_s(buffer, bufferSize, _T("Cannot Get Message."));
}
if(pSProps != NULL)
MAPIFreeBuffer(pSProps);
//僅僅讀取一條Message
break;
}
if(pRows != NULL)
{
FreeProws(pRows);
pRows = NULL;
}
if(pTable != NULL)
{
pTable->Release();
pTable = NULL;
}
TAG_CLEAR:
if(pMsg != NULL)
pMsg->Release();
if(pFolder != NULL)
pFolder->Release();
if(pMsgStore != NULL)
pMsgStore->Release();
if(pSession != NULL)
{
pSession->Logoff(0, 0, 0);
pSession->Release();
}
MAPIUninitialize();
CoUninitialize();
return result;
}
本段代碼已運用於 LedScreen插件V2.0 中:
【例子2】:在ActiveX控件中調用頁面上的 JavaScrpt 方法。
(1)IE調用ActiveX控件的方法,本質上是通過控件的 IDispatch 接口調用的,該技術也就是自動化技術。該技術綁定時間比自定義接口最晚,因此效率不如通過自定義接口調用,但是也最靈活。 當我們在ActiveX 控件中調用頁面的腳本時,我們同樣通過腳本對象的 IDispatch 接口調用。
(2)假設 ActiveX 控件事先知道頁面上的方法的參數列表,(相當於我們已知一個C#委託或者C++函數指針定義),如果不這樣假定,則實在意義不大。
(3) 我們在控件接口中添加一個方法,稱爲InvokeJS。在這個方法裏我們試圖調用 頁面上的一個指定名稱的javascript方法。並且我們給這個方法一個字符串參數,內容是”hello world“。代碼如下:
STDMETHODIMP CMyControl::InvokeJS(BSTR funName)
{
if(m_document == NULL)
return S_OK;
CComPtr<IHTMLDocument2> pHtmlDoc;
CComPtr<IDispatch> pScript; //CComPtr:會自動調用Release();
HRESULT hr;
CComBSTR bstrMember;
DISPID dispid; //DISPID即LONG(int32),接口函數的數字序號。
bstrMember.AssignBSTR(funName);
hr = this->m_document->QueryInterface(IID_IHTMLDocument2, (void**)&pHtmlDoc);
hr = pHtmlDoc->get_Script(&pScript);
hr = pScript->GetIDsOfNames(IID_NULL, &bstrMember, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
//調用腳本對象的Invoke方法執行腳本函數:
DISPPARAMS dispparams;
memset(&dispparams, 0, sizeof dispparams);
dispparams.cArgs = 1;
dispparams.rgvarg = new VARIANT[dispparams.cArgs];
for( int i = 0; i < dispparams.cArgs; i++)
{
CComBSTR bstr = "hello world"; // back reading
bstr.CopyTo(&dispparams.rgvarg[i].bstrVal);
dispparams.rgvarg[i].vt = VT_BSTR;
}
dispparams.cNamedArgs = 0;
EXCEPINFO excepInfo;
memset(&excepInfo, 0, sizeof excepInfo);
CComVariant vaResult;
UINT nArgErr = (UINT)-1; // initialize to invalid arg
hr = pScript->Invoke(dispid, IID_NULL, 0, DISPATCH_METHOD, &dispparams, &vaResult, &excepInfo, &nArgErr);
return S_OK;
}
瀏覽器對象實際上我們是在控件的 SetClientSite 方法中獲取的, 該方法在創建控件時由容器調用,以給ActiveX控件一個時機去獲取一些容器信息。則在MSDN文檔中有專門介紹。控件的SetClientSite代碼如下:
private:
IWebBrowser2* m_browser; //瀏覽器對象
IDispatch* m_document; //文檔對象
STDMETHODIMP CMyControl::SetClientSite(IOleClientSite* pClientSite)
{
HRESULT hr = S_OK;
IServiceProvider *isp, *isp2 = NULL;
//BSTR url;
if (!pClientSite)
{
COMRELEASE(m_browser);
}
else
{
hr = pClientSite->QueryInterface(IID_IServiceProvider, reinterpret_cast<void **>(&isp));
if (FAILED(hr))
{
hr = S_OK;
goto cleanup;
}
hr = isp->QueryService(SID_STopLevelBrowser, IID_IServiceProvider, reinterpret_cast<void **>(&isp2));
if (FAILED(hr))
{
hr = S_OK;
goto cleanup;
}
hr = isp2->QueryService(SID_SWebBrowserApp, IID_IWebBrowser2, reinterpret_cast<void **>(&m_browser));
if (FAILED(hr))
{
hr = S_OK;
goto cleanup;
}
//獲取文檔對象
hr = m_browser->get_Document(&m_document);
if(FAILED(hr))
{
m_document = NULL;
}
//m_browser->get_LocationURL(&url);
//MessageBox((LPCTSTR)url, _T(""), MB_OK);
cleanup:
// Free resources.
COMRELEASE(isp);
COMRELEASE(isp2);
return hr;
}
return S_OK;
}
(4)假設在頁面上具有這樣一個JS方法:當我們點擊按鈕時,頁面首先通過IDispatch接口找到控件的InvokeJS方法,然後在控件中又通過IDispatch接口找到腳本的AlertMessage方法,彈出一個MessageBox。
<HEAD>
<TITLE>對象測試頁</TITLE>
</HEAD>
<BODY>
<script type="text/javascript" >
function AlertMessage(info)
{
alert(info);
}
</script>
<OBJECT ID="MyControl" CLASSID="CLSID:......"></OBJECT>
<button onclick="MyControl.InvokeJS('AlertMessage');">InvokeJavaScript</button>
</BODY>
</HTML>
【例子3】:通過 ITaskBar 對象隱藏或顯示任務欄按鈕。
{
HRESULT hr;
ITaskbarList* pTaskbarList;
CoInitialize(NULL);
hr = CoCreateInstance(CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER,
IID_ITaskbarList, (void**)&pTaskbarList);
//============== HrInit();==================
//Initializes the taskbar list object. This method must be called before any other
//ITaskbarList methods can be called.
pTaskbarList->HrInit();
if(bShow)
{
pTaskbarList->AddTab(hWnd);
}
else
{
pTaskbarList->DeleteTab(hWnd);
}
pTaskbarList->Release();
CoUninitialize();
}
參考內容:
《Mapi實例 》:http://bingqingwu5799.blog.163.com/blog/static/338365512009320111114912/
MSDN & Windows Mobile 6 SDK Documents;
CSDN 關於 ActiveX 調用頁面腳本的文章。(原連接未記錄)