Uh oh!
There was an error while loading. Please reload this page.
hal: C++ API (hal.hh) and pybind11 bindings on the new HAL API - #4251
hal: C++ API (hal.hh) and pybind11 bindings on the new HAL API#4251grandixximo wants to merge 4 commits into
Conversation
grandixximo
commented
Jul 19, 2026
This seems to be working, but I am not sure if it is the right shape we want, these are the direction I took on the issue raised in the meeting, subject to revision.
@BsAtHome A bare hal_sint_t is width-blind, and wrapping foreign raw handles across widths is unsafe in the interim. But the C++ layer never selects from the handle alone. Component-created pins carry the type at construction via traits (int32_t vs int64_t are distinct C++ types, compile-time selection), and the runtime path uses q->pp.type from your query API to pick the variant alternative, the stored-type-tag multiplex you described in #4099. The only unsafe path is explicitly reinterpreting a raw handle, which can be guarded with a query lookup. So the layer works transitionally, it just gets simpler after the break. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
rene-dev
commented
Jul 19, 2026
oh, that was quick. I think this should be separate from the other big PR, as this can go in master fairly quickly. the only reason I stopped was that buster had no pybind11 package. this should also replace the current pyhal/halmodule instead of adding a third module. and, the same cpp api should be used in xhc, task and other places which are implemented in cpp. |
grandixximo
commented
Jul 19, 2026
Buster is basically not supported anymore with master, I will fix the review points tomorrow, thank you both |
BsAtHome
commented
Jul 20, 2026
There is another issue. The new hal_lib uses a reference counted init/exit and has separated out Calling The python class must initialize using hal_lib_init() and terminate by calling hal_lib_exit(). If the user has not called hal_exit() on any created components, you will now get a proper error message (because the user forgot to terminate correctly). |
rene-dev
commented
Jul 20, 2026
can you rename the module to halmodule, so that the UIs and the tests that are already in place use the new bindings, and remove the old ones? |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
grandixximo
commented
Jul 20, 2026
Replacing halmodule is the goal, agreed, but renaming now would break things for two separate reasons. First, a hard technical one: on master the new bindings cannot cover the existing Python surface. Second, the compatibility tail: So the proposal stands as: land this additive now (hal.hh + halpp alongside, nothing replaced), and do the rename + replacement as a follow-up once #4247 is in. Your old branch's consumer sweep (hal_glib, qtvcp core, raster, tests) is the starting checklist for that PR. Is this acceptable? Open for discussion... |
rene-dev
commented
Jul 20, 2026
I see that #4231 changes halmodule.cc, we should also add those functions via c++/pybind11 |
BsAtHome
commented
Jul 20, 2026
No, all of this should be in halquery. |
rene-dev
commented
Jul 21, 2026
What is halquery? |
BsAtHome
commented
Jul 21, 2026
That is the new interface to all of HAL's internals nicely wrapped up from the hal query interface without someone needing to dig into HAL's internals. Hal_lib's internals are fully isolated in #4247 and nobody should be writing code that circumvents that. Inclusion of hal_priv.h is strictly forbidden in any code that is not part of hal_lib. |
rene-dev
commented
Jul 21, 2026
ah, found it. I would much prefer if it would be a c++ api, which can be used to automatically generate the python bindings. |
BsAtHome
commented
Jul 21, 2026
It is a pure C version, so it does not have the same problems as C++ interfacing C-based Python ;-) |
This is exactly the split #4251 (this PR) implements: the query API stays pure C in the library (Bertho's layer, no bypass possible), and the C++ API sits on top of it (hal.hh), which is where Python bindings get generated (halpybind) , no hand-written CPython code. halquery.c proves the query API is sufficient; the same functions can be exposed as pybind wrappers over |
BsAtHome
commented
Jul 22, 2026
Fine by me to get it C++ wrapped. It does not yet have priority one. There is one thing that should be fixed in both halmodule and halquery: use IntEnum types for both type and direction. It may be necessary to have a |
A shared pure-Python |
BsAtHome
commented
Jul 22, 2026
That would be the simple way. If it works, then it should be nice. The real benefit is in halquery to have the iteration result show the right name instead of a number. There it is big value. Probably also need to add the same for other constants.
Global namespace naming and fixing hierarchy is a problem we need to fix in a later version. This would be a version 3.0 thing, I guess ;-)
Ha, and so much for "C++ is easy to maintain" ;-) |
grandixximo
commented
Jul 23, 2026
Going back to draft, waiting for master new API to materialize ;-) |
a3ab3c4 to
33012dbCompareReintroduce a C++ interface for HAL, built strictly on the public C API and the user-land query API: no hal_priv.h, no direct shared memory access, no re-implemented library internals. - hal.hh: type-safe, header-only C++ layer in linuxcnc::hal. Typed pin/param handles via traits<T> over the rtapi_ types (compile-time accessor selection), runtime-typed pin_t variant and anypin for name-based access, component class with add_pin for the struct-member idiom, and ULAPI by-name query/set functions when the query API is present. Handles re-read the shmem slot on every access so hal_link() slot rewrites stay visible (C pointer-variable semantics). Handles are move-only; HAL objects are unique. Query callback paths are exception-free; errors are reported after the library releases the HAL mutex. - halpybind.cc: pybind11 module (halpp.so) exposing component, Pin, enums and the by-name functions. Built alongside _hal/hal.py, replacing nothing. Library init/exit at import; string set values delegate to setps_common_cb for halcmd-consistent parsing. - HAL_PORT intentionally exempted until the API break: the port handle type and creation semantics are still in flux. - tests/halpp: Python and native C++ smoke suites. Based on the pybind11 branch by rene-dev, re-based on the new HAL API.
grandixximo
commented
Aug 9, 2026
Branch rewritten and pushed (33012db => 63c8a05), four commits on current master:
Build notes: the pybind11 bindings are skipped with a note when the headers are absent, not failed, so the tree still builds without python3-pybind11; tests/halpp self-skips in that case. hal.hh is exported to include/ alongside hal.h. tests/halpp/test.sh compiles cpp_test.cc against the tree and runs it in a live HAL session, so the native C++ side has CI coverage too. Still draft on purpose: #4247 moves the same ground (hal.hh, the type system, HAL isolation), so this waits for that to land before the query bindings follow as hal.query. |
hal.hh gains linuxcnc::hal::stream, a move-only wrapper around hal_stream_t: create with a depth and a typestring, or attach to an existing key. Element types are read back from the library once at open time and drive the conversion of samples in both directions, with the same range checks the by-name setters use. Failures the library reports as a negative errno are thrown as std::system_error. halpybind exposes it as halpp.stream with the semantics of the _hal stream: read() returns a tuple or None on an empty stream, write() takes one value per element, and readable/writable/depth/maxdepth/sampleno and the underrun/overrun counters are read-only properties. A pybind11 translator turns std::system_error into OSError so the library's errno failures surface as they do in _hal. The component that owns the stream is kept alive for as long as the stream is. The module now also raises KeyboardInterrupt on SIGTERM, as _hal does. Without it a component unloaded by halcmd was killed outright, losing whatever it had buffered on stdout. tests/halpp becomes a runnable test: smoke.py grows stream coverage against the component that created the stream, stream_writer.py and stream_reader.py cover the create/attach pair across two processes the way sampler and streamer are used, and cpp_test.cc exercises the same ground from C++.
Prototype of the registration mechanics only. _hal gains an empty query submodule and hal.py publishes it in sys.modules under its dotted name, so 'import hal.query', 'from hal import query', hal.query and _hal.query all resolve to the same object even though hal is a plain module rather than a package. The submodule carries no bindings yet; tests/halquery.0 checks the four access paths, which is what has to keep working once it does.
grandixximo
commented
Aug 9, 2026
Branch reworked after feedback Bertho sent me in a private mail (63c8a05 => 7beaaf3). The haltype.py commit is gone, replaced by:
pybind11 note: real enum.IntEnum via py::native_enum needs pybind11 >= 3.0 and trixie ships 2.13, so construction goes through the enum module's functional API. The classes are genuine IntEnums either way, so moving to py::native_enum later (TODO in the header) is invisible to user code. Query results stay plain dicts as discussed; the future hal.query bindings tag type/dir with these classes, no per-function wrappers. tests/haltype.0 is now a sanity suite for the classes, tests/halpp checks halpp shares them. All HAL suites pass. |
Per Bertho's review, the tagging enums have a single source of truth in native code instead of a Python module that can drift from hal.h: - src/hal/halenum.hh builds enum.IntEnum subclasses through the plain Python C API (no pybind11 needed), with member values taken from the hal.h constants, so the classes cannot drift from the headers. Canonical members are the platform-stable spellings (BOOL, REAL, S32, U32, PORT, S64, U64 and IN..RW); SINT/UINT follow the platform width and all HAL_* spellings are aliases. - _hal registers the shared classes as _hal.type/_hal.dir (plus the HAL_WO constant, completing the direction set). hal.py serves hal.type/hal.dir and the HALType/HALDir class names lazily so the builtins stay usable inside the module; pickle round-trips through hal.HALType. - halpp drops its private py::enum_ copies, re-exports the shared classes and casts hal_type_t/hal::dir arguments and results through them: pin tags and stream element types print with their names and compare equal to the plain integer constants, which stay exported for compatibility. - tests/haltype.0 becomes a sanity suite for the classes; tests/halpp checks that halpp shares the hal.type/hal.dir objects and accepts the enums directly. TODO: when the oldest supported pybind11 is 3.0 or newer, construction can move to py::native_enum; the Python-visible classes stay enum.IntEnum either way, so user code will not notice. This replaces the haltype.py module, which is removed.
| static const halenum_member halenum_type_members[] = { | ||
| {"BOOL", HAL_BOOL}, | ||
| {"REAL", HAL_REAL}, | ||
| {"S32", HAL_S32}, | ||
| {"U32", HAL_U32}, | ||
| {"PORT", HAL_PORT}, | ||
| {"S64", HAL_S64}, | ||
| {"U64", HAL_U64}, | ||
| {"SINT", HAL_SINT}, | ||
| {"UINT", HAL_UINT}, |
There was a problem hiding this comment.
These must be ordered differently. I found out in interactive tests that the auto-complete uses the order when duplicates are found (braindead, yes). Order: bool, real, sint, uint, port, s32, u32, s64, u64 (also for the prefixed).
@rene-dev@BsAtHome - this is the unification pass we discussed in the #4099 meeting: Rene's C++/pybind11 interface rebuilt on Bertho's getter/setter API, satisfying both constraints (no HAL internals access; simple, low-maintenance, self-documenting C++ surface).
Based on master, fully additive:
_hal/hal.pyare untouched and nothing is replaced. The by-name query functions and HAL_PORT creation are compiled out when the query API header is absent and activate automatically when #4247 lands.What this adds
src/hal/hal.hh- type-safe C++ layer, header-only, innamespace linuxcnc::hal:hal::pin<T>typed handles viatraits<T>over the rtapi_ types:rtapi_s32/rtapi_sintare distinct C++ types, so 32/64 accessor selection happens at compile time with zero cost. Handles re-read the shmem slot per access, sohal_link()rewrites stay visible (same semantics as C pin pointers). Handles are move-only; HAL objects are unique.hal::pin_tvariant +hal::anypin: runtime-typed access multiplexes on the variant tag (the stored-type pattern required for name-keyed collections), with the tag supplied by the query API.hal::component: pins + params,newpin/newparam(typed and runtime-typed),add_pinfor the struct-member idiom, item access, prefix handling.get_value,set_value,set_signal,component_exists,pin_has_writer, signal management) implemented entirely onhal_get_p/hal_set_p/hal_get_s/hal_set_s/hal_comp_by_name. Nohal_priv.h, no direct shmem access, no re-implemented parsers.src/hal/halpybind.cc- pybind11 module (halpp.so), adapted from @rene-dev's bindings, extended with params and error paths. Built alongside_hal/hal.py; module replacement is a separate (3.0) discussion.src/emc/task/taskclass.cc- converted to the new API (it was the only hal.hh consumer).tests/halpp/- Python and native C++ smoke suites (both green), pluspython3-pybind11build dep.Safety properties worth noting
Relationship to the original pybind11 branch
Kept: the variant/map/PyPin structure and the binding shape.
Dropped: the private
set_commonstring parser (superseded byhal_set_p),hal_mutex_guard, theget_info_signalsstub,waitWritable(was non-functional).Compatibility aliases (
hal_comp,hal_pin<T>,hal_dir,PyPin) are provided so existing consumers of the old hal.hh keep working.Test evidence
Full tree builds;
tests/halmodule.0/.1pass; new smoke suites cover component lifecycle, typed pins/params (incl. negative S32 and 64-bit values), signals, linking, by-name get/set with coercion, port pins, and error paths. The suites auto-skip query-API checks when built without the query API, so they run on both master and the #4247 tree.