[release/11.0.1xx-preview7] [msbuild] Don't turn missing Objective-C classes into hard link errors when inlining Class.GetHandle. Fixes #26268 - #26347
Conversation
…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
There was a problem hiding this comment.
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 targetObjCTypeisIsFakeProtocol(has[Protocol]on a non-protocol, registered type). - Remove now-redundant
ReferenceNativeSymbol ... Ignoreentries 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. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…302-release/11.0.1xx-preview7-2026-07-29
✅ API diff for current PR / commitNET (empty diffs)✅ API diff vs stableNET (empty diffs)ℹ️ Generator diffGenerator Diff: vsdrops (html) vsdrops (raw diff) gist (raw diff) - Please review changes) Pipeline on Agent |
🚀 [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 macOS tests✅ Tests on macOS Sonoma (14): All 5 tests passed. Html Report (VSDrops) Download Linux Build VerificationPipeline on Agent |
The
Class.GetHandleinlining feature (compatibility/strictInlineClassGetHandle) rewrites a survivingClass.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 withUndefined 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.FFMpegKitSessiontype 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.GetHandlewhen 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 beforeClass.GetHandleinlining existed. The change is contained withinInlineClassGetHandleStep.The
ReferenceNativeSymbol ... Ignoreworkarounds for theP2andProtocolWithBlockPropertiestest bindings are now redundant (they're fake protocols, handled automatically) and are removed;TestProtocolRegisterkeeps 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.