Skip to content

Fix Windows/MSVC build: Handle_X is a distinct class under MSVC - #234

Open
RM975 wants to merge 1 commit into
bschwind:mainfrom
RM975:fix-msvc-windows-build
Open

RM975 wants to merge 1 commit into
bschwind:mainfrom
RM975:fix-msvc-windows-build

Conversation

@RM975

@RM975 RM975 commented Sep 19, 2026

Copy link
Copy Markdown

Fix Windows/MSVC build (Handle_X is a distinct class under MSVC)

opencascade-sys does not build with MSVC today. I hit this while using the crate on Windows 11 with VS 2022 Build Tools (MSVC 14.44), OCCT 7.8.1 via occt-sys (builtin feature), at 32758df.

Root cause

OCCT's Standard_Handle.hxx defines Handle_X differently per compiler:

#if (defined(_MSC_VER) && _MSC_VER >= 1800)
// For Visual Studio 2013+, define Handle_Class as non-template class to allow exporting this type in C++/CLI.
#define DEFINE_STANDARD_HANDLECLASS(C1,C2,BC) class C1; class Handle_##C1 : public Handle(C1) { ... };
#else
#define DEFINE_STANDARD_HANDLECLASS(C1,C2,BC) class C1; typedef Handle(C1) Handle_##C1;
#endif

Under MSVC Handle_X is a subclass of opencascade::handle<X>, not an alias. The bridge assumes the alias in a few places, which shows up in three ways.

What this PR changes

  1. std::unique_ptr<Handle_X>(new opencascade::handle<X>(...)) fails with C2440 (base-to-derived pointer). Now new Handle_X(...), which is identical where Handle_X is a typedef (b_rep, gc, geom, geom2d, geom_api, top_tools). In GCE2d_MakeSegment_point_point the direct construction from GCE2d_MakeSegment is rejected by Handle_X's own constructors, so it goes through a local opencascade::handle<...> first.
  2. cxx binds a function/member pointer straight to an OCCT method taking Handle(X). A pointer needs an exact type, so the Handle_X parameter no longer matches (C2440 "overloaded-function"). These go through small inline wrappers instead: BRepLib_ToolTriangulatedShape::ComputeNormals, BRepOffsetAPI_MakePipeShell::SetLaw, ShapeAnalysis_FreeBounds::ConnectEdgesToWires, and handle_try_deref<T> (bound from poly.rs and top_tools.rs). The call sites in opencascade are updated.
  3. Link error: TKernel's OSD_* uses the Win32 security/registry API, so any final binary fails with ~23 unresolved externals (SetFileSecurityW, OpenProcessToken, RegOpenKeyExW, ...). Adds advapi32 next to the existing user32.

19 files, +96/-31. No behaviour change intended on GCC/Clang.

Testing

Windows 11, MSVC 14.44, OCCT 7.8.1: cargo build succeeds, and a downstream test that builds a box and a cylinder, does a union and a subtraction, checks the exact volume via BRepGProp::VolumeProperties (1e-6), and writes STEP, passes.

Not tested on Linux or macOS; I have no access to them. Reviewers there please check that nothing regresses. I also have not run the repo's own examples or cargo test for the whole workspace on Windows.

Related note (not changed here)

The #include "opencascade-sys/include/..." paths only resolve when the crate directory is literally named opencascade-sys. Cargo's registry checkout is opencascade-sys-0.3.0, so the crates.io release does not build on Windows for that reason alone (the git dependency does). I worked around it by vendoring and did not touch it here; happy to open a separate issue if useful.

🤖 Generated with Claude Code

On MSVC, OCCT's Standard_Handle.hxx defines `Handle_X` as a non-template
subclass of `opencascade::handle<X>` (for C++/CLI export), whereas on
other compilers it is a plain typedef. The C++ bridge assumed the
typedef everywhere, so opencascade-sys fails to compile with MSVC
(error C2440 / C2665) and, once that is fixed, fails to link.

