(转)关于MFC中如何使用CEF内核(CEF初解析)

原文地址:http://blog.csdn.net/lixiang987654321/article/details/52143009

上一篇文章中写道了关于CEF内核基本使用用法,这章我将带领大家讲CEF应用到MFC中,大家都知道libCEF自带的cefclient和cefsimple都是基于win32的Demon,那么在MFC中如何使用cef呢?正如duilib中自带的demon也是基于win32一样,同样的做法我将告诉大家如何在MFC中使用CEF,做法很简单,如果大家看了cefsimiple这个简单的Demon,我们就知道大概的做法了。首先分析一下cefsimple步骤:

// Entry point function for all processes.
int APIENTRY wWinMain(HINSTANCE hInstance,
                      HINSTANCE hPrevInstance,
                      LPTSTR    lpCmdLine,
                      int       nCmdShow) {
  UNREFERENCED_PARAMETER(hPrevInstance);
  UNREFERENCED_PARAMETER(lpCmdLine);

  // Enable High-DPI support on Windows 7 or newer.
  CefEnableHighDPISupport();

  void* sandbox_info = NULL;

 #if defined(CEF_USE_SANDBOX)
  // Manage the life span of the sandbox information object. This is necessary
  // for sandbox support on Windows. See cef_sandbox_win.h for complete details.
  CefScopedSandboxInfo scoped_sandbox;
  sandbox_info = scoped_sandbox.sandbox_info();
 #endif

  // Provide CEF with command-line arguments.
  CefMainArgs main_args(hInstance);


  // CEF applications have multiple sub-processes (render, plugin, GPU, etc)
  // that share the same executable. This function checks the command-line and,
  // if this is a sub-process, executes the appropriate logic.
  int exit_code = CefExecuteProcess(main_args, NULL, sandbox_info);
  if (exit_code >= 0) {
    // The sub-process has completed so return here.
    return exit_code;
  }

  // Specify CEF global settings here.
  CefSettings settings;

/**/#if !defined(CEF_USE_SANDBOX)
  settings.no_sandbox = true;
/**/#endif
  // SimpleApp implements application-level callbacks for the browser process.
  // It will create the first browser instance in OnContextInitialized() after
  // CEF has initialized.
  CefRefPtr<SimpleApp> app(new SimpleApp);

  // Initialize CEF.
  CefInitialize(main_args, settings, app.get(), sandbox_info);

  // Run the CEF message loop. This will block until CefQuitMessageLoop() is
  // called.
  CefRunMessageLoop();

  // Shut down CEF.
  CefShutdown();

  return 0;
}

综合以上代码,我们可以得出一下步骤(可要可不要的非关键步骤我们就略过了):

(1)CefExecuteProcess,根据英文解释是,CEF是多进程的,包括其他子进程如render、plugin、GPU等等,改函数检查命令行如果为子进程直接返回了。所以作用仅仅限于检查命令行参数是否是子进程,那么对于主进程browser来说可要可不要改步骤;

(2)CefInitialize,也就是cef初始化过程,这个肯定是必须的步骤,根据cef设置和对应初始化的APP来进程初始化,这里我要说明的事app,我们仔细观察app的代码

// Implement application-level callbacks for the browser process.
class SimpleApp : public CefApp,
                  public CefBrowserProcessHandler {
 public:
  SimpleApp();

  // CefApp methods:
  virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler()
      OVERRIDE { return this; }

  // CefBrowserProcessHandler methods:
  virtual void OnContextInitialized() OVERRIDE;

 private:
  // Include the default reference counting implementation.
  IMPLEMENT_REFCOUNTING(SimpleApp);
};

 #endif  // CEF_TESTS_CEFSIMPLE_SIMPLE_APP_H_ 
 <span style="background-color:rgb(255,255,255);">
