Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Draft for Assembly Loading in a mixed netstandard/netcore environment
// Read additional notes at the end of the file
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Reflection.Metadata;
using System.Diagnostics;
#if NET462
#endif
#if NETCORE
using System.Runtime.Loader;
#endif
using System.Reflection;
namespace ConsoleTest
{
public class AssemblyLoader
{
private static readonly string NetUnknown = null;
private static readonly string NetStandard = ".NETStandard";
private static readonly string NetFramework = ".NETFramework";
private static readonly string NetCoreApp = ".NETCoreApp";
private System.IO.FileInfo[] _netCoreDependencyFiles;
private System.IO.FileInfo[] _net462DependencyFiles;
public AssemblyLoader(string netcoreDependencies, string net462Dependencies)
{
Debug.WriteLine(GetRunningFramework());
#if NET462
var dependenciesPath = new System.IO.DirectoryInfo(net462Dependencies);
_net462DependencyFiles = dependenciesPath.GetFiles("*.dll", System.IO.SearchOption.AllDirectories);
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainAssemblyResolve;
#endif
#if NETCORE
var dependenciesPath = new System.IO.DirectoryInfo(netcoreDependencies);
_netCoreDependencyFiles = dependenciesPath.GetFiles("*.dll", System.IO.SearchOption.AllDirectories);
System.Runtime.Loader.AssemblyLoadContext.Default.Resolving += AssemblyResolving;
#endif
}
#if NET462
// netstandard framework specific
private Assembly CurrentDomainAssemblyResolve(object sender, ResolveEventArgs args)
{
var assemblyName = new AssemblyName(args.Name);
var fileInfo = _net462DependencyFiles.Where(f => f.Name.StartsWith(assemblyName.Name)).FirstOrDefault();
if (fileInfo == null)
{
return null;
}
var assembly = LoadFrom(fileInfo.FullName);
return assembly;
}
#endif
#if NETCORE
private Assembly AssemblyResolving(System.Runtime.Loader.AssemblyLoadContext assemblyLoadContext, AssemblyName assemblyName)
{
var fileInfo = _netCoreDependencyFiles.Where(f => f.Name.StartsWith(assemblyName.Name)).FirstOrDefault();
if (fileInfo == null)
{
return null;
}
var assembly = LoadFrom(fileInfo.FullName);
return assembly;
}
#endif
public static IList<Type> GetTypesInheriting<T>(string path)
{
Assembly assembly = LoadFrom(path);
if (assembly == null)
{
return new List<Type>();
}
IList<Type> types;
try
{
types = assembly.GetTypes()
.Where(t => typeof(T).IsAssignableFrom(t))
.ToList();
}
catch (System.Reflection.ReflectionTypeLoadException err)
{
Console.WriteLine($"Error loading {path}");
foreach (var exception in err.LoaderExceptions)
{
Console.WriteLine($"{exception.Message}");
}
Console.WriteLine();
types = new List<Type>();
}
catch (Exception err)
{
Console.WriteLine($"Error loading {path}");
Console.WriteLine(err.Message);
types = new List<Type>();
}
return types;
}
public static Assembly LoadFrom(string path)
{
var allowedFrameworks = AllowedFrameworks(path);
Assembly assembly = null;
try
{
#if NETCORE
//if(allowedFrameworks.RunsOnNetCore)
{
assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
}
#endif
#if NET462
//if (allowedFrameworks.RunsOnNetFramework)
{
assembly = Assembly.LoadFrom(path);
}
#endif
}
catch (Exception)
{
return null;
}
return assembly;
}
// netcore framework specific
private string GetRunningFramework()
{
var attributeValue = Assembly.GetEntryAssembly().GetCustomAttribute<System.Runtime.Versioning.TargetFrameworkAttribute>();
return attributeValue.FrameworkName;
}
public static (bool RunsOnNetCore, bool RunsOnNetFramework) AllowedFrameworks(string path)
{
using (var fileReader = System.IO.File.OpenRead(path))
{
using (var peReader = new System.Reflection.PortableExecutable.PEReader(fileReader))
{
var metadataReader = peReader.GetMetadataReader(MetadataReaderOptions.Default);
var attributeValue = GetTargetFrameworkAttributeValue(metadataReader);
var frameworkAndVersion = GetFrameworkAndVersion(attributeValue);
return
(frameworkAndVersion.Framework == NetStandard || frameworkAndVersion.Framework == NetCoreApp,
frameworkAndVersion.Framework == NetStandard || frameworkAndVersion.Framework == NetFramework);
}
}
}
private static string ReadBlob(MetadataReader metadataReader, BlobHandle blobHandle)
{
var blobReader = metadataReader.GetBlobReader(blobHandle);
var str = blobReader.ReadUTF16(blobReader.Length);
return str;
}
private static IList<string> GetReferences(MetadataReader metadataReader)
{
var references = metadataReader.AssemblyReferences
.OfType<AssemblyReferenceHandle>()
.Select(h => metadataReader.GetAssemblyReference(h))
.Select(a => metadataReader.GetString(a.Name))
.ToList();
return references;
}
private static (string Framework, Version Version) GetFrameworkAndVersion(string targetFrameworkAttributeValue)
{
if (string.IsNullOrEmpty(targetFrameworkAttributeValue))
{
// can't find the attribute ... assume is .net framework
return (NetUnknown, new Version("0.0.0.0"));
//throw new ArgumentException("Invalid TargetFrameworkAttribute Value");
}
string[] parts = targetFrameworkAttributeValue.Split(',');
if (parts.Length != 2)
{
throw new ArgumentException("Invalid TargetFrameworkAttribute Value: comma separator not found");
}
var version = "Version=v";
if (!parts[1].StartsWith(version) || parts[1].Length <= version.Length)
{
throw new ArgumentException("Invalid TargetFrameworkAttribute Value: The second part of the value should contain the Version string");
}
return (parts[0], new Version(parts[1].Substring(version.Length)));
}
// "System.Runtime.Versioning", "TargetFrameworkAttribute", ".NETStandard,Version=v1.4"
// "System.Runtime.Versioning", "TargetFrameworkAttribute", ".NETFramework,Version=v4.6.2"
// "System.Runtime.Versioning", "TargetFrameworkAttribute", ".NETCoreApp,Version=v1.1"
private static string GetTargetFrameworkAttributeValue(MetadataReader metadataReader)
{
var attributeValue = metadataReader.GetAssemblyDefinition().GetCustomAttributes()
.Select(h => metadataReader.GetCustomAttribute(h))
.Select(a => TryGetAttributeNameAsStrings(metadataReader, a))
.Where(a => a.IsSuccess && a.TypeNamespace == "System.Runtime.Versioning" && a.TypeName == "TargetFrameworkAttribute")
.Select(a => a.ValueString)
.FirstOrDefault();
return attributeValue;
}
// https://github.com/dotnet/corefx/blob/bffef76f6af208e2042a2f27bc081ee908bb390b/src/System.Diagnostics.FileVersionInfo/src/System/Diagnostics/FileVersionInfo.Metadata.cs
private static (bool IsSuccess, string TypeNamespace, string TypeName, string ValueString)
TryGetAttributeNameAsStrings(MetadataReader reader, CustomAttribute attr)
{
var res = TryGetAttributeName(reader, attr);
if (!res.IsSuccess)
{
return (false, null, null, null);
}
var valueResult = GetStringAttributeArgumentValue(reader, attr);
return (true, reader.GetString(res.typeNamespaceHandle), reader.GetString(res.typeNameHandle), valueResult.Value);
}
private static (bool IsSuccess, StringHandle typeNamespaceHandle, StringHandle typeNameHandle)
TryGetAttributeName(MetadataReader reader, CustomAttribute attr)
{
EntityHandle ctorHandle = attr.Constructor;
switch (ctorHandle.Kind)
{
case HandleKind.MemberReference:
EntityHandle container = reader.GetMemberReference((MemberReferenceHandle)ctorHandle).Parent;
if (container.Kind == HandleKind.TypeReference)
{
TypeReference tr = reader.GetTypeReference((TypeReferenceHandle)container);
return (true, tr.Namespace, tr.Name);
}
break;
case HandleKind.MethodDefinition:
MethodDefinition md = reader.GetMethodDefinition((MethodDefinitionHandle)ctorHandle);
TypeDefinition td = reader.GetTypeDefinition(md.GetDeclaringType());
return (true, td.Namespace, td.Name);
}
// Unusual case, potentially invalid IL
return (false, default(StringHandle), default(StringHandle));
}
private static (bool IsSuccess, string Value) GetStringAttributeArgumentValue(MetadataReader reader, CustomAttribute attr)
{
EntityHandle ctorHandle = attr.Constructor;
BlobHandle signature;
switch (ctorHandle.Kind)
{
case HandleKind.MemberReference:
signature = reader.GetMemberReference((MemberReferenceHandle)ctorHandle).Signature;
break;
case HandleKind.MethodDefinition:
signature = reader.GetMethodDefinition((MethodDefinitionHandle)ctorHandle).Signature;
break;
default:
// Unusual case, potentially invalid IL
return (false, null);
}
BlobReader signatureReader = reader.GetBlobReader(signature);
BlobReader valueReader = reader.GetBlobReader(attr.Value);
const ushort Prolog = 1; // two-byte "prolog" defined by ECMA-335 (II.23.3) to be at the beginning of attribute value blobs
if (valueReader.ReadUInt16() == Prolog)
{
SignatureHeader header = signatureReader.ReadSignatureHeader();
int parameterCount;
if (header.Kind == SignatureKind.Method && // attr ctor must be a method
!header.IsGeneric && // attr ctor must be non-generic
signatureReader.TryReadCompressedInteger(out parameterCount) && // read parameter count
parameterCount == 1 && // attr ctor must have 1 parameter
signatureReader.ReadSignatureTypeCode() == SignatureTypeCode.Void && // attr ctor return type must be void
signatureReader.ReadSignatureTypeCode() == SignatureTypeCode.String) // attr ctor first parameter must be string
{
return (true, valueReader.ReadSerializedString());
}
}
return (false, null);
}
}
}
// Referenced packages:
// - System.Reflection.Metadata (1.4.2)
// - System.Runtime.Loader (4.3.0)
//
// This source is not yet complete and will not probably work under nestandard
// because it is still missing the AssemblyResolve event.
// Revision 2
// Since I could not find a reliable way to retrieve the framework type from an assembly's metadata,
// I commented out the code reading the metadata information
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.