Changes:

- Construct `Handle_X` directly instead of `opencascade::handle<X>` when
  wrapping in `std::unique_ptr<Handle_X>` (b_rep, gc, geom, geom2d,
  geom_api, top_tools). Identical on typedef compilers. In
  GCE2d_MakeSegment_point_point, go through a local
  `opencascade::handle<...>` first, since Handle_X's own constructors
  reject the MakeSegment implicit conversion.
- cxx binds a function/member pointer directly to some OCCT methods that
  take `Handle(X)`. A pointer needs an exact type match, so the
  `Handle_X` parameter no longer matches on MSVC. Route these through
  small inline wrappers instead:
  BRepLib_ToolTriangulatedShape::ComputeNormals,
  BRepOffsetAPI_MakePipeShell::SetLaw,
  ShapeAnalysis_FreeBounds::ConnectEdgesToWires, and the generic
  `handle_try_deref<T>` (bound from poly.rs and top_tools.rs).
  The high-level crate call sites are updated accordingly.
- Link advapi32 on Windows: TKernel's OSD_* uses the Win32 security and
  registry API, and linking any binary otherwise fails with ~23
  unresolved externals (SetFileSecurityW, OpenProcessToken, ...).

Tested only on Windows 11, MSVC 14.44 (VS 2022 17.14), OCCT 7.8.1 via
occt-sys: `cargo build` and a test that builds a box and cylinder,
subtracts/unions them, checks the exact volume, and writes STEP all pass.
Not tested on Linux or macOS; the changes are meant to be no-ops there.
@dan335

dan335 commented Oct 12, 2026

Copy link
Copy Markdown

Thanks for this. camcraft is a CAM app built on opencascade 0.3.0. It has shipped this exact patch since 23 September 2026 so that it builds with MSVC. Its vendor/ directory holds the crates.io 0.3.0 sources with this PR applied at 00b4505, and the C++ headers, bindings and build.rs match this PR's line for line. When we added it, camcraft's release and debug builds linked on Windows 11 with MSVC 14.44, and its checks passed there.

You mentioned you couldn't test on macOS or Linux, so I tried this PR's head (00b4505) on macOS with Apple clang and OCCT 7.8.1 via builtin:

  • cargo test --all-targets -p opencascade-sys -p opencascade -p examples passes.
  • write_model writes every one of the 24 examples to STL, including swept-face-variable and swept-wire-variable, which go through the new SetLaw wrapper.

camcraft is also developed on macOS and has built there with these sources since September. I haven't tried Linux.

It would be great to see this merged so camcraft can drop its vendored copy.

Vanuan added a commit to ferrocad/ferrocad that referenced this pull request Oct 25, 2026
…e-rs#234

No duplicate issue upstream: #234 is open, mergeable and a superset of the
vendored patch here (it also wraps the cxx pointer bindings and links advapi32),
with one downstream confirmation on Windows and macOS. Recorded in #1,
the backlog and edition-publishing.md; the next step is to port it onto
third_party/opencascade-sys rather than extend our own 17-line edit.
Vanuan added a commit to ferrocad/ferrocad that referenced this pull request Oct 25, 2026
`third_party/opencascade-sys` is now a verbatim 0.3.0 with
bschwind/opencascade-rs#234 applied (head 00b4505), replacing the local fix for
the `unique_ptr` sites. The PR is a superset: it also wraps the cxx
member/function-pointer bindings (`ComputeNormals`, `SetLaw`,
`ConnectEdgesToWires`, `handle_try_deref<T>`), which we had not solved, and adds
`advapi32` to the Windows link line — TKernel's `OSD_*` needs the Win32
security/registry API, so the final binary would have hit ~23 unresolved
externals after the compile errors were gone.

Only the `crates/opencascade-sys/` hunks are taken; the PR's `crates/opencascade/`
call-site updates are for a crate this workspace does not use.

