CEFShutdown hangs in Windows MFC MDI application

Having problems with building or using CEF's C/C++ APIs? This forum is here to help. Please do not post bug reports or feature requests here.

CEFShutdown hangs in Windows MFC MDI application

Postby keith456 » Tue Oct 03, 2017 10:02 am

I have tried to create a simple MDI application using Visual Studio 2013 C++ and MFC Doc/View architecture under Windows 8.1 using CEF build 3163.1670 32 bit.

I initialize CEF in InitInstance -

Code: Select all
BOOL APP::InitInstance()
{
  CWinApp::InitInstance();

  CefMainArgs mainargs(m_hInstance);
  CefSettings settings;
  settings.multi_threaded_message_loop = true;
#ifdef _DEBUG
  settings.single_process = true;
#else
  settings.single_process = false;
#endif
  settings.no_sandbox = true;
  CefInitialize(mainargs, settings, m_app, nullptr);

  CMultiDocTemplate* pDocTemplate;
  pDocTemplate = new CMultiDocTemplate(IDR_CEFMDITestTYPE,
    RUNTIME_CLASS(CCEFMDITestDoc),
    RUNTIME_CLASS(CChildFrame), // custom MDI child frame
    RUNTIME_CLASS(CCEFMDITestView));
  if (!pDocTemplate) return FALSE;
  AddDocTemplate(pDocTemplate);

  MAINFRAME* pMainFrame = new MAINFRAME;
  if (!pMainFrame || !pMainFrame->LoadFrame(IDR_MAINFRAME)) {
    delete pMainFrame;
    return FALSE;
  }
  m_pMainWnd = pMainFrame;

  pMainFrame->ShowWindow(m_nCmdShow);
  pMainFrame->UpdateWindow();

  return TRUE;
}


and shut it down in ExitInstance -

Code: Select all
int APP::ExitInstance()
{
  CefShutdown();
  return CWinApp::ExitInstance();
}


I am creating a single MDI window (there seem to be other problems when I create multiple windows but one problem at a time and it may be related to this problem) and it seems to close down ok but there must be something missing for CEFShutdown to hang.

This is the relevant code from the doc/view -

Code: Select all
class CCEFMDITestDoc : public CDocument {
public:
  virtual BOOL OnNewDocument();
  BOOL CanCloseFrame(CFrameWnd*);
protected:
  DECLARE_DYNCREATE(CCEFMDITestDoc)
};

class CCEFMDITestView : public CView, public CEFClientHandler::Delegate {
protected:
  CCEFMDITestView();
  virtual ~CCEFMDITestView();

  CefRefPtr<CefBrowser>       MyBrowser;
  CefRefPtr<CEFClientHandler> MyClientHandler;

  CCEFMDITestDoc* GetDocument() const { return (CCEFMDITestDoc*) m_pDocument; }
  virtual void OnDraw(CDC*) {}
  virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
  virtual void OnInitialUpdate();
  void OnSize(UINT nType, int cx, int cy);
  void DoClose();

  virtual void OnBrowserCreated(CefRefPtr<CefBrowser> browser);
  virtual void OnBrowserClosing(CefRefPtr<CefBrowser> browser);
  virtual void OnBrowserClosed(CefRefPtr<CefBrowser> browser);
  virtual void OnSetAddress(std::string const & url);
  virtual void OnSetTitle(std::string const & title);
  virtual void OnSetFullscreen(bool const fullscreen);
  virtual void OnSetLoadingState(bool const isLoading, bool const canGoBack, bool const canGoForward);

  friend class CCEFMDITestDoc;

  DECLARE_DYNCREATE(CCEFMDITestView)
  DECLARE_MESSAGE_MAP()
};


Code: Select all
BOOL CCEFMDITestDoc::OnNewDocument()
{
  return CDocument::OnNewDocument();
}

BOOL CCEFMDITestDoc::CanCloseFrame(CFrameWnd*)
{
  POSITION Pos = GetFirstViewPosition();
  if (Pos) {
    CCEFMDITestView* pView = (CCEFMDITestView*) GetNextView(Pos);
    if (pView) pView->DoClose();
  }
  return TRUE;
}

