Chromium Embedded Framework documentation
This page provides information on how to use the CEF C API in a client application.
Contents
The CEF C API is a C-based interface exported by the CEF library/framework following certain API versioning guidelines. The C header files which define this interface are automatically generated by the CEF translator tool and mirror the CEF C++ API structure.
Example application: cefsimple_capi - A minimal CEF browser application written in pure C, demonstrating proper C API usage including reference counting, handler callbacks, and cross-platform implementation.
All CEF C API structures inherit from one of two base structure types, which determine how their memory and lifetime are managed.
cef_base_ref_counted_t)Most CEF structures inherit from cef_base_ref_counted_t and require manual reference counting. These structures have a longer lifetime and may be shared across multiple owners.
Required function pointers:
add_ref - Increment the reference countrelease - Decrement the reference count and free when it reaches zerohas_one_ref - Check if reference count is exactly 1has_at_least_one_ref - Check if reference count is at least 1Common examples:
cef_app_t - Application handlercef_client_t - Client handlercef_browser_t - Browser instancecef_frame_t - Frame instancecef_request_t - Request objectcef_base_scoped_t)Some structures inherit from cef_base_scoped_t for stack-based or temporary objects with simpler lifetime management.
Required function pointers:
del - Destructor function (may be NULL if the object is not owned by you)Common examples:
cef_window_info_t - Window configurationcef_browser_settings_t - Browser settingscef_settings_t - CEF initialization settingsThese structures are typically used for configuration and are not ref-counted.
Understanding who allocates and owns each structure type is critical for correct CEF C API usage.
source=client in comments)You allocate, implement, and manage these structures. They are typically handler interfaces that you implement to receive callbacks from CEF.
Examples:
cef_app_t - Your application implementationcef_client_t - Your client handlercef_life_span_handler_t - Browser lifecycle eventscef_request_handler_t - Request handlingcef_display_handler_t - Display updatesYour responsibilities:
calloc)size fieldsource=library in comments)CEF allocates, implements, and manages these structures. They are objects that CEF provides to you for reading data or calling methods.
Examples:
cef_browser_t - Browser instance provided by CEFcef_frame_t - Frame instance provided by CEFcef_request_t - Request data provided by CEFcef_response_t - Response data provided by CEFYour responsibilities:
add_ref if you need to keep the pointer beyond the current functionrelease when you’re done with a referenceIn general, the C API is used similarly to the C++ API, but instead of defining new classes and overriding methods, you create structures and set fields to custom function pointers.
// A new class is defined and specific handlers are overridden
class SimpleApp: public CefApp, public CefBrowserProcessHandler {
public:
SimpleApp() {}
CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() override {
return this;
}
void OnContextInitialized() override;
CefRefPtr<CefClient> GetDefaultClient() override;
private:
// A default RC implementation can be used
IMPLEMENT_REFCOUNTING(SimpleApp);
}
// Define a structure with the CEF type as the first member
typedef struct _my_app_t {
cef_app_t app; // MUST be first member
atomic_int ref_count; // Custom fields (reference count, state, etc.)
} my_app_t;
// Implement reference counting functions
void CEF_CALLBACK app_add_ref(cef_base_ref_counted_t* self) {
/* ... */
}
int CEF_CALLBACK app_release(cef_base_ref_counted_t* self) {
/* ... */
}
int CEF_CALLBACK app_has_one_ref(cef_base_ref_counted_t* self) {
/* ... */
}
int CEF_CALLBACK app_has_at_least_one_ref(cef_base_ref_counted_t* self) {
/* ... */
}
// Implement handler methods as function pointers
cef_browser_process_handler_t* CEF_CALLBACK
get_browser_process_handler(cef_app_t* self) {
// Return your browser process handler implementation
return my_browser_process_handler;
}
// Create and initialize the structure
my_app_t* create_my_app() {
my_app_t* app = (my_app_t*)calloc(1, sizeof(my_app_t));
// Set size and function pointers
app->app.base.size = sizeof(cef_app_t);
app->app.base.add_ref = app_add_ref;
app->app.base.release = app_release;
app->app.base.has_one_ref = app_has_one_ref;
app->app.base.has_at_least_one_ref = app_has_at_least_one_ref;
app->app.get_browser_process_handler = get_browser_process_handler;
atomic_store(&app->ref_count, 1);
return app;
}
Key differences from C++:
typedef struct to define types (see “Base Structure Types”)add_ref, release, has_one_ref, has_at_least_one_ref (see “Reference Counting” and “Atomic Operations for Reference Counting”)calloc to allocate, free in the release function (see “Structure Allocation and Ownership”)See the “Complete Minimal Example” section for full working code.
This section shows a complete CEF usage example using only the C API. This is a working starting point you can build upon.
For a complete working example, see cefsimple_capi, which demonstrates all concepts from this guide in a buildable application.
The cef_app_t structure is optional (you can pass NULL to cef_initialize()), but implementing it allows you to:
get_browser_process_handler)get_render_process_handler)on_before_command_line_processing)on_register_custom_schemes)This example shows a minimal implementation with just reference counting.
#include <stdatomic.h>
#include <stdlib.h>
#include "include/capi/cef_app_capi.h"
// Custom app structure
typedef struct _my_app_t {
cef_app_t app; // MUST be first member
atomic_int ref_count; // Reference counter
} my_app_t;
// App handler - returns NULL for this simple example
cef_browser_process_handler_t* CEF_CALLBACK
app_get_browser_process_handler(cef_app_t* self) {
return NULL; // No browser process handler needed
}
// Create app instance
my_app_t* create_app() {
// calloc zero-initializes all memory, so all fields start as 0/NULL
my_app_t* app = (my_app_t*)calloc(1, sizeof(my_app_t));
// Initialize base structure - CRITICAL: set size correctly
app->app.base.size = sizeof(cef_app_t);
// Reference counting
// Implement app_add_ref, app_release, app_has_one_ref,
// app_has_at_least_one_ref following the pattern from
// "Atomic Operations for Reference Counting"
app->app.base.add_ref = app_add_ref;
app->app.base.release = app_release;
app->app.base.has_one_ref = app_has_one_ref;
app->app.base.has_at_least_one_ref = app_has_at_least_one_ref;
// Only set the handlers we need (others already NULL from calloc)
app->app.get_browser_process_handler = app_get_browser_process_handler;
// Start with ref count of 1
atomic_store(&app->ref_count, 1);
return app;
}
The cef_client_t structure provides handlers for browser events and is required to create a browser. This example shows a minimal implementation that returns NULL for all handlers (using default behavior).
#include <stdatomic.h>
#include <stdlib.h>
#include "include/capi/cef_client_capi.h"
// Custom client structure
typedef struct _my_client_t {
cef_client_t client; // MUST be first member
atomic_int ref_count; // Reference counter
} my_client_t;
// Create client instance
my_client_t* create_client() {
// calloc zero-initializes all memory, so all fields start as 0/NULL
my_client_t* client = (my_client_t*)calloc(1, sizeof(my_client_t));
// Initialize base structure
client->client.base.size = sizeof(cef_client_t);
// Reference counting
// Implement client_add_ref, client_release, client_has_one_ref,
// client_has_at_least_one_ref following the pattern from
// "Atomic Operations for Reference Counting"
client->client.base.add_ref = client_add_ref;
client->client.base.release = client_release;
client->client.base.has_one_ref = client_has_one_ref;
client->client.base.has_at_least_one_ref = client_has_at_least_one_ref;
// All handler getters are NULL from calloc, using default behavior
// Set client->client.get_life_span_handler, etc. if you need custom handlers
// Start with ref count of 1
atomic_store(&client->ref_count, 1);
return client;
}
After initializing CEF, create a browser window using cef_browser_host_create_browser(). Pass the client instance from the previous section.
#ifdef _WIN32
#include <windows.h>
#endif
#include "include/capi/cef_browser_capi.h"
void create_browser(cef_client_t* client) {
cef_window_info_t window_info = {};
window_info.size = sizeof(cef_window_info_t);
// Create as a top-level window (with NULL parent)
#ifdef _WIN32
// Windows: CefWindowInfo::SetAsPopup equivalent
window_info.style = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN |
WS_CLIPSIBLINGS | WS_VISIBLE;
window_info.bounds.x = CW_USEDEFAULT;
window_info.bounds.y = CW_USEDEFAULT;
window_info.bounds.width = CW_USEDEFAULT;
window_info.bounds.height = CW_USEDEFAULT;
#else
// Linux/macOS: CefWindowInfo::SetAsChild equivalent
window_info.bounds.width = 800;
window_info.bounds.height = 600;
#endif
cef_browser_settings_t browser_settings = {};
browser_settings.size = sizeof(cef_browser_settings_t);
cef_string_t url = {};
cef_string_from_ascii("https://www.google.com", 22, &url);
// Create browser asynchronously (recommended)
// The browser is created on the UI thread
// Your client's life span handler on_after_created will be called when ready
// IMPORTANT: We transfer our client reference to CEF - don't release it!
// CEF takes ownership and will release it when the browser closes.
cef_browser_host_create_browser(
&window_info,
client,
&url,
&browser_settings,
NULL, // extra_info
NULL // request_context
);
cef_string_clear(&url);
// Note: We DON'T release the client here - we transferred ownership to CEF
}
CRITICAL: Before calling any other CEF API functions, you must initialize the API version by calling cef_api_hash(). This function configures the CEF API version for your application.
#include "include/cef_api_hash.h"
// Configure the CEF API version. This must be called before any other CEF
// API functions.
cef_api_hash(CEF_API_VERSION, 0);
When to call:
cef_load_library() in both the main process and helper processesmain() before any other CEF callsImportant: On macOS, CEF uses dynamic library loading via cef_load_library(). The API hash initialization MUST occur after the library is loaded, otherwise CEF functions will not be available. See cefsimple_capi/cefsimple_mac.m for the correct macOS implementation pattern.
Why this is required:
cef_api_hash() locks in the API version for the entire processcef_api_hash() will result in undefined behaviorFor details on how CEF API versioning works, see API Versioning.
This example brings together all the pieces from the previous sections, showing a complete working application with browser process handler and browser creation.
#include <stdatomic.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#endif
#include "include/capi/cef_app_capi.h"
#include "include/capi/cef_browser_process_handler_capi.h"
#include "include/capi/cef_client_capi.h"
#include "include/capi/cef_browser_capi.h"
#include "include/cef_api_hash.h"
// Browser process handler structure
typedef struct _my_browser_process_handler_t {
cef_browser_process_handler_t handler;
atomic_int ref_count;
} my_browser_process_handler_t;
// Called after CEF initialization to create the browser
void CEF_CALLBACK on_context_initialized(cef_browser_process_handler_t* self) {
// Create client using the function from "CefClient Creation" section
my_client_t* client = create_client();
// Create browser using the function from "CefBrowser Creation" section
// We transfer our reference to CEF (don't release)
create_browser(&client->client);
}
// Create browser process handler
my_browser_process_handler_t* create_browser_process_handler() {
my_browser_process_handler_t* handler =
(my_browser_process_handler_t*)calloc(
1, sizeof(my_browser_process_handler_t));
handler->handler.base.size = sizeof(cef_browser_process_handler_t);
// Reference counting
// Implement bph_add_ref, bph_release, bph_has_one_ref,
// bph_has_at_least_one_ref following the pattern from
// "Atomic Operations for Reference Counting"
handler->handler.base.add_ref = bph_add_ref;
handler->handler.base.release = bph_release;
handler->handler.base.has_one_ref = bph_has_one_ref;
handler->handler.base.has_at_least_one_ref = bph_has_at_least_one_ref;
// Set the callback that will create the browser
handler->handler.on_context_initialized = on_context_initialized;
atomic_store(&handler->ref_count, 1);
return handler;
}
// Updated app structure that holds the browser process handler
typedef struct _my_app_complete_t {
cef_app_t app;
atomic_int ref_count;
my_browser_process_handler_t* browser_process_handler;
} my_app_complete_t;
// Updated app release to clean up browser process handler
int CEF_CALLBACK app_complete_release(cef_base_ref_counted_t* self) {
my_app_complete_t* app = (my_app_complete_t*)self;
int count = atomic_fetch_sub(&app->ref_count, 1) - 1;
if (count == 0) {
if (app->browser_process_handler) {
app->browser_process_handler->handler.base.release(
&app->browser_process_handler->handler.base);
}
free(app);
return 1;
}
return 0;
}
// Return browser process handler (adds reference before returning)
cef_browser_process_handler_t* CEF_CALLBACK
app_complete_get_browser_process_handler(cef_app_t* self) {
my_app_complete_t* app = (my_app_complete_t*)self;
if (app->browser_process_handler) {
app->browser_process_handler->handler.base.add_ref(
&app->browser_process_handler->handler.base);
return &app->browser_process_handler->handler;
}
return NULL;
}
// Create complete app with browser process handler
my_app_complete_t* create_app_complete() {
my_app_complete_t* app =
(my_app_complete_t*)calloc(1, sizeof(my_app_complete_t));
app->app.base.size = sizeof(cef_app_t);
// Reference counting
// Implement app_add_ref, app_has_one_ref, app_has_at_least_one_ref
// following the pattern from "Atomic Operations for Reference Counting".
// app_complete_release is custom to clean up the browser process handler.
app->app.base.add_ref = app_add_ref;
app->app.base.release = app_complete_release;
app->app.base.has_one_ref = app_has_one_ref;
app->app.base.has_at_least_one_ref = app_has_at_least_one_ref;
app->app.get_browser_process_handler =
app_complete_get_browser_process_handler;
// Create and store browser process handler
app->browser_process_handler = create_browser_process_handler();
atomic_store(&app->ref_count, 1);
return app;
}
// Complete main() function (Windows/Linux pattern)
// NOTE: On macOS, you must call cef_load_library() first, then cef_api_hash().
// See cefsimple_capi/cefsimple_mac.m for the macOS implementation.
int main(int argc, char* argv[]) {
// Configure the CEF API version. This must be called before any other CEF
// API functions.
cef_api_hash(CEF_API_VERSION, 0);
cef_main_args_t main_args = {};
#ifdef _WIN32
main_args.instance = GetModuleHandle(NULL);
#else
main_args.argc = argc;
main_args.argv = argv;
#endif
// Create app with browser process handler (with 1 reference)
my_app_complete_t* app = create_app_complete();
// Add a reference before cef_execute_process. Both cef_execute_process and
// cef_initialize will take ownership of a reference, so we need 2 total.
app->app.base.add_ref(&app->app.base);
// Execute sub-processes if needed
int exit_code = cef_execute_process(&main_args, &app->app, NULL);
if (exit_code >= 0) {
// The sub-process has completed so return here.
// cef_execute_process took ownership of one reference.
// Release only the additional reference we added.
app->app.base.release(&app->app.base);
return exit_code;
}
// Initialize CEF for the browser process
cef_settings_t settings = {};
settings.size = sizeof(cef_settings_t);
settings.no_sandbox = 1;
if (!cef_initialize(&main_args, &settings, &app->app, NULL)) {
// cef_initialize took ownership of the remaining app reference so we don't
// need to release any.
return cef_get_exit_code();
}
// Run the CEF message loop
// on_context_initialized will be called, which creates the browser
cef_run_message_loop();
// Shutdown CEF
cef_shutdown();
// Note: We DON'T release the app here. The 2 total references have been
// given to cef_execute_process and cef_initialize. If cef_initialize
// succeeded the final reference will be released during cef_shutdown.
return 0;
}
This complete example demonstrates:
on_context_initialized callback where the browser is createdcreate_client() and create_browser() from previous sectionsCEF uses multiple threads internally. Many APIs must be called on specific threads, particularly the UI thread (TID_UI). Always verify you’re on the correct thread before calling CEF APIs.
Use cef_currently_on() to check if you’re on a specific thread:
#include "include/capi/cef_task_capi.h"
void some_function() {
// Check if we're on the UI thread
if (cef_currently_on(TID_UI)) {
// Safe to call UI thread APIs here
// ...
} else {
// Not on UI thread - need to post a task
}
}
If you need to call a UI thread API from another thread, post a task to the UI thread:
#include <stdatomic.h>
#include <stdlib.h>
#include <string.h>
#include "include/capi/cef_task_capi.h"
// Custom task structure
typedef struct _my_task_t {
cef_task_t task; // MUST be first
atomic_int ref_count;
// Task-specific data
cef_browser_t* browser; // Example: browser to navigate
cef_string_t url; // Example: URL to navigate to
} my_task_t;
// Custom task release that cleans up task resources
int CEF_CALLBACK task_release(cef_base_ref_counted_t* self) {
my_task_t* task = (my_task_t*)self;
int count = atomic_fetch_sub(&task->ref_count, 1) - 1;
if (count == 0) {
// Clean up task resources
if (task->browser) {
task->browser->base.release(&task->browser->base);
}
cef_string_clear(&task->url);
free(task);
return 1;
}
return 0;
}
// Task execution - called on the UI thread
void CEF_CALLBACK task_execute(cef_task_t* self) {
my_task_t* task = (my_task_t*)self;
// This executes on the UI thread
// Safe to call UI thread APIs here
cef_frame_t* frame = task->browser->get_main_frame(task->browser);
if (frame) {
frame->load_url(frame, &task->url);
frame->base.release(&frame->base);
}
}
// Create and post task
void navigate_from_any_thread(cef_browser_t* browser, const char* url_str) {
// Create task
my_task_t* task = (my_task_t*)calloc(1, sizeof(my_task_t));
// Initialize base structure
task->task.base.size = sizeof(cef_task_t);
// Reference counting
// Implement task_add_ref, task_has_one_ref, task_has_at_least_one_ref
// following the pattern from "Atomic Operations for Reference Counting".
// task_release is shown above with custom cleanup logic.
task->task.base.add_ref = task_add_ref;
task->task.base.release = task_release;
task->task.base.has_one_ref = task_has_one_ref;
task->task.base.has_at_least_one_ref = task_has_at_least_one_ref;
// Set execute callback
task->task.execute = task_execute;
// Store task data (keep browser reference, copy URL)
task->browser = browser;
browser->base.add_ref(&browser->base);
cef_string_from_ascii(url_str, strlen(url_str), &task->url);
// Initialize ref count
atomic_store(&task->ref_count, 1);
// Post to UI thread
// cef_post_task takes ownership of the reference
cef_post_task(TID_UI, &task->task);
}
add_ref, release) can be called from ANY threadcef_initialize() and cef_run_message_loop() must be called on the main application threadCEF uses UTF-16 strings by default. Understanding string handling is essential for C API usage.
The default string type is UTF-16 (cef_string_t is aliased to cef_string_utf16_t):
typedef struct _cef_string_utf16_t {
char16_t* str; // Pointer to UTF-16 string data
size_t length; // Number of char16_t elements in the array (not bytes)
void (*dtor)(char16_t* str); // Destructor function (can be NULL)
} cef_string_utf16_t;
typedef cef_string_utf16_t cef_string_t;
To create a string from ASCII or UTF-8:
cef_string_t my_string = {};
cef_string_from_ascii("Hello World", 11, &my_string);
// Use the string...
// Remember to free when done
cef_string_clear(&my_string);
For UTF-8 input:
cef_string_t my_string = {};
const char* utf8_text = "Hello 世界";
// strlen() returns byte count, not character count, which is correct here
// because cef_string_from_utf8() expects the UTF-8 byte length
cef_string_from_utf8(utf8_text, strlen(utf8_text), &my_string);
// Use the string...
cef_string_clear(&my_string);
Functions that return cef_string_userfree_t transfer ownership to you. You must free these strings:
// Get the URL from a frame (transfers ownership)
cef_string_userfree_t url = frame->get_url(frame);
// Use the string...
// (url->str, url->length)
// Free it when done - REQUIRED
cef_string_userfree_free(url);
When passing strings to CEF functions, you typically pass a pointer to cef_string_t:
void navigate_to_url(cef_browser_t* browser, const char* url_str) {
cef_string_t url = {};
cef_string_from_ascii(url_str, strlen(url_str), &url);
cef_frame_t* frame = browser->get_main_frame(browser);
frame->load_url(frame, &url);
// Release the frame (get_main_frame returns a new reference)
frame->base.release(&frame->base);
cef_string_clear(&url);
}
String lists are used for arrays of strings (like command-line arguments):
// Create a string list
cef_string_list_t list = cef_string_list_alloc();
// Add items
cef_string_t item = {};
cef_string_from_ascii("item1", 5, &item);
cef_string_list_append(list, &item);
cef_string_clear(&item);
cef_string_from_ascii("item2", 5, &item);
cef_string_list_append(list, &item);
cef_string_clear(&item);
// Get the size
size_t count = cef_string_list_size(list);
// Retrieve items
cef_string_t retrieved = {};
cef_string_list_value(list, 0, &retrieved);
// Use retrieved.str and retrieved.length
cef_string_clear(&retrieved);
// Free the list when done
cef_string_list_free(list);
String maps store key-value pairs:
// Create a string map
cef_string_map_t map = cef_string_map_alloc();
// Add key-value pairs
cef_string_t key = {}, value = {};
cef_string_from_ascii("User-Agent", 10, &key);
cef_string_from_ascii("MyBrowser/1.0", 13, &value);
cef_string_map_append(map, &key, &value);
cef_string_clear(&key);
cef_string_clear(&value);
// Get the size
size_t count = cef_string_map_size(map);
// Retrieve values by key
cef_string_from_ascii("User-Agent", 10, &key);
cef_string_map_find(map, &key, &value);
// Use value.str and value.length
cef_string_clear(&key);
cef_string_clear(&value);
// Free the map when done
cef_string_map_free(map);
String multimaps allow multiple values per key (useful for HTTP headers):
// Create a string multimap
cef_string_multimap_t map = cef_string_multimap_alloc();
// Add multiple values for the same key
cef_string_t key = {}, value = {};
cef_string_from_ascii("Set-Cookie", 10, &key);
cef_string_from_ascii("cookie1=value1", 14, &value);
cef_string_multimap_append(map, &key, &value);
cef_string_clear(&value);
cef_string_from_ascii("cookie2=value2", 14, &value);
cef_string_multimap_append(map, &key, &value);
cef_string_clear(&value);
cef_string_clear(&key);
// Enumerate all values for a key
cef_string_from_ascii("Set-Cookie", 10, &key);
size_t count = cef_string_multimap_find_count(map, &key);
for (size_t i = 0; i < count; ++i) {
cef_string_multimap_enumerate(map, &key, i, &value);
// Use value.str and value.length
cef_string_clear(&value);
}
cef_string_clear(&key);
// Free the multimap when done
cef_string_multimap_free(map);
Understanding reference counting is the most challenging part of working with the CEF C API. The reference counting concept used by CEF is similar to COM (Component Object Model).
Unlike the C++ API, which provides automatic reference counting through CefRefPtr and the IMPLEMENT_REFCOUNTING macro, the C API requires manual reference counting. You must implement the reference counting logic yourself, typically using atomic operations to store the reference count.
To understand why the C API reference counting rules exist, it helps to understand how the C++ API handles this automatically through the CefRefPtr<T> smart pointer template class.
CefRefPtr<T> is a smart pointer that automatically manages reference counts:
AddRef() on the objectRelease() on the objectExample of automatic behavior in C++:
void UseFrameInCpp(CefRefPtr<CefBrowser> browser) {
// browser parameter: CefRefPtr automatically added a reference when called
CefRefPtr<CefFrame> frame = browser->GetMainFrame();
// frame: GetMainFrame() returned a CefRefPtr with a reference
frame->LoadURL("https://example.com");
// Exiting function:
// - frame goes out of scope → CefRefPtr automatically calls Release()
// - browser goes out of scope → CefRefPtr automatically calls Release()
// No manual memory management needed!
}
In the C API, you must perform ALL the operations that CefRefPtr does automatically. The reference counting rules in the following sections directly correspond to CefRefPtr behavior:
CefRefPtr adding a ref when you pass itCefRefPtr transferring a referenceCefRefPtr releasing when it goes out of scopeCefRefPtr as a member variableSame example in C API (manual version):
void use_frame_in_c(cef_browser_t* browser) {
// browser parameter: CEF already added a reference before calling us
cef_frame_t* frame = browser->get_main_frame(browser);
// frame: get_main_frame() returns a pointer with a reference
cef_string_t url = {};
cef_string_from_ascii("https://example.com", 19, &url);
frame->load_url(frame, &url);
cef_string_clear(&url);
// Exiting function - MUST manually release:
frame->base.release(&frame->base); // Manual (CefRefPtr does automatically)
browser->base.release(&browser->base); // Manual (CefRefPtr does automatically)
}
Understanding that you’re manually implementing what CefRefPtr does automatically makes the rules easier to remember and apply correctly.
Memory management rule: An object is deleted when its reference count reaches zero.
Threading rule: Reference counting functions may be called from any thread, so atomic operations are required.
Ownership rule: Each reference represents one owner. When you’re done with an object, release your reference.
Critical callback rule: When CEF calls your callback with object parameters (browser, frame, request, etc.), you MUST call release() on each object parameter (other than self) before returning. CEF has already added a reference for each parameter, and it expects you to release it. Failing to do so will cause memory leaks.
When calling a method on a structure, don’t change its reference count:
// CORRECT: No reference counting change for the self parameter
struct->call_func(struct, params);
When calling CEF methods that take object parameters (beyond the self parameter), CEF takes ownership of those parameters. You must add a reference before calling:
// Example: Adding a child view to a panel
cef_panel_t* panel = ...;
cef_view_t* child_view = ...;
// Add reference before passing child_view to CEF method
child_view->base.add_ref(&child_view->base);
// panel is "self" (no ownership transfer)
// child_view is a non-self parameter (ownership transferred to CEF)
panel->add_child_view(panel, child_view);
// DON'T release child_view here - add_child_view consumed the reference
Important distinction:
Shortcut: If you received the object as a callback parameter and are immediately passing it to a CEF function, you can transfer CEF’s reference directly without add_ref/release:
void CEF_CALLBACK on_before_close(
cef_life_span_handler_t* self,
cef_browser_t* browser) {
// browser has +1 ref from CEF callback
// Transfer CEF's reference directly to is_same
stored_browser->is_same(stored_browser, browser);
// DON'T release browser - is_same consumed CEF's reference
// DON'T add_ref before calling - we transferred the callback's reference
}
When you return an object to CEF (or pass it to a CEF function), you’re transferring ownership of one reference to CEF. Add a reference before returning because CEF will release it when done:
// Example: Returning a handler from a getter function
cef_life_span_handler_t* CEF_CALLBACK
my_get_life_span_handler(cef_client_t* self) {
my_client_t* client = (my_client_t*)self;
// Add a reference because we're giving ownership to CEF
// CEF will call release() on this reference when it's done with the handler
client->life_span_handler->base.add_ref(
&client->life_span_handler->base);
return client->life_span_handler;
}
Why? You still have your own reference (stored in client->life_span_handler), and now CEF has a separate reference. When CEF no longer needs the handler, it will release its reference, but yours remains until you explicitly release it.
When CEF calls your callback with structure parameters, it has already added a reference for each one. You MUST release them before returning:
void CEF_CALLBACK on_before_browse(
cef_request_handler_t* self,
cef_browser_t* browser,
cef_frame_t* frame,
cef_request_t* request,
int user_gesture,
int is_redirect) {
// Use the objects...
cef_string_userfree_t url = request->get_url(request);
// ... do something with the URL
cef_string_userfree_free(url);
// MUST release all object parameters before returning
// CEF added a reference for each parameter before calling this function
browser->base.release(&browser->base);
frame->base.release(&frame->base);
request->base.release(&request->base);
return 0; // Allow navigation
}
Exception: Do NOT release the self parameter - that’s your own structure.
To keep a parameter longer: If you need to store a parameter beyond the callback, call add_ref on it BEFORE releasing the original reference (see Rule 5).
If you store a pointer beyond the current function, you need to keep a reference. You have two options:
Option 1: Simply don’t release the parameter (recommended)
typedef struct {
cef_life_span_handler_t handler;
cef_browser_t* browser; // Long-term storage
atomic_int ref_count;
} my_handler_t;
void CEF_CALLBACK on_after_created(
cef_life_span_handler_t* self,
cef_browser_t* browser) {
my_handler_t* handler = (my_handler_t*)self;
// Store the browser and keep CEF's reference
handler->browser = browser;
// Don't call release() - we're keeping the reference CEF gave us
}
// Remember to release when you're done with it
int CEF_CALLBACK my_handler_release(cef_base_ref_counted_t* self) {
my_handler_t* handler = (my_handler_t*)self;
int count = atomic_fetch_sub(&handler->ref_count, 1) - 1;
if (count == 0) {
// Release the browser reference we kept
if (handler->browser) {
handler->browser->base.release(&handler->browser->base);
}
free(handler);
return 1;
}
return 0;
}
Option 2: Add your own reference and release the parameter
This is more explicit but does the same thing:
void CEF_CALLBACK on_after_created(
cef_life_span_handler_t* self,
cef_browser_t* browser) {
my_handler_t* handler = (my_handler_t*)self;
// Add our own reference
handler->browser = browser;
browser->base.add_ref(&browser->base);
// Release the reference CEF gave us
browser->base.release(&browser->base);
// Net effect: ref count unchanged, but we "own" the reference now
}
Both approaches end up with the same reference count. Option 1 is simpler and more efficient.
Reference counting functions are called from multiple threads. Always use thread-safe atomic operations.
See the “Atomic Operations for Reference Counting” section below for complete platform-specific implementations.
// CORRECT: Thread-safe
void CEF_CALLBACK app_add_ref(cef_base_ref_counted_t* self) {
my_app_t* app = (my_app_t*)self;
atomic_fetch_add(&app->ref_count, 1);
}
// WRONG: Not thread-safe!
void CEF_CALLBACK app_add_ref(cef_base_ref_counted_t* self) {
my_app_t* app = (my_app_t*)self;
app->ref_count++; // RACE CONDITION!
}
The release function should delete the object when the count reaches zero:
int CEF_CALLBACK app_release(cef_base_ref_counted_t* self) {
my_app_t* app = (my_app_t*)self;
int count = atomic_fetch_sub(&app->ref_count, 1) - 1;
if (count == 0) {
// Clean up any resources this structure owns
if (app->some_browser) {
app->some_browser->base.release(&app->some_browser->base);
}
// Free the structure itself
free(app);
return 1; // Indicate the object was deleted
}
return 0; // Object still alive
}
Make sure to release any references you’ve stored when your handler is destroyed. For browser-related handlers, the last opportunity is typically in the life span handler’s on_before_close callback:
void CEF_CALLBACK on_before_close(
cef_life_span_handler_t* self,
cef_browser_t* browser) {
my_handler_t* handler = (my_handler_t*)self;
// Release our stored browser reference
if (handler->browser) {
handler->browser->base.release(&handler->browser->base);
handler->browser = NULL;
}
// Release the browser reference passed to this function
browser->base.release(&browser->base);
}
You’ll know reference counting is working correctly when:
If you’re having issues, add logging to your add_ref and release functions to track the reference count.
These are common mistakes that will cause crashes or undefined behavior. Pay close attention to these details.
The CEF base structure MUST be the first member of your custom structure. This allows safe casting between the base type and your type.
// CORRECT: Base structure first
typedef struct {
cef_app_t app; // CEF structure FIRST
atomic_int ref_count;
void* user_data;
} my_app_t;
// WRONG: Will cause crashes!
typedef struct {
atomic_int ref_count;
void* user_data;
cef_app_t app; // Not first - WRONG!
} broken_app_t;
This ordering requirement exists because CEF will cast the pointer to the base type and expects the base structure to start at offset zero.
Always set the size field to the size of the CEF structure type, NOT your wrapper structure:
my_app_t* app = (my_app_t*)calloc(1, sizeof(my_app_t));
// CORRECT: Size of the CEF structure
app->app.base.size = sizeof(cef_app_t);
// WRONG: Don't use the size of your wrapper!
app->app.base.size = sizeof(my_app_t); // WRONG!
The size field is used for versioning and validation by CEF. It must match the size of the CEF API structure.
Reference counting functions (add_ref, release, has_one_ref, has_at_least_one_ref) are called from multiple threads. You MUST use atomic operations or platform-specific thread-safe functions.
Never use plain integer operations:
// WRONG - RACE CONDITION!
void CEF_CALLBACK bad_add_ref(cef_base_ref_counted_t* self) {
my_app_t* app = (my_app_t*)self;
app->ref_count++; // NOT THREAD-SAFE!
}
Use platform-appropriate atomic operations:
atomic_int with atomic_fetch_add(), atomic_fetch_sub(), atomic_load()InterlockedIncrement(), InterlockedDecrement()__sync_fetch_and_add(), __sync_fetch_and_sub()OSAtomicIncrement32(), OSAtomicDecrement32()When implementing methods that return structures (like get_browser_process_handler):
cef_browser_process_handler_t* CEF_CALLBACK
get_browser_process_handler(cef_app_t* self) {
my_app_t* app = (my_app_t*)self;
if (app->browser_process_handler) {
// Add reference before returning - CEF will release when done
app->browser_process_handler->base.add_ref(
&app->browser_process_handler->base);
return app->browser_process_handler;
}
// Return NULL if no handler needed
return NULL;
}
Key points:
release when it’s done with the returned handlerCheck parameters for NULL before dereferencing them. The CEF API documentation indicates which parameters may be NULL:
void CEF_CALLBACK some_handler(
cef_handler_t* self,
cef_browser_t* browser) {
// NOTE: 'self' is never NULL - CEF is calling your callback on your structure
// Check other parameters that may be NULL (see API docs)
if (!browser) {
return;
}
// Now safe to use
cef_frame_t* frame = browser->get_main_frame(browser);
if (frame) {
// Methods can return NULL - always check return values
// Use frame...
frame->base.release(&frame->base);
}
browser->base.release(&browser->base);
}
Important notes:
self parameter is never NULL - it’s always your own structureget_main_frame() may return NULL), so always check return valuesDifferent platforms require different approaches to implementing thread-safe atomic operations for reference counting.
Use this approach if you can. Modern compilers support the C11 standard <stdatomic.h> header, which provides portable atomic operations across all platforms:
If you need to support older platform versions, see the sections below.
Compiler flags required:
-std=c11 (GCC/Clang)/std:c11 /experimental:c11atomics-std=c11Without these flags, the compiler will use an older C standard (typically C89/C90) and <stdatomic.h> will not be available.
#include <stdatomic.h>
#include <stdlib.h>
typedef struct {
cef_app_t app;
atomic_int ref_count; // C11 atomic type
} my_app_t;
void CEF_CALLBACK app_add_ref(cef_base_ref_counted_t* self) {
my_app_t* app = (my_app_t*)self;
atomic_fetch_add(&app->ref_count, 1);
}
int CEF_CALLBACK app_release(cef_base_ref_counted_t* self) {
my_app_t* app = (my_app_t*)self;
int count = atomic_fetch_sub(&app->ref_count, 1) - 1;
if (count == 0) {
free(app);
return 1;
}
return 0;
}
int CEF_CALLBACK app_has_one_ref(cef_base_ref_counted_t* self) {
my_app_t* app = (my_app_t*)self;
return atomic_load(&app->ref_count) == 1;
}
int CEF_CALLBACK app_has_at_least_one_ref(cef_base_ref_counted_t* self) {
my_app_t* app = (my_app_t*)self;
return atomic_load(&app->ref_count) >= 1;
}
Use this approach for compatibility with older Visual Studio versions (before 2022 17.5):
#include <stdlib.h>
#include <windows.h>
typedef struct {
cef_app_t app;
volatile LONG ref_count; // Use LONG for Interlocked functions
} my_app_t;
void CEF_CALLBACK app_add_ref(cef_base_ref_counted_t* self) {
my_app_t* app = (my_app_t*)self;
InterlockedIncrement(&app->ref_count);
}
int CEF_CALLBACK app_release(cef_base_ref_counted_t* self) {
my_app_t* app = (my_app_t*)self;
LONG count = InterlockedDecrement(&app->ref_count);
if (count == 0) {
free(app);
return 1;
}
return 0;
}
int CEF_CALLBACK app_has_one_ref(cef_base_ref_counted_t* self) {
my_app_t* app = (my_app_t*)self;
// InterlockedCompareExchange(ptr, 0, 0) atomically reads the value
return InterlockedCompareExchange(&app->ref_count, 0, 0) == 1;
}
int CEF_CALLBACK app_has_at_least_one_ref(cef_base_ref_counted_t* self) {
my_app_t* app = (my_app_t*)self;
// InterlockedCompareExchange(ptr, 0, 0) atomically reads the value
return InterlockedCompareExchange(&app->ref_count, 0, 0) >= 1;
}
Use this approach for older GCC (before 4.9) or Clang (before 3.1) on Linux/macOS that don’t support C11 atomics:
#include <stdlib.h>
typedef struct {
cef_app_t app;
int ref_count; // Plain int, protected by GCC built-ins
} my_app_t;
void CEF_CALLBACK app_add_ref(cef_base_ref_counted_t* self) {
my_app_t* app = (my_app_t*)self;
__sync_fetch_and_add(&app->ref_count, 1);
}
int CEF_CALLBACK app_release(cef_base_ref_counted_t* self) {
my_app_t* app = (my_app_t*)self;
int count = __sync_fetch_and_sub(&app->ref_count, 1) - 1;
if (count == 0) {
free(app);
return 1;
}
return 0;
}
int CEF_CALLBACK app_has_one_ref(cef_base_ref_counted_t* self) {
my_app_t* app = (my_app_t*)self;
// __sync_fetch_and_add(ptr, 0) atomically reads the value
return __sync_fetch_and_add(&app->ref_count, 0) == 1;
}
int CEF_CALLBACK app_has_at_least_one_ref(cef_base_ref_counted_t* self) {
my_app_t* app = (my_app_t*)self;
// __sync_fetch_and_add(ptr, 0) atomically reads the value
return __sync_fetch_and_add(&app->ref_count, 0) >= 1;
}
Use this approach for older macOS versions. Note that OSAtomic functions are deprecated in modern macOS - use C11 atomics instead if possible:
#include <libkern/OSAtomic.h>
#include <stdlib.h>
typedef struct {
cef_app_t app;
int32_t ref_count; // Must be int32_t for OSAtomic functions
} my_app_t;
void CEF_CALLBACK app_add_ref(cef_base_ref_counted_t* self) {
my_app_t* app = (my_app_t*)self;
OSAtomicIncrement32(&app->ref_count);
}
int CEF_CALLBACK app_release(cef_base_ref_counted_t* self) {
my_app_t* app = (my_app_t*)self;
int32_t count = OSAtomicDecrement32(&app->ref_count);
if (count == 0) {
free(app);
return 1;
}
return 0;
}
int CEF_CALLBACK app_has_one_ref(cef_base_ref_counted_t* self) {
my_app_t* app = (my_app_t*)self;
// OSAtomicAdd32(0, ptr) atomically reads the value
return OSAtomicAdd32(0, &app->ref_count) == 1;
}
int CEF_CALLBACK app_has_at_least_one_ref(cef_base_ref_counted_t* self) {
my_app_t* app = (my_app_t*)self;
// OSAtomicAdd32(0, ptr) atomically reads the value
return OSAtomicAdd32(0, &app->ref_count) >= 1;
}
These patterns solve common scenarios you’ll encounter when using the CEF C API.
Many handler methods can simply return NULL if you don’t need custom behavior. Since calloc zero-initializes all memory, you can either:
Option 1: Don’t set the function pointer at all (it’s already NULL):
my_client_t* client = (my_client_t*)calloc(1, sizeof(my_client_t));
// client->client.get_render_handler is already NULL - nothing to do!
// client->client.get_request_handler is already NULL - nothing to do!
Option 2: Implement the getter to explicitly return NULL (useful for clarity):
// Don't need a render handler? Just return NULL
cef_render_handler_t* CEF_CALLBACK
client_get_render_handler(cef_client_t* self) {
return NULL; // Use default rendering
}
// Don't need a request handler? Return NULL
cef_request_handler_t* CEF_CALLBACK
client_get_request_handler(cef_client_t* self) {
return NULL; // Use default request handling
}
Both approaches are valid. Option 1 is simpler (no code needed), while Option 2 provides better documentation of your intent. Only implement handlers for the functionality you actually need.
Important note about default implementations: The C++ API provides default implementations for many handler methods (often defined in the header files). The C API does not automatically provide these defaults. If you don’t implement a method in the C API, CEF will use its internal default behavior, which may differ from the C++ API’s documented defaults.
For example, CefWindowDelegate::CanResize() defaults to true in the C++ header. To get the same behavior in the C API, you must explicitly implement can_resize and return 1 (true). See cefsimple_capi/simple_views.c for examples of implementing methods like can_resize, can_maximize, and can_minimize that replicate C++ default behavior.
When casting from a CEF structure back to your custom structure, verify integrity:
my_handler_t* safe_cast_to_my_handler(cef_life_span_handler_t* handler) {
// Verify the structure is valid
if (!handler) {
return NULL;
}
// Verify the size matches (helps catch version mismatches)
if (handler->base.size != sizeof(cef_life_span_handler_t)) {
return NULL;
}
// Safe to cast because our structure has the handler as the first member
return (my_handler_t*)handler;
}
Always extract any data you need from a structure before releasing it:
void CEF_CALLBACK on_before_close(
cef_life_span_handler_t* self,
cef_browser_t* browser) {
// Extract data BEFORE releasing
int browser_id = browser->get_identifier(browser);
// Now safe to release
browser->base.release(&browser->base);
// Use the extracted data
printf("Browser %d is closing\n", browser_id);
}
Use your custom structure to store application-specific data:
typedef struct {
cef_client_t client;
atomic_int ref_count;
// Application-specific data
void* user_data;
cef_browser_t* main_browser;
int window_id;
} my_client_t;
// Access your data in callbacks
void CEF_CALLBACK on_after_created(
cef_life_span_handler_t* self,
cef_browser_t* browser) {
my_handler_t* handler = (my_handler_t*)self;
// Access application-specific data
printf("Window ID: %d\n", handler->window_id);
// Store browser reference and keep CEF's reference
handler->main_browser = browser;
// Don't call release() - we're keeping the reference CEF gave us
}
// Remember to release stored references when cleaning up
int CEF_CALLBACK my_handler_release(cef_base_ref_counted_t* self) {
my_handler_t* handler = (my_handler_t*)self;
int count = atomic_fetch_sub(&handler->ref_count, 1) - 1;
if (count == 0) {
// Release the browser reference we kept
if (handler->main_browser) {
handler->main_browser->base.release(&handler->main_browser->base);
}
free(handler);
return 1;
}
return 0;
}
Pass context through handler structures:
typedef struct {
cef_load_handler_t handler;
atomic_int ref_count;
// Context for callbacks
void (*on_load_complete)(void* user_data, const char* url);
void* callback_user_data;
} my_load_handler_t;
void CEF_CALLBACK on_load_end(
cef_load_handler_t* self,
cef_browser_t* browser,
cef_frame_t* frame,
int httpStatusCode) {
my_load_handler_t* handler = (my_load_handler_t*)self;
// Get the URL
cef_string_userfree_t url = frame->get_url(frame);
// Call application callback if set
if (handler->on_load_complete) {
// Convert to UTF-8 for the callback
cef_string_utf8_t url_utf8 = {};
cef_string_to_utf8(url->str, url->length, &url_utf8);
handler->on_load_complete(
handler->callback_user_data,
url_utf8.str);
cef_string_utf8_clear(&url_utf8);
}
cef_string_userfree_free(url);
browser->base.release(&browser->base);
frame->base.release(&frame->base);
}
When using calloc, all function pointers are automatically NULL, so you only need to set the ones you actually implement:
my_client_t* create_client() {
// calloc zeros all memory - all function pointers are NULL by default
my_client_t* client = (my_client_t*)calloc(1, sizeof(my_client_t));
// Set up base structure
client->client.base.size = sizeof(cef_client_t);
client->client.base.add_ref = client_add_ref;
client->client.base.release = client_release;
client->client.base.has_one_ref = client_has_one_ref;
client->client.base.has_at_least_one_ref = client_has_at_least_one_ref;
// Only set the handlers you actually implement
client->client.get_life_span_handler = client_get_life_span_handler;
client->client.get_load_handler = client_get_load_handler;
// All other handlers (render, request, display, etc.) are already NULL
atomic_store(&client->ref_count, 1);
return client;
}
Important note about malloc vs calloc:
If you use malloc instead of calloc, you MUST explicitly initialize all function pointers to NULL:
my_client_t* create_client_with_malloc() {
// malloc does NOT initialize memory - fields contain garbage
my_client_t* client = (my_client_t*)malloc(sizeof(my_client_t));
// MUST explicitly zero or set every function pointer
memset(client, 0, sizeof(my_client_t)); // Or set each field individually
// Now continue with initialization...
client->client.base.size = sizeof(cef_client_t);
// ... rest of setup
}
Recommendation: Use calloc for CEF structures. It’s safer and requires less code.