Skip to content

Conversation

@morrisonlevi
Copy link
Contributor

@morrisonlevi morrisonlevi commented Jan 27, 2026

This PR adds first-class support for hybrid extensions: a single extension which is both a zend_extension and a zend_module_entry. Hybrid extensions can be loaded from INI via either zend_extension= and extension= and in either case, both the zend_extension and zend_module_entry will be loaded (there are caveats here, please read the instructions below).

API/ABI Overview:

  • New: ZEND_MODULE_ENTRY macro in Zend/zend_API.h (expands to &name##_module_entry).
  • TODO: consider making helpers public, like php_try_load_hybrid_zend_extension and zend_try_register_hybrid_module (but make sure their names are good).

Backwards Compatibility Breaks:

  • zend_extension::reserved5 is now zend_module_entry *module_entry. This is a BC break for any extension using reserved5.

What should hybrid extension authors do?

  1. Author the code as a Zend Extension, i.e. export symbols:
    • zend_extension zend_extension_entry
    • zend_extension_version_info extension_version_info
  2. Author the code as a module, except do not export get_module i.e. don't use the macro ZEND_GET_MODULE(extname).
  3. Set zend_extension_entry's module_entry member to the address of the static zend_module_entry, i.e. use the new macro ZEND_MODULE_ENTRY(extname).

Why shouldn't authors publish get_module for hybrid extensions?

If authors publish get_module, then the zend_extension will not be automatically loaded when called with extension=. This allows a migration path for "old" hybrid extensions: if they set get_module, they get the old behavior (no code changes, unless they use reserved5).

Why change the status quo?

  1. This is much simpler (see PHP Internals Book: Zend Extensions, Hybrid Extensions). Notably, the book mentions crashes because of unloading due to sharing a DL_HANDLE. One less (very) sharp edge for authors.
  2. Every hook fires when it is supposed to fire, and not arbitrarily early or late because of the primary/secondary issue mentioned in the book (it uses master/slave language, not primary/secondary).
  3. There are multiple extensions doing this, this is clearly something valuable to do.

@morrisonlevi
Copy link
Contributor Author

I see that an ABI break tag was applied, and this is true semantically but not actually an ABI break in the usual sense, the type layout is preserved. This is why I picked to embed a pointer to a zend_module_entry in the zend_extension and not the other way around.

@TimWolla
Copy link
Member

TimWolla commented Jan 27, 2026

this is true semantically but not actually an ABI break in the usual sense

This happens automatically when touching a public header. No further logic is applied. It's primarily for the non-master branches to not accidentally break the ABI for an existing release. ABI breaks in master are fine either way.

@morrisonlevi morrisonlevi changed the title feat: zend_extension can have a zend_module_entry feat: first-class hybrid zend_extension/zend_module_entry Jan 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants