65.9K
CodeProject 正在变化。 阅读更多。
Home

可用的 Internet Explorer 自动化 Shell

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.54/5 (14投票s)

2006年3月4日

CPOL

10分钟阅读

viewsIcon

85501

downloadIcon

975

一个正常运行的外壳,用于进一步开发IE自动化应用程序。

引言

以下代码有几个基本目标

  • 自动启动Internet Explorer实例。
  • 检测Internet Explorer加载和退出时发送给窗口的基本自动化调度事件消息。
  • 正确设置我们的自动化程序,以便我们可以使用MFC ClassWizard添加任何或所有可用的Internet Explorer调度事件。
  • 通知我们顶级Internet Explorer页面已加载,适用于触发多个DocumentComplete事件的多帧网页。
  • 完成一项出人意料的困难任务,即能够从Internet Explorer或代码中退出,而不会留下任何一个实例仍在运行或导致Windows崩溃。

关于崩溃

如果在开始新项目时未按以下所述使用自动化选项,则每次构建时都会收到Windows错误框,因为没有永久GUID。

你无法单向自动化Internet Explorer,即在不检测传入调度事件的情况下控制它,因为如果有人关闭它而你在没有正确AfxConnectionUnadvise的情况下退出代码,你将遇到Windows崩溃。

您必须按如下所示检测事件并正确退出,否则MFC生成的代码可能会在您的计算机上留下一个隐藏的代码实例,阻止任何未来的构建,并使您必须使用Ctrl-Alt-Del任务管理器来查找并强制关闭它。

背景

该代码是Microsoft Visual C++ 6.0(更新到Service Pack 6)专用的。

如果你已经升级,可以翻出你的旧副本并再次安装。在掌握了上述所有内容之前,你无法对自动化网页进行任何其他操作。因此,在实际操作自动化网页之前,你必须学习所有这些知识。

你可以使用C++控制Internet Explorer实例的方式有很多种。我们将使用IWebBrowser2控件启动它,并使用DWebBrowserEvents2检测调度映射消息。事实证明,使用IWebBrowser2控制IE比使用DWebBrowserEvents2检测IE事件要容易得多。

由于我们将要自动化IE,我们需要对自动化所需的神秘MFC命令有一个基本的了解。

AfxOleInit(); Application Top Level
AfxEnableControlContainer(); Application Top Level
EnableAutomation(); AutoProxy Class
AfxOleLockApp(); AutoProxy Class
AutoProxy->GetIDispatch(); Dialog Class
CoCreateInstance(CLSID_InternetExplorer); Dialog Class
AfxConnectionAdvise(); Dialog Class
AfxConnectionUnadvise(); Dialog Class Exit
AfxOleUnlockApp(); AutoProxy Destructor

第一步

从“文件”-“新建”菜单,项目选项卡中启动一个新的MFC AppWizard(EXE)项目,输入项目名称:IEC2,然后点击“确定”按钮。

MFC AppWizard – 步骤 1: 选择“基于对话框”按钮,然后点击“下一步”。

MFC AppWizard – 步骤 2: 勾选“自动化”选项,然后点击“下一步”。

如果你现在不选择“自动化”选项,仍然可以像其他人一样继续,但每次编译新的临时GUID时,你无疑会不断收到令人烦恼的Windows对话框,需要点击。

不要更改MFC AppWizard – 步骤 3 和 4。直接到末尾,点击“完成”。点击“确定”接受自动生成的文件。

MFC生成的对话框将出现。删除“TODO: Place dialog controls here.”静态文本框,并调整对话框大小,使其比“确定”和“取消”按钮稍大。

第二步

转到你的 StdAfx.h 头文件并添加以下包含文件

#include <atlbase.h> //needed for CComQIPtr
#include <afxctl.h> //needed for AfxConnectionAdvise
// stdafx.h : include file for standard system include files,
#include <afxwin.h> // MFC core and standard components
#include <afxext.h> // MFC extensions
#include <afxdisp.h> // MFC Automation classes
#include <afxdtctl.h> // MFC support for Internet Explorer 4 Common Controls
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h> // MFC support for Windows Common Controls
#endif // _AFX_NO_AFXCMN_SUPPORT
#include <atlbase.h>
#include <afxctl.h>

