il-repack icon indicating copy to clipboard operation
il-repack copied to clipboard

WPF App not starting after IL Repack

Open robertmuehsig opened this issue 7 years ago • 23 comments

Hi

We tried to use ILRepack to get a single exe of our WPF application, but it didn't work and we are not sure how to fix it - maybe we miss something?

The full source of the application can be found here: https://github.com/Sevitec/oneoffixx-connectclient/

Repo-Steps:

  1. Build the application via the build.ps1
  2. Under this path ".\Windows\src\OneOffixx.ConnectClient.WinApp\bin\Release" you should see this: image
  3. Now we invoke ILRepack like this:

(C:\Users\amacher\Documents\GitHub\oneoffixx-connectclient\ must be replaced with your clone directory)

ILRepack.exe /verbose /log /out:C:\Users\amacher\Documents\GitHub\oneoffixx-connectclient\Windows\src\OneOffixx.ConnectClient.WinApp\bin\Release\merged\Merged.exe C:\Users\amacher\Documents\GitHub\oneoffixx-connectclient\Windows\src\OneOffixx.ConnectClient.WinApp\Release\Debug\OneOffixx.ConnectClient.WinApp.exe C:\Users\amacher\Documents\GitHub\oneoffixx-connectclient\Windows\src\OneOffixx.ConnectClient.WinApp\Release\Debug\System.Windows.Interactivity.dll C:\Users\amacher\Documents\GitHub\oneoffixx-connectclient\Windows\src\OneOffixx.ConnectClient.WinApp\Release\Debug\MahApps.Metro.dll C:\Users\amacher\Documents\GitHub\oneoffixx-connectclient\Windows\src\OneOffixx.ConnectClient.WinApp\Release\Debug\Microsoft.Practices.ServiceLocation.dll C:\Users\amacher\Documents\GitHub\oneoffixx-connectclient\Windows\src\OneOffixx.ConnectClient.WinApp\Release\Debug\ICSharpCode.AvalonEdit.dll C:\Users\amacher\Documents\GitHub\oneoffixx-connectclient\Windows\src\OneOffixx.ConnectClient.WinApp\Release\Debug\FontAwesome.WPF.dll

The full log output can be found here

  1. Result is a Merged.exe, but it fails to start.

The Windows-Eventlog tells me this:

Anwendung: Merged.exe
Frameworkversion: v4.0.30319
Beschreibung: Der Prozess wurde aufgrund einer unbehandelten Ausnahme beendet.
Ausnahmeinformationen: System.IO.FileNotFoundException
bei System.Reflection.RuntimeAssembly._nLoad(System.Reflection.AssemblyName, System.String, System.Security.Policy.Evidence, System.Reflection.RuntimeAssembly, System.Threading.StackCrawlMark ByRef, IntPtr, Boolean, Boolean, Boolean)
bei System.Reflection.RuntimeAssembly.nLoad(System.Reflection.AssemblyName, System.String, System.Security.Policy.Evidence, System.Reflection.RuntimeAssembly, System.Threading.StackCrawlMark ByRef, IntPtr, Boolean, Boolean, Boolean)
bei System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(System.Reflection.AssemblyName, System.Security.Policy.Evidence, System.Reflection.RuntimeAssembly, System.Threading.StackCrawlMark ByRef, IntPtr, Boolean, Boolean, Boolean)
bei System.Reflection.Assembly.Load(System.Reflection.AssemblyName)
bei System.Windows.Navigation.BaseUriHelper.GetLoadedAssembly(System.String, System.String, System.String)
bei MS.Internal.AppModel.ResourceContainer.GetResourceManagerWrapper(System.Uri, System.String ByRef, Boolean ByRef)
bei MS.Internal.AppModel.ResourceContainer.GetPartCore(System.Uri)
bei System.IO.Packaging.Package.GetPartHelper(System.Uri)
bei System.IO.Packaging.Package.GetPart(System.Uri)
bei System.Windows.Application.GetResourceOrContentPart(System.Uri)
bei System.Windows.Application.LoadComponent(System.Object, System.Uri)
bei OneOffixx.ConnectClient.WinApp.App.InitializeComponent()
bei OneOffixx.ConnectClient.WinApp.App.Main()

