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 方法把游标游动到最后一行读出既是最近收到的短信。
![](https://yqfile.alicdn.com/img_1c53668bcee393edac0d7b3b3daff1ae.gif?x-oss-process=image/resize,w_1400/format,webp)
![img_405b18b4b6584ae338e0f6ecaf736533.gif](https://yqfile.alicdn.com/img_405b18b4b6584ae338e0f6ecaf736533.gif?x-oss-process=image/resize,w_1400/format,webp)
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“。代码如下:
![](https://yqfile.alicdn.com/img_1c53668bcee393edac0d7b3b3daff1ae.gif?x-oss-process=image/resize,w_1400/format,webp)
![img_405b18b4b6584ae338e0f6ecaf736533.gif](https://yqfile.alicdn.com/img_405b18b4b6584ae338e0f6ecaf736533.gif?x-oss-process=image/resize,w_1400/format,webp)
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代码如下:
![](https://yqfile.alicdn.com/img_1c53668bcee393edac0d7b3b3daff1ae.gif?x-oss-process=image/resize,w_1400/format,webp)
![img_405b18b4b6584ae338e0f6ecaf736533.gif](https://yqfile.alicdn.com/img_405b18b4b6584ae338e0f6ecaf736533.gif?x-oss-process=image/resize,w_1400/format,webp)
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。
![](https://yqfile.alicdn.com/img_1c53668bcee393edac0d7b3b3daff1ae.gif?x-oss-process=image/resize,w_1400/format,webp)
![img_405b18b4b6584ae338e0f6ecaf736533.gif](https://yqfile.alicdn.com/img_405b18b4b6584ae338e0f6ecaf736533.gif?x-oss-process=image/resize,w_1400/format,webp)
< 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 对象隐藏或显示任务栏按钮。
![](https://yqfile.alicdn.com/img_1c53668bcee393edac0d7b3b3daff1ae.gif?x-oss-process=image/resize,w_1400/format,webp)
![img_405b18b4b6584ae338e0f6ecaf736533.gif](https://yqfile.alicdn.com/img_405b18b4b6584ae338e0f6ecaf736533.gif?x-oss-process=image/resize,w_1400/format,webp)
{
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 调用页面脚本的文章。(原连接未记录)