第三步

DlgProxy.h 头文件中的 AutoProxy 析构函数位置不正确。将其从 protected 函数移到 public 函数。向 CIEC2DlgAutoProxy 类添加 protected 变量 CComBSTR bstrURLCComBSTR bstrName

// DlgProxy.h : header file class CIEC2DlgAutoProxy : public CCmdTarget
{
    DECLARE_DYNCREATE(CIEC2DlgAutoProxy)
    CIEC2DlgAutoProxy(); // protected constructor used by dynamic creation

// Attributes
public:
    CIEC2Dlg* m_pDialog;
    virtual ~CIEC2DlgAutoProxy();

// Operations
public:

// Overrides

// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CIEC2DlgAutoProxy)

public:
    virtual void OnFinalRelease();
//}}AFX_VIRTUAL
// Implementation

protected:
    CComBSTR bstrURL;
    CComBSTR bstrName;

第四步

将以下粗体 publicprotected 变量添加到 CIEC2Dlg

// IEC2Dlg.h : header file
class CIEC2Dlg : public CDialog
{
    DECLARE_DYNAMIC(CIEC2Dlg);
    friend class CIEC2DlgAutoProxy;

// Construction
public:
    BOOL m_bLoaded;
    CString m_csMyStr;
    CComQIPtr<IWebBrowser2>m_pIE;
    DWORD m_dwConnAdvise;
    BOOL m_bQuit;

    CIEC2Dlg(CWnd* pParent = NULL); // standard constructor
    virtual ~CIEC2Dlg();

    // Dialog Data
    //{{AFX_DATA(CIEC2Dlg)
    enum { IDD = IDD_IEC2_DIALOG };

    // NOTE: the ClassWizard will add data members here
    //}}AFX_DATA
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CIEC2Dlg)

protected:
    virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support

//}}AFX_VIRTUAL
// Implementation

protected:
    IUnknown* m_pUnk;
    CIEC2DlgAutoProxy* m_pAutoProxy;
    HICON m_hIcon;
    BOOL CanExit();

第五步

将粗体文本添加到 CIEC2Dlg::OnInitDialog() 函数中。

BOOL CIEC2Dlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    // Add "About..." menu item to system menu.
    // IDM_ABOUTBOX must be in the system command range.
    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX < 0xF000);

    CMenu* pSysMenu = GetSystemMenu(FALSE);
    if (pSysMenu != NULL)
    {
        CString strAboutMenu;
        strAboutMenu.LoadString(IDS_ABOUTBOX);
        if (!strAboutMenu.IsEmpty())
        {
            pSysMenu->AppendMenu(MF_SEPARATOR);
            pSysMenu->AppendMenu(MF_STRING, 
            IDM_ABOUTBOX, strAboutMenu);
        }
    }

    // Set the icon for this dialog. The framework does this automatically
    // when the application's main window is not a dialog
    SetIcon(m_hIcon, TRUE); // Set big icon
    SetIcon(m_hIcon, FALSE); // Set small icon

    // TODO: Add extra initialization here
    m_pAutoProxy=new CIEC2DlgAutoProxy;
    m_pUnk=m_pAutoProxy->GetIDispatch(FALSE);

    m_pIE.CoCreateInstance(CLSID_InternetExplorer);
    m_pIE->put_AddressBar(TRUE);
    m_pIE->put_ToolBar(TRUE);
    m_pIE->put_MenuBar(TRUE);
    m_pIE->put_Visible(TRUE);
    m_csMyStr=_T(<a href="http://www.msdn.com/">"www.msdn.com");</a />

    COleVariant vaURL((LPCTSTR)m_csMyStr);
    m_pIE->Navigate2(&vaURL,COleVariant((long)0,VT_I4),

    COleVariant((LPCTSTR)NULL,VT_BSTR),COleSafeArray(),
    COleVariant((LPCTSTR)NULL,VT_BSTR));
    m_pIE->put_Top(600);
    m_pIE->put_Left(700);

    BOOL bResult=AfxConnectionAdvise((LPUNKNOWN)m_pIE,
    DIID_DWebBrowserEvents2,m_pUnk,FALSE,&m_dwConnAdvise);
    if(bResult==FALSE)
        AfxMessageBox("Failed to AfxConnectionAdvise.");

    //maximize IE automation window
    HDC hdc = ::GetDC(NULL);  // Screen DC used to get current display settings
    if (hdc == NULL)
        return FALSE;
    DWORD m_dwWidth=GetDeviceCaps(hdc, HORZRES);
    DWORD m_dwHeight=GetDeviceCaps(hdc, VERTRES);
    m_pIE->put_Top(0);
    m_pIE->put_Left(0);
    m_pIE->put_Height(m_dwHeight-48);
    m_pIE->put_Width(m_dwWidth);

    return TRUE; // return TRUE unless you set the focus to a control
}

