zoukankan      html  css  js  c++  java
  • 多线程浏览器编程总结

    由于工作原因,我需要一个浏览器框架,以便可以让我使用脚本进行编程,一来可以简化系统,而来方便调试
    由于系统中需要使用多线程,所以要求这个浏览器框架也必须是多线程的。开始的时候使用MFC的MDI模式的窗口,发现MDI模式的窗口虽然可以很容易的作出多页面的浏览器框架,但是一个重要的原因是创建的浏览器窗口不是多线程的,这样将导致其中如果其中一个窗口的js运行被阻塞,那么其他窗口的JS运行也会被阻塞。
    我后来分析了3种解决方案
    ////////////////////////////////////////////////////////////////
    方案一:
       我们可以确认一个EXE文件是一定是在一个独立的进程内的,如果EXE执行多次,必然这几个exe程序是相对独立的,于是我想了一个相对取巧的方法
       首先建立一个 NewExe ,这个程序中有一个WebBrowser控件,然后建立 MainExe ,这个程序里面也有一个WebBrowser控件,当需要打开新网页的时候,用CreateProcess 创建一个 NewExe 的进程,然后将需要打开新网页的句柄传递给这个新进程里面的WebBrowser控件即可,关闭主程序时通过枚举打开的窗口的进程ID,依次关闭打开的新进程。
    关键为以下5个函数
    函数1,创建新进程
    void CMainBrowserDlg::CreateNewBrowser()
    {
     STARTUPINFO   si;   
     ZeroMemory(&si,   sizeof(si));   
     si.cb   =   sizeof(STARTUPINFO);
     si.dwFlags=STARTF_USESHOWWINDOW;
     if ( !showNewWindows ) {
      si.wShowWindow=SW_SHOWMINIMIZED;
      si.wShowWindow = SW_HIDE;
     }
     
     CString path;
     GetSystemDir(path);
     NewWindowsState = 1 ;
     openWindowCount++;
     path = path + "\\NewBrowser.exe";
     if(CreateProcess( path ,     
      "",   
      NULL,   NULL,   FALSE,   0,   NULL,   NULL,   &si,   &pi[openWindowCount-1]))   
     {   
      NewWindowsState = 2;
      //   等待这个进程结束   
      //WaitForSingleObject(pi.hProcess,   INFINITE);   
      // CloseHandle(pi.hThread);   
      // CloseHandle(pi.hProcess);   
      //CWebBrowser2 *pNewDlg;
      //pNewDlg = (CWebBrowser2 *)::GetDlgItem((HWND)pi.hThread,102);
      //pNewDlg->SetWindowText("111");
      //pNewDlg->Navigate("about:blank",NULL,NULL,NULL,NULL);
     }   
     else   
     {   
      NewWindowsState = 3;
      openWindowCount--;
      AfxMessageBox( "无法启动进程!" );
     }
    }
    函数二,获取程序中的WebBrowser控件的接口:
    BOOL   CALLBACK   EnumThreadWndProc(HWND   hwnd,     LPARAM   lParam)   
    {   
     if( showNewWindows )
     {
     // ShowWindow(hwnd,SW_SHOW);
     }
     else
     {
      ShowWindow(hwnd,SW_HIDE);
     }
     //SendMessage(hwnd,WM_CLOSE,NULL,NULL);
     //return TRUE;
     HWND H1,H2,H3,H4;
     H1=H2=H3=H4=NULL;
     // "Shell Embedding"
     //pWeb = FindWindowEx(hwnd,(HWND)0,"Shell Embedding",NULL);
     H1 = hwnd;
     if (H1) 
     {
      H2=::FindWindowEx(H1,NULL,"Shell Embedding",NULL);
      if (H2)
      {
       H3=::FindWindowEx(H2,NULL,"Shell DocObject View",NULL);
       if (H3) 
       {
        //AfxMessageBox("ok");
        H4=::FindWindowEx(H3,NULL,"Internet Explorer_Server",NULL);
        if (H4)  {
         //AfxMessageBox("Server");
         pNewDlg[ openWindowCount-1 ] = GetIEFromWnd(H4);
         // pNewDlg = ( CWebBrowser2 * )pWeb;
         // GetDlgItem(0,pWeb);
         //pNewDlg->put_Width( 300);
         //pNewDlg->Navigate(abc.AllocSysString(),NULL,NULL,NULL,NULL);
         //pNewDlg->put_Visible( VARIANT_FALSE );
         //ShowWindow(pWeb,SW_HIDE);
         //pNewDlg->ShowWindow( SW_HIDE);
        }else
        {
         //AfxMessageBox("not search");
        }
       }
       else
       {
        //AfxMessageBox("3");
       }
      }
      else
      {
       //AfxMessageBox("2");
      }
     }
     else
     {
      //AfxMessageBox("1");
     }
     //pNewDlg = (CWebBrowser2 *)GetDlgItem(hwnd,1000-102);
     //SetWindowPos(hwnd,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_SHOWWINDOW);   
     return   TRUE;   

    函数三,将新窗口的句柄传递给自己开的程序的新控件
    void CMainBrowserDlg::OnNewWindow2Explorermain(LPDISPATCH FAR* ppDisp, BOOL FAR* Cancel) 
    {
     // TODO: Add your control notification handler code here
     //* ppDisp =
     CreateNewBrowser();
     int count=0;
     
     while( NewWindowsState == 1 && count<50 )
     {
     // 等新程序的打开
      count ++ ;
      Sleep(100);
     }
     if ( NewWindowsState == 3 )
     {
      *Cancel = true;
     }
     if ( openWindowCount>0 ) {
      if ( pi[openWindowCount-1].dwThreadId !=0 ) {
       // 获取 webbrowser 句柄
       count = 0;
       pNewDlg[openWindowCount-1] = NULL;
       while( pNewDlg[openWindowCount-1] == NULL && count<50 )
       {
        count ++ ;
        EnumThreadWindows(pi[openWindowCount-1].dwThreadId,EnumThreadWndProc,0);
        Sleep(100);
       }
       if( pNewDlg[openWindowCount-1] != NULL )
       {
        READYSTATE state;
        pNewDlg[openWindowCount-1]->get_ReadyState(&state);
        count =0;
        while ( state != READYSTATE_COMPLETE && count<50) {
         count++;
         Sleep(100);
        }
        //CString abc;
        //abc.Format("count = %d",count);
        //AfxMessageBox(abc);
        pNewDlg[openWindowCount-1]->get_Application( ppDisp );
       }else
       {
        *Cancel = true;
        AfxMessageBox("无法找到窗口对象");
       }
      }
     }
    }
    函数4,5,关闭主程序时需要关闭自己通过CreateProcess 打开的进程
    BOOL   CALLBACK   EnumThreadWndClose(HWND   hwnd,     LPARAM   lParam)   

     SendMessage(hwnd,WM_CLOSE,NULL,NULL);
     return TRUE;
    }
    void CMainBrowserDlg::CloseAllWindow()
    {
     for( int i=0;i< openWindowCount ;i++)
     {
      EnumThreadWindows(pi[i].dwThreadId,EnumThreadWndClose,0);
     }
     openWindowCount = 0;
     memset(pi,0,sizeof(pi));
    }
    此方法采用的是非正常手法,类似于木马程序,不算正规,而且效率不高,尽量不采用此方法
    ////////////////////////////////////////////////////////////////
    方案二:
        通过学习微软的MTMDI程序,创建自己的界面多线程程序,但是不直接将Cview改为Chtmlview,通过在View里面创建窗体,窗体上放置WebBrowser控件来实现,此方法需要使用网上别人封装好的CHtmlCtrl类,主要方法
        1.建立一个窗体,在窗体中插入一个 static 控件,用CHtmlCtrl类转换此 static控件,将其改为WebBrowser控件,整个窗体我这里创建为  CDialogIE m_dialogBar 这个对象
        2.View创建的时候,顺便创建窗体
        if(!m_dialogBar.Create( IDD_DIALOG_IE,this))
     {
      return -1;
     }
     m_dialogBar.SetWindowPos(NULL,0,0,100,200,SWP_NOSIZE); // 定义一下大小即可
    此方法有点怪胎,个人不喜欢用此方式,而且此方式效率可能较低,代码比较混乱,不想采用此方法
    方案三:
        此方法是在方法二的基础上使用 继承 Chtmlview 类的方法来实现,基础也是微软的界面多线程程序MTMDI,在此基础上将窗口类CChildView,改为继承CHtmlview而不是CView类。
    主要关键难点:
    关键点1:
        首先由于CView正常的PostNcDestroy实现是使用delete this销毁View。对于Views来说,这是正常的处理方式,因为View是直接在堆里建立的。但是,控件一般是作为另一个窗口对象的成员存在的。这时你就不要delete了,它会被父对象delete掉。所以必须 CChildView 也重载了PostNcDestroy 
    void CHtmlCtrl::PostNcDestroy()
    {
     // do nothing
    }不过我的方法是创建 CChildView对象的时候不采用系统默认的 CChildView m_view 方式创建一个,改为CChildView *m_wndChildView; 创建时候 m_wndChildView = new CChildView;
    BOOL bReturn = m_wndChildView->Create(_T("ChildViewMTChildWnd"),
      WS_CHILD | WS_VISIBLE, rect, pParent);
    这样
    void CHtmlCtrl::PostNcDestroy()
    {
     CHtmlCtrl::PostNcDestroy();
    }就不必重载,或者用默认的函数,让PostNcDestroy中delete this
    关键点2:
        程序必须是界面多线程,关于界面多线程可以参考网上很多文章和微软的MTMDI例子,其关键的地方就是继承 CWinThread 类,然后 这个类的CreateThread() 来调用,此线程被执行的第一个函数就是 InitInstance 函数,,可以在其中做自己需要的操作我主要的操作如下
        m_wndChildView = new CChildView; 
     BOOL bReturn = m_wndChildView->Create(_T("ChildViewMTChildWnd"),
      WS_CHILD | WS_VISIBLE, rect, pParent);

     // It is important to set CWinThread::m_pMainWnd to the user interface
     // window.  This is required so that when the m_pMainWnd is destroyed,
     // the CWinThread is also automatically destroyed.  For insight into
     // how the CWinThread is automatically destroyed when the m_pMainWnd
     // window is destroyed, see the implementation of CWnd::OnNcDestroy
     // in wincore.cpp of the MFC sources.
     
     if (bReturn)
      m_pMainWnd = m_wndChildView;
    在线程中创建视图,创建用户界面线程
    关键点3:
         在View视图中不可以直接调用 MainFrame的创建 新视图的函数,具体原因不清楚,我是通过发送消息给主框架,然后由主框架创建新View来实现
    关键点4:
         在MTMDI的程序中,如果想一开始就建立一个窗口,请使用PostMessage()传递消息来调用创建窗口的函数,因为在mtmdi窗体中需要自己手工创建第一个窗口,而系统本身会有一个消息队列,如果不按照系统的队列进行操作,会存在诸如菜单栏失效的bug。
    关键点5:
        在OnNewWindow2函数中,如果想调用另外一个视图的WebBrowser接口,必须使用 列集和散集来传递参数,至于原因可以学习相关COM的知识,具体做法,在创建HTMLView窗口时候,使用CoMarshalInterThreadInterfaceInStream 这个API函数来列集接口。
     CHtmlView::Create(lpszIEWindowClass, szTitle, style, rect, pParent,IDC_CHILDVIEW_WND);


     pDispatch = (LPDISPATCH)this->GetApplication();
     hr = CoMarshalInterThreadInterfaceInStream(
      IID_IWebBrowser2,    // interface ID to marshal
      pDispatch,        // ptr to interface to marshal
      &pStream) ;       // output variable

     if (hr != S_OK)
     {
      AfxMessageBox (_T("Couldn't marshal ptr to thread") );
     }
    在 OnNewWindow2 中使用 CoGetInterfaceAndReleaseStream 函数来 散集接口
     if ( pView != NULL  )
     {
      pView->flagID = this->flagID;
      hr = CoGetInterfaceAndReleaseStream(
       pStream ,                  // stream containing marshaling info
       IID_IWebBrowser2,             // interface desired
       (void **) &pDispatch) ;    // output variable

      if (hr != S_OK)
      {
       AfxMessageBox(_T("Couldn't get interface")) ;
      }
      *ppDisp = pDispatch; // IID_IWebBrowser2 IID_IDispatch IID_IServiceProvider
     }else
     {
      * Cancel = True;
     }
    关键点6:
        如果想对于WebBrowser 的内容进行控制,在VC7 以上版本中,请重载 CreateControlSite 函数
        CCustomControlSite类继承于 COleControlSite ,网上可以找到相关解释CreateControlSite(COleControlContainer * pContainer, COleControlSite ** ppSite, UINT /*nID*/, REFCLSID /*clsid*/)
    {
     *ppSite = new CCustomControlSite(pContainer,this);;// 创建自己的控制站点实例
     (*ppSite)->m_pCtrlCont = pContainer;
     return (*ppSite) ? TRUE : FALSE;
    }
    如果在VC6的版本中,我只知道如果是单线程程序,可以使用 在APP函数中修改 AfxEnableControlContainer() 为如下
     CCustomOccManager *theManager = new CCustomOccManager;
     AfxEnableControlContainer(theManager); 进行容器的重新控制
    多线程的还不知道如何解决

        另外需要注意的是 CCustomControlSite 类其实也控制了拖放模式,一开始我发现VC8编译的CHtmlView的程序,开始的时候是支持 拖放的,但是一旦载入一个页面后,就不支持拖放了,即使你设置了SetRegisterAsDropTarget(TRUE); 也是无效,后来发现原来是 COleControlSite 的默认控制在VC7 以上版本中默认值不一样了,只需要重载 COleControlSite 类就可以实现拖放

  • 相关阅读:
    1新随笔
    MySQL--DML语言
    记一下Spring整合MyBatis踩的坑
    MyBatis(二)动态sql
    Mybatis动态代理注意事项
    几个练习指法和盲打的网站
    友链
    博客初心&心情小计
    博客园美化之标题显示
    博客园美化鼠标点击效果【富强民主文明和谐……】
  • 原文地址:https://www.cnblogs.com/txk1452/p/2850412.html
Copyright © 2011-2022 走看看