fix(interop): two ABI defects, struct field order and the announced API version - #2
Open
Juliusz Kopczewski (julkiewicz) wants to merge 3 commits into
Conversation
The include tree was still the 150.0.11 baseline, so it did not contain ReleaseAcceleratedPaintSurface at all. The binding for it was added by hand and the version constants were set by hand to a build nobody had vendored, which left regeneration as a trap: it would have quietly deleted the binding. Overlays the headers from the build we actually ship, cef_binary_150.0.23- 2026-08-04-surface-lease.3556+ge7ab2e1+chromium-150.0.7871.187_windows64_readym, and regenerates version.g.cs from them, so the constants now name that build rather than the stock 150.0.17 they claimed. cef_version stays an upstream release number on purpose. Directory.Packages.props pins chromiumembeddedframework.runtime at $(CefVersion) and CefGlue.Packages.props references it unconditionally, so a value with no published package fails restore everywhere. UPGRADE.md now explains why the two version fields diverge.
The generated structs mirror CEF's C API structs field for field, so their order is ABI. The generator emitted C++ declaration order; CEF's own translator emits version order, appending members added by a later API version after every member of earlier ones. That is what keeps a client built against an older version finding the members it knows at the offsets it expects. Where the two disagreed, a member landed mid-struct and silently shifted every member below it, so calls dispatched through the wrong function pointer. cef_browser_host_t was the worst: release_accelerated_paint_surface sat 30 slots early, which put send_key_event onward and everything through get_runtime_style on the wrong entry. cef_command_line_t, cef_download_item_t and cef_request_context_t were wrong the same way, and had been before we patched anything, since the cause is any method carrying an added= annotation. Sorts by added version, stably, so declaration order still decides within a version. Verified by extracting the field order of all 130 generated structs and diffing each against the corresponding capi struct in the CEF distribution we ship: all 130 now match, resolving inherited bases and treating cef_base_ref_counted_t as the opaque _base field the generator emits.
…ered one A client announces its API version by calling cef_api_hash(version) before initializing, and the library then builds its C API structs to match. This is not bookkeeping: announce an explicit version and every method added since is compiled out of those structs, so a binding that knows about a newer method reads a different method's pointer, or reads past the end of the struct. The generator picked CEF_API_VERSION_LAST, so the bindings announced 15000 while being generated from the experimental headers. Calling ReleaseAcceleratedPaintSurface then jumped through whatever that field position holds in a 15000-layout struct and took the process down with an access violation. Our own additions live in the experimental surface, since that is where CEF puts API not yet frozen into a numbered version, so that is the version to announce. It costs the forward and backward compatibility an explicit version buys across a range of CEF releases, which is a fair trade while we ship the libcef we bind against. Verified by running a browser through the managed harness: 368 accelerated paints, every one carrying a lease id, every lease released, clean shutdown. Before this change the same run died on the first release call.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two ABI defects in the interop, both of which compiled, loaded, passed the API hash check, and then crashed or
silently mis-dispatched at runtime. Found while checking the open item to repoint
cef-version.jsonat ourpublished CEF build.
1. Struct field order
The generated structs mirror CEF's C API structs field for field, so their field order is ABI. The generator
emitted C++ declaration order. CEF's own translator emits version order: a member added by a later API version
is appended after every member of earlier versions, wherever its C++ declaration sits. That is what lets a client
built against an older version still find its members at the offsets it expects.
Where the two disagreed, a member landed mid-struct and shifted every member below it onto the wrong function
pointer.
cef_browser_host_twas the worst case:release_accelerated_paint_surfacesat 30 slots early, puttingsend_key_eventonward, throughget_runtime_style, on the wrong entry.cef_command_line_t,cef_download_item_tandcef_request_context_twere wrong the same way, and had been before we patchedanything: the trigger is any method carrying an
added=annotation.Fixed by sorting members by the version that added them, stably, so declaration order still decides within a
version.
2. The announced API version
A client announces its API version by calling
cef_api_hash(version)before initializing, and the library thenbuilds its C API structs to match. Announce an explicit numbered version and every method added since is compiled
out of those structs, so a binding that knows about a newer method reads a different method's pointer, or reads
past the end.
The generator picked
CEF_API_VERSION_LAST, so the bindings announced 15000 while being generated from theexperimental headers.
ReleaseAcceleratedPaintSurfacethen jumped through whatever that field position holds ina 15000-layout struct and took the process down with an access violation.
Fixed by announcing experimental, which is where CEF puts API not yet frozen into a numbered version, and so
where our additions live. It costs the compatibility across a range of CEF releases that an explicit version
buys, which is a fair trade while we ship the libcef we bind against.
Verification
Static: extracted the field order of all 130 generated structs and diffed each against the corresponding
capistruct in the CEF distribution we ship, resolving inherited bases and treating
cef_base_ref_counted_tas theopaque
_basefield the generator emits. Before: 4 mismatched. After: all 130 match.Runtime: a managed harness now drives a real browser through these bindings against our patched libcef
(
readym-cef,harness/glue). A 10 second run gives 368 accelerated paints, every one carrying a lease id,every lease released through the managed binding, BGRA8888 at 1280x720, no software fallback, clean shutdown.
Reproducing either defect kills that run, so the check has teeth rather than merely passing.
Also here
The vendored
CefGlue.Interop.Gen/include/tree was still the 150.0.11 baseline and did not containReleaseAcceleratedPaintSurfaceat all, so regeneration would have quietly deleted the hand-added binding.Overlays the headers from the build we actually ship and regenerates from them.
cef_versiondeliberately stays an upstream release number:Directory.Packages.propspinschromiumembeddedframework.runtimeat$(CefVersion)and it is referenced unconditionally, so a value with nopublished package fails
restoreon every platform.UPGRADE.mdnow explains why the two version fieldsdiverge, including the consequence that the Linux and macOS
cef.runtime.*packages cannot be built from ourcef_build_version, which the CDN does not host.