注意,MFC 向导在此处未能为您创建上述列出的 m_pAutoProxy=new CIEC2DlgAutoProxy 实例,尽管其代码没有其他用途。这浪费了我至少 40 小时的时间。您尝试创建的 AutoProxy 类的任何其他实例都会因该类复杂的构造函数而立即导致计算机崩溃。

第六步

执行此步骤必须非常准确,以避免 Windows 崩溃。更改以下四个函数,使它们看起来像下面这样,删除多余的 CanExit() 函数调用并添加粗体文本。

void CIEC2Dlg::OnClose()
{
    CDialog::OnClose();
}

void CIEC2Dlg::OnOK()
{
    if(m_bLoaded==TRUE)
    {
        AfxMessageBox("The page is loaded.");
    }
    else
        AfxMessageBox("The page is not loaded yet.");
}

void CIEC2Dlg::OnCancel()
{
    if(m_bLoaded==FALSE)
        ::Sleep(100);
    if (CanExit())
        CDialog::OnCancel();
}

BOOL CIEC2Dlg::CanExit()
{
    // If the proxy object is still around, then the automation
    // controller is still holding on to this application. Leave
    // the dialog around, but hide its UI.
    if(m_bQuit==FALSE)
    {
        BOOL bResult = AfxConnectionUnadvise((LPUNKNOWN)m_pIE,
        DIID_DWebBrowserEvents2, m_pUnk, FALSE, m_dwConnAdvise);
        if(bResult==FALSE)
            AfxMessageBox("Failed to AfxConnectionUnadvise.");
        m_pIE->Quit();
        delete m_pAutoProxy;
    }

    if (m_pAutoProxy != NULL)
    {
        ShowWindow(SW_HIDE);
        return FALSE;
    }

    return TRUE;
}

第七步

CIEC2Dlg 构造函数内部将 m_bQuitm_bLoaded 设置为 FALSE

CIEC2Dlg::CIEC2Dlg(CWnd* pParent /*=NULL*/)
    : CDialog(CIEC2Dlg::IDD, pParent)
{
    //{{AFX_DATA_INIT(CIEC2Dlg)
    // NOTE: the ClassWizard will add member initialization here
    //}}AFX_DATA_INIT

    // Note that LoadIcon does not require a subsequent DestroyIcon in Win32
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
    m_pAutoProxy = NULL;
    m_bQuit=FALSE;
    m_bLoaded=FALSE;
}

第八步

我们终于可以使用类向导添加 DISP_ID 事件了!

正如Microsoft在MSDN上告诉我们的,DWebBrowserEvents2::DocumentComplete事件必须使用以下格式

void DocumentComplete(IDispatch *pDisp, Variant *URL);

我们很快意识到我们必须尝试使用类向导以某种方式输入上述精确格式。

所以,我们按下ctrl-w调出类向导并选择自动化选项卡。在类名选择框中,我们必须高亮显示CIEC2DlgAutoProxy自动化类。完成此操作后,我们现在可以点击“添加方法”按钮。

