Can't embed EPPlus.dll into my exe - c#

I need to embed EPPlus.dll to my standalone exe app. I don't want it to be copied along the exe. Apparently icluding it as an assembly resource would solve my problem. I found many decriptions on how to do it. For e.g. I performed all the below:
https://www.youtube.com/watch?v=x-KK7bmo1AM
http://adamthetech.com/2011/06/embed-dll-files-within-an-exe-c-sharp-winforms/
Embedding DLL's into .exe in in Visual C# 2010
Still I get Could not load file or assembly 'EPPlus.dll'...
Can you please give me some idea as I have never done something like this before?
P.S.: I use VS C# 2010 Express

Try having a look at this in your application:
In the program.cs main method:
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
Create the event:
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
if (args.Name.StartsWith("MyAssembly,"))
{
return Assembly.Load(Properties.Resources.MyAssembly_Dll);
}
return null;
}
I am using something similar for dynamically loading an assembly that requires other assemblies that have not yet been loaded and so I load them as required.
Comments welcome.

Download ILMerge from here then install ILMerge.
After that you should have the following file C:\Program Files (x86)\Microsoft\ILMerge\ILMerge.exe or C:\Program Files\Microsoft\ILMerge\ILMerge.exe if you are on a 32bit system.
Then you execute ILMerge.exe with the following parameters /out:OutputExeFile.exe InputExeFile.exe dllfile.dll
After that ILMerge should have merged InputExeFile.exe and dllfile.dll and stored the result in OutputExeFile.exe
if you just run the ilmerge you will get the following help
Usage: ilmerge [/lib:directory]* [/log[:filename]] [/keyfile:filename
[/delaysign]] [/internalize[:filename]]
[/t[arget]:(library|exe|winexe)] [/closed] [/ndebug] [/ver:version]
[/copyattrs [/allowMultiple] [/keepFirst]] [/xmldocs] [/attr:filename]
[/targetplatform:[,] | /v1 | /v1.1 | /v2 | /v4]
[/useFullPublicKeyForReferences] [/wildcards] [/zeroPeKind]
[/allowDup:type]* [/union] [/align:n] /out:filename
[...]

Related

Relocating dependencies for my application throws System.IO.FileNotFoundException

I'm trying to clean up my Release folder so dependency files have some type of organization to them. My goal in this case is to contain all the dependencies I need for my compression module into a single folder. For this first part of my new class library I'm just trying to support 7zip files. I do this using the SevenZipSharp.Interop NuGet which depends on the SevenZipSharp NuGet.
By default, the above NuGets build results in my Release/Debug folders and look like the following...
Release
|- x64
| `- 7z.dll
|- x86
| `- 7z.dll
|- SevenZipSharp.dll
`- compressionmodule.dll
As I add more support to my class library I want to make sure everything has it's own folder etc. So, I want to organize it in the following way...
Release
|- compressionmodule
| `- 7zip
| |- x64
| | `- 7z.dll
| |- x86
| | `- 7z.dll
| `- SevenZipSharp.dll
`- compressionmodule.dll
I do this by running a few POST BUILD commands and once the project builds I get the structure I want above (the latter). However, when debugging, after I compile and the POST BUILD commands occur, the application tries to run and I get a System.IO.FileNotFoundException error telling me it can't find SevenZipSharp.dll. I knew this would happen in the back of my mind but was hoping there was a way to let the application know the new location of the dependencies.
Is it possible to relocate the files and then let my application know they are in a different place somehow?
I ended up using the NuGet package Costura.Fody (repository found here). The way it wraps up dependencies in your class module (or program) is nothing short of magic. When using Costura.Fody it will look for any dependencies and automatically compress them into you class library dll making a single dll. It will then make all references to the resource point to the embedded resources in your dll. All you need to do is add the two NuGet packages and it will do all the work when you build the project. It's really slick!
Important Note: Fody's (a dependency of Costura.Fody needed to
run Costura.Fody) current version is only compatible with Visual
Studio 2019 (using MSBuild 16). In my case I have Visual Studio 2017
(which uses MSBuild 15). I had to use an old version of
Costura.Fody that still used the old MSBuild. That also required me
to downgrade Fody as well. So I ended up using the latest versions
that would work with MSBuild 15. Fody version 4.2.1 and Costura.Fody
version 3.3.3 works great with Visual Studio 2017.
It even supports extracting the resource if needed. For example, I was looking to use this on the SevenZipSharp library. It requires a path to the 7zip.dll file to be used. So I used the following in the FodyWeavers.xml file that is located in the same directory as your solution.
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Costura CreateTemporaryAssemblies='true' IncludeDebugSymbols='false'>
<Unmanaged32Assemblies>
7z
</Unmanaged32Assemblies>
<Unmanaged64Assemblies>
7z
</Unmanaged64Assemblies>
</Costura>
</Weavers>
You need to also include the 7zip.dll in your project so it compiles the dlls as they are not used like resources (like the SevenZipSharp library is). You do this by adding the following folders in the root of your project as well as the appropriate architectures files. Then add them as Build Action: Embedded Resource.
This would extract the 7zip.dll dependency at run-time when needed so I could reference the path when using the SevenZipSharp library. At this point I have not found out how to get a reference to the (C83815D61CE49B2E8D23145A1B95A956 maybe a checksum of all the files in the directory itself?) directory it uses but in my case this is where is was extracting it to...
%tmp%\Costura\C83815D61CE49B2E8D23145A1B95A956\32\
Update: I found a workaround to getting the path of that checksum like directory using Assembly information. I created the following function to try and set the 7Zip library path. It might be helpful to some so I'm including it here.
private bool SetupSevenZipLibrary()
{
string costuraExtractionPath = null;
try
{
//This will use the embeded resource to try and set a base path to the extraction
//location created by by Costura.Fody
string sevenZipAssembly = typeof(SevenZip.ArchiveFileInfo).Assembly.Location;
if (File.Exists(sevenZipAssembly))
costuraExtractionPath = Path.GetDirectoryName(sevenZipAssembly);
}
catch
{
//Issue trying to grab the extraction path from the Assembly information so try
//to use the first path found in the Costura directory as a last ditch effort
DirectoryInfo di = null;
string costuraTempPath = Path.Combine(
Path.GetTempPath(), //ex: C:\Users\username\AppData\Local\Temp
"Costura" //ex: Costura
);
di = new DirectoryInfo(costuraTempPath);
if (!di.Exists)
return false;
//ex: C:\Users\username\AppData\Local\Temp\Costura\C83815D61CE49B2E8D23145A1B95A956\
costuraExtractionPath = di.GetDirectories().First().FullName;
}
try
{
if (!Directory.Exists(costuraExtractionPath))
throw new Exception();
string sevenZipPath = Path.Combine(
costuraExtractionPath, //ex: C:\Users\username\AppData\Local\Temp\Costura\C83815D61CE49B2E8D23145A1B95A956
Environment.Is64BitProcess ? "64" : "32", //ex: 32
"7z.dll" //ex: 7z.dll
);
if (!File.Exists(sevenZipPath))
throw new Exception();
SevenZipBase.SetLibraryPath(sevenZipPath);
return true;
}
catch { return false; }
}
It's usage...
if (!SetupSevenZipLibrary())
throw new Exception("Error setting the path of the 7zip library.");

Opening a solution with msbuildworkspace gives diagnostics errors without details

I am trying to analyse a solution with Roslyn, with MSBuildWorkspace.
The solution is a new solution, with 2 class library projects in them, one referencing the other.
They are created in Visual Studio 2017, .Net 4.6.2.
When I open the solution, I receive two generic errors in workspace.Diagnostics, both are :
Msbuild failed when processing the file 'PathToProject'
There is nothing more in the diagnostics or output window, to indicate WHY it failed to process the project file.
The code for opening the solution:
namespace RoslynAnalyse
{
class Program
{
static void Main(string[] args)
{
LocalAnalysis();
}
private static void LocalAnalysis()
{
var workspace = MSBuildWorkspace.Create();
var solution = workspace.OpenSolutionAsync(#"D:\Code\Roslyn\RoslynAnalyse\SolutionToAnalyse\SolutionToAnalyse.sln").Result;
var workspaceDiagnostics = workspace.Diagnostics;
}
}
}
The version of Microsoft.CodeAnalysis is 2.0.0.0.
Does anybody have any idea why MSBuild failed, how I can get more information ?
When MSBuildWorkspace fails to open a project or solution this way, it is almost always because the application using MSBuildWorkspace does not include the same binding redirects that msbuild.exe.config has in it.
MSBuild uses binding redirects to allow tasks (typically already compiled C# code using possibly different versions of msbuild API libraries) to all use the current msbuild API's. Otherwise, msbuild gets runtime load failures.
The solution is to add an app.config file to your project and copy the binding redirects (the assemblyBinding section of the msbuild.exe.config file) into your file.

TuesPechkin unable to load DLL 'wkhtmltox.dll'

I've been using TuesPechkin for some time now and today I went to update the nuget package to the new version 2.0.0+ and noticed that Factory.Create() no longer resolved, so I went to read on the GitHub the changes made and noticed it now expects the path to the dll?
IConverter converter =
new ThreadSafeConverter(
new PdfToolset(
new StaticDeployment(DLL_FOLDER_PATH)));
For the past few hours I've tried almost all the paths I can think of, "\bin", "\app_data", "\app_start", etc and I can't seem to find or figure out what it wants for the path and what dll?
I can see the TuesPechkin dll in my bin folder and it was the first path I tried, but I got the following error:
Additional information: Unable to load DLL 'wkhtmltox.dll': The
specified module could not be found. (Exception from HRESULT:
0x8007007E)
Where is that dll and now can I get it as the library doesn't seem to contain it, I tried installing the TuesPechkin.Wkhtmltox.Win32 package but the dll still is nowhere to be found. Also I am using this in a asp.net website project so I assume that using the following should work for obtaining the path, right?
var path = HttpContext.Current.Server.MapPath(#"~\bin\TuesPechkin.dll");
Further information: https://github.com/tuespetre/TuesPechkin/issues/57
The Tuespechkin has a zip file as a resource in the Win32 and Win64 embedded packages for the 'wkhtmltox.dll' file.
What it does when you use the Win32 or Win64 Embedded package is unzips the file and places it in the directory that you specify.
I have been putting a copy of the wkhtmltox dll at the root portion of my web app directory and pointing the DLL_FOLDER_PATH to it using the server physical path of my web app to get to it.
According to the author, you must set the converter in a static field for best results.
I do that, but set the converter to null when I am finished using it, and that seems to work.
Tuespechkin is wrapper for the wmkhtmlox dll file.
The original file is written in C++ and so will not automatically be usable in C# or VB.NET or any of the other managed code domains.
The Tuespechkin.dll file DOES NOT contain a copy of 'wkhtmltox.dll'. You either have to use one of the other embedded deployment modules or install a copy of the 'wkhtmltox.dll' in your web app after downloading it from the internet. That is what I do, and it seems to work just fine.
I am using Team Foundation Server, and attempts to compile code after using the Tuespechkin routines will fail the first time because the 'wkhtmltox.dll' file gets locked, but all you have to do is simply retry your build and it will go through.
I had issues with the 32-bit routine not working in a 64-bit environment and the 64-bit environment not being testable on localhost. I went with the workaround I came up with after examining the source code for Tuespechkin and the Win32 and Win64 embedded deployment packages.
It works well as long as you specify a url for the input rather than raw html.
The older package didn't render css very well.
If you are using a print.aspx routine, you can create the url for it as an offset from your main url.
I don't have the source code I am using with me at this point to offset to your base url for your web application, but it is simply an offshoot of HttpRequest.
You have to use the physical path to find the .dll, but you can use a web path for the print routine.
I hope this answers your question a bit.
If you are getting this error -> Could not load file or assembly 'TuesPechkin.Wkhtmltox.Win64' or one of its dependencies. An attempt was made to load a program with an incorrect format.
In Visual Studio Go to -
Tools -> Options -> Projects and Solutions -> Web Projects -> Use the 64 bit version of IIS Express for web sites and projects.
I installed TuesPechkin.Wkhtmltox.Win64 Nuget package and used the following code in a singleton:
public class PechkinPDFConvertor : IPDFConvertor
{
IConverter converter =
new ThreadSafeConverter(
new RemotingToolset<PdfToolset>(
new Win64EmbeddedDeployment(
new TempFolderDeployment())));
public byte[] Convert(string html)
{
// return PechkinSync.Convert(new GlobalConfig(), html);
return converter.Convert(new HtmlToPdfDocument(html));
}
}
The web application then has to be run in x64 otherwise you will get an error about trying to load an x64 assembly in an x86 environment. Presumably you have to choose x64 or x86 at design time and use the corresponding nuget package, it would be nicer to choose this in the web.config.
EDIT: The above code failed on one server with the exact same message as yours - it was due to having not installed VC++ 2013. So the new code is running x86 as follows
try
{
string path = Path.Combine(Path.GetTempPath(), "MyApp_PDF_32");
Converter = new ThreadSafeConverter(
new RemotingToolset<PdfToolset>(
new Win32EmbeddedDeployment(
new StaticDeployment(path))));
}
catch (Exception e)
{
if (e.Message.StartsWith("Unable to load DLL 'wkhtmltox.dll'"))
{
throw new InvalidOperationException(
"Ensure the prerequisite C++ 2013 Redistributable is installed", e);
}
else
throw;
}
If you do not want run the installer for wkhtmltox just to get the dll, you can do the following:
As #Timothy suggests, if you use the embedded version of wkhtmltox.dll from TuesPechkin, it will unzip it and place it in a temp directory. I copied this dll and referenced it with the StaticDeployment option without any issues.
To find the exact location, I just used Process Monitor (procmon.exe). For me it was C:\Windows\Temp\-169958574\8\0.12.2.1\wkhtmltox.dll
In my case, I am deploying on a 64-bit VPS then I got this error. I have solved the problem by installing the wkhtmltopdf that I downloaded from http://wkhtmltopdf.org/downloads.html. I chose the 32-bit installer.
In my case, I have solved the problem by installing the Wkhtmltox for win32 at https://www.nuget.org/packages/TuesPechkin.Wkhtmltox.Win32/
This error: Unable to load DLL 'wkhtmltox.dll': The specified module could not be found. (Exception from HRESULT: 0x8007007E) is returned in two situations:
1- Deploy dependency not installed:
For solve this, you can install nuget package "TuesPechkin.Wkhtmltox.Win64" and use this code (for WebApplications running in IIS):
IConverter converter =
new ThreadSafeConverter(
new RemotingToolset<PdfToolset>(
new Win64EmbeddedDeployment(
new TempFolderDeployment())));
// Keep the converter somewhere static, or as a singleton instance!
// Do NOT run the above code more than once in the application lifecycle!
byte[] result = converter.Convert(document);
In runtime this code will copy the dependency "wkhtmltox.dll" in a temporary directory like: "C:\Windows\Temp\1402166677\8\0.12.2.1". It's possible to get the destination of file using:
var deployment = new Win64EmbeddedDeployment(new TempFolderDeployment());
Console.WriteLine(deployment.Path);
2- Microsoft Visual C++ 2013 Redistributable not installed:
As described here:
https://github.com/tuespetre/TuesPechkin/issues/65#issuecomment-71266114, the Visual C++ 2013 Runtime is required.
The solution from README is:
You must have Visual C++ 2013 runtime installed to use these packages. Otherwise, you will need to download the MingW build of wkhtmltopdf and its dependencies from their website and use that with the library. https://github.com/tuespetre/TuesPechkin#wkhtmltoxdll
or, you can install the Microsoft Visual C++ 2013 Redistributable:
choco install msvisualcplusplus2013-redist
Here is AnyCpu version, also support iis-base or winform application
using TuesPechkin.Wkhtmltox.AnyCPU;
...
var converter = PDFHelper.Factory.GetConverter();
var result = converter.Convert(This.Document);
Reference : https://github.com/tloy1966/TuesPechkin
Installing the Visual C++ Redistributable for Visual Studio 2013 resolved the error for me.
https://www.microsoft.com/en-us/download/details.aspx?id=40784

Merge DLL into EXE?

I have two DLL files which I'd like to include in my EXE file to make it easier to distribute it. I've read a bit here and there how to do this, even found a good thread here, and here, but it's far too complicated for me and I need real basic instructions on how to do this.
I'm using Microsoft Visual C# Express 2010, and please excuse my "low standard" question, but I feel like I'm one or two level below everyone else's expercise :-/ If someone could point out how to merge these DDL files into my EXE in a step-by-step guide, this would be really awesome!
For .NET Framework 4.5
ILMerge.exe /target:winexe /targetplatform:"v4,C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0" /out:finish.exe insert1.exe insert2.dll
ILMerge
Open CMD and cd to your directory. Let's say: cd C:\test
Insert the above code.
/out:finish.exe replace finish.exe with any filename you want.
Behind the /out:finish.exe you have to give the files you want to be
combined.
Use Costura.Fody.
You just have to install the nuget and then do a build. The final executable will be standalone.
Download ilmerge and ilmergre gui . makes joining the files so easy
ive used these and works great
Reference the DLL´s to your Resources and and use the AssemblyResolve-Event to return the Resource-DLL.
public partial class App : Application
{
public App()
{
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
Assembly thisAssembly = Assembly.GetExecutingAssembly();
//Get the Name of the AssemblyFile
var name = args.Name.Substring(0, args.Name.IndexOf(',')) + ".dll";
//Load form Embedded Resources - This Function is not called if the Assembly is in the Application Folder
var resources = thisAssembly.GetManifestResourceNames().Where(s => s.EndsWith(name));
if (resources.Count() > 0)
{
var resourceName = resources.First();
using (Stream stream = thisAssembly.GetManifestResourceStream(resourceName))
{
if (stream == null) return null;
var block = new byte[stream.Length];
stream.Read(block, 0, block.Length);
return Assembly.Load(block);
}
}
return null;
};
}
}
Download
ILMerge
Call
ilmerge /target:winexe /out:c:\output.exe c:\input.exe C:\input.dll
Install ILMerge
as the other threads tell you to
Then go to the installation folder, by default
C:\Program Files (x86)\Microsoft\ILMerge
Drag your Dll's and Exes to that folder
Shift-Rightclick in that folder and choose open command prompt
Write
ilmerge myExe.exe Dll1.dll /out:merged.exe
Note that you should write your exe first.
There you got your merged exe. This might not be the best way if your going to
do this multiple times, but the simplest one for a one time use, I would
recommend putting Ilmerge to your path.
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
/* PUT THIS LINE IN YOUR CLASS PROGRAM MAIN() */
AppDomain.CurrentDomain.AssemblyResolve += (sender, arg) => { if (arg.Name.StartsWith("YOURDLL")) return Assembly.Load(Properties.Resources.YOURDLL); return null; };
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
First add the DLL´s to your project-Resources. Add a folder "Resources"
2019 Update (just for reference):
Starting with .NET Core 3.0, this feature is supported out of the box. To take advantage of the single-file executable publishing, just add the following line to the project configuration file:
<PropertyGroup>
<PublishSingleFile>true</PublishSingleFile>
</PropertyGroup>
Now, dotnet publish should produce a single .exe file without using any external tool.
More documentation for this feature is available at https://github.com/dotnet/designs/blob/master/accepted/single-file/design.md.
Also you can use ilmergertool at codeplex with GUI interface.
Here is the official documentation. This is also automatically downloaded at step 2.
Below is a really simple way to do it and I've successfully built my app using .NET framework 4.6.1
Install ILMerge nuget package either via gui or commandline:
Install-Package ilmerge
Verify you have downloaded it. Now Install (not sure the command for this, but just go to your nuget packages):
Note: You probably only need to install it for one of your solutions if you have multiple
Navigate to your solution folder and in the packages folder you should see 'ILMerge' with an executable:
\FindMyiPhone-master\FindMyiPhone-master\packages\ILMerge.2.14.1208\tools
Now here is the executable which you could copy over to your \bin\Debug (or whereever your app is built) and then in commandline/powershell do something like below:
ILMerge.exe myExecutable.exe myDll1.dll myDll2.dll myDlln.dll myNEWExecutable.exe
You will now have a new executable with all your libraries in one!
I answered a similar question for VB.NET. It shouldn't however be too hard to convert. You embedd the DLL's into your Ressource folder and on the first usage, the
AppDomain.CurrentDomain.AssemblyResolve event gets fired.
If you want to reference it during development, just add a normal DLL reference to your project.
Embedd a DLL into a project
NOTE: if you're trying to load a non-ILOnly assembly, then
Assembly.Load(block)
won't work, and an exception will be thrown:
more details
I overcame this by creating a temporary file, and using
Assembly.LoadFile(dllFile)
I Found The Solution Below are the Stpes:-
Download ILMerge.msi and Install it on your Machine.
Open Command Prompt
type cd C:\Program Files (x86)\Microsoft\ILMerge Preess Enter
C:\Program Files (x86)\Microsoft\ILMerge>ILMerge.exe /target:winexe /targetplatform:"v4,C:\Windows\Microsoft.NET\Framework\v4.0.30319"
/out:NewExeName.exe SourceExeName.exe DllName.dll
For Multiple Dll :-
C:\Program Files (x86)\Microsoft\ILMerge>ILMerge.exe /target:winexe /targetplatform:"v4,C:\Windows\Microsoft.NET\Framework\v4.0.30319"
/out:NewExeName.exe SourceExeName.exe DllName1.dll DllName2.dll DllName3.dll
The command should be the following script:
ilmerge myExe.exe Dll1.dll /target:winexe /targetplatform:"v4,c:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\" /out:merged.exe /out:merged.exe

Using Side-by-Side assemblies to load the x64 or x32 version of a DLL

We have two versions of a managed C++ assembly, one for x86 and one for x64. This assembly is called by a .net application complied for AnyCPU. We are deploying our code via a file copy install, and would like to continue to do so.
Is it possible to use a Side-by-Side assembly manifest to loading a x86 or x64 assembly respectively when an application is dynamically selecting it's processor architecture? Or is there another way to get this done in a file copy deployment (e.g. not using the GAC)?
I created a simple solution that is able to load platform-specific assembly from an executable compiled as AnyCPU. The technique used can be summarized as follows:
Make sure default .NET assembly loading mechanism ("Fusion" engine) can't find either x86 or x64 version of the platform-specific assembly
Before the main application attempts loading the platform-specific assembly, install a custom assembly resolver in the current AppDomain
Now when the main application needs the platform-specific assembly, Fusion engine will give up (because of step 1) and call our custom resolver (because of step 2); in the custom resolver we determine current platform and use directory-based lookup to load appropriate DLL.
To demonstrate this technique, I am attaching a short, command-line based tutorial. I tested the resulting binaries on Windows XP x86 and then Vista SP1 x64 (by copying the binaries over, just like your deployment).
Note 1: "csc.exe" is a C-sharp compiler. This tutorial assumes it is in your path (my tests were using "C:\WINDOWS\Microsoft.NET\Framework\v3.5\csc.exe")
Note 2: I recommend you create a temporary folder for the tests and run command line (or powershell) whose current working directory is set to this location, e.g.
(cmd.exe)
C:
mkdir \TEMP\CrossPlatformTest
cd \TEMP\CrossPlatformTest
Step 1: The platform-specific assembly is represented by a simple C# class library:
// file 'library.cs' in C:\TEMP\CrossPlatformTest
namespace Cross.Platform.Library
{
public static class Worker
{
public static void Run()
{
System.Console.WriteLine("Worker is running");
System.Console.WriteLine("(Enter to continue)");
System.Console.ReadLine();
}
}
}
Step 2: We compile platform-specific assemblies using simple command-line commands:
(cmd.exe from Note 2)
mkdir platform\x86
csc /out:platform\x86\library.dll /target:library /platform:x86 library.cs
mkdir platform\amd64
csc /out:platform\amd64\library.dll /target:library /platform:x64 library.cs
Step 3: Main program is split into two parts. "Bootstrapper" contains main entry point for the executable and it registers a custom assembly resolver in current appdomain:
// file 'bootstrapper.cs' in C:\TEMP\CrossPlatformTest
namespace Cross.Platform.Program
{
public static class Bootstrapper
{
public static void Main()
{
System.AppDomain.CurrentDomain.AssemblyResolve += CustomResolve;
App.Run();
}
private static System.Reflection.Assembly CustomResolve(
object sender,
System.ResolveEventArgs args)
{
if (args.Name.StartsWith("library"))
{
string fileName = System.IO.Path.GetFullPath(
"platform\\"
+ System.Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")
+ "\\library.dll");
System.Console.WriteLine(fileName);
if (System.IO.File.Exists(fileName))
{
return System.Reflection.Assembly.LoadFile(fileName);
}
}
return null;
}
}
}
"Program" is the "real" implementation of the application (note that App.Run was invoked at the end of Bootstrapper.Main):
// file 'program.cs' in C:\TEMP\CrossPlatformTest
namespace Cross.Platform.Program
{
public static class App
{
public static void Run()
{
Cross.Platform.Library.Worker.Run();
}
}
}
Step 4: Compile the main application on command line:
(cmd.exe from Note 2)
csc /reference:platform\x86\library.dll /out:program.exe program.cs bootstrapper.cs
Step 5: We're now finished. The structure of the directory we created should be as follows:
(C:\TEMP\CrossPlatformTest, root dir)
platform (dir)
amd64 (dir)
library.dll
x86 (dir)
library.dll
program.exe
*.cs (source files)
If you now run program.exe on a 32bit platform, platform\x86\library.dll will be loaded; if you run program.exe on a 64bit platform, platform\amd64\library.dll will be loaded. Note that I added Console.ReadLine() at the end of the Worker.Run method so that you can use task manager/process explorer to investigate loaded DLLs, or you can use Visual Studio/Windows Debugger to attach to the process to see the call stack etc.
When program.exe is run, our custom assembly resolver is attached to current appdomain. As soon as .NET starts loading the Program class, it sees a dependency on 'library' assembly, so it tries loading it. However, no such assembly is found (because we've hidden it in platform/* subdirectories). Luckily, our custom resolver knows our trickery and based on the current platform it tries loading the assembly from appropriate platform/* subdirectory.
My version, similar to #Milan, but with several important changes:
Works for ALL DLLs that were not found
Can be turned on and off
AppDomain.CurrentDomain.SetupInformation.ApplicationBase is used instead of Path.GetFullPath() because the current directory might be different, e.g. in hosting scenarios, Excel might load your plugin but the current directory will not be set to your DLL.
Environment.Is64BitProcess is used instead of PROCESSOR_ARCHITECTURE, as we should not depend on what the OS is, rather how this process was started - it could have been x86 process on a x64 OS. Before .NET 4, use IntPtr.Size == 8 instead.
Call this code in a static constructor of some main class that is loaded before all else.
public static class MultiplatformDllLoader
{
private static bool _isEnabled;
public static bool Enable
{
get { return _isEnabled; }
set
{
lock (typeof (MultiplatformDllLoader))
{
if (_isEnabled != value)
{
if (value)
AppDomain.CurrentDomain.AssemblyResolve += Resolver;
else
AppDomain.CurrentDomain.AssemblyResolve -= Resolver;
_isEnabled = value;
}
}
}
}
/// Will attempt to load missing assembly from either x86 or x64 subdir
private static Assembly Resolver(object sender, ResolveEventArgs args)
{
string assemblyName = args.Name.Split(new[] {','}, 2)[0] + ".dll";
string archSpecificPath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
Environment.Is64BitProcess ? "x64" : "x86",
assemblyName);
return File.Exists(archSpecificPath)
? Assembly.LoadFile(archSpecificPath)
: null;
}
}
Have a look at SetDllDirectory. I used it around the dynamically loading of an IBM spss assembly for both x64 and x86. It also solved paths for non assembly support dll's loaded by the assemblies in my case was the case with the spss dll's.
http://msdn.microsoft.com/en-us/library/ms686203%28VS.85%29.aspx
You can use the corflags utility to force an AnyCPU exe to load as an x86 or x64 executable, but that doesn't totally meet the file copy deployment requirement unless you choose which exe to copy based on the target.
This solution can work for non managed assemblies as well. I have created a simple example similar to Milan Gardian's great example. The example I created dynamically loads a Managed C++ dll into a C# dll compiled for the Any CPU platform. The solution makes use of the InjectModuleInitializer nuget package to subscribe to the AssemblyResolve event before the dependencies of the assembly are loaded.
https://github.com/kevin-marshall/Managed.AnyCPU.git

Categories