Reputation: 143
I followed this tutorial to combine a few DLL's into my EXE.
The way I understand this works is: - it starts by telling the compiler to embed (as embedded resources) each and every DLL that have their Local Copy set to True.
That part is working fine. It apparently doesn't "add" them as resources to my project (figure 1 in the tutorial kind of says otherwise), but I can tell that the size of my EXE is correct.
FYI, my program uses WPFtoolkit, in my case, that's 2 DLL's: system.windows.controls.datavisualization.toolkit.dll WPFToolkit.dll
Then, I set the Build Action of my App.xaml to Page, and made a program.cs file which I added to my project.
this is my project.cs:
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Media.TextFormatting;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Shell;
using System.IO;
using System.Reflection;
using System.Globalization;
namespace Swf_perium {
public class Program {
//[System.Diagnostics.DebuggerNonUserCodeAttribute()]
//[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
[STAThreadAttribute]
public static void Main() {
Swft_perium.App app = new Swf_perium.App();
AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
app.InitializeComponent();
app.Run();
}
private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
{
Assembly executingAssembly = Assembly.GetExecutingAssembly();
AssemblyName assemblyName = new AssemblyName(args.Name);
string path = assemblyName.Name + ".dll";
Console.WriteLine(path);
if (assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture) == false)
{
path = String.Format(@"{0}\{1}", assemblyName.CultureInfo, path);
}
using (Stream stream = executingAssembly.GetManifestResourceStream(path))
{
if (stream == null)
return null;
byte[] assemblyRawBytes = new byte[stream.Length];
stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
return Assembly.Load(assemblyRawBytes);
}
}
}
}
After I build the project, I run it off VS2013, no problem, since both DLL's have their local copy set to true. If I go in my debug folder, take both DLL's out and run the EXE off windows explorer, then the program instantly crashes because it can find the DLL's.
What this tutorial should allow me to do is being able to run that EXE by itself without the DLL's, so yeah, it doesn't work.
I added a console writeline of the path that are being read by the OnResolveAssembly method of my program.cs. And here's what I get: 4 times the same path: "Swf_perium.resources.dll"
Obviously, when it gets to the Stream, it's null and the method then returns null.
I am trying to understand where these paths are coming from? I don't understand why 4? And why this path?
Has anyone ever tried this technique? Comments on the blog show pretty good success rate. Does anyone have an idea?
I made several mistakes to get to this stage, but at this point I don't see what I am doing wrong.
Thanks Steve
EDIT: following HB's guidance, here's what I did:
I took the MSBuild target "mod" out. Set both references' copy local to FALSE. Added both DLL as embedded resources manually. They're both into the "Resources" directory at the root of the project. I set App.xaml build action back to "ApplicationDefinition". And I excluded my program.cs out of the project.
and added this code to App.xaml.cs:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Reflection;
namespace Swf_perium
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
public App()
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
var execAssembly = Assembly.GetExecutingAssembly();
string resourceName = execAssembly.FullName.Split(',').First() + ".Resources." + new AssemblyName(args.Name).Name + ".dll";
Console.WriteLine(resourceName);
using (var stream = execAssembly.GetManifestResourceStream(resourceName))
{
byte[] assemblyData = new byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
return Assembly.Load(assemblyData);
}
}
}
}
now, the console prints out of the 2 DLL's filename, but not the other.. I am guessing that's why it's still not working..
that's where I'm at.
edit:
The DLL that doesn't show is not called by my code directly. it's a dependence from the first DLL. I took that second DLL out of references and resources.. If I set copy local to true for the first DLL (which my program actually uses), building the project generates both DLL at the root - in this case with both dlls generated the program works, funny thing is if I delete that second DLL, the program still works. So the problem isn't that second DLL but the first one.
the error I have (which I've had all along no matter what technique I use) is that my XAML is calling that namespace and can't find it!
edit:
Ok, well it still doesn't work. I've brought my program.cs back into the solution, set it as the entry point. And added the code suggested by HB into it. I made sure that the assemblyresolve is done on the first line of the main so that's it's done before any wpf is done. I even added a 5s sleep just to make sure that the dll was loaded before any wpf happens. Still no go.
Either the dependence to the second DLL is what's causing a problem (?) or maybe the way I import the namespace in my XAML is incorrect. Do I need to specify that this namespace is embedded? and where it's located - i.e. its path?.
thanks
Upvotes: 0
Views: 1118
Reputation: 8696
Perhaps look at Costura where it will do all the hard work of embedding assemblies for you.
Upvotes: 1
Reputation: 184296
Don't know your project structure but i usually add a directory for the assemblies to the root of the project and then add the dlls to that directory as embedded resource. I also then turn off the local copy of the references to make sure that it works.
Here is the code i use in my App.xaml.cs
:
static App()
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
var execAssembly = Assembly.GetExecutingAssembly();
string resourceName = execAssembly.FullName.Split(',').First() + ".ReferencedAssemblies." +
new AssemblyName(args.Name).Name + ".dll";
using (var stream = execAssembly.GetManifestResourceStream(resourceName))
{
byte[] assemblyData = new byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
return Assembly.Load(assemblyData);
}
}
Simply replace the ".ReferencedAssemblies."
string according to the directory you placed the dlls in.
(Using the static constructor of the class makes sure that the event is hooked up before any code that potentially accesses referenced assemblies is executed, in your code i would move the hook to the first line of Main
, that may already solve your problem.)
Upvotes: 0