Skip to content

Support decimal in automation ComVariant (fixes NumericUpDown crash under UIA) - #21634

Merged
MrJul merged 1 commit into
AvaloniaUI:masterfrom
valentino-saitz-optris:fix/comvariant-decimal
Jun 24, 2026
Merged

MrJul merged 1 commit into
AvaloniaUI:masterfrom
valentino-saitz-optris:fix/comvariant-decimal

Conversation

@valentino-saitz-optris

Copy link
Copy Markdown
Contributor

What does the pull request do?

ComVariant.Create has no case for decimal, so it throws ArgumentException for any decimal automation value. NumericUpDown.Value is a decimal?, so changing it while a UI Automation client is connected crashes the app. This adds VT_DECIMAL support to ComVariant.

This is the root-cause ComVariant fix that @maxkatz6 identified in #21515 (which was closed with the CLA unsigned). I've scoped it to just ComVariant. There are no NumericUpDownAutomationPeer changes.

Fixes #21491.

What is the current behavior?

Setup: Windows, with any UI Automation client connected so Narrator, Magnifier, Voice Access, the touch keyboard (TabTip) that Steam opens, the "Show pointer location" setting, etc. Then spinning, typing, or setting a NumericUpDown value crashes the process:

System.ArgumentException: UnsupportedType (Parameter 'Decimal')
   at Avalonia.Win32.Automation.Marshalling.ComVariant.Create(Object value)
   at Avalonia.Win32.Automation.Marshalling.ComVariantMarshaller.ConvertToUnmanaged(Object managed)
   at Avalonia.Win32.Automation.Interop.UiaCoreProviderApi.UiaRaiseAutomationPropertyChangedEvent(...)
   at Avalonia.Win32.Automation.AutomationNode.OnPeerPropertyChanged(...)
   at Avalonia.Automation.Peers.NumericUpDownAutomationPeer.OwnerPropertyChanged(...)
   at Avalonia.Controls.NumericUpDown.set_Value(Nullable`1 value)

The event is only raised when a UIA client is listening, which is why it looks like it only happens on some machines.

What is the updated/expected behavior with this PR?

decimal marshals to VT_DECIMAL and round-trips back, so the property-changed event is raised without throwing and the app keeps running.

To check: dotnet new avalonia.app, add a <NumericUpDown/>, run with Narrator on (Ctrl+Win+Enter), and change the value. It crashes on master and works with this change.

I've created a minimal demo Project to replicate and prove the issue. When running it, attach Narrator (or the included trigger-uia.ps1, which subscribes a programmatic UIA client so no screen reader is needed), then try to change the value.
UiaDecimalCrashPoc.zip

How was the solution implemented (if it's not obvious)?

Same approach as the BCL System.Runtime.InteropServices.Marshalling.ComVariant, which this type is based on:

  • A decimal field that overlaps the whole variant. A decimal doesn't use its first two bytes, so _vt still encodes the type; the existing TypeUnion layout already lines up with Decimal._flags. Automation here is Windows-only (little-endian), so there's no big-endian case to handle.
  • In Create, set the value before VarType = VT_DECIMAL, since the type discriminator overlaps the decimal's unused leading bytes.
  • In AsObject, clear the discriminator on a copy (VarType = VT_EMPTY) before returning, so the VT_DECIMAL flag doesn't leak into the value.

Checklist

  • Added unit tests (if possible)? Avalonia.Win32.Automation exposes its internals only to Avalonia.Win32 and has no unit-test project yet
  • Added XML documentation to any related classes?
  • Consider submitting a PR to avalonia-docs: No. Internal n/a (internal marshalling).

Breaking changes

None.

Obsoletions / Deprecations

None.

Fixed issues

Fixes #21491

NumericUpDown.Value is a decimal?, so changing it while a UI Automation
client is attached raised an automation property-changed event whose boxed
decimal hit no case in ComVariant.Create and threw an unhandled
ArgumentException that crashed the process. The event only fires when a UIA
client is listening, so it reproduced only on "some machines".

Mirror the BCL System.Runtime.InteropServices.Marshalling.ComVariant (which
this type is based on): overlay a decimal field on the variant, write it
before setting the VT_DECIMAL discriminator in Create (the discriminator
overlaps the decimal's unused leading bytes), and clear that discriminator on
a copy before reading the value back in AsObject.

Fixes AvaloniaUI#21491
@MrJul MrJul added bug os-windows backport-candidate-12.0.x Consider this PR for backporting to 12.0 branch labels Jun 23, 2026
@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.1.999-cibuild0066828-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@cla-avalonia

cla-avalonia commented Jun 23, 2026 •

Copy link
Copy Markdown
Collaborator
  • All contributors have signed the CLA.

@valentino-saitz-optris

Copy link
Copy Markdown
Contributor Author

@cla-avalonia agree

@MrJul MrJul left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The change matches .NET's own ComVariant.
LGTM!

@MrJul
MrJul added this pull request to the merge queue Jun 24, 2026
Merged via the queue into AvaloniaUI:master with commit 39f0ffd Jun 24, 2026
12 checks passed
@valentino-saitz-optris
valentino-saitz-optris deleted the fix/comvariant-decimal branch June 24, 2026 10:53
@MrJul MrJul removed the backport-candidate-12.0.x Consider this PR for backporting to 12.0 branch label Jul 9, 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.

NumericUpDown update causes Windows users with Narrator enabled to crash (System.ArgumentException)

4 participants