qt cef嵌入web
最近项目需要,研究了下libcef库,
Cef(Chromium Embedded Framework)简述
嵌入式Chromium框架(简称CEF) 是一个由Marshall Greenblatt在2008建立的开源项目,它主要目的是开发一个基于Google Chromium的Webbrowser控件。CEF支持一系列的编程语言和操作系统,并且能很容易地整合到新的或已有的工程中去。
它的设计思想政治就是易用且兼顾性能。CEF基本的框架包含C/C++程序接口,通过本地库的接口来实现,而这个库则会隔离宿主程序和Chromium&Webkit的操作细节。它在浏览器控件和宿主程序之间提供紧密的整合,它支持用户插件,协议,javascript对象以及javascript扩展,宿主程序可以随意地控件资源下载,导航和打印等,并且可以跟Google Chrome浏览器一起,支持高性能和Html5 技术。
Cef使用
首先下载cef库的源码,源码有2个大的版本,cef1和cef3,我使用的是cef3,因此cef1我就不过多解释,其实我也不太了解。刚开始使用的时候一定不能怕,可能有些人看了源码之后会发现源码异常的复杂,这个时候我建议网上多差点儿资料,因为我学的时候也是在网上找到了不少好的文章。
下边是我在编译cef库的时候遇到的一些问题:
- 因为我的项目是基于qt的来做的,而qt的运行时库是MDd类型的,因此cef3编译的时候也应该遵循这个运行时库的编译方式
- 我在网上也看到了不少介绍创建cef项目的办法,不过个人觉得好多都是只讲过程,不讲原理,其实使用这个库很简单,我直说debug模式,release照搬。首先拷贝exe执行所需资源文件和运行时库(Resources目录下的除include文件夹、Debug目录下所有动态库),然后拷贝连接器的静态库(out/Debug/lib目录下的静态库、Debug目录下的所有静态库)
- 根据个人使用工具的不同自行包含头文化和静态库,我使用的是vs2013,工程属性->配置属性->VC++目录,添加包含目录和库目录,在工程属性->配置属性->连接器->输入,页面附加依赖项添加依赖动态库
注释:关于libCef库中每个类的作用,我就不多说了,自己网上随便一搜索一大堆,在这里我直说几个重要的,在我的项目里使用到的:
- CefDownloadHandler:下载回调类,当web页面上有文件下载的时候,会调用该类中的相应接口。注意一点,cef库默认是禁止了文件下载,如果想要响应这一事件,需要在OnBeforeDownload重写接口中加入代码:callback->Continue(suggested_name, true);
- CefClient:获取注册回调类
- CefDisplayHandler:地址、标题等改变调用接口,重写此类可以处理导航相关事件
- CefRenderProcessHandler:渲染进程,当浏览器创建的时候,该类中的接口会被调用,因此可是在该类的接口中注册方法或者对象到web。包含webkit初始化、导航、上下文创建等回调接口
- CefBrowserProcessHandler:浏览器进程,上下文初始化、渲染进程创建等回调接口
cef库嵌入已有工程步骤:
1、首先需要自己集成QWidget,重写一个web窗口,如图1所示;
2、main.cpp函数添加如图2所示方法,main方法中初始化Cef库,代码如下,退出时调用CefQuit();
int result = CefInit(argc, argv);
if (result >= 0)
{
return result;
}
CefLoadPlugins(IsWow64());
图1
图2
图3
图4
3、QCefWebView,重写ClientApp::RenderDelegate的方法OnContextCreated,完成对象和方法的注册,代码如图3,图中CefMapV8handler是js在调用该接口时的回调类,该类继承自CefV8Handler,我们只需要重写该类中的Execute接口,然后根据参数name来获取js调用的是qt的哪个接口,如图4所示。
4、最后也是最终的部分,我贴上cef库初始化和我自己封装的类文件源代码,当然了,有很大一部分代码也是从网上找的
cefclient.h如下:
// Initialize CEF. int CefInit(int &argc, char **argv); // Load web plugins. void CefLoadPlugins(bool isWow64); // Quit CEF. void CefQuit(); // Quit CEF until all browser windows have closed. void CefQuitUntilAllBrowserClosed(); // Returns the application working directory. QString AppGetWorkingDirectory(); // Notify all browser windows have closed. void NotifyAllBrowserClosed();
cefclient.cpp如下
namespace { // Initialize the CEF settings. void CefInitSettings(CefSettings& settings) { // Make browser process message loop run in a separate thread. settings.multi_threaded_message_loop = true; // Store cache data will on disk. std::string cache_path = QString2StdStr(AppGetWorkingDirectory()) + "/.cache"; CefString(&settings.cache_path) = CefString(cache_path); // Completely disable logging. settings.log_severity = LOGSEVERITY_DISABLE; // The resources(cef.pak and/or devtools_resources.pak) directory. CefString(&settings.resources_dir_path) = CefString(); // The locales directory. CefString(&settings.locales_dir_path) = CefString(); // Enable remote debugging on the specified port. settings.remote_debugging_port = 8088; // Ignore errors related to invalid SSL certificates. //settings.ignore_certificate_errors = true; } } // namespace CefRefPtr g_handler; CefRefPtr g_appHandler; int CefInit(int &argc, char **argv) { qDebug() << __FUNCTION__; HINSTANCE hInstance = (HINSTANCE)GetModuleHandle(NULL); CefMainArgs main_args(hInstance); g_appHandler = new ClientApp; CefRefPtr app(g_appHandler); // Execute the secondary process, if any. int exit_code = CefExecuteProcess(main_args, app.get(), NULL); if (exit_code >= 0) return exit_code; CefSettings settings; CefInitSettings(settings); #ifndef SUB_PROCESS_DISABLED // Specify the path for the sub-process executable. CefString(&settings.browser_subprocess_path).FromASCII("cefclient_process.exe"); #endif settings.single_process = true; settings.no_sandbox = true; settings.multi_threaded_message_loop = true; // Initialize CEF. CefInitialize(main_args, settings, app.get(), NULL); g_handler = new ClientHandler(); return -1; } void CefLoadPlugins(bool isWow64) { CefString flash_plugin_dir = isWow64 ? "C:\\Windows\\SysWOW64\\Macromed\\Flash" : "C:\\Windows\\System32\\Macromed\\Flash"; CefAddWebPluginDirectory(flash_plugin_dir); CefRefreshWebPlugins(); } void CefQuit() { qDebug() << __FUNCTION__; // Shut down CEF. CefShutdown(); }
qcefwebview.h如下:
class QCefWebView : public QWidget , public ClientHandler::Listener , public ClientApp::RenderDelegate { Q_OBJECT public: enum BrowserState { kNone, kCreating, kCreated, }; static const QString kUrlBlank; QCefWebView(QWidget* parent = 0); virtual ~QCefWebView(); void load(const QUrl& url); void setHtml(const QString& html, const QUrl& baseUrl = QUrl()); QUrl url() const; public slots: void back(); void forward(); void reload(); void stop(); QVariant evaluateJavaScript(const QString& scriptSource);//执行js脚本 signals: void titleChanged(QString title); void urlChanged(QUrl url); void loadStarted(); void loadFinished(bool ok); void webRequest(const QString & title); void navStateChanged(bool canGoBack, bool canGoForward); void jsMessage(QString name, QVariantList args); protected: virtual void resizeEvent(QResizeEvent*); virtual void closeEvent(QCloseEvent*); virtual void showEvent(QShowEvent*); virtual void customEvent(QEvent*); //ClientHandler::Listener virtual void OnAddressChange(const QString& url); virtual void OnTitleChange(const QString& title); virtual void SetLoading(bool isLoading); virtual void SetNavState(bool canGoBack, bool canGoForward); virtual void OnAfterCreated(); virtual void OnMessageEvent(MessageEvent* e); virtual void OnWebRequest(const QString & order); //ClientApp::RenderDelegate virtual void OnContextCreated(CefRefPtr app, CefRefPtr browser, CefRefPtr frame, CefRefPtr context); private: bool CreateBrowser(const QSize& size); CefRefPtr GetBrowser() const; void ResizeBrowser(const QSize& size); bool BrowserLoadUrl(const QUrl& url); BrowserState browser_state_; bool need_resize_; bool need_load_; QUrl url_; QMutex mutex_; Q_DISABLE_COPY(QCefWebView); IMPLEMENT_REFCOUNTING(QCefWebView); };
qcefwebview.cpp如下:
extern CefRefPtr g_handler; extern CefRefPtr g_appHandler; const QString QCefWebView::kUrlBlank = "about:blank"; QCefWebView::QCefWebView(QWidget* parent) : QWidget(parent), browser_state_(kNone), need_resize_(false), need_load_(false) { setAttribute(Qt::WA_NativeWindow); setAttribute(Qt::WA_DontCreateNativeAncestors); } QCefWebView::~QCefWebView() { } void QCefWebView::load(const QUrl& url) { url_ = url; switch (browser_state_) { case kNone: CreateBrowser(size()); break; case kCreating: // If resizeEvent()/showEvent() before you load a url, it will // CreateBrowser() as soon as possible with "about:blank". need_load_ = true; break; default: // The browser should have been created. BrowserLoadUrl(url); } } void QCefWebView::setHtml(const QString& html, const QUrl& baseUrl) { if (GetBrowser().get()) { QUrl url = baseUrl.isEmpty() ? this->url() : baseUrl; if (!url.isEmpty()) { CefRefPtr frame = GetBrowser()->GetMainFrame(); frame->LoadString(CefString(html.toStdWString()), CefString(url.toString().toStdWString())); } } } QUrl QCefWebView::url() const { if (GetBrowser().get()) { CefString url = GetBrowser()->GetMainFrame()->GetURL(); return QUrl(QString::fromStdWString(url.ToWString())); } return QUrl(); } void QCefWebView::back() { CefRefPtr browser = GetBrowser(); if (browser.get()) browser->GoBack(); } void QCefWebView::forward() { CefRefPtr browser = GetBrowser(); if (browser.get()) browser->GoForward(); } void QCefWebView::reload() { CefRefPtr browser = GetBrowser(); if (browser.get()) browser->Reload(); } void QCefWebView::stop() { CefRefPtr browser = GetBrowser(); if (browser.get()) browser->StopLoad(); } QVariant QCefWebView::evaluateJavaScript(const QString& scriptSource) { if (GetBrowser().get()) { CefString code(scriptSource.toStdWString()); GetBrowser()->GetMainFrame()->ExecuteJavaScript(code, "", 0); return true; } return false; } void QCefWebView::resizeEvent(QResizeEvent* e) { switch (browser_state_) { case kNone: CreateBrowser(e->size()); break; case kCreating: need_resize_ = true; break; default: ResizeBrowser(e->size()); } } void QCefWebView::closeEvent(QCloseEvent* e) { if (g_handler.get() && !g_handler->IsClosing()) { CefRefPtr browser = g_handler->GetBrowser(); if (browser.get()) { browser->GetHost()->CloseBrowser(false); } } e->accept(); } void QCefWebView::showEvent(QShowEvent* e) { CreateBrowser(size()); } void QCefWebView::customEvent(QEvent* e) { if (e->type() == MessageEvent::MessageEventType) { MessageEvent* event = static_cast(e); QString name = event->name(); QVariantList args = event->args(); emit jsMessage(name, args); } } void QCefWebView::OnAddressChange(const QString& url) { if (url != "about:blank") { emit urlChanged(QUrl(url)); } } void QCefWebView::OnTitleChange(const QString& title) { emit titleChanged(title); } void QCefWebView::SetLoading(bool isLoading) { if (isLoading) { if (!need_load_ && !url_.isEmpty()) emit loadStarted(); } else { if (need_load_) { BrowserLoadUrl(url_); need_load_ = false; } else if (!url_.isEmpty()) { emit loadFinished(true); } } } void QCefWebView::SetNavState(bool canGoBack, bool canGoForward) { emit navStateChanged(canGoBack, canGoForward); } void QCefWebView::OnAfterCreated() { browser_state_ = kCreated; if (need_resize_) { ResizeBrowser(size()); need_resize_ = false; } } void QCefWebView::OnMessageEvent(MessageEvent* e) { QCoreApplication::postEvent(this, e, Qt::HighEventPriority); } void QCefWebView::OnWebRequest(const QString & order) { emit webRequest(order); } bool QCefWebView::CreateBrowser(const QSize& size) { if (browser_state_ != kNone || size.isEmpty()) { return false; } mutex_.lock(); if (browser_state_ != kNone) { mutex_.unlock(); return false; } RECT rect; rect.left = 0; rect.top = 0; rect.right = size.width(); rect.bottom = size.height(); CefWindowInfo info; CefBrowserSettings settings; info.SetAsChild((HWND)this->winId(), rect); g_handler->set_listener(this); g_appHandler->insertMainRenderDelegate(this); QString url = url_.isEmpty() ? kUrlBlank : url_.toString(); CefBrowserHost::CreateBrowser(info, g_handler.get(), CefString(url.toStdWString()), settings, NULL); browser_state_ = kCreating; mutex_.unlock(); return true; } CefRefPtr QCefWebView::GetBrowser() const { CefRefPtr browser; if (g_handler.get()) browser = g_handler->GetBrowser(); return browser; } void QCefWebView::ResizeBrowser(const QSize& size) { if (g_handler.get() && g_handler->GetBrowser()) { CefWindowHandle hwnd = g_handler->GetBrowser()->GetHost()->GetWindowHandle(); if (hwnd) { HDWP hdwp = BeginDeferWindowPos(1); hdwp = DeferWindowPos(hdwp, hwnd, NULL, 0, 0, size.width(), size.height(), SWP_NOZORDER); EndDeferWindowPos(hdwp); } } } bool QCefWebView::BrowserLoadUrl(const QUrl& url) { if (!url.isEmpty() && GetBrowser().get()) { CefString cefurl(url_.toString().toStdWString()); GetBrowser()->GetMainFrame()->LoadURL(cefurl); return true; } return false; } void QCefWebView::OnContextCreated(CefRefPtr app , CefRefPtr browser , CefRefPtr frame , CefRefPtr context) { CefRefPtr object = context->GetGlobal(); CefRefPtr myV8Acc = new QCefV8Accessor; CefRefPtr val = CefV8Value::CreateString(L"videoHandler"); CefString cefException; myV8Acc->Set(L"videoHandler", object, val, cefException); CefRefPtr pObjApp = CefV8Value::CreateObject(myV8Acc); object->SetValue(L"videoHandler", pObjApp, V8_PROPERTY_ATTRIBUTE_NONE); CefRefPtr mapHandler = new CefMapV8Handler(g_appHandler); CefRefPtr ExecuteRequestOrder = CefV8Value::CreateFunction("ExecuteRequestOrder", mapHandler); object->SetValue("ExecuteRequestOrder", ExecuteRequestOrder, V8_PROPERTY_ATTRIBUTE_NONE); }
注意:以上代码只提供了一个思路,还有部分处理代码我没有贴出来,但是在论述的时候我都已经提到了,有C++基础的就基本可以搞定了,上述功能完成后就基本可以实现嵌入web的功能了。