然后我们在“外部名称”框中输入 DocumentComplete,在“返回类型”框中输入 void,并接受“内部名称”框中的默认值。

在“参数列表”短语下方,我们点击“名称”一词下方完全空白的区域,看起来好像没有可点击的东西。一个输入框出现了,输入pDisp作为名称,然后参考我们的Microsoft定义,点击下一个完全空白的区域,并尝试从出现的选择列表中输入IDispatch*作为类型。您会发现IDispatch*不是选项之一,但LPDispatch*是。我们很快又发现LPDispatchDispatch的长指针,即使它没有星号也能工作。而LPDispatch*即使有星号也无法工作,因为它前面的LP使其成为**类型。

在我们刚输入的 pDisp, LPDispatch 参数下方,点击更多空白区域,输入 URL 作为名称,Variant* 作为类型。值得庆幸的是,带星号的 Variant* 已经在提供的列表中。点击“确定”,这个就完成了。

现在我们必须输入我们想要的第二个 Dispatch 事件,即 OnQuitOnQuit 必须使用以下格式。

void OnQuit();

点击“确定”并返回到“添加方法”,输入 OnQuit 作为外部名称,void 作为返回类型。这次我们不需要输入任何参数列表,因为没有。再次点击“确定”,然后再次点击MFC ClassWizard 框底部的“确定”,否则所有更改都将丢失。

您将看到向导添加了新函数并填充了 DlgProxy.cpp 的 Dispatch Map。

后续Visual Studio版本的更新

添加控制其他应用程序的整个调度库很容易;只需转到“项目”-“添加类”,然后选择从类型库添加文件的选项即可。本文的重点并非这一简单的任务。本文专注于接收应用程序发送回的“事件”这一更困难的任务。您无法在可以从类型库导入的类中找到这些事件。整个Visual Studio的接口只写了一半,它总是帮助您处理传出命令,但从未完全支持传入事件消息。那么这些事件在哪里?您如何找出实际可用的事件?

您会发现它们在网络空间或MSDN.com上几乎无处可寻。这会令人沮丧。它们是这样可用的。对于Microsoft Office应用程序,加载随您的Visual Studio应用程序附带的“OleView.exe”程序。转到“文件”-“查看类型库”,然后导航到Microsoft Office的“Program Files”文件夹,以Excel.exe为例,并打开它。在1,062页的idl库中的某个地方,您会找到以下内容

"dispinterface AppEvents" uuid(00024413-0000-0000-C000-000000000046) 

浏览此段落,您将最终看到所有将从Excel发送回您的程序的调度成员事件ID,以及它们发送的原因。

对于 Internet Explorer,没有用于事件或命令的类型库。相反,整个 IDL 已内置到您的 Visual Studio 包含库中,并且未附加到程序 EXE 或其程序目录中,就像所有其他较小的程序一样。相反,您将在名为 ExDisp.idl 的文件中找到事件和命令的整个类型库。

另一个恼人的地方是,MDI 项目的 Visual Studio 向导将调度接口添加到文档类中,但我认为由于文档类不是从 CCmdTargetIDispatch 类派生出来的,它无法接收任何事件,尽管它可以发送命令。将带自动化功能的 Dialog 项目的 Visual Studio 向导中的 AutoProxy 类复制到 MDI 项目中并使用它,否则就派生自己的 CCmdTarget 类。向导又只写了一半,因为只能发送命令而不能接收事件的自动化客户端是无用的。

对于 Visual Studio 2005 及更高版本,要使用上面描述的调度映射事件向导,您需要转到类视图,查找最底部的那个小放大镜符号。这就是“接口”符号。右键单击接口符号,以便您可以选择“添加”-“方法”将您的调度接口方法添加到映射中。Visual Studio 2005 及更高版本中似乎没有用于添加调度接口的类向导。

更新结束

第九步

我想不言而喻,我们刚刚正确添加的调度映射仍然不起作用,每次我们运行程序时,Windows 都会反复崩溃,因为我们的代码仍然无法检测到 IE OnQuit 事件。