CCEFMDITestView::CCEFMDITestView()
{
}

CCEFMDITestView::~CCEFMDITestView()
{
  if (MyClientHandler) {
    MyClientHandler->DetachDelegate(); // just sets it to null
    MyClientHandler = 0;
  }
}

BOOL CCEFMDITestView::PreCreateWindow(CREATESTRUCT& cs)
{
  return CView::PreCreateWindow(cs);
}

void CCEFMDITestView::OnInitialUpdate()
{
  CView::OnInitialUpdate();

  CRect rect;
  GetClientRect(rect);

  CefWindowInfo info;
  info.SetAsChild(GetSafeHwnd(), rect);

  CefBrowserSettings browserSettings;
  browserSettings.web_security = STATE_DISABLED;

  MyClientHandler = new CEFClientHandler(this);
  MyClientHandler->CreateBrowser(info, browserSettings, CefString("file://c:\\temp\\x.htm"));

  CFrameWnd* pFrame = GetParentFrame();
  if (pFrame) pFrame->ShowWindow(SW_MAXIMIZE);
}

void CCEFMDITestView::DoClose()
{
  if (MyBrowser) {
    MyBrowser->GetHost()->CloseBrowser(true);
    //HWND hWnd = MyBrowser->GetHost()->GetWindowHandle();
    //::DestroyWindow(hWnd);
    // ^^ doesn't seem to actually destroy the window when called here
  }
}

void CCEFMDITestView::OnSize(UINT nType, int cx, int cy)
{
  CView::OnSize(nType, cx, cy);
  if (MyBrowser && MyBrowser->GetHost()->GetWindowHandle()) ::MoveWindow(MyBrowser->GetHost()->GetWindowHandle(), 0, 0, cx, cy, TRUE);
}

void CCEFMDITestView::OnBrowserCreated(CefRefPtr<CefBrowser> browser)
{
  ASSERT(!MyBrowser);
  MyBrowser = browser;
  if (MyBrowser && MyBrowser->GetHost()->GetWindowHandle()) {
    HWND h = MyBrowser->GetHost()->GetWindowHandle();
    CRect r;
    GetClientRect(r);
    ::MoveWindow(MyBrowser->GetHost()->GetWindowHandle(), 0, 0, r.Width(), r.Height(), TRUE);
  }
}

void CCEFMDITestView::OnBrowserClosing(CefRefPtr<CefBrowser> browser)
{
  ASSERT(MyBrowser);
  if (MyBrowser && MyBrowser->GetIdentifier() == browser->GetIdentifier()) {
    HWND hWnd = MyBrowser->GetHost()->GetWindowHandle();
    ::DestroyWindow(hWnd);
  }
}

void CCEFMDITestView::OnBrowserClosed(CefRefPtr<CefBrowser> browser)
{
  ASSERT(MyBrowser);
  if (MyBrowser && MyBrowser->GetIdentifier() == browser->GetIdentifier()) {
    MyBrowser = 0;
    ASSERT(MyClientHandler);
    if (MyClientHandler) MyClientHandler->DetachDelegate(); // just sets it to null
  }
}


The OnBrowserClosing function gets called when I close the window and the DestroyWindow call causes the OnBrowserClosed function to be called (though it is called immediately DestroyWindow is called which might be a problem as OnBrowserClosing has not finished at that point).

