Skip to content

Instantly share code, notes, and snippets.

@raffaeler
Last active March 31, 2020 23:07
Show Gist options
  • Save raffaeler/f8cf7c87ff9cd8e48c2b608b95b7cff4 to your computer and use it in GitHub Desktop.
Save raffaeler/f8cf7c87ff9cd8e48c2b608b95b7cff4 to your computer and use it in GitHub Desktop.
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