Skip to content

[release/11.0.1xx-preview7] [msbuild] Don't turn missing Objective-C classes into hard link errors when inlining Class.GetHandle. Fixes #26268 - #26347

Merged
rolfbjarne merged 4 commits into
release/11.0.1xx-preview7from
dev/rolf/backport-pr-26302-release/11.0.1xx-preview7-2026-07-29
Jul 30, 2026
Merged

[release/11.0.1xx-preview7] [msbuild] Don't turn missing Objective-C classes into hard link errors when inlining Class.GetHandle. Fixes #26268#26347
rolfbjarne merged 4 commits into
release/11.0.1xx-preview7from
dev/rolf/backport-pr-26302-release/11.0.1xx-preview7-2026-07-29

Conversation

@rolfbjarne

Copy link
Copy Markdown
Member

The Class.GetHandle inlining feature (compatibility/strict InlineClassGetHandle) rewrites a surviving Class.GetHandle ("X") call into a direct native reference ([X class] -> _OBJC_CLASS_$_X). If the native Objective-C class doesn't actually exist in any linked library, the native link fails with Undefined symbols: _OBJC_CLASS_$_X, even if the class is never used at runtime.

This happens for third-party bindings that declare a [BaseType (typeof (NSObject))] type for something that is a protocol - and not a class - natively (the reported Drastic.FFMpegKit Session type is one example). bgen generates a [Protocol] attribute on the concrete class it emits for these types, and the static registrar never registers such classes, so there's no native Objective-C class to reference.

Fix: don't inline Class.GetHandle when the target class has a [Protocol] attribute (ObjCType.IsFakeProtocol). The call falls back to a runtime lookup instead, which returns a zero handle (IntPtr.Zero) for a missing native class - the same behavior these bindings had before Class.GetHandle inlining existed. The change is contained within InlineClassGetHandleStep.

The ReferenceNativeSymbol ... Ignore workarounds for the P2 and ProtocolWithBlockProperties test bindings are now redundant (they're fake protocols, handled automatically) and are removed; TestProtocolRegister keeps its workaround since it's a plain [Register] class, not a [Protocol].

Fixes #26268

🤖 Pull request created by Copilot


Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Backport of #26302.

rolfbjarne and others added 3 commits July 29, 2026 16:26
…s when inlining Class.GetHandle.

The Class.GetHandle inlining feature (compatibility/strict InlineClassGetHandle)
emits a direct native reference ([X class] -> _OBJC_CLASS_$_X) for every surviving
Class.GetHandle call. If the native Objective-C class doesn't actually exist in any
linked library, the native link fails with 'Undefined symbols: _OBJC_CLASS_$_X',
even if the class is never used at runtime.

This happens for third-party bindings that declare a [BaseType (typeof (NSObject))]
type for something that is a protocol - and not a class - natively (the reported
Drastic.FFMpegKit 'Session' type is one example). The existing __attribute__((weak_import))
on the generated @interface isn't enough: ld still errors on a weak-external undefined
symbol that isn't defined in any linked library.

Fix: only emit a direct native reference for classes we can prove exist (classes that
belong to a known platform/SDK framework). For any other class (a third-party binding
class, or a class with no managed type) we:

* pass '-U _OBJC_CLASS_$_X' to the native linker so a missing class doesn't break the
  link (this is a no-op for classes that do exist), and
* fall back to objc_getClass at runtime if the direct reference is null.

This mirrors the weak + dlsym fallback we already do for inlined dlfcn symbols, and
restores the net10 behavior (a missing class handle resolves to null) for these classes
while keeping the fast direct reference + native dead-strip protection for SDK classes.

Fixes #26268

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 07efe22e-a3b1-444f-8ae9-08325ffcaf1c
…ttribute.

The new InlineClassGetHandle optimization rewrites `Class.GetHandle ("X")`
into a direct native reference to `_OBJC_CLASS_$_X`. This breaks third-party
bindings that declare a `[BaseType (typeof (NSObject))]` type for something
that is a protocol - not a class - natively (e.g. Drastic.FFMpegKit's
`Session`). bgen generates a `[Protocol]` attribute on the concrete class it
emits for these, and the static registrar never registers such classes, so
there's no native Objective-C class to reference. Inlining the call then turns
into a hard link error (`Undefined symbols: _OBJC_CLASS_$_X`), even when the
class is never used at runtime.

Fix this by skipping the inlining when the target class has a `[Protocol]`
attribute (`ObjCType.IsFakeProtocol`). The call falls back to a runtime lookup
instead, which returns null for a missing native class - the same behavior
these bindings had before Class.GetHandle inlining existed.

This replaces the previous, more complex approach (emitting linker `-U` flags
plus an `objc_getClass` fallback), which is reverted here.

The `ReferenceNativeSymbol ... Ignore` workarounds for the P2 and
ProtocolWithBlockProperties test bindings are now redundant (they're fake
protocols, handled automatically) and are removed; TestProtocolRegister keeps
its workaround since it's a plain `[Register]` class, not a `[Protocol]`.

Fixes #26268

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 07efe22e-a3b1-444f-8ae9-08325ffcaf1c
Address review feedback: Class.GetHandle returns a NativeHandle (a value
type), so for a missing native class it returns a zero handle (IntPtr.Zero),
not null. Fix the wording in the code comment and the documentation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 07efe22e-a3b1-444f-8ae9-08325ffcaf1c

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Prevents Class.GetHandle inlining from turning “fake protocol” bindings (managed classes annotated with [Protocol] but with no corresponding native ObjC class) into hard native link failures, restoring the pre-inlining behavior of runtime lookup returning IntPtr.Zero for missing classes.

Changes:

  • Skip Class.GetHandle ("X") inlining when the resolved target ObjCType is IsFakeProtocol (has [Protocol] on a non-protocol, registered type).
  • Remove now-redundant ReferenceNativeSymbol ... Ignore entries for fake-protocol test bindings (P2, ProtocolWithBlockProperties) in linker test projects.
  • Document the fake-protocol scenario and the rationale for the inlining exception.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
tools/dotnet-linker/Steps/InlineClassGetHandleStep.cs Adds an IsFakeProtocol guard to avoid emitting hard _OBJC_CLASS_$_X references for fake protocols.
tests/linker/trimmode link/dotnet/shared.csproj Removes unnecessary ReferenceNativeSymbol ignores for fake-protocol types; keeps the non-protocol workaround.
tests/linker/link all/dotnet/shared.csproj Same as above for the “link all” test configuration.
docs/code/class-handles.md Documents why [Protocol]-annotated fake protocol classes must not be inlined.

@rolfbjarne rolfbjarne added the ready-to-review This PR is ready to review/merge. label Jul 29, 2026
@rolfbjarne
rolfbjarne enabled auto-merge (squash) July 29, 2026 15:02
@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

✅ API diff for current PR / commit

NET (empty diffs)

✅ API diff vs stable

NET (empty diffs)

ℹ️ Generator diff

Generator Diff: vsdrops (html) vsdrops (raw diff) gist (raw diff) - Please review changes)

Pipeline on Agent
Hash: a37156ec4f4d61d784603e22beadc7901a921dd7 [PR build]

@vs-mobiletools-engineering-service2

Copy link
Copy Markdown
Collaborator

🚀 [CI Build #a37156e] Test results 🚀

Test results

✅ All tests passed on VSTS: test results.

🎉 All 256 tests passed 🎉

Tests counts

✅ assembly-processing: All 1 tests passed. Html Report (VSDrops) Download
✅ cecil: All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (iOS): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (MacCatalyst): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (macOS): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (Multiple platforms): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (tvOS): All 1 tests passed. Html Report (VSDrops) Download
✅ framework: All 2 tests passed. Html Report (VSDrops) Download
✅ fsharp: All 4 tests passed. Html Report (VSDrops) Download
✅ generator: All 5 tests passed. Html Report (VSDrops) Download
✅ interdependent-binding-projects: All 4 tests passed. Html Report (VSDrops) Download
✅ introspection: All 7 tests passed. Html Report (VSDrops) Download
✅ linker (iOS): All 31 tests passed. Html Report (VSDrops) Download
✅ linker (MacCatalyst): All 31 tests passed. Html Report (VSDrops) Download
✅ linker (macOS): All 21 tests passed. Html Report (VSDrops) Download
✅ linker (tvOS): All 31 tests passed. Html Report (VSDrops) Download
✅ monotouch (iOS): All 23 tests passed. Html Report (VSDrops) Download
✅ monotouch (MacCatalyst): All 23 tests passed. Html Report (VSDrops) Download
✅ monotouch (macOS): All 18 tests passed. Html Report (VSDrops) Download
✅ monotouch (tvOS): All 23 tests passed. Html Report (VSDrops) Download
✅ msbuild: All 2 tests passed. Html Report (VSDrops) Download
✅ sharpie: All 1 tests passed. Html Report (VSDrops) Download
✅ windows: All 3 tests passed. Html Report (VSDrops) Download
✅ xcframework: All 4 tests passed. Html Report (VSDrops) Download
✅ xtro: All 1 tests passed. Html Report (VSDrops) Download

macOS tests

✅ Tests on macOS Sonoma (14): All 5 tests passed. Html Report (VSDrops) Download
✅ Tests on macOS Sequoia (15): All 5 tests passed. Html Report (VSDrops) Download
✅ Tests on macOS Tahoe (26): All 5 tests passed. Html Report (VSDrops) Download

Linux Build Verification

Linux build succeeded

Pipeline on Agent
Hash: a37156ec4f4d61d784603e22beadc7901a921dd7 [PR build]

@rolfbjarne
rolfbjarne merged commit 07928c6 into release/11.0.1xx-preview7 Jul 30, 2026
54 checks passed
@rolfbjarne
rolfbjarne deleted the dev/rolf/backport-pr-26302-release/11.0.1xx-preview7-2026-07-29 branch July 30, 2026 09:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backported ready-to-review This PR is ready to review/merge.

Projects

None yet

4 participants