Any ideas?

Note that we don't reference ILRepack directly - we saw this in your sample application, but even with the reference it didn't worked.

robertmuehsig avatar Oct 21 '16 12:10 robertmuehsig

Just ran into this exact issue. In my case, the application was trying to reference a MahApps.Metro part and failed to "find" MahApps.Metro. Placing the dll allowed it to work (but obviously isn't ideal).

RichiCoder1 avatar Dec 14 '16 06:12 RichiCoder1

Does the merged file still reference any DLLs besides the default ones?

Also, which ILRepack version are you using?

timotei avatar Dec 14 '16 14:12 timotei

You'll have to elaborate on what you mean by "default ones".

Ran into this issue with latest ILRepack (2.0.12 as of writing).

RichiCoder1 avatar Dec 14 '16 14:12 RichiCoder1

My target was to get a single .exe, but I need to try if the "place MahApps.Metro.dll beside the .exe" works for my application. The workaround is of course not ideal, but maybe helps to find the root cause.

robertmuehsig avatar Dec 14 '16 14:12 robertmuehsig

@RichiCoder1 Sorry. By "default ones" I mean the .NET Framework dependencies (System, System.Data, etc), that are all available by default in a vanilla .NET installation.

@robertmuehsig I'll try and find some time next week to test your scenario. But it's weird, since we already have an integration test for testing out the repack on MahApps and System.Interactivity: https://github.com/gluck/il-repack/tree/master/ILRepack.IntegrationTests/Scenarios/WPFSampleApplication

timotei avatar Dec 16 '16 18:12 timotei

I had a similar problem (my stack trace was not the same as the one shown here) and my problem was changing the name of the assembly while merging. At first my /out param was "$(TargetDir)\ProgramRepack.exe" and the app would crash before it could even boot up WPF. After chaning the /out param to "$(TargetDir)\merged\Program.exe" everything worked as expected.

smack0007 avatar Jun 16 '17 15:06 smack0007

I tried the following with the latest ILRepack (I copied the ILRepack.exe in the bin/debug folder so I don't have those long paths)

ILRepack.exe /verbose /log /out:merged/OneOffixx.ConnectClient.WinApp.exe OneOffixx.ConnectClient.WinApp.exe System.Windows.Interactivity.dll Microsoft.Practices.ServiceLocation.dll FontAwesome.WPF.dll ICSharpCode.AvalonEdit.dll MahApps.Metro.dll  

In this case I don't rename the resulting exe anymore.

Result: Still app crash on startup.

Strangly many other WPFs works as expected (even the MahApps sample app). Then I started to remove code until it loads and this is the result:

I just cleaned up the mainview.xaml:

<Controls:MetroWindow  x:Class="OneOffixx.ConnectClient.WinApp.MainWindow"
        xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:OneOffixx.ConnectClient.WinApp"
        xmlns:views = "clr-namespace:OneOffixx.ConnectClient.WinApp.Views"
        mc:Ignorable="d"
        xmlns:fa="clr-namespace:FontAwesome.WPF;assembly=FontAwesome.WPF"
        Title="OneOffixx Connect Client" Height="800" Width="1100" MinHeight="400" MinWidth="700"
        Icon="/Ressources/OneConnectIcon.ico"
        WindowStartupLocation="CenterScreen"
        BorderBrush="{DynamicResource AccentColorBrush}"
        BorderThickness="0"
        GlowBrush="{DynamicResource NonActiveBorderColorBrush}"
        NonActiveWindowTitleBrush="{DynamicResource AccentColorBrush}"
        ShowIconOnTitleBar="False">
    <Window.InputBindings>
        <KeyBinding Command="{Binding Path=Close}" Key="Esc" />
    </Window.InputBindings>
    <Controls:MetroWindow.RightWindowCommands>
        <Controls:WindowCommands>
            <Button Command="{Binding Path=OpenFlyout}">
                <StackPanel Orientation="Horizontal">
                    <fa:ImageAwesome Icon="Info" Height="12" Width="12" Foreground="White" />
                </StackPanel>
            </Button>
        </Controls:WindowCommands>
    </Controls:MetroWindow.RightWindowCommands>
    <Controls:MetroWindow.Flyouts>
        <Controls:FlyoutsControl>
            <Controls:Flyout Header="Info" HorizontalAlignment="Center" Position="Right" Width="300" IsOpen="{Binding Path=IsFlyoutOpen}"  Background="#EA4333">
                <!--<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
                        <TextBlock Margin="64,0,0,0" FontSize="16" Text="Version:" />
                        <TextBlock Name="AppVersion" Margin="10,0,0,0" FontSize="16" Text="{Binding Path=VersionNumber, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
                    </StackPanel>

                    <StackPanel Margin="4,0,4,0" VerticalAlignment="Bottom">
                        <TextBlock Margin="4,0,4,20" FontWeight="Bold" Text="Used Open Source Libraries:" />
                        <Grid Margin="4,0,4,20">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto"/>
                                <ColumnDefinition Width="Auto"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>

                            <TextBlock Grid.Row="0" Grid.Column="0" Text="MahApps" Height="20"/>
                            <Button Style="{StaticResource AccentedSquareButtonStyle}" Height="20" VerticalAlignment="Center" Background="Transparent" BorderBrush="Transparent" Click="InvokeLicenseUrl" ToolTip="http://mahapps.com/" Margin="4,0,4,2" Grid.Row="0" Grid.Column="1" >
                                <Button.Content>
                                    <fa:ImageAwesome Icon="Globe" ></fa:ImageAwesome>
                                </Button.Content>
                            </Button>
                            <TextBlock TextWrapping="Wrap" Grid.Row="0" Height="20" Grid.Column="2" Text="MIT" />
                            <TextBlock Grid.Row="1" Grid.Column="0" Height="20" Text="Font Awesome" />
                            <Button Style="{StaticResource AccentedSquareButtonStyle}" VerticalAlignment="Center" Height="20" Background="Transparent" BorderBrush="Transparent" Click="InvokeLicenseUrl" ToolTip="http://fontawesome.io/" Margin="4,0,4,2" Grid.Row="1" Grid.Column="1" >
                                <Button.Content>
                                    <fa:ImageAwesome Icon="Globe" ></fa:ImageAwesome>
                                </Button.Content>
                            </Button>
                            <TextBlock Grid.Row="1" Grid.Column="2" Text="MIT" Height="20" />

                            <TextBlock Grid.Row="2" Grid.Column="0" Text="AvalonEdit" Height="20" />
                            <Button Style="{StaticResource AccentedSquareButtonStyle}" Height="20" VerticalAlignment="Center" Background="Transparent" BorderBrush="Transparent" Click="InvokeLicenseUrl" ToolTip="http://avalonedit.net/" Margin="4,0,4,2" Grid.Row="2" Grid.Column="1" >
                                <Button.Content>
                                    <fa:ImageAwesome Icon="Globe" ></fa:ImageAwesome>
                                </Button.Content>
                            </Button>
                            <TextBlock Grid.Row="2" Grid.Column="2" Height="20" Text="MIT" />

                            <TextBlock Grid.Row="3" Grid.Column="0" Text="FontAwesome.WPF" Height="20" />
                            <Button Style="{StaticResource AccentedSquareButtonStyle}" Height="20" VerticalAlignment="Center" Background="Transparent" BorderBrush="Transparent" Click="InvokeLicenseUrl" ToolTip="https://github.com/charri/Font-Awesome-WPF" Margin="4,0,4,2" Grid.Row="3" Grid.Column="1" >
                                <Button.Content>
                                    <fa:ImageAwesome Icon="Globe" ></fa:ImageAwesome>
                                </Button.Content>
                            </Button>
                            <TextBlock Grid.Row="3" Grid.Column="2" Height="20" Text="MIT" />
                        </Grid>
                        <Button Click="ShowFullLicense" Style="{StaticResource AccentedSquareButtonStyle}" Foreground="Black" HorizontalAlignment="Left" Content="Show Licenses" Background="#EA4333" BorderBrush="#EA4333" />
                    </StackPanel>
                </Grid>-->
            </Controls:Flyout>
        </Controls:FlyoutsControl>
    </Controls:MetroWindow.Flyouts>
    <Grid>
        <!--<views:Shell x:Name="RequestViewControl"/>-->
    </Grid>
</Controls:MetroWindow >

I removed almost everything - the "mainshell" and the flyout control just to get it running. When I include the content of the Controls:Flyout control it will crash after ILRepack. Even just the simple stackpanel with a static text will result in an app crash at startup.

EventViewer tells me this. A similar exception can be seen with the most recent MahApps Metro sample app when you change the accent or theme at runtime after ILRepack

Application: OneOffixx.ConnectClient.WinApp.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.IO.FileNotFoundException
   at System.Reflection.RuntimeAssembly._nLoad(System.Reflection.AssemblyName, System.String, System.Security.Policy.Evidence, System.Reflection.RuntimeAssembly, System.Threading.StackCrawlMark ByRef, IntPtr, Boolean, Boolean, Boolean)
   at System.Reflection.RuntimeAssembly.nLoad(System.Reflection.AssemblyName, System.String, System.Security.Policy.Evidence, System.Reflection.RuntimeAssembly, System.Threading.StackCrawlMark ByRef, IntPtr, Boolean, Boolean, Boolean)
   at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(System.Reflection.AssemblyName, System.Security.Policy.Evidence, System.Reflection.RuntimeAssembly, System.Threading.StackCrawlMark ByRef, IntPtr, Boolean, Boolean, Boolean)
   at System.Reflection.Assembly.Load(System.Reflection.AssemblyName)
   at System.Windows.Navigation.BaseUriHelper.GetLoadedAssembly(System.String, System.String, System.String)
   at MS.Internal.AppModel.ResourceContainer.GetResourceManagerWrapper(System.Uri, System.String ByRef, Boolean ByRef)
   at MS.Internal.AppModel.ResourceContainer.GetPartCore(System.Uri)
   at System.IO.Packaging.Package.GetPartHelper(System.Uri)
   at System.IO.Packaging.Package.GetPart(System.Uri)
   at System.IO.Packaging.PackWebResponse+CachedResponse.GetResponseStream()
   at System.IO.Packaging.PackWebResponse.GetResponseStream()
   at System.IO.Packaging.PackWebResponse.get_ContentType()
   at MS.Internal.WpfWebRequestHelper.GetContentType(System.Net.WebResponse)
   at MS.Internal.WpfWebRequestHelper.GetResponseStream(System.Net.WebRequest, MS.Internal.ContentType ByRef)
   at System.Windows.ResourceDictionary.set_Source(System.Uri)
   at MahApps.Metro.AppTheme..ctor(System.String, System.Uri)
   at MahApps.Metro.ThemeManager.get_AppThemes()

Exception Info: MahApps.Metro.MahAppsException
   at MahApps.Metro.ThemeManager.get_AppThemes()
   at MahApps.Metro.ThemeManager.GetAppTheme(System.Windows.ResourceDictionary)
   at MahApps.Metro.ThemeManager.DetectThemeFromResources(MahApps.Metro.AppTheme ByRef, System.Windows.ResourceDictionary)
   at MahApps.Metro.ThemeManager.DetectAppStyle(System.Windows.ResourceDictionary)
   at MahApps.Metro.ThemeManager.DetectAppStyle(System.Windows.Window)
   at MahApps.Metro.Controls.Flyout.DetectTheme(MahApps.Metro.Controls.Flyout)
   at MahApps.Metro.Controls.Flyout.UpdateFlyoutTheme()
   at MahApps.Metro.Controls.Flyout.<.ctor>b__93_0(System.Object, System.Windows.RoutedEventArgs)
   at System.Windows.RoutedEventHandlerInfo.InvokeHandler(System.Object, System.Windows.RoutedEventArgs)
   at System.Windows.EventRoute.InvokeHandlersImpl(System.Object, System.Windows.RoutedEventArgs, Boolean)
   at System.Windows.UIElement.RaiseEventImpl(System.Windows.DependencyObject, System.Windows.RoutedEventArgs)
   at System.Windows.UIElement.RaiseEvent(System.Windows.RoutedEventArgs)
   at System.Windows.BroadcastEventHelper.BroadcastEvent(System.Windows.DependencyObject, System.Windows.RoutedEvent)
   at System.Windows.BroadcastEventHelper.BroadcastLoadedEvent(System.Object)
   at MS.Internal.LoadedOrUnloadedOperation.DoWork()
   at System.Windows.Media.MediaContext.FireLoadedPendingCallbacks()
   at System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()
   at System.Windows.Media.MediaContext.RenderMessageHandlerCore(System.Object)
   at System.Windows.Media.MediaContext.RenderMessageHandler(System.Object)
   at System.Windows.Media.MediaContext.Resize(System.Windows.Media.ICompositionTarget)
   at System.Windows.Interop.HwndTarget.OnResize()
   at System.Windows.Interop.HwndTarget.HandleMessage(MS.Internal.Interop.WindowMessage, IntPtr, IntPtr)
   at System.Windows.Interop.HwndSource.HwndTargetFilterMessage(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)
   at MS.Win32.HwndWrapper.WndProc(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)
   at MS.Win32.HwndSubclass.DispatcherCallbackOperation(System.Object)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Int32)
   at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(System.Object, System.Delegate, System.Object, Int32, System.Delegate)
   at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(System.Windows.Threading.DispatcherPriority, System.TimeSpan, System.Delegate, System.Object, Int32)
   at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr, Int32, IntPtr, IntPtr)
   at MS.Win32.UnsafeNativeMethods.CallWindowProc(IntPtr, IntPtr, Int32, IntPtr, IntPtr)
   at MS.Win32.HwndSubclass.DefWndProcWrapper(IntPtr, Int32, IntPtr, IntPtr)
   at MS.Win32.UnsafeNativeMethods.CallWindowProc(IntPtr, IntPtr, Int32, IntPtr, IntPtr)
   at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr, Int32, IntPtr, IntPtr)

