AssemblyResolve Does not fire - c#

I have an asp.net application. I want to load some assemblies dynamically.
this is my code on application start
protected void Application_Start(Object sender, EventArgs e)
{
LoadPrivateAssemblies();
}
private static void LoadPrivateAssemblies()
{
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainAssemblyResolve;
Assembly.Load("MyDLL");
}
static Assembly CurrentDomainAssemblyResolve(object sender, ResolveEventArgs args)
{
//loads and returns assembly successfully.
}
this code works fine except when a nested c# code calls a class from my dynamic dll Inside an asp.net page (not code-behind)
sample :
<%if(MyDLL.TestObject.Value){%>white some ting<%}%>
what should I do now ?
I think If I know When a new AppDomain is created, it may solve my problem.

I really think you are barking up the wrong tree here.
Assembly.Load(byte[]) does "persist" the assembly in the app domain - otherwise, what else would it be doing?
To illustrate the fact it does, try this:
Create a solution with one console application, and one class library, named OtherAssembly.
In the class library, OtherAssembly, add a single class:
namespace OtherAssembly
{
public class Class1
{
public string HelloWorld()
{
return "Hello World";
}
}
}
In the console application, use this as your program:
public class Program
{
static void Main(string[] args)
{
try
{
using (var fs = new FileStream("OtherAssembly.dll", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
var buffer = new byte[fs.Length];
// load my assembly into a byte array from disk
fs.Read(buffer, 0, (int) fs.Length);
// load the assembly in the byte array into the current app domain
AppDomain.CurrentDomain.Load(buffer);
}
// get my type from the other assembly that we just loaded
var class1 = Type.GetType("OtherAssembly.Class1, OtherAssembly");
// create an instance of the type
var class1Instance = class1.GetConstructor(Type.EmptyTypes).Invoke(null);
// find and invoke the HelloWorld method.
var hellowWorldMethod = class1.GetMethod("HelloWorld");
Console.WriteLine(hellowWorldMethod.Invoke(class1Instance, null));
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
finally
{
Console.ReadLine();
}
}
}
Don't reference OtherAssembly from your main program assembly, instead, compile the solution and manually drop OtherAssembly.dll in the main program's bin folder.
Run the main program. It outputs "Hello World", which it can only have done if the assembly was loaded and retained in memory. You'll note I've been very careful not to give Visual Studio or C# any hint to load this OtherAssembly. No reference was added, the type is not explicitly referenced in C#.
You need to look again at your problem.
[EDIT: in response to you commenting on the fact this is not an ASP.NET application]
OK - I've moved my main program to an ASP.NET web page and tried accessing the assembly both from code behind and the markup - and it works in both situations. I'm sure you are missing something here - it just doesn't make sense that the behaviour of this method - whose job it is to load an assembly into the current app domain - is different in an ASP.NET scenario.
It seems to me there are at least two things to investigate:
If it really is a new app domain being created on each call, it would only be because of an unhandled exception in your application. The quickest way to diagnose this is to use SysInternals Process Explorer. Fire it up - add .Net -> Total AppDomains to the list of columns and watch the app domain count for your process (IIS worker process or web dev) as you run your app. If this increases on each call, you've got unhandled exceptions tearing down one AppDomain and forcing another to be created.
Are you sure this assembly is really being loaded? When you pass the byte array to Assembly.Load, try also writing it out to disk using FileStream.Write. Open the file in Reflector or some similar tool. Is this the assembly you expected it to be. Is it really being loaded at all, or is it an empty/corrupt array?
I'm not trying to be argumentative, but this really feels like you are looking in the wrong place for the cause of the problem.

I think If I know When a new AppDomain is created, it may solve my problem
You should use AssemblyLoad event. AssemblyResolved occurs when the resolution of assembly is fails

I found that the problem is because Assembly.Load(bytes); does not persist the assembly in appdomain. does any body know how to persist loaded assembly using Assembly.Load(bytes); in appdomain ?
finally I decided to switch to LoadFile method instead of load.
EDIT
Finally I switched from Assemly.Load(byte[]) to Assembly.LoadFile(string).
But it did not correct the problem itself. I have added <%#Assembly name="MyDLL"%> To All of my ASPX files that has markup C# code emebeded.
And This Solved my Problem.
Thanks to your answers. I have voted up answers that helped me, but I can not accept your solutions because no one is not complete enough.

Related

Access windows forms controls from external DLLs in C#

This is my first Topic here and I didn't find any similar Topics so I try to describe my problem as good as I can:
I was ordered by my Company to create a modular C# program to assist our Software Developers with Background tasks. The Programm is composed of a Windows Forms application with a User Interface that calls external DLLs that do the actual work. All These DLLs are written by me aswell and follow certain rules to make them compatible to the Main App. That way I can easily add new funcions to the Programm just by putting the DLL into a predefined Folder. So to say Plug-and-Run
The main program contains a ListBox that shows all available PlugIns and if one get's selected and the "start" button is clicked, the Main program calls the corresponding DLL and Invokes the method "program" that starts the DLLs actual function. Furthermore the Main contains a method "Output" that is supposed to write the result of every PlugIn into a Tab of my TabControl. That way the results of every PlugIn running in separate threads can be viewed independently. The Access to the tab already has a delegate to make it threadsafe. The Information is gathered by invoke from the PlugIn's own "returnOutput" method that simply Returns a List of strings containing the results to the Main.
My Problem now is: How can i implement a Kind of a callback into my PlugIn DLLs so they can order the Main Program to gather the results at any time?
My first idea was to simply add the result as return values to the "program" method itself but that would make the Information only available at the end of the program and some of the Tasks require a "live update" during runtime.
My second idea was to use the delegate for the Control as Parameter and pass it to the PlugIn so the PlugIn DLL could Access the Control on it's own. This idea failed because the DLL doesn't "know" the Main program and can't Access it's Methods or the delegates instance so I am Always missing a reference.
Is there a way to solve my problem? If necessary I can provide Code snippets but the program has already around 800 lines of Code and each PlugIn adds a few hundred more..
Thanks in advance for every answer and sorry for my non-native english :D
Best Regards
Gerrit "Raketenmaulwurf" M.
Edit: I am using SharpDevelop 5.1
Code Snippet for the DLL call:
PlugIn = PlugIns.SelectedItem.ToString();
Assembly PlugInDLL = Assembly.LoadFile(#PlugInOrdner+"\\"+PlugIn+".dll");
Object Objekt = PlugInDLL.CreateInstance("DLL.PlugIn");
MethodInfo Info1 = Objekt.GetType().GetMethod("Programm");
Info1.Invoke(Objekt, new Object[]{Projekt, TIAInstanz});
it basically Looks for a DLL file that has the same Name as the highlighted item in the ListBox
There are many different ways to do this. Some of the suggestions in the comments are really good and implementing them would make a robust and extendable solution.
If you are looking for a quick and easy way to get messages from your plugins, though, then you can pass your callback directly to the plugin as an Action:
public class PluginRunner
{
public class PluginMessageEventArgs
{
public string Text { get; set; }
}
public event EventHandler<PluginMessageEventArgs> PluginMessage;
public void Run( string pluginPath )
{
Assembly PlugInDLL = Assembly.LoadFile(pluginPath);
Object Objekt = PlugInDLL.CreateInstance("DLL.PlugIn");
MethodInfo Info1 = Objekt.GetType().GetMethod("Programm");
Info1.Invoke(Objekt, new Object[] { Projekt, TIAInstanz, new Action<string>(Log) });
}
private void Log(string s)
{
PluginMessage?.Invoke(this, new PluginMessageEventArgs { Text = s });
}
}
so you can use it like:
var path =
Path.Combine(
Path.GetDirectoryName(Assembly.GetEntryAssembly().Location),
"Plugins",
"MyAwesomePlugin.dll");
var pr = new PluginRunner();
// be aware that your event delegate might be invoked on a plugin's thread, not the application's UI thread!
pr.PluginMessage += (s,e) => Console.WriteLine("LOG: " + e.Text);
pr.Run(path);
then your plugin's Programm method writes its logs:
public void Programm( ProjektClass p0, TIAClass p1, Action<string> log )
{
Task.Run(() =>
{
// do something
log.Invoke("here am I!");
// do something else
log.Invoke("here am I again!");
// do something more
});
}
I must admit, that this is not the ideal way to deal with messaging. There are far better (and, unfortunately, more complicated to implement) solutions out there. This one is fairly simple though. Just don't forget that you receive your message on the same thread that have sent it and avoid deadlocks.

Load a .NET assembly from the application's resources and run It from memory, but without terminating the main/host application

INTRODUCTION
I'm using the next C# code example shared by David Heffernan' for loading a .NET assembly from the application's resources and run It from memory:
Assembly a = Assembly.Load(bytes);
MethodInfo method = a.EntryPoint;
if (method != null)
method.Invoke(a.CreateInstance(method.Name), null);
Here I just share an adaptation in VB.NET that I am using too:
Public Shared Sub Execute(ByVal resource As Byte(), ByVal parameters As Object())
Dim ass As Assembly = Assembly.Load(resource)
Dim method As MethodInfo = ass.EntryPoint
If (method IsNot Nothing) Then
Dim instance As Object = ass.CreateInstance(method.Name)
method.Invoke(instance, parameters)
If (instance IsNot Nothing) AndAlso (instance.GetType().GetInterfaces.Contains(GetType(IDisposable))) Then
DirectCast(instance, IDisposable).Dispose()
End If
instance = Nothing
method = Nothing
ass = Nothing
Else
Throw New EntryPointNotFoundException("Entrypoint not found in the specified resource. Are you sure it is a .NET assembly?")
End If
End Sub
PROBLEM
The problem is that if the executed assembly has an application exit instruction, then it also terminates my main/host application too. For example:
ConsoleApplication1.exe compiled from this source-code:
Module Module1
Sub Main()
Environment.Exit(0)
End Sub
End Module
When I add ConsoleApplication1.exe to the application resources, and then I load it and run it with the Assembly.Load methodology, it also terminates my application because the call to Environment.Exit.
QUESTION
How can I prevent this, without modifying the source code of the executed assembly?.
Maybe I could do somehting like associate a kind of exit event handler to the executed assembly to handle/ignore it properly?. What are my options at this point?.
PS: For me no matter if the given solution is written in C# or VB.NET.
Please note two things, the first is that my intention is to resolve this issue in an automated/abstracted way, I mean the final result should just need to call the "Execute" method passing the resource and the arguments and don't worry about the rest; and secondly, I want the executed assembly to be ran synchronuslly, not async... in case of that could matter for a possible solution.
Update: My first solution doesn't work for assemblies contained in the resources of a program like OP asked; instead it loads it from the disk. The solution for loading from a byte array will follow (in progress). Note that the following points apply to both solutions:
As the Environment.Exit() method throws an exception because of lack of permissions, execution of the method will not continue after it is encountered.
You're going to need all the permissions that your Main method needs, but you can find those quickly by just typing in "Permission" in intellisense, or by checking the SecurityException's TargetSite property (which is an instance of MethodBase and will tell you which method failed).
If another method in your Main needs the UnmanagedCode permission, you're out of luck, at least using this solution.
Note that I found that the UnmanagedCode permission was the one that Environment.Exit() needs purely by trial and error.
Solution 1: When the assembly is on the disk
Okay, here's what I've found so far, bear with me. We're going to create a sandboxed AppDomain:
AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
// This is where the main executable resides. For more info on this, see "Remarks" in
// https://msdn.microsoft.com/en-us/library/system.appdomainsetup.applicationbase(v=vs.110).aspx#Anchor_1
PermissionSet permission = new PermissionSet(PermissionState.None);
// Permissions of the AppDomain/what it can do
permission.AddPermission(new SecurityPermission(SecurityPermissionFlag.AllFlags & ~SecurityPermissionFlag.UnmanagedCode));
// All SecurityPermission flags EXCEPT UnmanagedCode, which is required by Environment.Exit()
// BUT the assembly needs SecurityPermissionFlag.Execution to be run;
// otherwise you'll get an exception.
permission.AddPermission(new FileIOPermission(PermissionState.Unrestricted));
permission.AddPermission(new UIPermission(PermissionState.Unrestricted));
// the above two are for Console.WriteLine() to run, which is what I had in the Main method
var assembly = Assembly.LoadFile(exePath); // path to ConsoleApplication1.exe
var domain = AppDomain.CreateDomain("SomeGenericName", null, adSetup, permission, null); // sandboxed AppDomain
try
{
domain.ExecuteAssemblyByName(assembly.GetName(), new string[] { });
}
// The SecurityException is thrown by Environment.Exit() not being able to run
catch (SecurityException e) when (e.TargetSite == typeof(Environment).GetMethod("Exit"))
{
Console.WriteLine("Tried to run Exit");
}
catch (SecurityException e)
{
// Some other action in your method needs SecurityPermissionFlag.UnmanagedCode to run,
// or the PermissionSet is missing some other permission
}
catch
{
Console.WriteLine("Something else failed in ConsoleApplication1.exe's main...");
}
Solution 2: When the assembly is a byte array
Warning: cancerous solution follows.
When changing my solution to load a byte array, OP and I discovered a weird exception file not found exception: even if you pass in a byte array to Assembly.Load(), domain.ExecuteAssemblyByName() still searches the disk for the assembly, for some weird reason. Apparently we weren't the only ones with the issue: Loading Byte Array Assembly.
First, we have a Helper class:
public class Helper : MarshalByRefObject
{
public void LoadAssembly(Byte[] data)
{
var a = Assembly.Load(data);
a.EntryPoint.Invoke(null, null);
}
}
which as you can see, loads the assembly using Assembly.Load() and calls it's entry point. This is the code we'll be loading into the AppDomain:
AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
// This is where the main executable resides. For more info on this, see "Remarks" in
// https://msdn.microsoft.com/en-us/library/system.appdomainsetup.applicationbase(v=vs.110).aspx#Anchor_1
PermissionSet permission = new PermissionSet(PermissionState.None);
// Permissions of the AppDomain/what it can do
permission.AddPermission(new SecurityPermission(SecurityPermissionFlag.AllFlags & ~SecurityPermissionFlag.UnmanagedCode));
// All SecurityPermission flags EXCEPT UnmanagedCode, which is required by Environment.Exit()
// BUT the assembly needs SecurityPermissionFlag.Execution to be run;
// otherwise you'll get an exception.
permission.AddPermission(new FileIOPermission(PermissionState.Unrestricted));
permission.AddPermission(new UIPermission(PermissionState.Unrestricted));
// the above two are for Console.WriteLine() to run, which is what I had in the Main method
var domain = AppDomain.CreateDomain("SomeGenericName", null, adSetup, permission, null); // sandboxed AppDomain
try
{
Helper helper = (Helper)domain.CreateInstanceAndUnwrap(typeof(Helper).Assembly.FullName, typeof(Helper).FullName);
// create an instance of Helper in the new AppDomain
helper.LoadAssembly(bytes); // bytes is the in-memory assembly
}
catch (TargetInvocationException e) when (e.InnerException.GetType() == typeof(SecurityException))
{
Console.WriteLine("some kind of permissions issue here");
}
catch (Exception e)
{
Console.WriteLine("Something else failed in ConsoleApplication1.exe's main... " + e.Message);
}
Note that in the second solution, the SecurityException becomes a TargetInvocationException with it's InnerException property being a SecurityException. Unfortunately, this means that you cannot use e.TargetSite to see which method threw the exception.
Conclusion/Things to keep in mind
This solution isn't perfect. It would be much better to somehow go through the IL of the method and artificially remove the call to Environment.Exit().
All credits go to Kirill Osenkov - MSFT
I can successfully load the assembly into another AppDomain and call its entry point.
Environment.Exit always shuts down the hosting process.
Workaround for this would be, to return an int from Main of the loaded console application. Zero for success and other numbers for errors.
Instead of this:
Module Module1
Sub Main()
// your code
Environment.Exit(0)
End Sub
End Module
write: (I hope this is valid VB.NET :-))
Module Module1
Function Main() As Integer
// your code
Return 0 // 0 == no error
End Function
End Module
Demo - C#
class Program
{
static void Main(string[] args)
{
Launcher.Start(#"C:\Users\path\to\your\console\app.exe");
}
}
public class Launcher : MarshalByRefObject
{
public static void Start(string pathToAssembly)
{
TextWriter originalConsoleOutput = Console.Out;
StringWriter writer = new StringWriter();
Console.SetOut(writer);
AppDomain appDomain = AppDomain.CreateDomain("Loading Domain");
Launcher program = (Launcher)appDomain.CreateInstanceAndUnwrap(
typeof(Launcher).Assembly.FullName,
typeof(Launcher).FullName);
program.Execute(pathToAssembly);
AppDomain.Unload(appDomain);
Console.SetOut(originalConsoleOutput);
string result = writer.ToString();
Console.WriteLine(result);
}
/// <summary>
/// This gets executed in the temporary appdomain.
/// No error handling to simplify demo.
/// </summary>
public void Execute(string pathToAssembly)
{
// load the bytes and run Main() using reflection
// working with bytes is useful if the assembly doesn't come from disk
byte[] bytes = File.ReadAllBytes(pathToAssembly); //"Program.exe"
Assembly assembly = Assembly.Load(bytes);
MethodInfo main = assembly.EntryPoint;
main.Invoke(null, new object[] { null });
}
}
Also to note:
Also, note that if you use LoadFrom you'll likely get a FileNotFound
exception because the Assembly resolver will attempt to find the
assembly you're loading in the GAC or the current application's bin
folder. Use LoadFile to load an arbitrary assembly file instead--but
note that if you do this you'll need to load any dependencies
yourself.
There is only one way to do this. You have to dynamically instrument all the code that the assembly is going to get executed. This boils down to intercepting system calls. There is no easy way to do this. Note that this does not require modifying the source code.
Why can't the .NET security system do this? While the system could have provided you with a security permission that you can use to control calls to Environment.Exit, that would not really solve the problem. The assembly could still call into unmanaged code. Other answers have pointed out that this can be done by creating an AppDomain and revoking SecurityPermissionFlag.UnmanagedCode. Indeed, this works, but you indicated in the comments that you to enable the assembly to call unmanaged code.
That's it if you want to run the code in the same process.You could also run the code in another process, but then you have to do interprocess communication.
More AppDomain code could help in finding a solution.
Code can be found at LoadUnload
The small applications included in the project LoadUnload contain AppDomain code that you maybe able to adapt in your solution.

Loading an assembly into an AppDomain outsite of applicationBase C#

So lately I've been working on a project where the application (or executable,whatever you wish to call it) needs to be able to load and unload assemblies not found within the executable's folder at all. (might even be another drive)
For the sake of an example , I want to be able to have my application at D:\AAA\theAppFolder , and the assemblies of the DLL files at C:\BBB\Assemblies
Looking thoroughly , I found out AppDomain allow the ability to unload themselves and any attached assemblies , so I figured I'd give it a shot, however there seems to be an issue after a few hours worth of attempts : AppDomains cannot look anywhere outside the application base.
According to AppDomain's documentary (and my own experience) you cannot set the PrivateBinPath outside of ApplicationBase , and if I set the ApplicationBase outside of the drive the application is at (via AppDomainSetup) , I get System.IO.FileNotFoundException complaining it can't find the application itself.
Because of that I can't even reach the phase where I can use the AssemblyResolve ResolveEventHandler to attempt to get the assembly using MarhsalByRefObject inheriting classes...
Here's a few snippets of code related to what I am currently attempting
internal class RemoteDomain : MarshalByRefObject
{
public override object InitializeLifetimeService() //there's apparently an error for marshalbyref objects where they get removed after a while without this
{
return null;
}
public Assembly GetAssembly(byte[] assembly)
{
try
{
return Assembly.Load(assembly);
}
catch (Exception e)
{
Console.WriteLine(e);
}
return null;
}
public Assembly GetAssembly(string filepath)
{
try
{
return Assembly.LoadFrom(filepath);
}
catch (Exception e)
{
Console.WriteLine(e);
}
return null;
}
}
public static Assembly LoadAssembly(string modName, BinBuffer bb)
{
string assembly = pathDirTemp+"/"+modName+".dll";
File.WriteAllBytes(assembly, bb.ReadBytes(bb.BytesLeft()));
RemoteDomain loader = (RemoteDomain)modsDomain.CreateInstanceAndUnwrap(typeof(RemoteDomain).Assembly.FullName, typeof(RemoteDomain).FullName);
return loader.GetAssembly(assembly);
}
To be as specific as I can : Is there any way to get an unloadable AppDomain to load an assembly that is not within the application's base folder?
Each AppDomain has it's own base directory and is not constrained at all by the main application base dir (unless it is the main AppDomain of the application). So you can achieve what you want using AppDomains.
The reason your approach doesn't work is that you are passing Assembly objects between AppDomains. When you call any of the GetAssembly methods, the assembly will be loaded in the child AppDomain but when the method returns, the main AppDomain will try to load the Assembly as well. Of course, the assembly will not be resolved because it is not in the main AppDomains's base dir, private paths or the GAC.
So in general you should never pass Type or Assembly objects between AppDomains.
A simple way to load assemblies without leaking them to the main AppDomain can be found in this answer.
Of course to make your application work with assemblies loaded in child AppDomains you will have to make MarshalByRefObject derived classes that will be your access point between AppDomains.
Maybe you need to use global variable so if you use global variable to fix the problem you can declare readonly global variable for example:
public static string a = "Moosaie";
Convert it to
public static readonly a = "Moosaie";
Anyway you can not use global dynamic value variable for CLR assembly.

Call function in Remotable class

I have a remotable class derived from MarshalByRefObject.I have two public functions in it say TestMethod1 and TestMethod2. I am invoking these from an HttpHandler and it works fine. Now I have added a new function TestMethod3 that is similar to the two methods mentioned before. But, while calling this function from the same handler and getting an exception which says "undable to find the .exe for this application", where the exe is the process in which the remote object runs.
class HttpHandlerClass : IHttpHandler
{
RemotingConfiguration.RegisterWellKnownClientType(typeof(RemoteClass),"ipc://completeurl");
RemoteClass rc = new RemoteClass();
if (condition1)
rc.TestMethod1();
else
rc.TestMethod3();
}
class RemoteClass: MarshalByRefObject
{
public void TestMethod1(int a) { }
public void TestMethod2(string b) { }
public void TestMethod3(string c) { }
}
No clue why. Seems to me I need to make some changes(dont know where) so that the TestMethod3 is recognized.
Any ideas?
Output: Could not load file or assembly 'Project.exe, version=1.0.0.0, Culture-neutral, publickeytoken=blahblah' or one of its dependencies. The system cannot find the file specified."
Just noticed, it is looking for an older version of the Project.exe and not the one whihc is being executed.
Use ICE instead? I struggled for months in the same situation as you, then ended up solving everything within a week when I switched architectures.
This is just a wild guess, but I've run into this issue before with remoting.
Are you sure the .exe you're remoting to is up to date? It's possible you didn't copy a compiled file to the expected location, and then the old methods would still work, but the new one will break completely...

Assembly Resolve Not Fully Loading My DLL?

I'm using a method to load a dll on an AssemblyResolve event. It's not quite working the way I want it to. Here's some code:
in my form load:
var a = AppDomain.CurrentDomain;
a.AssemblyResolve += (object sender, ResolveEventArgs args) => LoadDLL(sender, args, anArg);
LoadDLL:
public Assembly LoadDLL(object sender, ResolveEventArgs agrs, bool anArg){
//Let's just ignore anArg, it doesn't affect anything related to the problem
asseblyPath = #"XXX";//path to my dll
return Assembly.LoadFrom(assemblyPath);
}
So that's all well and good. I set Copy Local to false for my DLL so the AssemblyResolve event is called and I can load the desired version of my DLL.
One strange thing that is happening is this: After running this code, my code tries to access a public variable from a class containing global constant values (this class should be in my DLL).
public class GCV{
public GCV(){}
public string value1= "asdf";
}
Unfortunately, when I'm in debug mode, GCV.value1 is set to null when it clearly has a value in the class definition. This is one symptom that I can describe here, hopefully enough to diagnose the problem?
Thanks!
It seems that in debug the version loaded differ from the one it load in release. Try to look in the Output folder when debugging, and look for the Loaded .... to find your dll. This should point to a different version that the one you expect.

Categories

Resources