I've made a piece of code that gives me the referenced assemblies of one assembly (in .net5) and it was working greatly, it gave me for exemple if I use the File.WriteAllText method the assembly "System.IO.FileSystem" (this is by using the Assembly.GetReferencedAssemblies method).
But now I need to get this code to work on .NET4 (for Unity Engine). But i've seen that the myAssembly.GetReferencedAssemblies do not give the same output as in .NET5.
It now gives me only: myAssembly.dll and mscorlib.dll
And I cannot make it work to give me like previously all the referenced assemblies (for exemple System or System.IO ...)
Here's a simple example:
using System.IO;
public class Plugin {
static Plugin() {
// Just use the File class to keep the System.IO assembly reference
File.WriteAllText("test", string.Empty);
}
}
public static void Test() {
string myPluginPath = "myAssembly.dll";
// Load the assembly
Assembly assembly = Assembly.LoadFrom(myPluginPath);
// Get all the referenced assemblies
foreach (AssemblyName name in assembly.GetReferencedAssemblies()) {
Console.WriteLine(name.Name);
// Different outputs:
//
// .NET 5
// - System.IO.FileSystem
// - ....
// .NET 4
// - mscorlib
// - Plugin
}
}
Any idea how to make the myAssembly.GetReferencedAssemblies work on .net4 ? Thanks!
Any idea how to make the myAssembly.GetReferencedAssemblies work on .net4?
System.IO.File resides in mscorlib.dll in .NET Framework so both versions work correctly. Why do you need the actual assembly names? You cannot rely on them across platforms.
On the other hand, if you need this to resolve assembly qualified type names, then you can do it by using the legacy assembly identities so Type.GetType("System.IO.File, mscorlib, Version=4.0.0.0") works also in .NET 5.
It's because even on the newer platforms there is an mscorlib.dll, which contains nothing but a bunch of [assembly:TypeForwardedTo(...)] attributes that provide a redirect to the new location.
But it will not work the other way around so do not expect that you can resolve the type File from a System.IO.FileSystem.dll in .NET Framework 4.0
Update after the comment:
In .NET Framework the best way for handling potentially harmful plugins is creating separate AppDomains for them with restricted permissions. Here is an example from my unit tests to create such a sandbox domain and here is an example usage. The AppDomains can even be unloaded along with their referenced assemblies.
The bad news is that this will not work in .NET 5 because starting with .NET Core you cannot create AppDomains anymore (not quite a problem if you already have a solution for .NET 5). But for the sake of completeness: starting with .NET Core 3.0 the AssemblyLoadContext type is the recommended way for handling plugins. Though it does not create a restricted environment the same way as AppDomain, you can use AssemblyDependencyResolver to control the assembly loading requests of the plugins.
Related
I'm trying to load System.Data.SqlClient dynamically.
The System.Data.SqlClient Nuget is installed but there is no reference to it in the project.
And I don't know the right path to the nuget directory.
Is there a way to do this dynamically?
Here is my code
internal static Type GetFastType(this string typeName, string assembly)
{
if (string.IsNullOrEmpty(assembly))
throw new Exception("AssemblyName cannot be empty");
if (!assembly.ToLower().EndsWith(".dll"))
assembly += ".dll";
var key = typeName + assembly;
if (CachedStringTypes.ContainsKey(key))
return CachedStringTypes.Get(key);
// Assembly.LoadFrom(assembly) // throw exception as the dll is not found
if (!CachedAssembly.ContainsKey(assembly))
CachedAssembly.Add(assembly, Assembly.LoadFrom(assembly));
return CachedStringTypes.GetOrAdd(key, CachedAssembly.Get(assembly).GetType(typeName, true, true));
}
And here is how I run it
var type ="System.Data.SqlClient.SqlConnection".GetFastType("System.Data.SqlClient");
Required reading:
Read this MSDN article: Best Practices for Assembly Loading
In short:
It looks like you're assuming the System.Data.SqlClient.SqlConnection class always exists inside System.Data.SqlClient.dll.
This is an incorrect assumption:
A NuGet package is not a .NET assembly.
A NuGet package does not map 1:1 with a .NET assembly nor namespaces.
A NuGet package can contain multiple assemblies.
A NuGet package can contain zero assemblies.
A NuGet package can contain assemblies that don't have any types defined in them at all!
They could be assemblies that only contain Resources or other embedded items
They could be assemblies that use Type-Forwarding to redirect types that previously existed in this assembly other assemblies. Only the JIT uses this feature, however, not reflection.
And those "forwarded-to" assemblies don't have to exist in NuGet packages either: they can be "in-box" assemblies built-in to the runtime like mscorlib.dll and System.Data.dll).
They could be stub assemblies that don't provide any types when those types are already provided by the Base Class Library - the NuGet package only exists to provide those types for other platforms.
This is the situation you're dealing with.
A NuGet package can have very different effects based on the project's target (.NET Framework, .NET Standard, .NET Core, etc)
Your code cannot assume that a specific class is located in a specific assembly file - this breaks .NET's notion of backwards-compatibility through type-forwarding.
In your case...
In your case, your code assumes System.Data.SqlClient.SqlConnection exists inside an assembly file named System.Data.SqlClient. This assumption is false in many cases, but true in some cases.
Here is the top-level directory structure of the System.Data.SqlClient NuGet package:
Observe how inside the package there are subdirectories for each supported target (in this case, MonoAndroid10, MonoTouch10, net46, net451, net461, netcoreapp2.1, netstandard1.2, etc). For each of these targets the package provides different assemblies:
When targeting .NET Framework 4.5.1, .NET Framework 4.6 or .NET Framework 4.6.1 the files from the net451, net46 and net461 directories (respectively) will be used. These folders contain a single file named System.Data.SqlClient.dll which does not contain any classes. This is because when you target the .NET Framework 4.x, the System.Data.SqlClient (namespace) types are already provided by the Base Class Library inside System.Data.dll, so there is no need for any additional types. (So if you're building only for .NET Framework 4.x then you don't need the System.Data.SqlClient NuGet package at all.
Here's a screenshot of the insides of that assembly file using the .NET Reflector tool (a tool which lets you see inside and decompile .NET assemblies) if you don't believe me:
When targeting other platforms via .NET Standard (i.e. where System.Data.dll isn't included by default, or when System.Data.dll does not include SqlClient) then the NuGet package will use the netstandard1.2, netstandard1.3, netstandard2.0 directories, which does contain a System.Data.SqlClient.dll that does contain the System.Data.SqlClient namespace with the types that you're after. Here's a screenshot of that assembly:
And other platforms like MonoAndroid, MonoTouch, xamarinios, xamarintvos, etc also have their own specific version of the assembly file (or files!).
But even if you know your program will only run on a single specific platform where a specific NuGet package contains an assembly DLL that contains a specific type - it's still "wrong" because of type-forwarding: https://learn.microsoft.com/en-us/dotnet/framework/app-domains/type-forwarding-in-the-common-language-runtime
While Type-Forwarding means that most programs that reference types in certain assemblies will continue to work fine, it does not apply to reflection-based assembly-loading and type-loading, which is what your code does. Consider this scenario:
A new version of the System.Data.SqlClient NuGet package comes out that now has two assemblies:
System.Data.SqlClient.dll (which is the same as before, except SqlConnection is removed but has a [TypeForwardedTo] attribute set that cites System.Data.SqlClient.SqlConnection.dll).
System.Data.SqlClient.SqlConnection.dll (the SqlConnection class now lives in this assembly).
Your code will now break because it explicitly loads only System.Data.SqlClient.dll and not System.Data.SqlClient.SqlConnection.dll and enumerates those types.
Here be dragons...
Now, assuming you're prepared to disregard all of that advice and still write programs that assume a specific type exists in a specific assembly, then the process is straightforward:
// Persistent state:
Dictionary<String,Assembly> loadedAssemblies = new Dictionary<String,Assembly>();
Dictionary<(String assembly, String typeName),Type> typesByAssemblyAndName = new Dictionary<(String assembly, String typeName),Type>();
// The function:
static Type GetExpectedTypeFromAssemblyFile( String assemblyFileName, String typeName )
{
var t = ( assemblyFileName, typeName );
if( !typesByName.TryGetValue( t, out Type type ) )
{
if( !loadedAssemblies.TryGetValue( assemblyFileName, out Assembly assembly ) )
{
assembly = Assembly.LoadFrom( assemblyFileName );
loadedAssemblies[ assemblyFileName ] = assembly;
}
type = assembly.GetType( typeName ); // throws if the type doesn't exist
typesByName[ t ] = type;
}
return type;
}
// Usage:
static IDbConnection CreateSqlConnection()
{
const String typeName = "System.Data.SqlClient.SqlConnection";
const String assemblyFileName = "System.Data.SqlClient.dll";
Type sqlConnectionType = GetExpectedTypeFromAssemblyFile( assemblyFileName, typeName );
Object sqlConnectionInstance = Activator.CreateInstance( sqlConnectionType ); // Creates an instance of the specified type using that type's default constructor.
return (IDbConnection)sqlConnectionInstance;
}
For anyone that may have hade the same problem, i found the solution
Here is how you could load the right type in the right way
var type = Type.GetType($"{typeName}, {assembly}");
eg.
var type =Type.GetType("System.Data.SqlClient.SqlConnection, System.Data.SqlClient");
This way, it should load the dll dynamicly.
I think you have to provide the full path to LoadFrom(...). You should be aware of the probing path of the application, so just concat that path to the name of the assembly. I don't think is straighforward to load from a path that is not in the probing path unless doing some thricks with the app domain.
I take a Type, query its assembly location, and then load the assembly from the same address, and find the same type from the loaded assembly. The resulting type is not equal to the original type.
Here's the test case:
[TestMethod]
public void TestTypeLoadingWithFilePath()
{
var originalType = typeof(SomeClass);
var assemblyAddress = originalType.Assembly.Location;
var loadedAssembly = Assembly.LoadFile(assemblyAddress);
Assert.IsNotNull(loadedAssembly);
var loadedType = loadedAssembly.GetType(originalType.FullName);
Assert.IsNotNull(loadedType);
Assert.AreEqual(originalType, loadedType);
}
The test fails on the last assertion.
This only happens on .NET Core on Windows. (I'm testing against latest version, 2.1.4). But this was not the case with .NET Framework.
My questions are:
Is this by design, or a bug?
If it's by design, why?
Again, if it's by design, doesn't this mean different behavior between two implementations of .NET Standard? (.NET Core vs. .NET Framework)
This is a normal behavior. Using Assembly.LoadFile will load the assembly and create a new "instance" of it. To fix this, simply use Assembly.LoadFrom instead. This will first look in the current context if the requested assembly is already loaded, and take this one if it is. Comparing types like you're doing will then work.
Edit: I don't know if it's intended, but this method works in both .NetFramework and .NetCore.
I'm in the process of creating a new minor release of a toy project of mine. This project is released on NuGet and is compatible with .NET 4.0 and up. Some of the new features I'm introducing require .NET 4.5 (users should be able to resolve IReadOnlyCollection<T> and IReadOnlyList<T>, both interfaces that were introduced in .NET 4.5), but I need to keep the project compatible with .NET 4.0, since not all developers can easily migrate to the latest .NET framework.
So the problem I’m facing is how to solve this ‘forward-compatibility’ problem. There are two solutions I’ve thought about, but both are not very attractive, so hopefully anybody can give me some ideas or guidance here.
Here are the two solutions I came up with:
Solution 1: Use #if compiler directives and build a DLL per .NET framework version and ship those versions using the NuGet packages and download at the project site.
Downside of this method is that when developers update their Visual Studio project from .NET 4.0 to .NET 4.5, they don't automatically get the .NET 4.5 version (with .NET 4.5 specific features). This violates the Principle of least astonishment and would leave developers dazed why the feature is not working, when they try using it a few months later.
Solution 2: Use one single DLL and emit type's on the fly that implement both new interfaces when they exist in the current app domain. This allows shipping a single DLL to the user and allows features to come available when the developer switches .NET framework versions in their project. This will make things 'just work'. This is the direction I’m currently heading btw.
Since I need to return a type that needs to implement the interfaces, the downside is that that type must be created at runtime using Reflection.Emit, ModuleBuilder, TypeBuilder, and the like. This is seriously nasty shizzle. But besides that, since this type must be created in a new (anonymous) assembly, I must make some internal types public (a type it needs to inherit from and an interface it needs to implement). Making those internal types public pollutes the API of the project and will disallow me from making changes to those types.
I believe these are my options, but I might be missing something obvious. So my question is, am I missing a possibility? Is there a way to circumvent the problems for solution 1 or would it be better to go with the hardcore root of runtime type emitting?
Have you thought about another custom assembly with the missing items in it? Then you test if a type/method exists (that would only exist in .net 4.5) and if it does, you load the assembly in.
That way you can keep the exact same methods and classes, and save yourself pain of doing all of that crazy emit (not to mention the perf hit you will take if you find yourself doing that much).
I have a project called Dynamitey that allows you to load a type at runtime and called it's static methods and constructors with the DLR. Which would be much less messy than a lot of reflection or emitting code to load an api that is not necessarily available.
dynamic bigIntType = new DynamicObjects.LateType("System.Numerics.BigInteger, System.Numerics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
if (bigIntType.IsAvailable)
{
var one = bigIntType.#new(1);
var two = bigIntType.#new(2);
Assert.IsFalse(one.IsEven);
Assert.AreEqual(true, two.IsEven);
var tParsed = bigIntType.Parse("4");
Assert.AreEqual(true, tParsed.IsEven);
}
I also have a project called ImpromptuInterface, that will emit proxy types for interfaces around objects that duck callable match it (also uses DLR).
var targetType =Type.GetType("System.Collections.Generic.IReadOnlyList`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
var list = new List<string>{"lala", "la","lala"};
object readonlyList;
if(targetType != null){
readonlyList = Impromptu.DynamicActLike(list, targetType);
}
Since version 3.0, .NET installs a bunch of different 'reference assemblies' under C:\Program Files\Reference Assemblies\Microsoft...., to support different profiles (say .NET 3.5 client profile, Silverlight profile). Each of these is a proper .NET assembly that contains only metadata - no IL code - and each assembly is marked with the ReferenceAssemblyAttribute. The metadata is restricted to those types and member available under the applicable profile - that's how intellisense shows a restricted set of types and members. The reference assemblies are not used at runtime.
I learnt a bit about it from this blog post.
I'd like to create and use such a reference assembly for my library.
How do I create a metadata-only assembly - is there some compiler flag or ildasm post-processor?
Are there attributes that control which types are exported to different 'profiles'?
How does the reference assembly resolution at runtime - if I had the reference assembly present in my application directory instead of the 'real' assembly, and not in the GAC at all, would probing continue and my AssemblyResolve event fire so that I can supply the actual assembly at runtime?
Any ideas or pointers to where I could learn more about this would be greatly appreciated.
Update: Looking around a bit, I see the .NET 3.0 'reference assemblies' do seem to have some code, and the Reference Assembly attribute was only added in .NET 4.0. So the behaviour might have changed a bit with the new runtime.
Why? For my Excel-DNA ( http://exceldna.codeplex.com ) add-in library, I create single-file .xll add-in by packing the referenced assemblies into the .xll file as resources. The packed assemblies include the user's add-in code, as well as the Excel-DNA managed library (which might be referenced by the user's assembly).
It sounds rather complicated, but works wonderfully well most of the time - the add-in is a single small file, so no installation of distribution issues. I run into (not unexpected) problems because of different versions - if there is an old version of the Excel-DNA managed library as a file, the runtime will load that instead of the packed one (I never get a chance to interfere with the loading).
I hope to make a reference assembly for my Excel-DNA managed part that users can point to when compiling their add-ins. But if they mistakenly have a version of this assembly at runtime, the runtime should fail to load it, and give me a chance to load the real assembly from resources.
To create a reference assembly, you would add this line to your AssemblyInfo.cs file:
[assembly: ReferenceAssembly]
To load others, you can reference them as usual from your VisualStudio project references, or dynamically at runtime using:
Assembly.ReflectionOnlyLoad()
or
Assembly.ReflectionOnlyLoadFrom()
If you have added a reference to a metadata/reference assembly using VisualStudio, then intellisense and building your project will work just fine, however if you try to execute your application against one, you will get an error:
System.BadImageFormatException: Cannot load a reference assembly for execution.
So the expectation is that at runtime you would substitute in a real assembly that has the same metadata signature.
If you have loaded an assembly dynamically with Assembly.ReflectionOnlyLoad() then you can only do all the reflection operations against it (read the types, methods, properties, attributes, etc, but can not dynamically invoke any of them).
I am curious as to what your use case is for creating a metadata-only assembly. I've never had to do that before, and would love to know if you have found some interesting use for them...
If you are still interested in this possibility, I've made a fork of the il-repack project based on Mono.Cecil which accepts a "/meta" command line argument to generate a metadata only assembly for the public and protected types.
https://github.com/KarimLUCCIN/il-repack/tree/xna
(I tried it on the full XNA Framework and its working afaik ...)
Yes, this is new for .NET 4.0. I'm fairly sure this was done to avoid the nasty versioning problems in the .NET 2.0 service packs. Best example is the WaitHandle.WaitOne(int) overload, added and documented in SP2. A popular overload because it avoids having to guess at the proper value for *exitContext" in the WaitOne(int, bool) overload. Problem is, the program bombs when it is run on a version of 2.0 that's older than SP2. Not a happy diagnostic either. Isolating the reference assemblies ensures that this can't happen again.
I think those reference assemblies were created by starting from a copy of the compiled assemblies (like it was done in previous versions) and running them through a tool that strips the IL from the assembly. That tool is however not available to us, nothing in the bin/netfx 4.0 tools Windows 7.1 SDK subdirectory that could do this. Not exactly a tool that gets used often so it is probably not production quality :)
You might have luck with the Cecil Library (from Mono); I think the implementation allows ILMerge functionality, it might just as well write metadata only assemblies.
I have scanned the code base (documentation is sparse), but haven't found any obvious clues yet...
YYMV
I'm currently working on project for the .NET Compact Framework which uses DotNetZip for reading ZIP-files. The project is split into two parts. One platform-independent library which should be available for both, a CF project and a desktop project. This common library contains the code for extracting ZIP-files. The problem is that there are two different DLLs of the DotNetZip library, one for .NET CF and one for the .NET Desktop Framework. It's not possible to use the CF version of the library on a Desktop Framework and vice versa but they share the same interface.
How do I organize my projects in Visual Studio to be able to use the common zip-code and dynamically load the appropriate DLL (which then is used by the zip-code)? It should also be possible to execute the CF application on a PC so is there a way to choose the DLL at runtime?
UPDATE:
A technically unsupported but working solution is to p/Invoke LoadLibrary and specifically load the correct DLL early in the process. All managed and p/Invoke'd libraries are loaded "as needed". The CLR will load them for you when you invoke a method that depends on one of the types in that assembly (note: this is the "unsupported" part; this is not documented and may change in a future CLR version).
The approach works because if an assembly is loaded via LoadLibrary, it's "found" by the CLR loader and the runtime will not attempt to load it again. We used this trick on CF to pre-load assemblies before doing much memory allocation to avoid out-of-memory situations.
So, you should be able to do:
public static void Main()
{
LoadCorrectDLLs();
// .NET will ensure DotNetZip is loaded at this point.
MethodInThisAssembly();
}
public static void MethodInThisAssembly()
{
// Since MethodInThisAssembly uses DotNetZip,
// its assembly will get loaded immediately before this method is called.
IDotNetZipInterface x = null;
...
}
public static void LoadCorrectDLLs()
{
// p/Invoke LoadLibrary to load the correct version of DotNetZip.
}
Note that the following will not work:
public static void Main()
{
LoadCorrectDLLs();
// This line would force DotNetZip to get loaded before Main() is called
IDotNetZipInterface x = null;
}
Old answer; works on desktop framework only:
One trick is to place them in directories that won't be found during DLL loading (e.g., different subdirectories of your executable's directory) and handle AppDomain.AssemblyResove. David Morton has a decent write-up on his blog.