The relevant code for a clienthandler I have taken from other samples, the header looks like this -
Code: Select all
class CEFClientHandler : public CefClient, public CefDisplayHandler, public CefLifeSpanHandler, public CefLoadHandler {
public:
  // Implement this interface to receive notification of ClientHandler
  // events. The methods of this class will be called on the main thread.
  class Delegate {
  public:
    // Called when the browser is created.
    virtual void OnBrowserCreated(CefRefPtr<CefBrowser> browser) = 0;
    // Called when the browser is closing.
    virtual void OnBrowserClosing(CefRefPtr<CefBrowser> browser) = 0;
    // Called when the browser has been closed.
    virtual void OnBrowserClosed(CefRefPtr<CefBrowser> browser) = 0;
    // Set the window URL address.
    virtual void OnSetAddress(std::string const & url) = 0;
    // Set the window title.
    virtual void OnSetTitle(std::string const & title) = 0;
    // Set fullscreen mode.
    virtual void OnSetFullscreen(bool const fullscreen) = 0;
    // Set the loading state.
    virtual void OnSetLoadingState(bool const isLoading, bool const canGoBack, bool const canGoForward) = 0;
  protected:
    virtual ~Delegate() {}
  };

  CEFClientHandler(Delegate* delegate) : m_delegate(delegate) {}

  void CreateBrowser(CefWindowInfo const & info, CefBrowserSettings const & settings, CefString const & url);

  // CefClient methods:
  virtual CefRefPtr<CefDisplayHandler> GetDisplayHandler() override { return this; }
  virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; }
  virtual CefRefPtr<CefLoadHandler> GetLoadHandler() override { return this; }

  // CefDisplayHandler methods:
  virtual void OnAddressChange(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, const CefString& url) override;
  virtual void OnTitleChange(CefRefPtr<CefBrowser> browser, const CefString& title) override;
  virtual void OnFullscreenModeChange(CefRefPtr<CefBrowser> browser, bool fullscreen) override;

  // CefLifeSpanHandler methods:
  virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser) override;
  virtual bool DoClose(CefRefPtr<CefBrowser> browser) override;
  virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser) override;

  // CefLoadHandler methods:
  virtual void OnLoadingStateChange(CefRefPtr<CefBrowser> browser, bool isLoading, bool canGoBack, bool canGoForward) override;

  virtual void OnLoadError(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, ErrorCode errorCode, const CefString& errorText,
    const CefString& failedUrl) override;

  // This object may outlive the Delegate object so it's necessary for the
  // Delegate to detach itself before destruction.
  void DetachDelegate();

private:
  // Include the default reference counting implementation.
  IMPLEMENT_REFCOUNTING(CEFClientHandler);
  // Include the default locking implementation.
  IMPLEMENT_LOCKING(CEFClientHandler);

private:
   Delegate* m_delegate;
};


and the code -
Code: Select all
void CEFClientHandler::CreateBrowser(CefWindowInfo const & info, CefBrowserSettings const & settings, CefString const & url)
{
   CefBrowserHost::CreateBrowser(info, this, url, settings, nullptr);
}

void CEFClientHandler::OnAddressChange(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, const CefString& url)
{
   CEF_REQUIRE_UI_THREAD();

   // Only update the address for the main (top-level) frame.
   if(frame->IsMain())
   {
      if(m_delegate != nullptr)
         m_delegate->OnSetAddress(url);
   }
}

void CEFClientHandler::OnTitleChange(CefRefPtr<CefBrowser> browser, const CefString& title)
{
   CEF_REQUIRE_UI_THREAD();

   if(m_delegate != nullptr)
      m_delegate->OnSetTitle(title);
}

void CEFClientHandler::OnFullscreenModeChange(CefRefPtr<CefBrowser> browser, bool fullscreen)
{
   CEF_REQUIRE_UI_THREAD();

   if(m_delegate != nullptr)
      m_delegate->OnSetFullscreen(fullscreen);
}

void CEFClientHandler::OnAfterCreated(CefRefPtr<CefBrowser> browser)
{
   CEF_REQUIRE_UI_THREAD();

   if(m_delegate != nullptr)
      m_delegate->OnBrowserCreated(browser);
}

bool CEFClientHandler::DoClose(CefRefPtr<CefBrowser> browser)
{
   CEF_REQUIRE_UI_THREAD();

   if (m_delegate != nullptr) {
      m_delegate->OnBrowserClosing(browser);
      // NB: this has to return true to say we've processed it otherwise it sends WM_CLOSE to all main window
      return true;
   }

   return false;
}

void CEFClientHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser)
{
   CEF_REQUIRE_UI_THREAD();

   if(m_delegate != nullptr)
      m_delegate->OnBrowserClosed(browser);
}

void CEFClientHandler::OnLoadError(CefRefPtr<CefBrowser> browser,
   CefRefPtr<CefFrame> frame,
   ErrorCode errorCode,
   const CefString& errorText,
   const CefString& failedUrl)
{
   CEF_REQUIRE_UI_THREAD();

   // Don't display an error for downloaded files.
   if(errorCode == ERR_ABORTED)
      return;

   // Display a load error message.
   std::stringstream ss;
   ss << "<html><body bgcolor=\"white\">"
      "<h2>Failed to load URL " << std::string(failedUrl) <<
      " with error " << std::string(errorText) << " (" << errorCode <<
      ").</h2></body></html>";
   frame->LoadString(ss.str(), failedUrl);
}

void CEFClientHandler::OnLoadingStateChange(CefRefPtr<CefBrowser> browser, bool isLoading, bool canGoBack, bool canGoForward)
{
   CEF_REQUIRE_UI_THREAD();

   if(m_delegate != nullptr)
      m_delegate->OnSetLoadingState(isLoading, canGoBack, canGoForward);
}

void CEFClientHandler::DetachDelegate()
{
   m_delegate = nullptr;
}


I set DoClose to return true otherwise the function sends a WM_CLOSE message to the main window and closes down the whole application.

I have tested this using a simple html file (just a bit of text), waiting for the browser to load it, then hitting the close button on the window, making sure it's closed, then closing the application. CEFShutdown always hangs.

When I break into the program the call stack looks like -

KernelBase.dll!__SEH_prolog4() + 0x2e bytes
KernelBase.dll!_Sleep@4() + 0xf bytes
Libcef.dll!51d1b7c8()
[Frames below may be incorrect and/or missing, no symbols loaded for Libcef.dll]
Libcef.dll!53271e9d()
Libcef.dll!5323d912()
Libcef.dll!5323e11b()
Libcef.dll!5323d8d7()
> CEFMDITest.exe!CefShutdown() Line 234 C++
CEFMDITest.exe!APP::ExitInstance() Line 76 C++
mfc120d.dll!CWinThread::Run() Line 630 + 0xf bytes C++
mfc120d.dll!CWinApp::Run() Line 787 C++
mfc120d.dll!AfxWinMain(HINSTANCE__ * hInstance, HINSTANCE__ * hPrevInstance, char * lpCmdLine, int nCmdShow) Line 47 + 0xf bytes C++
CEFMDITest.exe!WinMain(HINSTANCE__ * hInstance, HINSTANCE__ * hPrevInstance, char * lpCmdLine, int nCmdShow) Line 26 C++
CEFMDITest.exe!__tmainCRTStartup() Line 618 + 0x15 bytes C
CEFMDITest.exe!WinMainCRTStartup() Line 466 C
kernel32.dll!@BaseThreadInitThunk@12() + 0x24 bytes
ntdll.dll!__RtlUserThreadStart() + 0x2f bytes
ntdll.dll!__RtlUserThreadStart@8() + 0x1b bytes

I am running my program in debug mode and have compiled the wrapper code in debug mode. I have tried using the debug version of libcef but that crashes somewhere in CEFShutdown so I never get to the point where it hangs (this seems to be a reported won't-fix problem).

I suspect I'm not doing something when closing a window so CEF thinks it's still running. I've tried various things but cannot get to the bottom of this, any help appreciated.
keith456
Newbie
 
Posts: 1
Joined: Tue Oct 03, 2017 9:24 am

Re: CEFShutdown hangs in Windows MFC MDI application

Postby magreenblatt » Wed Oct 04, 2017 8:43 am

You're setting single_process = true for your debug build. Single-process mode is not supported.
magreenblatt
Site Admin
 
Posts: 12408
Joined: Fri May 29, 2009 6:57 pm


Return to Support Forum

Who is online

Users browsing this forum: No registered users and 53 guests