多线程浏览器编程总结

由于工作原因,我需要一个浏览器框架,以便可以让我使用脚本进行编程,一来可以简化系统,而来方便调试
由于系统中需要使用多线程,所以要求这个浏览器框架也必须是多线程的。开始的时候使用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 类就可以实现拖放

原文地址:https://www.cnblogs.com/txk1452/p/2850412.html