robertmuehsig avatar Jul 06 '17 21:07 robertmuehsig

MahApps theme/accent seem to use an hardcoded reference to MahApps.Metro assembly, which ILRepack doesn't fix: https://github.com/batzen/MahApps.Metro/blob/master/src/MahApps.Metro/MahApps.Metro.Shared/ThemeManager/ThemeManager.cs#L71

There doesn't seem to be a way in MahApps to workaround that (beside crazy reflection hack, which you could try to check if there's no issue beyond that one).

At first sight ILRepack could fix that one by replacing any string it finds in the code which matches the (pseudo-)pattern pack://application:,,,/[MERGED_ASSEMBLY];*. That wouldn't work for more trickier cases.

Ideally MahApps would change those URL by using the more flexible: pack://application:,,,/{typeof(ThemeManager).Assembly.Name};*

Which would no longer tie the app to the DLL name.

gluck avatar Jul 07 '17 07:07 gluck

Ah - will try to change it and see if MahApps will take it via a PR. Just to make sure I understand - such resources in XAML are detected by ILRepack and handled correctly, right?

https://github.com/batzen/MahApps.Metro/blob/master/src/MahApps.Metro.Samples/MahApps.Metro.Demo/MahApps.Metro.Demo.Shared/App.xaml#L10-L16