<span style="font-size:14px;">我们发现,这个app就是实现了两个方法,一个就是获取浏览器进程处理器,一个就是处理上下文初始化事件,在上下文初始化事件中将浏览器创建出来,所以这个步骤的app我们可以自己创建对应app并实现该方法,但是在mfc中这个app就没什么必要的,OnContextInitialized无非就是窗口初始化前我们要做的,所以我们直接不用实现app,传递NULL即可(这是我自己测试得到的,或者实现但是OnContextInitialized里面为空,不进行任何实现),至于初始化我们直接在mfc主窗口中OnInitDialog做就行(也就是浏览器的创建)。当然我们初始化做的工作就是创建浏览器:</span></span><br />

// SimpleHandler implements browser-level callbacks.
CefRefPtr<SimpleHandler> handler(new SimpleHandler(false));

// Specify CEF browser settings here.
CefBrowserSettings browser_settings;

std::string url = “http://www.baidu.com“;

CefWindowInfo window_info;

#if defined(OS_WIN)
// On Windows we need to specify certain flags that will be passed to
// CreateWindowEx().
window_info.SetAsPopup(NULL, “cefsimple”);
#endif

// Create the first browser window.
CefBrowserHost::CreateBrowser(window_info, handler, url, browser_settings,NULL);
注意观察,这段初始化代码我们要在OnInitDialog中必须做的,那么我们是不是直接拷贝过去就可以了?答案是否定的,因为Demon中给的是自己创建窗口的,那么在MFC中,我们如何让自己的窗口显示对应的网页,也就是让自己的窗口创建浏览器内核(换句话说浏览器作为MFC窗口的子窗口或子控件)?所以我们改动的地方就是将cef窗口作为mfc主窗口的子窗口!!看红色区域cef自己定义的窗口就是CefWindow对象,红色方法SetAsPopup就是设置为popup弹出样式窗口,并且给定了标题,根据这个方法我们可以知道既然能将窗口设置为Popup的弹出窗口,那么肯定有设置为子窗口样式的方法,果不其然,我们跟进去就知道它还有另一个方法:SetAsChild,设置为某窗口的子窗口,并告知将cef窗口放置到主窗口的那个位置(位置相对于桌面而言的windowRect区域),也就是我们在OnInitDialog中改造SetAsPopup方法为SetAsChild方法即可达到内嵌到我们窗口的目的!!

(3)CefRunMessageLoop,顾名思义就是启动消息循环,mfc其实就是win32的封装,在theapp过程中就已经启动了自己的消息循环,cef定义这套接口,目的无非就是考虑后续windows以及linux等系统的兼容性,因为我们这里已经有了自己消息循环,所以cef的这个实现就没必要了(windows下的消息循环肯定都是一样的),同样我们就没必要停止消息循环了,因为MFC做了,也就是退出的时候没有必要调用CefQuitMessageLoop这个接口了。

(4)CefShutdown,就是cef的关闭以及资源释放,这个肯定是要做的,而且是必须的,我们在MFC中只要在Dialog关闭或销毁的时候调用CefShutdown即可释放cef内核资源;


根据以上分析,我们主要做的步骤就是:

(1)CefInitialize

(2)在OnitDialog中调用以上修正后的创建浏览器方法

好以下根据我们步骤一步一步学习在MFC中如何使用CEF内核:

(1)创建工程,工程名为Cef_Demon,基于Dialog,静态使用MFC


(2)去掉关于对话框和系统菜单(我的习惯,不喜欢这两个经常没用的功能),然后点击完成


(3)删除所有按钮,拖入一个Edit编辑框和一个按钮和一个Picture并设置为Rectangle类型,背景为白色,运行Demon如下,对应控件IDC_EDIT_URL,ID_BUTTON_GO,ID_STATIC_BODY,分别代表编辑框、按钮以及图像控件,其中图像控件引入的目的就是将浏览器控件放置到图像控件的位置,仅仅起占位的作用。


注意,这里PIC控件属性visible是隐藏的,仅仅是用其位置;

(4)接着就是关键的步骤了,那就是引入cef库,我的cef库在e盘,首先引用头文件。为了其他项目能够使用,我直接在vs2008菜单中的“工具–>选项”弹出的对话框中设置对应的VC++目录为libcef所在的目录,如下:

(5)然后引入cef对应的lib库,这里我遇到一个问题,我们用的是Debug,运行库也是MTD的,按照常理讲Debug的使用libcef对应的Debug(运行库也是MTD的),Release下编译就使用libcef中Release对应的libcef.lib和对应的libcef_dll_wrapper.lib即可(Release运行库也是MT的),这样才能对应,但是我试了,引入后编译没有问题,但是跑起来就报错,错误就是定位在CefInitialize中,所以看来是有问题,那么怎么办呢? 既然如此为了防止编译错误,Debug下我们依然用libcef下Debug中的lib,但是实际的dll库我们依然用libcef下Release中的Dll,这样我们编译和运行都没有问题!!(补充说明一下,这个说法我后来验证了是错误的,而是因为我第一次没有将libcef中的Resources目录下的所有需要的资源拷贝到我的cef_demon中导致的错误!!)

所以按照这个想法,我们将libcef下的Debug目录的以及libcef_dll_wrapper\Debug下的lib



拷贝到cef_demon下我们的源码目录:


(6)将Release或Debug下的Dll以及Resources下的所有资源文件全部拷贝到cef_demon的Debug目录下(原因就是刚才说的)



是pak不是apk(咋看之下像是apk也就是andriod的安装文件,我以为是所以都删除了导致运行出错!)注意资源文件不可或缺!否则运行不起来!CefInitialize会报错!

(7)在cef_demon中使用cef,包含头文件和lib库,编译确定是包含使用libcef没有问题;


编译结果:


(8)按照分析的步骤编写浏览器并嵌入到我们的MFC主窗口中,首先就是在cef_demon.cpp的InitInstance中初始化cef,代码如下


初始化cef主参数和对应浏览器全局设置,全局设置为no_sanbox为真(具体含义自己百度一下),使用多线程消息循环(刚说了是多线程的),初始化得时候可以使用cef app,以便在cef初始化完成后回调通知(OnContextInitialized)通知您,但是你可以在通知你的函数中实现为空或者直接传递cefInitialize第三个参数为NULL,这里我试过,是不影响的,为了全面,我这里实现了一个CCefBrowserApp继承自CefApp,因为是browser进程,所以我也继承了CefBrowserProcessHandle(browser进程处理器,如果是Render你可以继承Render process处理器,捕获对应消息),对应的app实现如下:



代码:

#pragma once

class CCefBrowserApp : public CefApp, public CefBrowserProcessHandler
//, public CefRenderProcessHandler
{
public:
CCefBrowserApp(void);
virtual ~CCefBrowserApp(void);

public:
virtual CefRefPtr&lt;CefBrowserProcessHandler&gt; GetBrowserProcessHandler() OVERRIDE;

public:
virtual void OnContextInitialized();

protected:
IMPLEMENT_REFCOUNTING(CCefBrowserApp);
};

#include "StdAfx.h"
#include "CefBrowserApp.h"
CCefBrowserApp::CCefBrowserApp(void)
{
}
CCefBrowserApp::~CCefBrowserApp(void)
{
}
CefRefPtr&lt;CefBrowserProcessHandler&gt; CCefBrowserApp::GetBrowserProcessHandler() OVERRIDE
{
return this;
}
void CCefBrowserApp::OnContextInitialized()
{
// do nothing here, because we will create browser in my own dialog
}

(9)调试运行发现cef初始化完成后掉调用到OnContextInitialized方法中,这里我们什么都没有实现,因为我们将实现(浏览器创建)放到了DlgCef_Demon中的OnInitDialog中了,具体代码如下:

 BOOL CCef_DemonDlg::OnInitDialog() 
{
CDialog::OnInitDialog();
SetIcon(m_hIcon, TRUE);// 设置大图标
SetIcon(m_hIcon, FALSE);// 设置小图标
// position here
CRect rtBody;
GetDlgItem(IDC_STATIC_BODY)->GetWindowRect(&rtBody);
CefWindowInfo cefWindowInfo;
cefWindowInfo.SetAsChild(GetSafeHwnd(), rtBody);
CefBrowserSettings browserSetting;
CefRefPtr<CCefBrowserEventHandler> objEventHandler(new CCefBrowserEventHandler());
CefBrowserHost::CreateBrowser(cefWindowInfo, objEventHandler.get(), _T("http://www.baidu.com"), browserSetting, NULL);
return TRUE;
}
这里注意,CreateBrowser创建浏览器对象的时候,第二个参数必须为原始指针,不是引用计数对象,否则导致CPU到达100%,这个应该与其内部实现有关,具体参数含义如下:

1、窗口信息描述了要创建的浏览器的窗口样式信息,这里设置窗口信息为child子窗口,父窗口为我的Dialog,且将创建的浏览器窗口位置放置到pic控件位置

2、第二个参数为事件处理对象,这里所有的处理了所有浏览器对象的所有事件,包括 : public CefClient

, public CefDisplayHandler// 显示变化事件

, public CefLoadHandler // 加载错误事件

, public CefLifeSpanHandler // 声明周期事件

//, public CefContextMenuHandler// 上下文菜单事件

//, public CefDialogHandler// 对话框事件

//, public CefDownloadHandler// 下载事件

//, public CefDragHandler// 拖拽事件

//, public CefFindHandler// 查找事件

等等事件,需要处理对应子浏览器事件都可以添加对应事件的处理器,我们只实现了显示事件、加载事件、声明周期事件;

3、第三个参数表示加载的URL是什么

4、第四个加载URL对应的请求参数数据

具体的Handler实现如下:

 #pragma once
    #include &lt;list&gt;
    using namespace std;

    typedef std::list&lt;CefRefPtr&lt;CefBrowser&gt;&gt; BrowserList;
    class CCefBrowserEventHandler
    : public CefClient
    , public CefDisplayHandler// 显示变化事件
    , public CefLoadHandler   // 加载错误事件
    , public CefLifeSpanHandler   // 声明周期事件
    //, public CefContextMenuHandler// 上下文菜单事件
    //, public CefDialogHandler// 对话框事件
    //, public CefDownloadHandler// 下载事件
    //, public CefDragHandler// 拖拽事件
    //, public CefFindHandler// 查找事件
    //, public ...
    {
    public:
    CCefBrowserEventHandler(void);
    virtual ~CCefBrowserEventHandler(void);
    public:
    // CefClient 事件处理器,如果没有对应处理器则默认使用内部处理器
    virtual CefRefPtr&lt;CefDisplayHandler&gt; GetDisplayHandler() OVERRIDE;
    virtual CefRefPtr&lt;CefLifeSpanHandler&gt; GetLifeSpanHandler() OVERRIDE;
    virtual CefRefPtr&lt;CefLoadHandler&gt; GetLoadHandler() OVERRIDE;

    public: 
    // display handler method
    virtual void OnTitleChange(CefRefPtr&lt;CefBrowser&gt; browser, const CefString&amp; title) OVERRIDE;

    public:
    // load handler method
    virtual void OnLoadError(CefRefPtr&lt;CefBrowser&gt; browser, CefRefPtr&lt;CefFrame&gt; frame, ErrorCode errorCode, const CefString&amp; errorText, const CefString&amp; failedUrl) OVERRIDE;

    public:
    // display handler meethod
    virtual void OnAfterCreated(CefRefPtr&lt;CefBrowser&gt; browser) OVERRIDE;
    virtual bool DoClose(CefRefPtr&lt;CefBrowser&gt; browser) OVERRIDE;
    virtual void OnBeforeClose(CefRefPtr&lt;CefBrowser&gt; browser) OVERRIDE;
    public:
    // own method of cef browser event handler
    void CloseAllBrowser(bool bForceClose = true);
    protected:
    BrowserList m_browser_list;

    IMPLEMENT_REFCOUNTING(CCefBrowserEventHandler);
    };

    #include "StdAfx.h"
    #include "CefBrowserEventHandler.h"
    #include &lt;sstream&gt;
    #include &lt;string&gt;
    #include "include/base/cef_bind.h"
    #include "include/views/cef_browser_view.h"
    #include "include/views/cef_window.h"
    #include "include/wrapper/cef_closure_task.h"
    #include "include/wrapper/cef_helpers.h"

    CCefBrowserEventHandler::CCefBrowserEventHandler(void)
    {
    }

    CCefBrowserEventHandler::~CCefBrowserEventHandler(void)
    {
    }

    CefRefPtr&lt;CefDisplayHandler&gt; CCefBrowserEventHandler::GetDisplayHandler() OVERRIDE
    {
    return this;
    }

    CefRefPtr&lt;CefLifeSpanHandler&gt; CCefBrowserEventHandler::GetLifeSpanHandler() OVERRIDE
    {
    return this;
    }

    CefRefPtr&lt;CefLoadHandler&gt; CCefBrowserEventHandler::GetLoadHandler() OVERRIDE
    {
    return this;
    }

    void CCefBrowserEventHandler::OnTitleChange(CefRefPtr&lt;CefBrowser&gt; browser, const CefString&amp; title) OVERRIDE
    {
    CefWindowHandle hwnd = browser-&gt;GetHost()-&gt;GetWindowHandle();
    if (GetParent(hwnd) == AfxGetApp()-&gt;m_pMainWnd-&gt;GetSafeHwnd())
    {
    AfxGetApp()-&gt;m_pMainWnd-&gt;SetWindowText(std::wstring(title).c_str());
    }
    else
    {
    ::SetWindowText(hwnd, std::wstring(title).c_str());
    }
    }

    void CCefBrowserEventHandler::OnLoadError(CefRefPtr&lt;CefBrowser&gt; browser, CefRefPtr&lt;CefFrame&gt; frame, ErrorCode errorCode, 
     const CefString&amp; errorText, const CefString&amp; failedUrl) OVERRIDE
    {
    CEF_REQUIRE_UI_THREAD();
    if (ERR_ABORTED == errorCode)
    return ;

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

    void CCefBrowserEventHandler::OnAfterCreated(CefRefPtr&lt;CefBrowser&gt; browser) OVERRIDE
    {
    CEF_REQUIRE_UI_THREAD();
    m_browser_list.push_back(browser);
    }

    bool CCefBrowserEventHandler::DoClose(CefRefPtr&lt;CefBrowser&gt; browser) OVERRIDE
    {
    CEF_REQUIRE_UI_THREAD();
    if (1 == m_browser_list.size())
    {
    }
    return false;
    }

    void CCefBrowserEventHandler::OnBeforeClose(CefRefPtr&lt;CefBrowser&gt; browser) OVERRIDE
    {
    CEF_REQUIRE_UI_THREAD();
    for (BrowserList::iterator bit = m_browser_list.begin(); bit != m_browser_list.end(); ++bit) 
    {
    if ((*bit)-&gt;IsSame(browser)) {
    m_browser_list.erase(bit);
    break;
    }
    }

    if (m_browser_list.empty()) 
    {
    CefQuitMessageLoop();
    }
    }

    void CCefBrowserEventHandler::CloseAllBrowser(bool force_close) 
    {
    if (!CefCurrentlyOn(TID_UI)) 
    {
    CefPostTask(TID_UI,base::Bind(&amp;CCefBrowserEventHandler::CloseAllBrowser, this, force_close));
    return;
    }

    if (m_browser_list.empty())
    return;

    BrowserList::const_iterator it = m_browser_list.begin();
    for (; it != m_browser_list.end(); ++it)
    {
    (*it)-&gt;GetHost()-&gt;CloseBrowser(force_close);
    }
    }</pre><span style="font-size:14px;">(10)关闭主窗口时,关闭浏览器</span><br /><pre class="cpp">    void CCef_DemonDlg::OnDestroy()
    {
    CDialog::OnDestroy();
    CefShutdown();
    }

(11)运行效果


(12)实现代码示例
Demon示例代码:http://download.csdn.net/detail/lixiang987654321/9597481
相关的libCEf库: http://pan.baidu.com/s/1dFG5v0l

  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值