可能由于IE升级,MFC ClassWizard生成的DISP_FUNCTION映射将无法与IE一起使用。然而,通过查看其他人的代码,我们发现应该将#include “EXDISPID.h”添加到DlgProxy.cpp的包含列表中。

// DlgProxy.cpp : implementation file
#include "stdafx.h"
#include "IEC2.h"
#include "DlgProxy.h"
#include "IEC2Dlg.h"
#include "EXDISPID.h"

然后将 DISP_FUNCTION 改为 DISP_FUNCTION_ID,并在“DocumentComplete”和 DocumentComplete 之间添加一个额外的 DISPID_DOCUMENTCOMPLETE。对 OnQuit 也做同样的操作。

BEGIN_DISPATCH_MAP(CIEC2DlgAutoProxy, CCmdTarget)
//{{AFX_DISPATCH_MAP(CIEC2DlgAutoProxy)
DISP_FUNCTION_ID(CIEC2DlgAutoProxy, "DocumentComplete",
DISPID_DOCUMENTCOMPLETE, DocumentComplete, VT_EMPTY,
VTS_DISPATCH VTS_PVARIANT)

DISP_FUNCTION_ID(CIEC2DlgAutoProxy, "OnQuit", DISPID_ONQUIT,
OnQuit, VT_EMPTY, VTS_NONE)
//}}AFX_DISPATCH_MAP
END_DISPATCH_MAP()

我们认为通过使用以上规则,您可以将任何类向导生成的调度映射转换为与IE 6.0兼容的映射。

第十步

如下填充 DocumentComplete()OnQuit() 函数

void CIEC2DlgAutoProxy::DocumentComplete(LPDISPATCH pDisp, VARIANT FAR* URL)
{
    // TODO: Add your dispatch handler code here
    CComQIPtr<IUnknown,&IID_IUnknown> pIEUnk((IUnknown*)(m_pDialog->m_pIE));
    CComQIPtr<IUnknown,&IID_IUnknown> pLoadedUnk(pDisp);
    m_pDialog->m_pIE->get_LocationName(&bstrName);
    m_pDialog->m_pIE->get_LocationURL(&bstrURL);
    if(pIEUnk==pLoadedUnk)
    {
        m_pDialog->m_bLoaded=TRUE;
        AfxMessageBox("COMPLETELY LOADED: 
        "+(CString)bstrName+" "+(CString)bstrURL);
    }
    else
    {
        AfxMessageBox("Inside Frame loaded: "+(CString)URL->bstrVal);
    }
}

void CIEC2DlgAutoProxy::OnQuit()
{
    // TODO: Add your dispatch handler code here
    m_pDialog->m_bQuit=TRUE;
    m_pDialog->m_pIE->put_Visible(FALSE);
    AfxMessageBox("I know you quit, good-bye.");
    m_pDialog->CDialog::OnCancel();
}

我们现在已经完成了,您可以运行该软件,www.msdn.com在上面的代码中用作示例,您会看到它将在最终加载完成之前触发DocumentComplete三次。我相信这是因为它有两个内部框架。它会向您显示三个不同框架的三个不同URL名称,如果您在最后一个框架加载完成之前点击“确定”,它会警告您,让您知道它是否知道您退出IE,并且无论您以何种方式退出,它都会清理所有窗口。

在处理外部生成的调度事件一段时间后,开始清楚地认识到,在AutoProxy类和Dialog子程序之间实现轻松访问绝对至关重要。MFC通过在AutoProxy类构造函数中使用它创建的m_pDialog->指针,使其非常容易实现;任何其他方式都极其困难。

接下来,您会希望浏览msdn.com,以了解如何管理繁琐的IHTMLDocument2元素集合,将网页读入您的代码,将您想要的部分导出到Excel,以及(部分原因是我想不出其他自动化IE而非直接使用的理由)如何使用IHTMLDocument2填充表单并点击繁重网页上的链接。

© . All rights reserved.