I have a collection of DLLs(say 20). How do I find out all the DLLs on which one specific DLL (say DLL A) is depending upon?
If you mean programmatically, use Assembly.GetReferencedAssemblies.
You can use that recursively to find all the assemblies you need. (So you find the dependencies of X, then the dependencies of the dependencies, etc.)
Since the question is tagged "C#", I would assume you are talking about managed dlls (assemblies). In that case, dependencywalker is not useful. If you want to do that with a program, good ones are dotPeek by JetBrians and Reflector by RedGate. Or you can even use the object inspector in Visual Studio.
However, it can be a long process and cumbersome too. I would write a short C# program/F# script that uses Assembly.GetReferencedAssemblies, as Jon mentioned.
If instead you want to examine native DLLs dependencies with a program (C# code), you have to walk the examine the PE file (the MS dll and exe file format) and its IAT (import address table). Not easy, but not impossible...
I would start here on MSDN and here to understand PE sections, and use a managed library to read it (there are many, including some from the Mono project (I'm thinking of Cecil, it should work with native binaries too); in the past I have used this one from the good John Gough.
All answer credit goes to previous authors for the usage of Assembly.GetReferencedAssemblies. This is just a write-and-forget C# console app that works solely for .NET assemblies. return 0 on assemblies you were able to check, and when successful, outputs them to STDOUT. Everything else will return 1 and print some kind of error output. You can grab the gist here.
using System;
using System.Reflection;
using System.IO;
namespace DotNetInspectorGadget
{
class DotNetInspectorGadget
{
static int Main(string[] args)
{
if(args.GetLength(0) < 1)
{
Console.WriteLine("Add a single parameter that is your" +
" path to the file you want inspected.");
return 1;
}
try {
var assemblies = Assembly.LoadFile(#args[0]).GetReferencedAssemblies();
if (assemblies.GetLength(0) > 0)
{
foreach (var assembly in assemblies)
{
Console.WriteLine(assembly);
}
return 0;
}
}
catch(Exception e) {
Console.WriteLine("An exception occurred: {0}", e.Message);
return 1;
} finally{}
return 1;
}
}
}
Usage:
call %cd%\dotnet_inspector_gadget.exe C:\Windows\Microsoft.NET\assembly\GAC_64\Microsoft.ConfigCI.Commands\v4.0_10.0.0.0__31bf3856ad364e35\Microsoft.ConfigCI.Commands.dll
Output:
mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System.Security, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
System.Management, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
You can use dependency walker http://www.dependencywalker.com to figure this out. Take note on the difference between x32 and x64 though.
Dependency Walker is a free utility that scans any 32-bit or 64-bit
Windows module (exe, dll, ocx, sys, etc.) and builds a hierarchical
tree diagram of all dependent modules.
For .NET assemblies, a terrific tool to view the assemblies an assembly is dependent on is AsmSpy.
If you want the DLL's (the files) then, Assembly.GetReferencedAssemblies will also return the .Net Framework assemblies.
Here is a simple code snippet that will get the dll's it can find in the current directory (and also include some other related files):
private readonly string[] _extensions = { ".dll", ".exe", ".pdb", ".dll.config", ".exe.config" };
private string[] GetDependentFiles(Assembly assembly)
{
AssemblyName[] asm = assembly.GetReferencedAssemblies();
List<string> paths = new List<string>(asm.Length);
for (int t = asm.Length - 1; t >= 0; t--)
{
for (int e = _extensions.Length - 1; e >= 0; e--)
{
string path = Path.GetFullPath(asm[t].Name + _extensions[e]);
if (File.Exists(path)) paths.Add(path);
}
}
return paths.ToArray();
}
You can call it like so: MessageBox.Show(string.Join("\r\n", GetDependentFiles(Assembly.GetEntryAssembly())));
Related
I am trying to run MSBuild programmatically from a C# DLL (which will ultimately be loaded from PowerShell), and as a first step from a command-line application. I have used Microsoft.Build.Locator as recommended (or so I reckon) by installing its NuGet package to my project, and adding the following references to my test project:
<Reference Include="Microsoft.Build, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.Build.Framework, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.Build.Locator, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9dff12846e04bfbd, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Build.Locator.1.2.6\lib\net46\Microsoft.Build.Locator.dll</HintPath>
</Reference>
The project targets .NET Framework 4.8, and the source code is as follows:
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Locator;
using System.Collections.Generic;
namespace nrm_testing
{
class Program
{
static void Main(string[] args)
{
MSBuildLocator.RegisterDefaults();
DoStuff();
}
static void DoStuff()
{
using (var projectCollection = new ProjectCollection())
{
var buildParameters = new BuildParameters
{
MaxNodeCount = 1 // https://stackoverflow.com/q/62658963/3233393
};
var buildRequestData = new BuildRequestData(
#"path\to\a\project.vcxproj",
new Dictionary<string, string>(),
null,
new string[0],
null
);
var result = BuildManager.DefaultBuildManager.Build(buildParameters, buildRequestData);
}
}
}
}
Upon entering the using block, I receive the following exception:
System.IO.FileNotFoundException: 'Could not load file or assembly 'System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.'
The Modules window shows that MSBL did successfully locate my VS2019 installation:
Microsoft.Build.dll 16.07.0.37604 C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\Microsoft.Build.dll
Microsoft.Build.Framework.dll 16.07.0.37604 C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\Microsoft.Build.Framework.dll
Microsoft.Build.Locator.dll 1.02.6.49918 C:\dev\nrm3-tests\nrm\nrm-testing\.out\AnyCPU-Debug\Microsoft.Build.Locator.dll
System.Runtime.CompilerServices.Unsafe.dll is indeed present besides the located MSBuild assemblies, in version 4.0.6.0 (according to DotPeek).
What could be causing this error, and how could I fix it?
My attempts so far:
I have found this question, but the linked GitHub issue is still open and I'm unsure whether it's the same problem.
I have managed to get an binding redirects working, but I don't think I can use them from within a DLL, so that's a dead end.
Adding the System.Runtime.CompilerServices.Unsafe NuGet package to the project (and verifying that it is indeed copied alongside the project's executable) does nothing (thanks magicandre1981 for the suggestion).
Switching from packages.config to PackageReference (as suggested by Perry Qian), with no change in behaviour.
After a lot of fiddling with different ideas, I ended up writing this workaround based on manual assembly resolution.
RegisterMSBuildAssemblyPath detects when Microsoft.Build.dll gets loaded, and memorizes its directory. Upon subsequent assembly load failures, RedirectMSBuildAssemblies checks if the missing assembly exists inside that path, and loads it if it does.
class Program
{
private static string MSBuildAssemblyDir;
static void Main(string[] args)
{
MSBuildLocator.RegisterDefaults();
Thread.GetDomain().AssemblyLoad += RegisterMSBuildAssemblyPath;
Thread.GetDomain().AssemblyResolve += RedirectMSBuildAssemblies;
DoStuff();
}
private static void RegisterMSBuildAssemblyPath(object sender, AssemblyLoadEventArgs args)
{
var assemblyPath = args.LoadedAssembly.Location;
if (Path.GetFileName(assemblyPath) == "Microsoft.Build.dll")
MSBuildAssemblyDir = Path.GetDirectoryName(assemblyPath);
}
private static Assembly RedirectMSBuildAssemblies(object sender, ResolveEventArgs args)
{
if (MSBuildAssemblyDir == null)
return null;
try
{
var assemblyFilename = $"{args.Name.Split(',')[0]}.dll";
var potentialAssemblyPath = Path.Combine(MSBuildAssemblyDir, assemblyFilename);
return Assembly.LoadFrom(potentialAssemblyPath);
}
catch (Exception)
{
return null;
}
}
static void DoStuff()
{
// Same as before
}
}
I'm pretty sure there are (many) corner cases that will make this fail, but it will do for now.
Actually, this is an real issue for a long time for packages.config nuget management format. And Microsoft's recommended solution for this problem is to add a bindingRedirect.
Usually, you can use this node in xxx.csproj file to automatically generate bindingredirect.
However, for some specific dlls, this node may not work due to serveral reasons. And it is still an issue on the current packages.config nuget management format as your said.
Suggestion
As a suggestion, you could use the new PackageReference nuget manage format instead since VS2017. This format is simple, convenient and efficient.
Also, when you use this format, first, you should make a backup of your project.
Just right-click on the packages.config file-->click Migrate packages.config to PackageReference.
Besides, I have also reported this issue on DC Forum and I hope the team will provide a better suggestion.
Well my question is almost similar to Embedding DLLs in a compiled executable but the very great answer provided here looses the compatiblity with mono runtime, though it works on windows.
So how can I use Fody (Costura) and also maintain mono compatibility. Their docs at https://github.com/Fody/Costura#contents read:
CosturaUtility is a class that gives you access to initialize the
Costura system manually in your own code. This is mainly for scenarios
where the module initializer doesn't work, such as libraries and Mono.
To use, call CosturaUtility.Initialize() somewhere in your code, as
early as possible.
class Program {
static Program() {
CosturaUtility.Initialize();
}
static void Main(string[] args) { ... }
}
but even after initialising ConturaUtility manually it does not support mono runtime.
I dont think the error log is relevant but here it is:
Unhandled Exception:
System.IO.FileNotFoundException: Could not load file or assembly 'CommandLine, Version=2.2.1.0, Culture=neutral, PublicKeyToken=de6f01bd326f8c32' or one of its dependencies.
File name: 'CommandLine, Version=2.2.1.0, Culture=neutral, PublicKeyToken=de6f01bd326f8c32'
[ERROR] FATAL UNHANDLED EXCEPTION: System.IO.FileNotFoundException: Could not load file or assembly 'CommandLine, Version=2.2.1.0, Culture=neutral, PublicKeyToken=de6f01bd326f8c32' or one of its dependencies.
File name: 'CommandLine, Version=2.2.1.0, Culture=neutral, PublicKeyToken=de6f01bd326f8c32'
Use this code at the start of Main() in your Program:
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => {
String resourceName = "AssemblyLoadingAndReflection." +
new AssemblyName(args.Name).Name + ".dll";
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) {
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
return Assembly.Load(assemblyData);
}
};
Reference: https://blogs.msdn.microsoft.com/microsoft_press/2010/02/03/jeffrey-richter-excerpt-2-from-clr-via-c-third-edition/
I have a project going on witch uses a DLL from an ERP system.
The DLL is used to get information from the ERP, like invoices and such.
The error i am getting is:
Inner Exception 1: FileNotFoundException: Could not load file or
assembly 'SnelStartGatewayInterface, Version=12.48.37.0,
Culture=neutral, PublicKeyToken=null' or one of its dependencies. The
system cannot find the file specified.
But in the same window I used 'watch 1' to see the current using assembly's with the method:
AppDomain.CurrentDomain.GetAssemblies()
It returns a couple of assembly's.
This is the one loaded in and exactly the same as seen in the error:
+ [36] {SnelStartGatewayInterface, Version=12.48.37.0, Culture=neutral, PublicKeyToken=null} System.Reflection.Assembly
{System.Reflection.RuntimeAssembly}
Why would it return me the error?
Ps. I have tried the exact same method and dll in a windows forms test app and it was running fine.
Like Pawl Lukasik mentioned in the comments, you should look at the dependencies.
To do this, use:
private List<string> ListReferencedAssemblies()
{
List<string> refList = new List<string>();
var assemblies = Assembly.GetExecutingAssembly().GetReferencedAssemblies();
foreach (var assembly in assemblies)
{
refList.Add(assembly.Name);
}
return refList;
}
to see all referenced assemblies.
Or with LINQ:
private List<string> ListReferencedAssemblies()
{
return Assembly.GetExecutingAssembly().GetReferencedAssemblies().Select(x => x.FullName).ToList();
}
As a continuation of my previous question.
I load DLL through this code.
Example 1:
var assembly = Assembly.LoadFile("C:\\Temp\\PROCESSOR\\SKM.dll");
And that's work fine.
But I use serialization that internally use this code, example 2:
var ass1 = Assembly.Load("SKM, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
And this code throws an exception: System.Runtime.Serialization.SerializationException: Unable to find assembly "SKM, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null". - that's because the DLL in a separate folder.
How to force CLR to see DLL in separate directory (not in subfolders of main application)?
I tried this:
<codeBase version="1.0.0.0" href="C:\\Temp\\PROCESSOR\\SKM.dll"/> - do not work because it works only for subfolders.
<probing privatePath="paths"/> - do not work because it works only for subfolders.
First run first example, and then run second example. But even if the SKM.dll already loaded, CLR does not see my assembly.
I found resolution here.
Just adding an event to AssemblyResolve:
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
string fileName = new AssemblyName(args.Name).Name + ".dll";
string assemblyPath = Path.Combine("C:\\Temp\\PROCESSOR", fileName);
var assembly = Assembly.LoadFile(assemblyPath);
return assembly;
};
And if DLL can not be found standard way, the event fired and load DLL from my folder.
Is there any particular reason you don't want to go with example 1 if you know where the DLL is?
If you really don't then one option would be to register the DLL in the GAC.
https://msdn.microsoft.com/en-us/library/dkkx7f79%28v=vs.110%29.aspx
I want to get Assemblies Friendly Names in Current Application Domain and hence I wrote something like this :
static void Main(string[] args)
{
foreach (System.Reflection.Assembly item in AppDomain.CurrentDomain.GetAssemblies())
{
Console.WriteLine(item.FullName);
}
}
But the problem is this is the output what I got rather than what I desired to see :
mscorlib, Version=2.0.0.0,
Culture=neutral,
PublicKeyToken=b77a5c561934e0
ApplicationDomains, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null
Actually I was expecting those names :
alt text http://www.pixelshack.us/images/xjfkrjgwqiag9s6o76x6.png
Can someone tell me if there is something I mistook.
Thanks in advance.
Or the names I was expecting weren't assemblies ?
You won't always get the pretty namespace names when you use reflection, you get the real names of the assemblies.
You also won't get all referenced libraries, only the ones that CURRENTLY loaded. If you add "XmlDocument foo = new XmlDocument()" above your code, System.XML will show up.
static void Main(string[] args)
{
XmlDocument foo = new XmlDocument();
foreach (System.Reflection.Assembly item in AppDomain.CurrentDomain.GetAssemblies())
{
Console.WriteLine(item.FullName);
}
}
Output:
mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
ConsoleApplication2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
It's impossible to get list of all referenced assemblies during runtime. Even if you reference it in your visual studio project, if you don't use them, C# compiler will ignore them and therefore they won't make it into your output file (exe/dll) at all.
And for the rest of your assemblies, they won't get loaded until they are actually used.
AppDomain.CurrentDomain.GetAssemblies() gives you array of all loaded assemblies and this list could be very different from what you see in visual studio project.
foreach(var assem in AppDomain.CurrentDomain.GetAssemblies())
{
Console.WriteLine(assem.GetName().Name);
}
Assembly.GetName() returns an AssemblyName object which has a Name property. That's what you're looking for.
Either use Assemly.GetName().Name or use reflection to find the AssemblyTitleAttribute and use that value.