-
-
Notifications
You must be signed in to change notification settings - Fork 633
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Missing default AssemblyResolver on .NET Core #306
Comments
Good catch, we do not have a default assembly resolver for .NET Core. That's something we need to fix. ReadModule(moduleName, new ReaderParameters { AssemblyResolver = yourAssemblyResolver }); Thanks for reporting this! |
It should be relatively easy to create a IAssemblyResolver implementation for when the .NET Core application has been published (all .dll will be in the output folder). |
The following works during the development. We would need a way to switch to a simple one when running published.
{
"buildOptions": {
"preserveCompilationContext": true
}
}
{
"frameworks": {
"netcoreapp1.1": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.1.0"
},
"Mono.Cecil": "0.10.0-beta1-v2",
"Microsoft.Extensions.DependencyModel": "1.1.0-preview1-001100"
},
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
namespace Mono.Cecil
{
public sealed class AssemblyResolutionException : Exception {
readonly AssemblyNameReference reference;
public AssemblyNameReference AssemblyReference {
get { return reference; }
}
public AssemblyResolutionException (AssemblyNameReference reference)
: base (string.Format ("Failed to resolve assembly: '{0}'", reference))
{
this.reference = reference;
}
}
class DotNetCoreAssemblyResolver : IAssemblyResolver
{
Dictionary<string, Lazy<AssemblyDefinition>> _libraries;
public DotNetCoreAssemblyResolver()
{
_libraries = new Dictionary<string, Lazy<AssemblyDefinition>>();
var compileLibraries = DependencyContext.Default.CompileLibraries;
foreach (var library in compileLibraries)
{
var path = library.ResolveReferencePaths().FirstOrDefault();
if (string.IsNullOrEmpty(path))
continue;
_libraries.Add(library.Name, new Lazy<AssemblyDefinition>(() => AssemblyDefinition.ReadAssembly(path, new ReaderParameters() { AssemblyResolver = this })));
}
}
public virtual AssemblyDefinition Resolve(string fullName)
{
return Resolve(fullName, new ReaderParameters());
}
public virtual AssemblyDefinition Resolve(string fullName, ReaderParameters parameters)
{
if (fullName == null)
throw new ArgumentNullException("fullName");
return Resolve(AssemblyNameReference.Parse(fullName), parameters);
}
public AssemblyDefinition Resolve(AssemblyNameReference name)
{
return Resolve(name, new ReaderParameters());
}
public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
{
if (name == null)
throw new ArgumentNullException("name");
Lazy<AssemblyDefinition> asm;
if (_libraries.TryGetValue(name.Name, out asm))
return asm.Value;
throw new AssemblyResolutionException(name);
}
protected virtual void Dispose(bool disposing)
{
if (!disposing)
return;
foreach (var lazy in _libraries.Values)
{
if (!lazy.IsValueCreated)
continue;
lazy.Value.Dispose();
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}
using (var module = ModuleDefinition.ReadModule(fileName", new ReaderParameters { AssemblyResolver = new DotNetCoreAssemblyResolver() })) |
I've got a similar problem with resolving assemblies in .NET Core and the above does not seem to work (in VS 2017 release). I have a 'netstandard1.6.1' library "A" and "B". "B" depends on "A". I have a 'netcoreapp1.1' application that loads "B" and then attempts to resolve a parameter from a method in "B" whose parameter type is from "A". The problem seems to be that for all the libraries in Since the above example was written based on the .json project framework, I tried adding what I think is the correct line to my .csproj: <PreserveCompilationContext>true</PreserveCompilationContext> But that did not seem to help. This was based on this bug report which implies it was fixed for 'publish' (?) but I'm trying to get it to work during development currently (and in unit tests). |
@Ziflin could you put together a small repro that shows your issue and link it here, I'll have a look with the newer toolkit. Thanks! |
I've finally found some time to investigate the issue further. I've migrated to .NET Core 2.0 in the meantime. The following assembly resolver seems to work fine from the command line. I guess it should also work for published applications. using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Mono.Cecil;
namespace Some.Namespace
{
class DotNetCoreAssemblyResolver : IAssemblyResolver
{
private static readonly string BaseDirectory = System.AppContext.BaseDirectory;
private static readonly string RuntimeDirectory = Path.GetDirectoryName(typeof(object).Assembly.Location);
private readonly Dictionary<string,AssemblyDefinition> libraries;
public DotNetCoreAssemblyResolver()
{
this.libraries = new Dictionary<string,AssemblyDefinition>();
}
public virtual AssemblyDefinition Resolve(string fullName)
{
return Resolve(fullName, new ReaderParameters() { AssemblyResolver = this });
}
public virtual AssemblyDefinition Resolve(string fullName, ReaderParameters parameters)
{
if (fullName == null) {
throw new ArgumentNullException("fullName");
}
return Resolve(AssemblyNameReference.Parse(fullName), parameters);
}
// IAssemblyResolver API
public AssemblyDefinition Resolve(AssemblyNameReference name)
{
return Resolve(name, new ReaderParameters() { AssemblyResolver = this });
}
public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
{
if (name == null) {
throw new ArgumentNullException("name");
}
AssemblyDefinition def;
if (!this.libraries.TryGetValue(name.Name, out def)) {
var path = Path.Combine(BaseDirectory, $"{name.Name}.dll");
if (File.Exists(path)) {
def = AssemblyDefinition.ReadAssembly(path, parameters);
this.libraries.Add(name.Name, def);
}
else {
path = Path.Combine(RuntimeDirectory, $"{name.Name}.dll");
if (File.Exists(path)) {
def = AssemblyDefinition.ReadAssembly(path, parameters);
this.libraries.Add(name.Name, def);
}
else {
path = $"{name.Name}.dll";
if (File.Exists(path)) {
def = AssemblyDefinition.ReadAssembly(path, parameters);
this.libraries.Add(name.Name, def);
}
}
}
}
return def;
}
// IDisposable API
protected virtual void Dispose(bool disposing)
{
if (!disposing) {
return;
}
foreach (var def in this.libraries.Values) {
def.Dispose();
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
} The basic strategy to find assemblies is as follows:
Hopefully, this might be of some help to others. |
@uhrm What about when an assembly is referenced directly from the global NuGet package folder? I'm not seeing anything that would handle that case. |
@bording Unfortunately, I don't understand what that means... My (limited and incomplete) understanding of package management under .NET Core 2.0 goes only so far:
I don't know of other options to specify dependencies. |
@uhrm This is the step I'm talking about. Assemblies from package references do not get copied into the base directory, though there is a setting you can add to your project to make this happen. By default, those assemblies are directly referenced from their location in the global NuGet package folder. If you look at the *..runtimeconfig.json file in the bin folder, you'll see that additional probing paths are listed, and those are all places .NET Core will look for assemblies. You are correct about project references. Those will be copied into the bin folder. |
@sbomer FYI |
So can anyone kind make this more concrete for me. If... ...then: If so, that's quite a severe limitation, no? |
Nevermind my previous question, I had to dig into it myself already to find out. I see how things fit together now. I'll do some experimenting with a custom assembly resolver for .NET Core (2.0). I'll update here if I find anything of value I guess. |
I might be missing something essential here, but I just can't see how the custom resolver posted earlier could have ever worked. Basing it on I'm guessing "all" Cecil consumers would work on a given input assembly, and for that to work, resolving must be based on that, no? If you'd ever find time to reflect on that, could you also comment on the status of this:
(And just to make sure I'm not only coming here bringing pain: I really love Cecil; its code-base, the design, and the straightforwardness in using it - big props! 💘) |
Fixed in #444. |
I have essentially the following program:
This works fine in Mono, but gives an
ArgumentNullException
running in .NET Core (Linux) when callingmref.Resolve()
. Stacktrace isThe text was updated successfully, but these errors were encountered: