Last active
March 31, 2020 23:07
-
-
Save raffaeler/f8cf7c87ff9cd8e48c2b608b95b7cff4 to your computer and use it in GitHub Desktop.
Draft for Assembly Loading in a mixed netstandard/netcore environment
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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