We are in the beginning stages of converting a c# Winforms App from .NET Framework to .NET 6. We can get the project to build and run in .NET 6, but when it comes to a dynamically loaded assembly, we are having issues. We can get the assembly to load but attempting to access the custom class within it returns a null. I recreated this scenario in two smaller projects as an example.
Solution 1/Project 1 - The code for the assembly to be loaded into the main application. This is a class library that creates the TestAssembly.dll
namespace Custom.TestAssembly
{
public class TestClass : CallingModule
{
public override object GetValue()
{
return "Hello World";
}
}
}
Solution 2/Project 1 - This is a project and class within the main application's solution. This is a class library that creates the Custom.TestAssembly.dll
namespace Custom.TestAssembly
{
public class CallingModule
{
public virtual object? GetValue()
{
return null;
}
}
}
Solution 2/Project 2 - A button has been placed on a form. When it is clicked, the assembly should be loaded, which it is. However, attempting to extract the class from the assembly always returns a NULL.
Form1.cs
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Loader;
namespace TestCallingApplication
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Assembly dynamicAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(#"C:\LocationOf\TestAssembly.dll");
Module customizationModule = dynamicAssembly.GetModule("TestAssembly.dll");
Type customClientModule = customizationModule.GetType("Custom.TestAssembly.TestClass"); //THIS RETURNS A NULL??
}
}
}
Just trying to understand what I am missing. Any thoughts? Or a better way to load runtime assemblies and access classes within them in .NET 6?
Did you reference Solution 2/Project 1 ?
Since they have the same assembly name Custom.TestAssembly, the runtime will not load it again if already loaded in memory.
You can, however, load it under a different AssemblyLoadContext, there's an example on MSDN as well.
Also, you may want to take a look at DotNetCorePlugins, which takes care of assembly loading, reloading, isolation, shared type, and dependency resolving.
Related
I have a VS solution set up using build scripts to copy the compiled DLL into another project. The base project builds fine, and copies correctly to the target.
However, the target project won't compile as it can not find a particular class within the namespace:
foo.cs
namespace foo {
public class bar {
public static string myVar {
get { return "A string"; }
}
}
}
myPage.aspx.cs
using foo;
namespace foo.foo2 {
partial class bar2 {
protected void Page_Load(object sender, EventArgs e) {
// can access foo.bar here in the source project but not once the DLL is compiled and copied to target
var myVar = bar.myVar; // The name 'bar' does not exist in the current context
}
}
}
Why would this compile correctly in the source project, but prevent the target from building?
EDIT: Second project builds fine if I exclude myPage.aspx from the project. But I shouldn't have to do that.
You have very likely an incorrect assembly referenced.
Make sure you use the correct physical assembly. Sometimes, a dead (old) version lies around (such as in the GAC) and this one is referenced rather than the believed new version.
Easiest way to confirm is to rename the assembly file to something else and reference the newly named assembly. bar.myVar should show up immediately.
I have a Winforms application that relies on a 3rd party SDK. I've included the .NET reference in the application, but don't always need/use it. If I try and execute the program on a machine without the DLLs it will not open at all: it won't even enter Main.
Is it possible have a reference but instruct it to only load the DLLs when required?
(PDFSharp (another reference I use) appears to only load when a PdfSharp method is called, which makes me wonder if it's something I can control.)
Edit...I can't see any 3rd party reference in the Program class, but just in case here it is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace MyProgram
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
try
{
Application.Run(new Main(args));
}
catch (Exception Ex)
{
MessageBox.Show(Ex.Message, "The program has quit :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
string TracefileContents = "Please email this file to some#body.com\n\n" + "Exception:\n" + Ex.Message + "\n\nStack trace:\n" + Ex.StackTrace.ToString();
System.IO.File.WriteAllText("Issue Report " + DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss") + ".dat", TracefileContents);
}
}
}
}
.NET does that automatically, everything is loaded on demand by default.
Assemblies are only loaded when you start using some type from the assembly. Just having a reference to the assembly has no impact on the run-time behavior of the application
To be more accurate, CLR loads an assembly only when a it JIT-compiles a method that uses that type. This also includes using a type that derives from/implements one of the classes/interfaces of the assembly.
Even instantiating a class that have a field or property of a type from another assembly, does not enforce loading the assembly. Unless the field or property is being accessed in the class' constructor. For example when you set a field's value in its definition statment:
// `TypeFromAnotherAssembly` is loaded when the class is instantiated
class Test
{
private TypeFromAnotherAssembly myField = CreateTypeFromAnotherAssembly();
}
compiler emits initialization code in the class' constructor. Then, according to the rule above, when the constructor is JIT-compiled (the class is instantiated for the first time), the CLR loads the assembly. This also includes setting the field's value to null:
// `TypeFromAnotherAssembly` is loaded when the class is instantiated
class Test
{
private TypeFromAnotherAssembly myField = null;
}
This does not happen when you omit the initialization statement, although the result is exactly the same (the .NET runtime automatically initialized class fields to null or 0):
// `TypeFromAnotherAssembly` is NOT loaded when the class is instantiated
class Test
{
private TypeFromAnotherAssembly myField;
}
You should be careful about static fields' initialization, because accessing the class in any way causes the initialization to occur.
I'm creating an add-on system for a shell I'm developing using C#. I've followed this and this. Here is my function to load an add-on:
public void loadAppFromDLL(string assemblyFile)
{
Assembly a = Assembly.Load(assemblyFile);
Type app = a.GetType("App");
MethodInfo loadMethod = app.GetMethod("load");
object appInstance = Activator.CreateInstance(app);
loadMethod.Invoke(appInstance, null);
}
Here is the add-on:
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace App
{
public class App
{
public void load()
{
MessageBox.Show("Application loaded successfully!");
}
}
}
When I build the add-on, I place it in the same directory as the shell executable and call:
LoadExternalApp lea = new LoadExternalApp();
lea.loadAppFromDLL("SampleApp");
(LoadExternalApp contains the DLL loading function)
When I was debugging my shell, I noticed that:
The app didn't start
There was a System.NullReferenceException
What am I not doing right?
This:
Type app = a.GetType("App");
is looking for a type with a namespace-qualified name of App.
Your type is called App in a namespace of App, so Assembly.GetType is returning null, and then you're dereferencing it. Instead, you should use:
Type app = a.GetType("App.App");
However, you shouldn't give a class the same name as its namespace in the first place. Fix that, so that you end up with something more like:
Type app = a.GetType("App.PlugIn");
You should still check whether GetType (or GetMethod) returns null, in order to fail rather more gracefully and with more information.
Additionally, you should start following .NET naming conventions - give methods names in PascalCase. Oh, and you might want to consider a common interface for your add-ins rather than relying on reflection to call methods.
I have a interface as follows:
[InheritedExport(typeof(ITransform))]
public interface ITransform
{...}
Now, I have two classes:
namespace ProjectA
{
public class Transform:ITransform {....}
}
And
namespace ProjectB
{
public class Transform:ITransform {....}
}
I am using DirectoryCatalog for loading each parts. Each project is compiled and their binaries(build output) location is given as an input to DirectoryCatalog for further composition.
The code for fetching ITransform parts is as follows:
public static class ExtensionFactory
{
public static ITransform GetExtension(string extensionPath)
{
IEnumerable<ITransform> extensions = null;
try
{
AggregateCatalog catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(extensionPath));
CompositionContainer container = new CompositionContainer(catalog);
container.ComposeParts(catalog);
extensions = container.GetExportedValues<ITransform>();
return extensions.FirstOrDefault();
}
catch (Exception ex) {........}
return extensions.FirstOrDefault();
}
}
I have another project ProjectXYZ(auto generated by third party tool(Altova Mapforce 2012 SP1)).
For ProjectA:
namespace ProjectXYZ
{
public classA{...}
}
For ProjectB:
namespace ProjectXYZ
{
public classA{...}
public classB{...}
}
ProjectA.Transform uses ProjectXYZ.ClassA, whereas ProjectB.Transform uses ProjectXYZ.ClassB from another implementation of ProjectXYZ. The implementation and classes of ProjectXYZ varies across for different implementation of ITransform. The classes in ProjectXYZ are automatically generated through some third-party tools, which I need to use directly. So, I cannot make any changes to ProjectXYZ.
So, when first time MEF loads ProjectA.Transform, it also loads ProjectXYZ to be used as a reference for ProjectA. When ProjectB.Transform is getting loaded/exported, then as ProjectXYZ being already in MEF memory, it uses the ProjectXYZ reference available from ProjectA. Thus, when ProjectB.Transform is executing, it searches for ProjectXYZ.ClassB, which it does not gets as MEF has load ProjectXYZ reference available in ProjectA.
How to resolve this problem. The MEF loads the parts correctly, but it does not load the supporting dll's references in a desired manner. I have also tried PartCreationPolicy attribute, but the results are same.
It not a MEF problem. The problem is in the loading model of .NET. (or better the way you're objects are loaded by .net)
When MEF loads it returns the correct objects. But when looking for class ProjectXYZ when projectB is loaded there is already a ProjectXYZ dll loaded with the correct assembly name projectB is referring to. And the loader the dll actually referenced by projectB is not loaded.
I created a solution called Foo.
Added a class library called Foo.Common
Added a console app to call the library code from called ConsoleApp.
I referenced the Foo.Common from ConsoleApp and typed :
using Foo.Common;
public class Program
{
CommonClass c = new CommonClass();
static void Main(string[] args)
{
}
}
and get this back :
Error 1 The type or namespace name '**Foo**' could not be found (are you missing a using directive or an assembly reference?) Z:\Foo\Solution1\ConsoleApplication1\Program.cs 3 11 ConsoleApplication1
Why am i getting this?
what s going on?
Make sure that
The ConsoleApp project has a reference to the Foo.Common project (do not browse for Foo.Common.dll),
the file contains a using directive for the namespace in which CommonClass is declared, and
CommonClass is declared as public.
So your files should look like this:
CommonClass.cs in Foo.Common project:
namespace Foo.Common
{
public class CommonClass
{
public CommonClass()
{
}
}
}
Program.cs in ConsoleApp project:
using Foo.Common;
namespace ConsoleApp
{
public class Program
{
public static void Main()
{
CommonClass x = new CommonClass();
}
}
}
Ensure that under your project settings, the target framework is set as .NET Framework 4 and not .NET Framework 4 Client Profile. I got this same behavior when it was set to Client Profile and it was fixes as soon as I set it to just the regular .NET Framework 4.
Right Click on the new console app solution/project and Add Reference and add the project that contains the Foo namespace
Did you add a reference to the library? Look under "References" in the console project. If its not there, you need to add it.
I posted this as a comment, but I want to expand on it here. What's probably happening is it's seeing using as a statement and not a keyword. It appears you have something like the following:
using System;
namespace TestNamespace
{
using Foo.Common;
public Class { }
}
Try
using System;
using Foo.Common;
namespace TestNamespace
{
public Class { }
}
Instead.
It looks like Foo Bar got this error because his project's target framework was set to the client profile.
Just thought I'd add one more 'solution' -- I created a library that targeted the 4.5 framework. My older project was tarting the 4 framework. I got this error.
Changing the older project to 4.5 made it work.