A reusable CEF (Chromium Embedded Framework) WebView control for Win32/MFC and Linux/GTK3 applications.
- Embed Chromium browser in Win32 window, MFC dialog/view, or Linux GTK3 window
- JavaScript-to-C++ bidirectional communication via
CefMessageRouter - Local frontend build loading (
file://protocol) with auto-resize and Unicode path support - Supports both dedicated CEF message loop and integrated host loop
- Software rendering fallback for GPU-less environments
- PIMPL pattern hides CEF internals from public API
- Platform-abstracted types (
CefWebViewHandle,CefWebViewRect) for cross-platform API
- Windows 10+
- Visual Studio 2019 (MSVC 14.29, x64)
- CMake 3.21+
- CEF 144+ binary distribution (included in
third_party/cef/)
- Linux (x64, GTK3 desktop environment)
- GCC 9+ or Clang 10+
- CMake 3.21+
- GTK3 development libraries (
libgtk-3-dev) - CEF 144+ binary distribution (linux64 standard build)
Note: CEF binary distribution is not included in the repository (~800MB). Download manually from cef-builds.spotify.com and extract to
third_party/cef/, or let CMake handle it viacmake/DownloadCef.cmake. CopyCMakeUserPresets.json.exampletoCMakeUserPresets.jsonand setVCPKG_ROOTto your local vcpkg installation before building.
cmake --preset debug
cmake --build build --config DebugThe Win32 demo executable is at build/demos/win32_demo/Debug/CefWebViewWin32Demo.exe.
CEF runtime files and frontend assets are copied to the output directory automatically.
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug
make -j$(nproc)The Linux demo executable is at build/demos/linux_demo/CefWebViewLinuxDemo.
docker build -t cefwebview-linux -f docker/Dockerfile .
docker run --rm -v $(pwd)/build-linux:/build cefwebview-linux#include "cefwebview/CefManager.h"
#include "cefwebview/CefWebView.h"
int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR, int nCmdShow) {
// 1. Handle CEF sub-processes (must be first call in WinMain)
int exitCode = CefManager::Instance().HandleSubProcess(0, nullptr);
if (exitCode >= 0) return exitCode;
// 2. Initialize CEF with a callback for browser creation
CefManager::InitParams params;
params.loopMode = CefManager::DEDICATED_LOOP;
params.noSandbox = true;
params.onContextInitialized = [nCmdShow]() {
// Create your window here, then create CefWebView
CefWebView* webView = new CefWebView();
webView->Create(static_cast<CefWebViewHandle>(hWnd), rect, L"about:blank");
webView->LoadDirectory(exeDir + L"frontend_dist");
};
if (!CefManager::Instance().Initialize(params)) return -1;
// 3. Run message loop
CefManager::Instance().RunMessageLoop();
// 4. Shutdown
CefManager::Instance().Shutdown();
return 0;
}#include "cefwebview/CefManager.h"
#include "cefwebview/CefWebView.h"
#include <gtk/gtk.h>
int main(int argc, char* argv[]) {
// 1. Handle CEF sub-processes
int exitCode = CefManager::Instance().HandleSubProcess(argc, argv);
if (exitCode >= 0) return exitCode;
gtk_init(&argc, &argv);
// 2. Initialize CEF with INTEGRATED_LOOP for GTK main loop
CefManager::InitParams params;
params.loopMode = CefManager::INTEGRATED_LOOP;
params.noSandbox = true;
params.onContextInitialized = [&]() {
GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
// ... create CefWebView ...
webView->Create(static_cast<CefWebViewHandle>(window), rect, L"about:blank");
};
if (!CefManager::Instance().Initialize(params)) return -1;
// 3. GTK main loop with CEF work integration
while (gtk_events_pending()) {
gtk_main_iteration();
CefManager::Instance().DoMessageLoopWork();
}
CefManager::Instance().Shutdown();
return 0;
}| Method | Description |
|---|---|
Instance() |
Get the singleton instance |
HandleSubProcess(argc, argv) |
Call first in main(); returns exit code for sub-processes |
Initialize(params) |
Initialize CEF; returns false on failure |
Shutdown() |
Clean up CEF resources |
RunMessageLoop() |
Run CEF-owned message loop (DEDICATED_LOOP) |
QuitMessageLoop() |
Exit the CEF message loop |
DoMessageLoopWork() |
Pump CEF work in host-owned loop (INTEGRATED_LOOP) |
GetLoopMode() |
Get the current message loop mode |
InitParams fields: loopMode, cachePath, userAgent, noSandbox, multiThreadedMessageLoop, remoteDebuggingPort, onContextInitialized
| Method | Description |
|---|---|
Create(parent, rect, url) |
Create browser as child of parent (HWND on Win32, GtkWidget* on Linux) |
Destroy() |
Close and destroy the browser |
Navigate(url) |
Navigate to a URL |
LoadLocalFile(path) |
Load a local file via file:/// protocol (Unicode paths supported) |
LoadDirectory(dir) |
Load dir/index.html as a frontend build |
GoBack() / GoForward() / Reload() / StopLoad() |
Navigation controls |
SetBounds(rect) |
Resize browser to fill client area (0,0 origin) |
SetFocus() |
Focus the browser window |
ExecuteJavaScript(script) |
Run JS in the main frame |
RegisterJsHandler(name, callback) |
Register a C++ handler for JS messages |
IsCreated() / IsLoading() / GetTitle() / GetUrl() |
State queries |
GetHandle() |
Get native browser handle (HWND or GtkWidget*) |
SetOnTitleChange(cb) / ... |
Event callback setters |
| Type | Win32 | Linux |
|---|---|---|
CefWebViewHandle |
HWND |
void* (GtkWidget*) |
CefWebViewRect |
RECT |
{int left, top, right, bottom} |
JavaScript side (auto-injected on page load):
const result = await window.cefwebview.sendMessage('native.action', { key: 'value' });C++ side (uses JsHandlerCallback with JsResponseCallback):
webView->RegisterJsHandler("native.action",
[](const std::string& args, CefWebView::JsResponseCallback respond) {
// args is the serialized arguments from JS
respond(true, "response from C++");
});The library uses a PIMPL pattern — CefWebView and CefManager public headers only declare
the API and a struct Impl*. The full implementation (CEF types, ClientHandler, JsInteropHandler)
is in platform-specific internal headers (CefWebView_p.h), which are only accessible when building
the library itself. Users of the library do not need CEF headers in their include path.
Platform-specific code is separated by directory:
src/win32/— Windows HWND-based implementationsrc/linux/— Linux GTK3-based implementationsrc/core/— Platform-independent CEF integration (CefManager, ClientHandler, JsInterop)
- Browser creation must happen in
onContextInitializedcallback — this fires duringCefInitialize(), so the callback must be set before callingInitialize(). - Do not use
--disable-features=NetworkService— this causes a segfault in CEF 144+. - GPU process crashes are non-fatal — CEF auto-recovers with software rendering.
- Call
HandleSubProcess()first in main(); CEF sub-processes must exit immediately. - Stack size must be at least 8MB for x64 builds (
/STACK:0x800000on Windows). - Unicode paths in
LoadLocalFileare properly percent-encoded (UTF-8 bytes then %XX). - Linux uses INTEGRATED_LOOP — GTK3 owns the main loop; call
DoMessageLoopWork()within the GTK event loop.
| Problem | Solution |
|---|---|
| Exit code 139 (segfault) | Remove --disable-features=NetworkService from command line switches |
| GPU process crash (exit 143) | Non-fatal; CEF auto-recovers with software rendering |
| Browser not appearing | Ensure Create() is called inside onContextInitialized callback |
| Empty window / wrong size | Use SetBounds(rect) with client-area rect (0,0 based), not window rect |
| CEF sub-process hangs | Call HandleSubProcess() at the very start of main() |
| Build error: undefined CefWebView::Impl | Only occurs when building the library — consumers use the public header |
| Linux: libcef.so not found | Ensure CEF linux64 distribution is in third_party/cef/ |
| Linux: GTK3 not found | Install libgtk-3-dev package |
| CMake Option | Default | Description |
|---|---|---|
CEFWEBVIEW_BUILD_SHARED |
OFF | Build as shared DLL |
CEFWEBVIEW_BUILD_MFC |
OFF | Include MFC support (Windows only) |
CEFWEBVIEW_BUILD_DEMOS |
ON | Build demo applications |
CEFWEBVIEW_BUILD_TESTS |
OFF | Build tests |
CEFWEBVIEW_USE_VCPKG_CEF |
OFF | Use CEF from vcpkg (unofficial::cef) instead of local distribution |
CefWebView can be consumed via vcpkg overlay ports. This is useful when integrating CefWebView into another project without manually managing the CEF binary distribution.
-
Clone this repository:
git clone https://github.com/zhangkh/cefproj.git
-
Configure your project to use the overlay ports:
In
CMakePresets.json:{ "configurePresets": [{ "name": "default", "cacheVariables": { "VCPKG_OVERLAY_PORTS": "${sourceDir}/../cefproj/ports" } }] }Or via vcpkg CLI:
vcpkg install cefwebview:x64-windows --overlay-ports=cefproj/ports vcpkg install cefwebview:x64-linux --overlay-ports=cefproj/ports
-
Use in your CMake project:
find_package(CefWebView REQUIRED) target_link_libraries(myapp PRIVATE CefWebView::CefWebView)
The vcpkg integration uses two overlay ports:
-
cef-binary: Downloads the CEF binary distribution from Spotify CDN, builds
libcef_dll_wrapperfrom source, and installs headers/DLLs/libs. Providesfind_package(unofficial-cef)withunofficial::cef::libcefandunofficial::cef::libcef_dll_wrappertargets. -
cefwebview: Builds CefWebView from source with
CEFWEBVIEW_USE_VCPKG_CEF=ON, which uses the CEF installed by cef-binary. Providesfind_package(CefWebView)withCefWebView::CefWebViewtarget.
- CEF runtime libraries (libcef.dll/libcef.so, etc.) and resources are installed by the cef-binary port. Your application must copy them to the executable directory at runtime or deployment.
- The
CEFWEBVIEW_USE_VCPKG_CEFoption is automatically set when building through vcpkg — do not set it manually in standalone builds.
cefproj/
include/cefwebview/ Public API headers (PIMPL, no CEF exposure)
src/core/ CefManager, ClientHandler, CefAppImpl, JsInterop
src/win32/ CefWebView implementation + CefWebView_p.h (internal)
src/linux/ CefWebView GTK3 implementation + CefWebView_p.h (internal)
src/mfc/ CefMfcView MFC control
ports/cef-binary/ vcpkg overlay port for CEF binary distribution
ports/cefwebview/ vcpkg overlay port for CefWebView
demos/win32_demo/ Win32 demo with sample frontend
demos/linux_demo/ Linux/GTK3 demo with sample frontend
demos/interop_demo/ JS-C++ interop demo (Win32 + Linux)
demos/mfc_demo/ MFC demo
third_party/cef/ CEF binary distribution