最近在學習CEF,發現自己的編程能力實在太弱,看不懂應該怎么使用這個庫,也不知道可以向誰請教,盡管官方說提供的cefclient示例程序已經很清楚了啊,但是我看不懂啊,自己一個人慢慢磨真的十分痛苦。最近結合網上的資料,學習了一些些吧,寫下這篇日志,希望可以幫到后來的人(不過后來的人應該不會像我這么弱了的吧)。
這是一個將CEF嵌入MFC對話框的程序,說來慚愧,到現在我都還不會怎么寫一個好看的界面,只會在MFC上堆砌各種控件,唉。
這篇日志主要參考了以下資料:
- https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md(這里面的資料貌似也很完善,但我還沒有看完,主要是里面很多語句不理解,感覺是在給編程已經上道的人寫的,看得心塞)
- http://mickeymickstechblog.blogspot.com/2014/08/how-to-use-webkit-cef-in-mfc-project.html
- https://github.com/acristoffers/CEF3SimpleSample
再說一件慚愧的事情,下面說的程序也只是結合了上面的資料堆砌而成,一些API為什么要這么用,我也不清楚(好希望有人可以帶我裝逼帶我飛)。
預備工作:在http://www.magpcss.net/cef_downloads/中下載Windows版本的CEF3庫,本文下載的是cef_binary_3.2171.1979_windows32.7z。
下面正式開始。
1. 項目建立和配置
首先建立一個MFC基於對話框程序。注意要選上“在靜態庫中使用MFC”。如果不慎沒選,可以在“項目屬性->配置屬性->常規->MFC的使用”重新配置(這時可能還需要手動將“項目屬性->配置屬性->C/C++->代碼生成->運行庫”配置為“多線程調試(/MTd)”)。
在項目文件夾的代碼文件夾里(這里就是cefinmfcdialog/cefinmfcdialog中)建立一個CEF3文件夾,將項目要用到的和CEF3相關的頭文件和庫放在這個目錄中。
解壓cef_binary_3.2171.1979_windows32.7z,進入到解壓后的目錄(這里假設解壓到了cef_binary_3.2171.1979_windows32),打開cefclient2010.sln,將其中的項目libcef_dll_wrapper以Debug生成方案(默認就是)編譯生成一次。生成后,在目錄里會多出來一個out目錄,里面是生成的文件。
下面將編譯項目需要的CEF3頭文件和庫拷貝到我們的項目文件夾中。
-
將cef_binary_3.2171.1979_windows32/include文件夾拷貝到項目代碼目錄/CEF3中;
-
將cef_binary_3.2171.1979_windows32/out/Debug中的lib目錄拷貝到項目代碼目錄/CEF3中;
-
將cef_binary_3.2171.1979_windows32/Debug/libcef.lib拷貝到項目代碼目錄/CEF3/lib中(按理說libcef.lib也是應該可以用官方提供的CEF3項目文件重新生成一個的,但我還沒有找到方法,這里只好拷貝官方生成好的了)。
將CEF3目錄添加到項目的附加包含目錄中,將CEF3/lib/Debug目錄添加到項目的附加庫目錄中。附加依賴項中添加libcef.lib和libcef_dll_wrapper.lib。
2. 創建新的類
根據https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md#markdown-header-application-structure的描述,CEF3的程序的一般結構包括以下部分:
-
初始化CEF,並且運行子進程可執行邏輯(sub-process executable logic,不知道具體是什么鬼,大概就是自己做一個子進程來自己管理消息循環的意思?)或者CEF消息循環;
-
提供一個CefApp的實現用來處理進程相關的回調(什么鬼!);
-
提供一個CefClient的實現用來處理瀏覽器實例相關的回調(什么鬼!);
-
調用CefBrowserHost::CreateBrowser()來創建瀏覽器實例,以及使用CefLifeSpanHandler管理瀏覽器的生命周期(什么鬼!)。
下面是創建新的類,代碼都是照搬https://github.com/acristoffers/CEF3SimpleSample中的了。基本上我找不到文檔指導怎么繼承CEF3的類來創建自己要求的類的,自己又找不到方法,唉。
創建一個類ClientV8ExtensionHandler,繼承類CefV8Handler。
ClientV8ExtensionHandler.h
/************************************************************************************************ * Copyright (c) 2013 Álan Crístoffer * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ************************************************************************************************/ #ifndef __CEF3SimpleSample__ClientV8ExtensionHandler__ #define __CEF3SimpleSample__ClientV8ExtensionHandler__ #include "include/cef_app.h" struct ClientV8ExtensionHandler : public CefV8Handler { ClientV8ExtensionHandler(CefRefPtr<CefApp> app); bool Execute(const CefString &name, CefRefPtr<CefV8Value> object, const CefV8ValueList &arguments, CefRefPtr<CefV8Value> &retval, CefString &exception) OVERRIDE; private: CefRefPtr<CefApp> app; IMPLEMENT_REFCOUNTING(ClientV8ExtensionHandler); }; #endif /* defined(__CEF3SimpleSample__ClientV8ExtensionHandler__) */
ClientV8ExtensionHandler.cpp:
/************************************************************************************************ * Copyright (c) 2013 Álan Crístoffer * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ************************************************************************************************/ #include "stdafx.h" #include "ClientV8ExtensionHandler.h" ClientV8ExtensionHandler::ClientV8ExtensionHandler(CefRefPtr<CefApp> app) { this->app = app; } bool ClientV8ExtensionHandler::Execute(const CefString &name, CefRefPtr<CefV8Value> object, const CefV8ValueList &arguments, CefRefPtr<CefV8Value> &retval, CefString &exception) { if ( name == "ChangeTextInJS" ) { if ( (arguments.size() == 1) && arguments[0]->IsString() ) { CefString text = arguments[0]->GetStringValue(); CefRefPtr<CefFrame> frame = CefV8Context::GetCurrentContext()->GetBrowser()->GetMainFrame(); std::string jscall = "ChangeText('"; jscall += text; jscall += "');"; frame->ExecuteJavaScript(jscall, frame->GetURL(), 0); /* * If you want your method to return a value, just use retval, like this: * retval = CefV8Value::CreateString("Hello World!"); * you can use any CefV8Value, what means you can return arrays, objects or whatever you can create with CefV8Value::Create* methods */ return true; } } return false; }
創建類ClientHandler,繼承類CefClient和類CefLifeSpanHandler。這里有幾個方法是父類中的抽象方法,必須在實現類中給出實現。
ClientHandler.h:
/************************************************************************************************ * Copyright (c) 2013 Álan Crístoffer * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ************************************************************************************************/ #ifndef __CEFSimpleSample__ClientHandler__ #define __CEFSimpleSample__ClientHandler__ #include "include/cef_render_process_handler.h" #include "include/cef_client.h" #include "include/cef_v8.h" #include "include/cef_browser.h" class ClientHandler : public CefClient, public CefLifeSpanHandler { public: ClientHandler(); CefRefPtr<CefBrowser> GetBrowser() { return m_Browser; } CefWindowHandle GetBrowserHwnd() { return m_BrowserHwnd; } // CefClient methods virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() OVERRIDE { return this; } // Virutal on CefLifeSpanHandler virtual bool DoClose(CefRefPtr<CefBrowser> browser) OVERRIDE; virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser) OVERRIDE; virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser) OVERRIDE; protected: // The child browser window CefRefPtr<CefBrowser> m_Browser; // The child browser window handle CefWindowHandle m_BrowserHwnd; // / // Macro that provides a reference counting implementation for classes extending // CefBase. // / IMPLEMENT_REFCOUNTING(ClientHandler); }; #endif /* defined(__CEFSimpleSample__ClientHandler__) */
ClientHandler.cpp:
/************************************************************************************************ * Copyright (c) 2013 Álan Crístoffer * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ************************************************************************************************/ #include "stdafx.h" #include "ClientHandler.h" #include "include/cef_app.h" #include "include/cef_base.h" #include "include/cef_client.h" #include "include/cef_command_line.h" #include "include/cef_frame.h" #include "include/cef_runnable.h" #include "include/cef_web_plugin.h" ClientHandler::ClientHandler() { } bool ClientHandler::DoClose(CefRefPtr<CefBrowser> browser) { return false; } void ClientHandler::OnAfterCreated(CefRefPtr<CefBrowser> browser) { if ( !m_Browser.get() ) { // We need to keep the main child window, but not popup windows m_Browser = browser; m_BrowserHwnd = browser->GetHost()->GetWindowHandle(); } } void ClientHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser) { if ( m_BrowserHwnd == browser->GetHost()->GetWindowHandle() ) { // Free the browser pointer so that the browser can be destroyed m_Browser = NULL; } }
創建類ClientApp,繼承類CefApp。
ClientApp.h:
/************************************************************************************************ * Copyright (c) 2013 Álan Crístoffer * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ************************************************************************************************/ #ifndef __CEF3SimpleSample__ClientHandler__ #define __CEF3SimpleSample__ClientHandler__ #include "include/cef_app.h" #include "include/cef_client.h" class ClientApp : public CefApp, public CefRenderProcessHandler { public: ClientApp(); CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() OVERRIDE { return this; } void OnWebKitInitialized() OVERRIDE; IMPLEMENT_REFCOUNTING(ClientApp); }; #endif /* defined(__CEF3SimpleSample__ClientHandler__) */
ClientApp.cpp
/************************************************************************************************ * Copyright (c) 2013 Álan Crístoffer * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ************************************************************************************************/ #include "stdafx.h" #include "ClientApp.h" #include "ClientHandler.h" #include "ClientV8ExtensionHandler.h" ClientApp::ClientApp() { } void ClientApp::OnWebKitInitialized() { /*std::string app_code = "var app;" "if (!app)" " app = {};" "(function() {" " app.ChangeTextInJS = function(text) {" " native function ChangeTextInJS();" " return ChangeTextInJS(text);" " };" "})();;"; CefRegisterExtension( "v8/app", app_code, new ClientV8ExtensionHandler(this) );*/ }
3. 初始化和運行CEF
在cefinmfcdialogDlg.cpp中包含頭文件ClientApp.h和ClientHandler.h。
在類CcefinmfcdialogDlg的CcefinmfcdialogDlg::OnInitDialog()方法中添加如下代碼:
CefMainArgs main_args(theApp.m_hInstance); CefRefPtr<ClientApp> app(new ClientApp); int exit_code = CefExecuteProcess(main_args, app.get(), NULL); if (exit_code >= 0){ exit(exit_code); } RECT rect; GetDlgItem(IDC_BROWSER)->GetClientRect(&rect); CefSettings settings; CefSettingsTraits::init(&settings); settings.multi_threaded_message_loop = true; CefInitialize(main_args, settings, app.get(), NULL); CefWindowInfo info; CefBrowserSettings b_settings; CefRefPtr<CefClient> client(new ClientHandler); std::string site = "https://docs.python.org/2/c-api/"; info.SetAsChild(GetDlgItem(IDC_BROWSER)->GetSafeHwnd(), rect); CefBrowserHost::CreateBrowser(info, client.get(), site, b_settings, NULL);
這里要執行CefExecuteProcess函數必須傳遞一個類CefMainArgs的實例,該實例在Windows中是使用主程序的句柄來初始化的。 CefSettings.multi_threaded_message_loop = true的設置時Windows的CEF3庫中特有的,設置后會使CEF新建一個線程執行消息循環。std:string site變量用於指定程序運行時要打開的網頁。
給類CcefinmfcdialogDlg添加ON_DESTORY消息的處理函數,在處理函數中添加如下代碼:
CefShutdown();
之后執行生成,應該可以無錯生成程序的。
4. 拷貝運行依賴的資源
再次進入cef_binary_3.2171.1979_windows32目錄,將Resources目錄下的所有內容(不清楚有什么用)以及Debug目錄下的libcef.dll和pdf.dll(是的,不知道為什么也要這個dll,從名字看這個dll應該和pdf文件瀏覽相關吧,如果不拷貝過去,程序會瀏覽不了網頁,而且也會在程序所在文件夾中創建一個叫pdf.dll的目錄)拷貝到項目目錄的Debug文件夾中(就是程序生成所在的文件夾)。之后可以調試和運行了:
5. 還存在的問題
在VS中調試程序的話,會在關閉程序后,會連續提示好幾個中斷,原因還不明白。
當然還有對CEF還不熟悉啊,心塞。