【COM范例】WM上获取短信内容,AcitiveX控件调用页面JS方法

  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(10&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(10&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(000);
        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, 0sizeof dispparams);
    dispparams.cArgs 
= 1;
    dispparams.rgvarg 
= new VARIANT[dispparams.cArgs];
  
    
forint 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, 0sizeof 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 调用页面脚本的文章。(原连接未记录)

原文地址:https://www.cnblogs.com/hoodlum1980/p/1635702.html