Hard coded resources like pack://application:,,,,[ANYTHING THAT WILL BE MERGED] in code are problematic, right?

robertmuehsig avatar Jul 07 '17 07:07 robertmuehsig

Correct

gluck avatar Jul 07 '17 07:07 gluck

FYI, here's the code that does the patching: https://github.com/gluck/il-repack/blob/master/ILRepack/Steps/XamlResourcePathPatcherStep.cs#L68 (for the code in InitializeComponent which uses also this kind of URI Paths)

I even put a TODO at the top to maybe revisit this class later when we'll reach such cases like this one. Maybe adding a MahApps specific case (like I did for WPFToolkit) isn't a good idea. We could just go and fix the strings which are part of an Uri instance (Contributions are welcome). But that... might increase too much the repack time (didn't measure it btw)

timotei avatar Jul 07 '17 18:07 timotei

I try this approach and changed all lines which points directly to MahApps.Metro to a method which invokes the following code:

private static string GetCurrentAssemblyName()
    {
        return typeof(ThemeManager).Assembly.GetName().Name;
    }

Sadly this fails horrible with this exception at startup:

Exception Info: System.IO.IOException
   at MS.Internal.AppModel.ResourcePart.GetStreamCore(System.IO.FileMode, System.IO.FileAccess)
   at System.IO.Packaging.PackagePart.GetStream(System.IO.FileMode, System.IO.FileAccess)
   at System.IO.Packaging.PackWebResponse+CachedResponse.GetResponseStream()
   at System.IO.Packaging.PackWebResponse.get_ContentType()
   at MS.Internal.WpfWebRequestHelper.GetContentType(System.Net.WebResponse)
   at MS.Internal.WpfWebRequestHelper.GetResponseStream(System.Net.WebRequest, MS.Internal.ContentType ByRef)
   at System.Windows.ResourceDictionary.set_Source(System.Uri)
   at MahApps.Metro.Accent..ctor(System.String, System.Uri)
   at MahApps.Metro.ThemeManager.get_Accents()

After ILRepack the method will return "MetroDemo" (which is the name of the exe, not the "source" dlls name). To get a closer look I used ILSpy to see where ILRepack stores the resources etc.

Without ILRepack it looks like this:

image

With ILRepack it looks like this:

image

The desired resources are embedded with the original assembly name.

Just for fun I tried this and everything worked: $"pack://application:,,,/{GetCurrentAssemblyName()};component/mahapps.metro/Styles/Accents/{color}.xaml

As far as I understand ILRepack: ILRepack will move all resources etc. in the target assembly, but will prefix those resources with the original name. In the case of MahApps this will work for xaml stuff, but the ThemeManager (and the Dialog-System) tries to load resources via code.

My plan was just to make the MahApps code more flexible, but due to the prefixing this seems not very easy or do I miss something?

The code can be found in my MahApps fork here

For reference: I use this line to generate the merged assembly - maybe there is a switch or something to include specific resources in the "root"?

ILRepack.exe /verbose /log /out:merged/MetroDemo.exe MetroDemo.exe JetBrains.Annotations.dll MahApps.Metro.dll MahApps.Metro.IconPacks.dll NHotkey.dll NHotkey.Wpf.dll System.Windows.Interactivity.dll

robertmuehsig avatar Jul 07 '17 21:07 robertmuehsig

Missed the prefix indeed. Probably required to avoid conflicts but it will break for sure anytime someone loads a xaml resource dynamically. Option could be prefix only if conflict (won't be a perfect fix - not even a clean one - but will maybe cover most of real use cases ? that's what ILRepack does with types)

gluck avatar Jul 10 '17 07:07 gluck

This might work. As an alternative: Maybe the ILRepack user could use a special argument so that ILRepack doesn't do the prefixing:

ILRepack.exe /verbose /log /out:merged/MetroDemo.exe MetroDemo.exe JetBrains.Annotations.dll MahApps.Metro.dll MahApps.Metro.IconPacks.dll NHotkey.dll NHotkey.Wpf.dll System.Windows.Interactivity.dll /noXamlPrefix:MahApps.Metro.dll  

robertmuehsig avatar Jul 11 '17 06:07 robertmuehsig

Either way LGTM, the config way will be less "plug&play" (unlikely everybody using MahApps will understand why they need it), but also less magical 😄

gluck avatar Jul 11 '17 07:07 gluck

If we put the prefix skipping, we'll end up with potential duplicates which might be worse in the long run (e.g., Themes.xaml have a high chance to conflict since they have the same paths on all WPF apps -> kaboom).

Since MahApps is pretty known/used, maybe it would be fine to have a specific case for it also, like we have for WPFToolkit.

timotei avatar Jul 11 '17 19:07 timotei

Maybe yes, we're also a bit short on experience on WPF repacking to know what the right setting/behavior should be.

gluck avatar Jul 12 '17 07:07 gluck

I am pretty sure that I am experiencing exactly this issue with AvalonEdit and both Automatic ILMerge and ILRepack. Just add it to a Project and it falls over when trying to load the AvalonEditdit Control.

Be nice if someone could work out the command parameter to specify this if fixable that is in this way or else work out a way using say Fody or anything else to patch the Resource URI references in the assemblies: sounds like the go-to tool and be a nice project or addition that could fix this automatically..

Should be able to find all those application path Uri strings in all ilrepack-ed assemblies and change them!.. Be the perfect task for someone who knows and loves .NET IL way more than they should haha.

Going to make a third attempt per https://github.com/Fody/Costura/blob/develop/readme.md "In .NET Core 3 there are two new features: Single-file executables Assembly linking"

Then from https://docs.microsoft.com/en-us/dotnet/core/whats-new/dotnet-core-3-0#single-file-executables "dotnet publish"

Sounds like it might be the answer and worth a shot that I am seeking.. if it supports WPF and all of my dependencies are supported or will port to .NET Core 3 or Standard 2.1.. ..

matthewsheeran avatar Apr 02 '20 09:04 matthewsheeran

@matthewsheeran I have this working over here with a MahApps (with ThemeManager) + AvalonEdit WPF app targeting net452 that I build using dotnet build. No Costura.Fody/ILRepack/ILMerge required. Just collect your assemblies somehow (I fished them out of my bin folder), stick them in an Assemblies directory next to your .csproj, then in your .csproj inside <Project> add:

  <PropertyGroup>
    <StartupObject>YOURNAMESPACE.Program</StartupObject>
  </PropertyGroup>

  <ItemGroup>
    <EmbeddedResource Include="Assemblies\*.dll" />
  </ItemGroup>

Add a Program.cs next to your .csproj:

using System;
using System.Reflection;

namespace YOURNAMESPACE {
  public class Program {
    [STAThreadAttribute()]
    public static void Main() {
      AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => {
        using(var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(
          $"FOO.{typeof(Program).Namespace.ToString()}.Assemblies.{new AssemblyName(args.Name).Name}.dll")) {
          Byte[] assemblyData = new Byte[stream.Length];
          stream.Read(assemblyData, 0, assemblyData.Length);
          return Assembly.Load(assemblyData);
        }
      };

      App app = new App();
      app.InitializeComponent();
      app.Run();
    }
  }
}

where FOO = everything before YOURNAMESPACE in the <RootNamespace> tag defined in your .csproj.

ghost avatar May 30 '20 14:05 ghost

@giandvd It was really nice of you to share this code! I've written much more complicated AsemblyResolve's and don't like having to do it but I really do like the simplicity of your code and I will be using it as I was afterwards looking up this exact kind of approach: many thanks again.

MahApps (with ThemeManager) has some weird Resource loading behaviours so if it works - and methinks that your approach is the only one that actually will! - then I will also be impressed.

I'd even look at dropping the YOURNAMESPACE, YOURNAMESPACE.Program and Assemblies\ to make the code drop-in-able to an existing project (I don't have any problem putting some common code in the global root namespace myself)..

Regards, Matthew

matthewsheeran avatar May 31 '20 09:05 matthewsheeran

@matthewsheeran weird resource loading behaviours indeed - if you try the code as-is, it will mostly work but control styles will be slightly off as MahApps seems to fail to load the merged resource dictionaries in App.xaml, as you were hinting at. I didn't notice this at first. I have worked out a fix. Use this Program.cs instead:

using System;
using System.Reflection;
using System.Linq;
using System.Windows;

namespace Editor {
  public class Program {
    private static Assembly ExecutingAssembly = Assembly.GetExecutingAssembly();
    private static string[] EmbeddedLibraries =
      ExecutingAssembly.GetManifestResourceNames().Where(x => x.EndsWith(".dll")).ToArray();

    [STAThreadAttribute]
    public static void Main() {
      AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => {
        var assemblyName = new AssemblyName(args.Name).Name + ".dll";
        var resourceName = EmbeddedLibraries.FirstOrDefault(x => x.EndsWith(assemblyName));
        if(resourceName == null) return null;

        using(var stream = ExecutingAssembly.GetManifestResourceStream(resourceName)) {
          var bytes = new byte[stream.Length];
          stream.Read(bytes, 0, bytes.Length);
          var assembly = Assembly.Load(bytes);

          if(assemblyName == "MahApps.Metro.dll") {
            foreach(var xaml in new[]{"Controls", "Fonts"}) {
              ResourceDictionary rd = new ResourceDictionary();
              rd.Source = new Uri($"pack://application:,,,/MahApps.Metro;component/Styles/{xaml}.xaml");
              Application.Current.Resources.MergedDictionaries.Add(rd);
            }
          }

          return assembly;
        }
      };

      App.Main();
    }
  }
}

I found it in a SO question and it's nicer than mine as you don't need to explicitly specify namespace names. I have added the fix for MahApps, namely to load its XAMLs (which you'd normally put in App.xaml) right after loading its assembly. So in my case in App.xaml I had:

<Application.Resources>
  <ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
      <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
      <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
    </ResourceDictionary>
  </Application.Resources>
 </Application>

and I just removed MahApps.Metro's ResourceDictionaries from it and loaded them in Program.cs instead, as above.

As for embedding the assemblies, it turns out MSBuild can be told to do this, just drop the following in your .csproj. inside <Project>, and it should just work.

  <Target Name="EmbedReferencedAssemblies" AfterTargets="ResolveAssemblyReferences">
    <ItemGroup>
      <AssembliesToEmbed Include="@(ReferenceCopyLocalPaths)" Condition="'%(Extension)' == '.dll'" />
      <EmbeddedResource Include="@(AssembliesToEmbed)">
        <LogicalName>%(AssembliesToEmbed.DestinationSubDirectory)%(AssembliesToEmbed.Filename)%(AssembliesToEmbed.Extension)</LogicalName>
      </EmbeddedResource>
    </ItemGroup>
  </Target>

  <Target Name="_CopyFilesMarkedCopyLocal" />

ghost avatar May 31 '20 21:05 ghost

@giandvd that's really really great again! I have some time off the next few weeks so will get around to trying it out on my own projects (one of which started off for work but the dumb f*s aren't interested so I'll take it further commercially for myself haha).

Take this as a joke and not a criticism but must you've been doing too much bloody JavaScript development lately my friend @giandvd ! What with 2 space indents without braces on separate lines! Although this does make it fit into shorter GitHub postings admittedly.

Me too lately actually with Typescript BUT its a real language so you can use C# coding style or just foister it on others and I have this moment decided f'it from now on I am going to use C# style because if the whitespace really matters then can you just do a minify afterwards on the JavaScript in the node build process.

The real killer reason being that f'en lambdas are illegible with JS coding style and I just cannot read node JS shit with chained nested promises callbacks lambdas etc. only C# Coding Standard Styling make it's readable but then I am just a fussy Old F'en Developer so don't mind me.

Hell: you could write you own node Re-Styler module from Readable CS to Unreadable JS before giving your code back to the frigging JS Developers and that's just what I would call it "js-unreadify"! HAHAHA!!

Thanks again @giandvd !

matthewsheeran avatar Jun 05 '20 14:06 matthewsheeran

Wondering if it's still an issue in the latest version.

KirillOsenkov avatar Jan 07 '24 04:01 KirillOsenkov