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 方法把游标游动到最后一行读出既是最近收到的短信。
Code_GetLastMessage
//获取SMS的最近收到的短信,返回为“发件人:内容”的格式。
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."));
if(pSProps != NULL) MAPIFreeBuffer(pSProps);
goto TAG_CLEAR;
}
//打开收件箱文件夹
hr = pMsgStore->OpenEntry(pSProps[0].Value.bin.cb, (LPENTRYID)pSProps[0].Value.bin.lpb,NULL,0, &ulObjType, (IUnknown **)&pFolder);
if(pSProps != NULL)
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“。代码如下:
code_InvokeJS
//调用JS方法(无参数)
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代码如下:
code_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。
code_HTML
<HTML>
<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 对象隐藏或显示任务栏按钮。
Code_ShowInTaskBar
void ShowInTaskbarList(HWND hWnd, BOOL bShow)
{
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 调用页面脚本的文章。(原连接未记录)