`PATCHES.md` records the provenance (0.3.0 sha256 + PR head), the three failure
classes, why the directory must keep the name `opencascade-sys` (the headers use
`#include "opencascade-sys/include/…"`, which does not resolve in the registry's
`opencascade-sys-0.3.0` checkout) and the removal condition: drop both the
directory and the `[patch.crates-io]` entry once a published release carries the
fix.

Verified: `cargo check -p ferrocad_occt -p ferrocad_part` (every split bridge is
recompiled). The Windows proof is the dispatch-only
`.github/workflows/edition-windows.yml`.
@Vanuan

Vanuan commented Oct 25, 2026 •

Copy link
Copy Markdown

Facing this issue when attempting to port FreeCAD's Part workbench to rust.
This causes the windows MSVC build fail, blocking the windows app release.
For now, I'm vendoring opencascade-sys with this PR applied, but would be awesome if this was merged and released on crates. Is there anything I could help progressing this?

Alternatively, upgrading to OCCT 8.0 seems to be a way forward as it dropped legacy MSVC support.

Technical info:

Details Independent confirmation from a third downstream project [FerroCAD](https://github.com/ferrocad/ferrocad), a FreeCAD-compatible CAD app that links `opencascade-sys` through a vendored copy.

The same three failure classes on a different toolchain: VS 18 / MSVC 14.51.36231, OCCT 7.8.1 from conda-forge (not builtin), windows-latest GitHub runner.

  1. unique_ptr sites — b_rep.hxx(16/21/30) and friends, same C2440 text. Patched 17 of them to new Handle_T(…) before finding this PR; now this PR vendored directly.
  2. cxx pointer bindings — exactly b_rep_lib.rs.cc(20): cannot convert from 'overloaded-function' to 'void (__cdecl *)(const TopoDS_Face &, const Handle_Poly_Triangulation &)'. Not solved independently, this PR helped.
  3. advapi32 — Once (1) and (2) were gone this was exactly what the final link needed (SetFileSecurityW, OpenProcessToken, Reg*).

With this PR applied:

  • Windows, full pipeline green on windows-latest: cargo build -p ferrocad_occt --release, then xtask bundle --edition parametric --occt --check-init (boots the staged payload headlessly with only the bundled lib/ on the loader path), the portable zip, and ferrocad.bat --check-init on the unpacked artifact. So the kernel, the embedded CPython facade and the bundling path all work on MSVC — not just the crate compile.
  • Linux: cargo check -p ferrocad_occt -p ferrocad_part with every split bridge recompiled — no regression.
  • macOS: untested here (no hardware).

Nothing here looked risky to a consumer: the wrappers are all inline in the same headers, and behaviour on GCC/Clang is unchanged. This unblocks Windows for at least two downstream apps (camcraft and FerroCAD).

Vanuan added a commit to ferrocad/ferrocad that referenced this pull request Oct 25, 2026
…lds on MSVC

`gh workflow run edition-windows.yml` is green end to end on the vendored
`opencascade-sys` (0.3.0 + upstream PR #234): kernel build, `xtask bundle
--edition parametric --occt --check-init` boots the staged payload with only the
bundled `lib/` on the loader path, the portable zip is produced, and
`ferrocad.bat --check-init` passes on the unpacked artifact.

- `release.yml`'s `edition` matrix regains the `windows-latest` entry, so a tag
  from here yields four artifacts (two base, two Parametric). Its comment records
  the OCCT `Handle_T` dependency.
- `edition-windows.yml` stays, re-described as the dispatch-only reproduction of
  that leg rather than a pre-merge gate.
- `v0.1.4` stays documented as three artifacts (its Windows leg predated the
  fix); `releasing.md`, `distribution.md`, `edition-publishing.md` §6,
  `packaging/*`, the README and the backlog now say the Windows Parametric zip
  returns with the next tag. The backlog item is closed with the mechanism and
  the links (#1, bschwind/opencascade-rs#234).
@Vanuan Vanuan mentioned this pull request Oct 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants