AvaloniaUI / Avalonia

Develop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The most popular .NET UI client technology
https://avaloniaui.net
MIT License
26.17k stars 2.27k forks source link

CompileBinding are unable to bind to nested property of generic class. #10485

Closed mysteryx93 closed 7 months ago

mysteryx93 commented 1 year ago

Using Avalonia 11.0.999-cibuild0030320-beta.

Set this in csproj

<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>

Add a ComboBox with SelectedItem binding.

Run.

System.BadImageFormatException: An attempt was made to load a program with an incorrect format.

In my project, I was having the error at compile-time, but I reproduced it in the blank template project and this time the error shows up at runtime instead. No idea why. I was also having another error that I'm unable to reproduce, but it could be directly related to this.

I created a repo project here Avalonia11-CompileBinding.zip

OS: Garuda Linux

thevortexcloud commented 1 year ago

The solution file seems to indicate you are building for AnyCPU. Have you tried explicitly building for x64/x86? BadImageFormatException typically indicates you are trying to use code for a different architecture.

mysteryx93 commented 1 year ago

Template only contains AnyCPU.

But in my real project, it uses unmanaged libraries and thus must target x64.

thevortexcloud commented 1 year ago

Template only contains AnyCPU.

Indeed, Which is why I was asking if you had tried the template version as x64. Because it sounds like you are possibly running into two different issues that produce the same exception type.

The template for v10 does the same thing and I have also seen it cause Avalonia to not function unless you manually change the architecture target.

mysteryx93 commented 1 year ago

OK added x64 to the solution. Getting the exact same behavior.

mysteryx93 commented 1 year ago

I've not been able to reproduce it, but when I set CompileBindings in my main projects, I also get this compile error

Member 'System.Version' is declared in another module and needs to be imported

Then if I compile again, I get

  Fody: An unhandled exception occurred:
Exception:
Format of the executable (.exe) or library (.dll) is invalid.
Type:
System.BadImageFormatException
StackTrace:
   at Mono.Cecil.PE.ImageReader.ReadImage() in C:\projects\fody\cecil\Mono.Cecil.PE\ImageReader.cs:line 48
   at Mono.Cecil.PE.ImageReader.ReadImage(Disposable`1 stream, String file_name) in C:\projects\fody\cecil\Mono.Cecil.PE\ImageReader.cs:line 766
   at Mono.Cecil.ModuleDefinition.ReadModule(Disposable`1 stream, String fileName, ReaderParameters parameters) in C:\projects\fody\cecil\Mono.Cecil\ModuleDefinition.cs:line 1146
   at Mono.Cecil.ModuleDefinition.ReadModule(String fileName, ReaderParameters parameters) in C:\projects\fody\cecil\Mono.Cecil\ModuleDefinition.cs:line 1115
   at InnerWeaver.ReadModule(String assemblyFilePath, IAssemblyResolver assemblyResolver) in C:\projects\fody\FodyIsolated\ModuleReader.cs:line 30
   at InnerWeaver.ReadModule() in C:\projects\fody\FodyIsolated\ModuleReader.cs:line 10
   at InnerWeaver.Execute() in C:\projects\fody\FodyIsolated\InnerWeaver.cs:line 100
Source:
Mono.Cecil
TargetSite:
Void ReadImage()

I doubt Reactive.Fody is to blame considering it's the same BadImageFormatException as reproduced above

thevortexcloud commented 1 year ago

Just some ideas here (I am not sure how any of this works at a low level so this is pure speculation):

Microsoft also lists some other causes but they are somewhat esoteric as far as I am concerned,

https://learn.microsoft.com/en-us/dotnet/api/system.badimageformatexception?view=net-7.0

This also assuming that Cecil library follows the same rules as what Microsoft lists.

With that said, the problem may not even be software either. I have seen bad RAM cause similar weird issues. But I am sure an Avalonia dev can shed more light on this.

maxkatz6 commented 1 year ago

Template only contains AnyCPU.

Microsoft did a huge mess with mixed runtime identifiers and platform architecture parameters. Last one isn't really useful anymore.

System.BadImageFormatException: An attempt was made to load a program with an incorrect format.

Decompiled and compiled again to find out the problem...

    static object Avalonia11.Controls.CollectionView`1,Avalonia11.CurrentItem!Getter(object P_0)
    {
        return ((CollectionView<ListItem<!0>>)P_0).CurrentItem;
    }

Compiler needs to generate code with generic, but is failing to do so. Interestingly, "ilverify" doesn't tell this problem, even though it's obvious. Moving "CurrentItem" out of the generic class works.

maxkatz6 commented 1 year ago

Discussed, and it seems like Cecil fails to import generic type for some reason. Which seems to be the same cause as in https://github.com/AvaloniaUI/Avalonia/issues/3395 and https://github.com/kekekeks/XamlX/pull/29.

Not sure if there is an easy solution unless we switch from Cecil to something like dnlib

maxkatz6 commented 1 year ago

As a workaround, you can use RelfectionBinding on this specific property (shouldn't be trimmed, if it's used in other places, or defined in Roots.xml). Or reorganize view model code.

mysteryx93 commented 1 year ago

Is it possible to set CompileBindings property via style setter for all ComboBox and ListBox?

Setting CompileBindings="False" on those solves that problem; now I got this problem

Member 'System.Version' is declared in another module and needs to be imported

It only happens if AvaloniaUseCompiledBindingsByDefault is True. It gives no hint as to where the problem comes from though. Any idea how to debug this?

I got 3 levels of instabilities to debug through. Just got #1 working (Avalonia Behaviors just fixed the problem). Has to be done one by one.

  1. Getting all components to work together with the same nightly build
  2. Getting CompileBindings to work
  3. Getting Trimming to work
mysteryx93 commented 1 year ago

This one seems to be an instance of this problem. ViewModel is defined in a generic base class to expose a strongly-typed DataContext. Although it doesn't crash in this case, but rather, binding simply fails.

Command="{Binding $parent[views:MainView].ViewModel.ShowAbout}"

Is there a work-around for this one?

mysteryx93 commented 1 year ago

Until this bug is fixed, one option could be to auto-detect when it happens and fallback to reflection binding instead of crashing?

Of course it would be much better to just solve the problem.

maxkatz6 commented 1 year ago

We can't nicely fallback from compiled bindings to reflection bindings on the flight. But developers can use ReflectionBinding in these places explicitly. If same members are used in the code (and I assume they are, since it's CurrentItem, probably used somewhere), it won't be trimmed.