How to access running program from dynamically loaded dll - c#

So I'm trying to access a running program from which I've injected an external DLL at runtime. I have two projects: console app and class library.
Console app:
using System;
using System.Reflection;
namespace SharpConsole
{
public class GameEvent
{
private bool _cancelled;
public bool Cancelled { get => _cancelled; }
public void SetCancelled(bool cancelled)
{
_cancelled = cancelled;
}
public override string ToString()
{
return $"Event (Cancelled={Cancelled})";
}
}
public class API
{
public static void Print(string text)
{
Console.WriteLine($"[API] {text}");
}
}
public class Program
{
public static string TestValue = "some initial test value";
static void Main(string[] args)
{
var evt = new GameEvent();
Console.WriteLine("[Console] " + evt.ToString());
Console.WriteLine("[Console] Loading dll...");
// var asm = Assembly.LoadFile(#"C:\Users\tinytengu\Desktop\SharpTest\SharpExternal\bin\Release\netcoreapp3.1\SharpExternal.dll");
var asm = Assembly.LoadFile(#"C:\Users\tinytengu\SharpExternal\SharpExternal.dll");
var classType = asm.GetType("SharpExternal.Class1");
Console.WriteLine("[Console] Invoking SharpExternal.Class1.Main method...");
var methodInfo = classType.GetMethod("Main", BindingFlags.Static | BindingFlags.Public);
methodInfo.Invoke(null, new object[] { evt });
Console.WriteLine("[Console] After changes: " + evt.ToString());
Console.WriteLine();
methodInfo = classType.GetMethod("ShowTestValue", BindingFlags.Static | BindingFlags.Public);
methodInfo.Invoke(null, null);
Console.ReadLine();
}
}
}
And a class library:
using System;
namespace SharpExternal
{
public class Class1
{
public static void Main(ref SharpConsole.GameEvent evt)
{
Console.WriteLine("[DLL] " + evt.ToString());
Console.WriteLine("[DLL] Cancelling an event");
evt.SetCancelled(true);
}
public static void ShowTestValue()
{
SharpConsole.API.Print(SharpConsole.Program.TestValue);
}
}
}
What is going on:
The console app creates GameEvent class instance
It injects the ExternalSharp.dll
ExternalSharp.Class1.Main gets called passing previously created GameEvent instance reference to it.
ExternalSharp changes GameEvent's Cancelled state and SharpConsole outputs an instance of a GameEvent that has been successfully modified from ExternalSharp
ExternalSharp's ShowTestValue gets called so it outputs a Console's TestValue field value.
The thing is, I can compile SharpExternal (which uses SharpConsole project as a dependency so it can use its classes. I can also compile SharpConsole and use its DLL file as a dependency, the result is the same) once and move its DLL file on the Desktop so it can't access any files, be recompiled and etc., i.e. in a completely empty folder. BUT I can change Console's TestValue at any moment, even from Console.ReadLine in runtime, then recompile only the Console app and the SharpExternal will output new value when the ShowTestValue method is called. I can add more static properties, methods, etc. before and after TestValue, change the file as I want, but unchanged from the first time SharpExternal.dll file which is still located on the Desktop manages to find TestValue (and other static fields, methods, ...) every time and output the correct value.
I'd like to know how this whole thing works, how ExternalSharp finds the correct TestValue address every time. I suppose it's because of the static modifier, but I haven't found any information beyond the fact that it allows type members to refer to the type itself, and not just to its instance, which I already knew.

Related

How to call a method from an external Assembly

I have sample c# project:
namespace SampleExe
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
And I have sample c# dll:
namespace SampleDll
{
public class Program
{
public static void TestMethod(string samplestr)
{
Console.WriteLine("TestMethod Void Runned! Your string is: "+samplestr);
}
}
}
How can i call TestMethod() from compilled SampleDll.DLL (i want to load external dll)
Here's a working example of using Reflection to load a library at runtime and execute a static method. Note that it assumes quite a lot: you must know the library name, the class name, the method name, and all of its arguments ahead of time. It's often much easier to just reference a library directly.
A great way to use Reflection successfully is together with inheritance/interfaces. Library A contains the base class or interface, and Library B contains a derived class. Library A can use reflection to load Library B , then find all class types in Library B that are derived from the base class or interface (using Type.IsAssignableFrom). In this way, Library A will have strongly typed properties and methods to work with coming from the base, instead of having to know string names of classes, methods, and properties in Library B a priori.
Code for main EXE doing the reflection:
using System;
using System.IO;
using System.Linq;
using System.Reflection;
namespace SomeNamespace
{
public class Program
{
static void Main()
{
string pathToSampleDLL = "<if you know the path ahead of time, use it>";
// if SampleDLL.dll is in same directory as this EXE (a common occurrence):
string workingDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
pathToSampleDLL = Path.Combine(workingDirectory, "SampleDLL.dll");
// load the DLL at runtime
Assembly sampleDLL = Assembly.LoadFrom(pathToSampleDLL);
// since you know the type name, you can use LINQ to return your type:
Type sampleType = sampleDLL.GetTypes().FirstOrDefault(t => t.Name == "Program");
// you are looking for a static method on this type, and you know its name, so use GetMethods:
MethodInfo staticMethod = sampleType.GetMethod("TestMethod", BindingFlags.Public | BindingFlags.Static);
// invoke the method. Since you know its arguments and return value ahead of time, just hard code it:
// you can use null for the object since this is a static method. It takes only one argument, a sample string
staticMethod.Invoke(null, new object[] { "sampleStr" });
}
}
}
Code for sample library (compiled to "SampleDLL.dll"):
using System;
namespace SampleDll
{
public class Program
{
public static void TestMethod(string sampleStr)
{
Console.WriteLine("TestMethod Void Runned! Your string is: " + sampleStr);
}
}
}
You have multiple options for this. You can create a dll and add the dll as a reference to the project. You can add the project as a reference also. You can create a NuGet package of dll also and use that.
Then simply call SampleDll.Program.TestMethod
To do this, you need to use reflection.
var assembly = Assembly.Load(File.ReadAllBytes("SampleDLL.dll"));
foreach(Type type in assembly.GetExportedTypes())
{
var c = Activator.CreateInstance(type);
type.InvokeMember("TestMethod", BindingFlags.InvokeMethod, null, c, new object[] { #"Hi!" });
}

How to create attribute to a function that will create a file before or without calling the function

I need to create attribute for functions that will create files according to a given name before the call to the function or even if there is no call to the function.
For example, if I have function with atribute [some("C:\\hello.txt")]:
[some("C:\\hello.txt")]
private void foo()
{
// do something
}
When I will run the application it will create this file ("C:\hello.txt") before calling the function or even if there is no call to the function..
I tried with two techniques:
1. Creating the file in the constructor
2. Creating the file with reflection.
But none of them worked for me.
First try (with constructor):
I tried create the file in the constructor every time there is a new attribute.
In this method I tried to create the file before the enter to the Main function.
While it parsing the functions it will find the attributes and create the files.
Expected:
Two files should be created:
1. C:\hello.txt
2. C:\bye.txt
In reality => nothing happen.
[some("C:\\hello.txt")]
private void foo()
{
// do something
}
[some("C:\\bye.txt")]
private void foo()
{
// do something
}
public class someAttribute : Attribute
{
public someAttribute(string fileToCreate)
{
// File.Create(fileToCreate);
Console.WriteLine("Create file " + fileToCreate);
}
}
static void Main(string[] args)
{
// something
}
Second try (with reflection):
Expected:
One file should be created:
1. C:\hello.txt
In reality => "types" variables is empty and nothing is being created.
[some(fileToCreate = "C:\\hello.txt")]
private void foo()
{
// do something
}
public class someAttribute : Attribute
{
public string fileToCreate {get; set;}
}
static void Main(string[] args)
{
var types = from t in Assembly.GetExecutingAssembly().GetTypes()
where t.GetCustomAttributes<someAttribute>().Count() > 0
select t;
foreach(var t in types) // types is null
{
string n = t.Name;
foreach(var p in t.GetProperties())
{
// File.Create(fileToCreate)
Console.WriteLine(p.fileToCreate);
}
}
}
Your first attempt didn't work because an attribute's constructor is run when an attribute is examined See When is a custom attribute's constructor run? for additional details. It won't be run just by the fact that a method has that attribute in the code. So reflection will be needed to get a list of methods that have the desired attribute.
Your second attempt came close, but didn't work because you only looked at the attributes attached to the class types. You'll need to go one step further to look at the methods within the classes.
I came up with a solution, but be warned that it could affect performance since it looks at every type and method in the assemblies linked to your project. You may want to limit the assemblies to only the ones that you can expect to have someAttribute. See C#: List All Classes in Assembly for some examples on how to do this.
static void Main()
{
var methods = AppDomain.CurrentDomain.GetAssemblies()
//Get a sequence of all types in the referenced assemblies
.SelectMany(assembly => assembly.GetTypes())
//Get a sequence of all the methods in those types.
//The BindingFlags are needed to make sure both public and non-public instance methods are included.
//Otherwise private methods are skipped.
.SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
//And finally, filter to only those methods that have someAttribute attached to them
.Where(method => Attribute.IsDefined(method, typeof(someAttribute)));
foreach (MethodInfo methodInfo in methods)
{
IEnumerable<someAttribute> SomeAttributes = methodInfo.GetCustomAttributes<someAttribute>();
foreach (var attr in SomeAttributes)
{
//Here, you can create the file.
Console.WriteLine(attr.fileToCreate);
}
}
}

Static Variable Null In Method Call, But Initialized In Program

I have a bit of a head-scratcher here that I wonder if someone may know the answer to.
The setup is basically this:
//in Visual Studio plug-in application
SpinUpProgramWithDebuggerAttached();
//in spun up program
void Start()
{
StaticClass.StaticVariable = "I want to use this.";
XmlSerializer.Deserialize(typeof(MyThingie), "xml");
}
class MyThingie : IXmlSerializable
{
ReadXml()
{
//why the heck is this null?!?
var thingIWantToUse = StaticClass.StaticVariable;
}
}
The problem that has me pulling my hair out is that StaticClass.StaticVariable is null in the IXmlSerializable.ReadXml() method, even though it's called RIGHT AFTER the variable is set.
Of note is that breakpoints aren't hit and Debugger.Launch() is ignored in the precise spot the problem occurs.
Mysteriously, I determined through raising exceptions that the AppDomain.CurrentDomain.FriendlyName property is the same for the place the static variable is populated vs. null!
Why the heck is the static variable out of scope?!? What's going on?!? How can I share my variable?
EDIT:
I added a static constructor, per a suggestion in the responses, and had it do a Debug.WriteLine. I noticed it was called twice, even though all the code appears to be running in the same AppDomain. Here is what I see in the output window, which I'm hoping will be a useful clue:
Static constructor called at: 2015-01-26T13:18:03.2852782-07:00
...Loaded 'C:...\GAC_MSIL\System.Numerics\v4.0_4.0.0.0__b77a5c561934e089\System.Numerics.dll'...
...Loaded 'Microsoft.GeneratedCode'...
...Loaded 'C:...\GAC_MSIL\System.Xml.Linq\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.Linq.dll'....
...Loaded 'C:\USERS...\APPDATA\LOCAL\MICROSOFT\VISUALSTUDIO\12.0EXP\EXTENSIONS...SharePointAdapter.dll'. Symbols loaded.
...Loaded 'Microsoft.GeneratedCode'.
Static constructor called at: 2015-01-26T13:18:03.5196524-07:00
ADDITIONAL DETAIL:
Here is the actual code, since a couple of commenters thought it might help:
//this starts a process called "Emulator.exe"
var testDebugInfo = new VsDebugTargetInfo4
{
fSendToOutputWindow = 1,
dlo = (uint)DEBUG_LAUNCH_OPERATION.DLO_CreateProcess,
bstrArg = "\"" + paramPath + "\"",
bstrExe = EmulatorPath,
LaunchFlags = grfLaunch | (uint)__VSDBGLAUNCHFLAGS.DBGLAUNCH_StopDebuggingOnEnd | (uint)__VSDBGLAUNCHFLAGS.DBGLAUNCH_WaitForAttachComplete,
dwDebugEngineCount = 0,
guidLaunchDebugEngine = VSConstants.CLSID_ComPlusOnlyDebugEngine,
};
var debugger = Project.GetService(typeof(SVsShellDebugger)) as IVsDebugger4;
var targets = new[] { testDebugInfo };
var processInfos = new[] { new VsDebugTargetProcessInfo() };
debugger.LaunchDebugTargets4(1, targets, processInfos);
//this is in the emulator program that spins up
public partial class App : Application
{
//***NOTE***: static constructors added to static classes.
//Problem still occurs and output is as follows (with some load messages in between):
//
//MefInitializer static constructor called at: 2015-01-26T15:34:19.8696427-07:00
//ContainerSingleton static constructor called at: 2015-01-26T15:34:21.0609845-07:00. Type: SystemTypes.ContainerSingleton, SystemTypes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=...
//ContainerSingleton static constructor called at: 2015-01-26T15:34:21.3399330-07:00. Type: SystemTypes.ContainerSingleton, SystemTypes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=...
protected override void OnStartup(StartupEventArgs e)
{
//...
//initializes a MEF container singleton (stored as static variable)
MefInitilizer.Run();
//here's where it blows up. the important details are that
//FullSelection implements IXmlSerializable, and its implemention
//ends up referencing the MEF container singleton, which ends up
//null, even though it was initialized in the previous line.
//NOTE: the approach works perfectly under a different context
//so the problem is not the code itself, per se, but a problem
//with the code in the environment it's running in.
var systems = XmlSerialization.FromXml<List<FullSelection>>(systemsXml);
}
}
public static class MefInitilizer
{
static MefInitilizer() { Debug.WriteLine("MefInitializer static constructor called at: " + DateTime.Now.ToString("o")); }
public static void Run()
{
var catalog = new AggregateCatalog();
//this directory should have all the defaults
var dirCatalog = new DirectoryCatalog(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
//add system type plug-ins, too
catalog.Catalogs.Add(dirCatalog);
var container = new CompositionContainer(catalog);
ContainerSingleton.Initialize(container);
}
}
public class ContainerSingleton
{
static ContainerSingleton()
{
Debug.WriteLine("ContainerSingleton static constructor called at: " + DateTime.Now.ToString("o") + ". Type: " + typeof(ContainerSingleton).AssemblyQualifiedName);
}
private static CompositionContainer compositionContainer;
public static CompositionContainer ContainerInstance
{
get
{
if (compositionContainer == null)
{
var appDomainName = AppDomain.CurrentDomain.FriendlyName;
throw new Exception("Composition container is null and must be initialized through the ContainerSingleton.Initialize()" + appDomainName);
}
return compositionContainer;
}
}
public static void Initialize(CompositionContainer container)
{
compositionContainer = container;
}
}
Bear in mind I've just copied your code to try to replicate your problem.
When running this code, I get a NullReferenceException on Debug.Write, AnotherClass hasn't properly initialized before the call is resolved.
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
MefInitilizer.Run();
Debug.Write(AnotherClass.Test);
}
}
public class AnotherClass
{
public static String Test = ContainerSingleton.ContainerInstance;
}
public static class MefInitilizer
{
public static void Run()
{
ContainerSingleton.Initialize("A string");
}
}
public class ContainerSingleton
{
private static String compositionContainer;
public static String ContainerInstance
{
get
{
if (compositionContainer != null) return compositionContainer;
var appDomainName = AppDomain.CurrentDomain.FriendlyName;
throw new Exception("Composition container is null and must be initialized through the ContainerSingleton.Initialize()" + appDomainName);
}
}
public static void Initialize(String container)
{
compositionContainer = container;
}
}
}
However, when I add static constructors to all classes with static fields it works as expected:
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
MefInitilizer.Run();
Debug.Write(AnotherClass.Test);
}
}
public class AnotherClass
{
static AnotherClass()
{
}
public static String Test = ContainerSingleton.ContainerInstance;
}
public static class MefInitilizer
{
static MefInitilizer()
{
}
public static void Run()
{
ContainerSingleton.Initialize("A string");
}
}
public class ContainerSingleton
{
static ContainerSingleton()
{
}
private static String compositionContainer;
public static String ContainerInstance
{
get
{
if (compositionContainer != null) return compositionContainer;
var appDomainName = AppDomain.CurrentDomain.FriendlyName;
throw new Exception("Composition container is null and must be initialized through the ContainerSingleton.Initialize()" + appDomainName);
}
}
public static void Initialize(String container)
{
compositionContainer = container;
}
}
}
I'd say this could definitely be a BeforeFieldInit problem.
As I understood, your code is an plug-in for a Visual Studio, and the main problem of your application is that your class is being instantiated twice, once for a normal AppDomain, and once for some other reason you can't really find out.
First of all, I see here a potential sandboxing from a Visual studio - it wants to test your code in various sets of rights to ensure your code won't harm any other parts of the Visual Studio or end user work. In this case your code could be loaded into another AppDomain, without some rights (You can find a good article at the MSDN), so you can understand why is your code called twice per application.
Second, I want to point out that you are misunderstanding the idea of static constructor and static method:
public static void Initialize(CompositionContainer container)
{
compositionContainer = container;
}
is not the same as
public static ContainerSingleton()
{
compositionContainer = container;
}
So, I suggest you to move the all initialization logic into a static container, something like this:
public class ContainerSingleton
{
private static CompositionContainer compositionContainer;
public static CompositionContainer ContainerInstance
{
get
{
if (compositionContainer == null)
{
var appDomainName = AppDomain.CurrentDomain.FriendlyName;
throw new Exception("Composition container is null and must be initialized through the ContainerSingleton.Initialize()" + appDomainName);
}
return compositionContainer;
}
}
public static ContainerSingleton()
{
var catalog = new AggregateCatalog();
//this directory should have all the defaults
var dirCatalog = new DirectoryCatalog(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
//add system type plug-ins, too
catalog.Catalogs.Add(dirCatalog);
compositionContainer = new CompositionContainer(catalog);
}
}
Second approach: I want to point out that the pattern you use for getting the singleton is outdated, try to use the Lazy<T> class, something like this:
public class ContainerSingleton
{
private static Lazy<CompositionContainer> compositionContainer;
public static CompositionContainer ContainerInstance
{
get
{
return compositionContainer.Value;
}
}
public static ContainerSingleton()
{
compositionContainer = new Lazy<CompositionContainer>(() => Initialize());
}
public static void Initialize()
{
// Full initialization logic here
}
}
Also, you should remember that simply adding the empty static constructors isn't enough - you should move all assignments to it, so you should replace such code:
public class AnotherClass
{
static AnotherClass()
{
}
public static String Test = ContainerSingleton.ContainerInstance;
}
with this one:
public class AnotherClass
{
static AnotherClass()
{
Test = ContainerSingleton.ContainerInstance;
}
public static String Test;
}
Update:
#Colin You can even use [LazyTask type][https://msdn.microsoft.com/en-us/magazine/dn683795.aspx] - simply pass a Func to your constructor, and it will be a thread-safe approach, see more in the article. The same Id of the AppDomain means nothing - the sandbox could run your code via AppDomain.ExecuteAssembly method (it's obsolete in 4.5, but still could be a possible variant) to see how it behaves in various set of permissions.
May be there is another technique for this in .NET 4.5, but can't find an article related right now.
Update 2:
As I can see in your code, you are reading some information from disk. Try to add a Code Access Security rule for this to see, if your code is being ran under restricted permissions, like this:
FileIOPermission f2 = new FileIOPermission(FileIOPermissionAccess.Read, Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
//f2.AddPathList(FileIOPermissionAccess.Write | FileIOPermissionAccess.Read, "C:\\example\\out.txt");
try
{
f2.Demand();
}
catch (SecurityException s)
{
Console.WriteLine(s.Message);
}
More about FileIOPermission Class on MSDN.
Try adding a static constructor to ContainerSingleton. I believe this is BeforeFieldInit raising its ugly head again.
Thanks to all who offered suggestions! I never did figure out exactly what was going on (and will continue to investigate/post updates if I ever do), but I did end up coming up with a workaround.
A few suggested that initialization of the static class be internalized, but due to the nature of the actual problem, initialization logic had to be externalized (I was basically loading a DI/service location container whose composition varied from environment to environment).
Also, I suspect it wouldn't have helped, since I could observe that the static constructor was called twice (thus, whatever initialization logic there was would just be called twice, which didn't directly address the problem).
However, the suggestion got me on the right track.
In my case, none of the services I loaded needed to be stateful, so it didn't really matter that the initialization happened twice aside from the performance hit.
Therefore, I simply had the static class check if the MEF container was loaded, and if it wasn't I'd read a configuration file which specified a class that handled initialization.
By doing so, I could still vary the composition of the MEF container from environment to environment, which is currently working pretty well, even if it's not an ideal solution.
I'd like to split the bounty between all who helped, but since that doesn't appear to be possible, I will probably reward OakNinja since he was a hero in spitting out as many good ideas as could realistically be expected given the information I provided. Thanks again!

Getting the assembly name for a process started within an app domain

I have a service that creates an app domain and starts it:
this._appDomain = AppDomain.CreateDomain(this._appName, AppDomain.CurrentDomain.Evidence, appDomainSetup);
this._startStopControllerToRun = (IStartStop)this._appDomain.CreateInstanceFromAndUnwrap(assemblyName, this._fullyQualifiedClassName);
this._startStopControllerToRun.Start();
That has been running well for a long time now. The issue is when the controller, started within this app domain, calls a framework logging class. The logger gets the entry assembly name and records that as the source in the event log. This is how the logger gets the source (caller) name:
private static string GetSource()
{
try
{
var assembly = Assembly.GetEntryAssembly();
// GetEntryAssembly() can return null when called in the context of a unit test project.
// That can also happen when called from an app hosted in IIS, or even a windows service.
if (assembly == null)
{
// From http://stackoverflow.com/a/14165787/279516:
assembly = new StackTrace().GetFrames().Last().GetMethod().Module.Assembly;
}
return assembly.GetName().Name;
}
catch
{
return "Unknown";
}
}
Before adding that if check, the logger would record "Unknown" for the source. After some research, I added that attempt in the if block. Now the logger records "mscorlib" as the source (entry assembly name).
This is the overview:
Host -> Controller (running within app domain)
How can I get the name of the assembly (that has the controller) running within the domain?
Note: I also tried this (below), but it gives me the name of the framework where the logging class exists (not the name of the assembly in which the controller is running within the app domain):
assembly = Assembly.GetExecutingAssembly();
This is perhaps one way to do what you want. What I'm demonstrating here is passing and receiving metadata to the created AppDomain via SetData and GetData methods so disregard how I am creating the actual remote type.
using System;
using System.Reflection;
namespace ConsoleApplication13
{
class Program
{
static void Main(string[] args)
{
AppDomain appDomain = AppDomain.CreateDomain("foo");
appDomain.SetData(FooUtility.SourceKey, FooUtility.SourceValue);
IFoo foo = (IFoo)appDomain.CreateInstanceFromAndUnwrap(Assembly.GetEntryAssembly().Location, typeof(Foo).FullName);
foo.DoSomething();
}
}
public static class FooUtility
{
public const string SourceKey = "Source";
public const string SourceValue = "Foo Host";
}
public interface IFoo
{
void DoSomething();
}
public class Foo : MarshalByRefObject, IFoo
{
public void DoSomething()
{
string source = AppDomain.CurrentDomain.GetData(FooUtility.SourceKey) as string;
if (String.IsNullOrWhiteSpace(source))
source = "some default";
Console.WriteLine(source);
}
}
}
Which outputs:
Foo Host
Press any key to continue ...
So in your case, you could pass whatever source metadata to the AppDomain:
this._appDomain = AppDomain.CreateDomain(this._appName, AppDomain.CurrentDomain.Evidence, appDomainSetup);
this._appDomain.SetData("Source", "MyController");
this._startStopControllerToRun = (IStartStop)this._appDomain.CreateInstanceFromAndUnwrap(assemblyName, this._fullyQualifiedClassName);
this._startStopControllerToRun.Start();
and in your GetSource method check for its existence.
private static string GetSource()
{
try
{
string source = AppDomain.CurrentDomain.GetData("Source") as string;
if (!String.IsNullOrWhiteSpace(source))
return source;
var assembly = Assembly.GetEntryAssembly();
// GetEntryAssembly() can return null when called in the context of a unit test project.
// That can also happen when called from an app hosted in IIS, or even a windows service.
if (assembly == null)
{
// From http://stackoverflow.com/a/14165787/279516:
assembly = new StackTrace().GetFrames().Last().GetMethod().Module.Assembly;
}
return assembly.GetName().Name;
}
catch
{
return "Unknown";
}
}
UPDATE ALTERNATIVE
You could also declare a public interface method for setting the source on a static location in the target domain as well.
using System;
using System.Reflection;
namespace ConsoleApplication13
{
class Program
{
static void Main(string[] args)
{
AppDomain appDomain = AppDomain.CreateDomain("foo");
IFoo foo = (IFoo)appDomain.CreateInstanceFromAndUnwrap(Assembly.GetEntryAssembly().Location, typeof(Foo).FullName);
foo.SetSource("Foo Host");
foo.DoSomething();
}
}
public interface IFoo
{
void DoSomething();
void SetSource(string source);
}
public class Foo : MarshalByRefObject, IFoo
{
public void DoSomething()
{
string source = Foo.Source;
if (String.IsNullOrWhiteSpace(source))
source = "some default";
Console.WriteLine(source);
}
public static string Source{get; private set;}
public void SetSource(string source)
{
Foo.Source = source;
}
}
}
I ran into a situation where somewhere buried in the .net code, it was relying on Assembly.GetEntryAssembly(). It would take the returned assembly and inspect it for an assembly level attribute. Which is going to fail if code is in an app domain.
Long story short, I had to work around this same issue. The solution is ugly, I hate that I needed to do this but it worked...
If you read the docs here - Assembly.GetEntryAssembly() Method
It contains this section:
Return Value
Type: System.Reflection.Assembly
The assembly that is the process executable in the default application domain, or the
first executable that was executed by AppDomain.ExecuteAssembly. Can
return null when called from unmanaged code.
To get around this, I added some code to my exe which makes the process exit if "/initializingappdomain" is passed as an argument.
Here is some code to do this...
// 1. Create your new app domain...
var newDomain = AppDomain.CreateDomain(...);
// 2. call domain.ExecuteAssembly, passing in this process and the "/initializingappdomain" argument which will cause the process to exit right away
newDomain.ExecuteAssembly(GetProcessName(), new[] { "/initializingappdomain" });
private static string GetProcessName()
{
return System.IO.Path.GetFileName(Process.GetCurrentProcess().MainModule.FileName.Replace(".vshost", ""));
}
// 3. Use your app domain as you see fit, Assembly.GetEntryAssembly will now return this hosting .net exe.
Again, this is far from ideal. There are better solutions if you can avoid this but if you find yourself in a situation where you don't own the code relying on Assembly.GetEntryAssembly(), this will get you by in a pinch.

Correct Way to Load Assembly, Find Class and Call Run() Method

Sample console program.
class Program
{
static void Main(string[] args)
{
// ... code to build dll ... not written yet ...
Assembly assembly = Assembly.LoadFile(#"C:\dyn.dll");
// don't know what or how to cast here
// looking for a better way to do next 3 lines
IRunnable r = assembly.CreateInstance("TestRunner");
if (r == null) throw new Exception("broke");
r.Run();
}
}
I want to dynamically build an assembly (.dll), and then load the assembly, instantiate a class, and call the Run() method of that class. Should I try casting the TestRunner class to something? Not sure how the types in one assembly (dynamic code) would know about my types in my (static assembly / shell app). Is it better to just use a few lines of reflection code to call Run() on just an object? What should that code look like?
UPDATE:
William Edmondson - see comment
Use an AppDomain
It is safer and more flexible to load the assembly into its own AppDomain first.
So instead of the answer given previously:
var asm = Assembly.LoadFile(#"C:\myDll.dll");
var type = asm.GetType("TestRunner");
var runnable = Activator.CreateInstance(type) as IRunnable;
if (runnable == null) throw new Exception("broke");
runnable.Run();
I would suggest the following (adapted from this answer to a related question):
var domain = AppDomain.CreateDomain("NewDomainName");
var t = typeof(TypeIWantToLoad);
var runnable = domain.CreateInstanceFromAndUnwrap(#"C:\myDll.dll", t.Name) as IRunnable;
if (runnable == null) throw new Exception("broke");
runnable.Run();
Now you can unload the assembly and have different security settings.
If you want even more flexibility and power for dynamic loading and unloading of assemblies, you should look at the Managed Add-ins Framework (i.e. the System.AddIn namespace). For more information, see this article on Add-ins and Extensibility on MSDN.
If you do not have access to the TestRunner type information in the calling assembly (it sounds like you may not), you can call the method like this:
Assembly assembly = Assembly.LoadFile(#"C:\dyn.dll");
Type type = assembly.GetType("TestRunner");
var obj = Activator.CreateInstance(type);
// Alternately you could get the MethodInfo for the TestRunner.Run method
type.InvokeMember("Run",
BindingFlags.Default | BindingFlags.InvokeMethod,
null,
obj,
null);
If you have access to the IRunnable interface type, you can cast your instance to that (rather than the TestRunner type, which is implemented in the dynamically created or loaded assembly, right?):
Assembly assembly = Assembly.LoadFile(#"C:\dyn.dll");
Type type = assembly.GetType("TestRunner");
IRunnable runnable = Activator.CreateInstance(type) as IRunnable;
if (runnable == null) throw new Exception("broke");
runnable.Run();
I'm doing exactly what you're looking for in my rules engine, which uses CS-Script for dynamically compiling, loading, and running C#. It should be easily translatable into what you're looking for, and I'll give an example. First, the code (stripped-down):
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using CSScriptLibrary;
namespace RulesEngine
{
/// <summary>
/// Make sure <typeparamref name="T"/> is an interface, not just any type of class.
///
/// Should be enforced by the compiler, but just in case it's not, here's your warning.
/// </summary>
/// <typeparam name="T"></typeparam>
public class RulesEngine<T> where T : class
{
public RulesEngine(string rulesScriptFileName, string classToInstantiate)
: this()
{
if (rulesScriptFileName == null) throw new ArgumentNullException("rulesScriptFileName");
if (classToInstantiate == null) throw new ArgumentNullException("classToInstantiate");
if (!File.Exists(rulesScriptFileName))
{
throw new FileNotFoundException("Unable to find rules script", rulesScriptFileName);
}
RulesScriptFileName = rulesScriptFileName;
ClassToInstantiate = classToInstantiate;
LoadRules();
}
public T #Interface;
public string RulesScriptFileName { get; private set; }
public string ClassToInstantiate { get; private set; }
public DateTime RulesLastModified { get; private set; }
private RulesEngine()
{
#Interface = null;
}
private void LoadRules()
{
if (!File.Exists(RulesScriptFileName))
{
throw new FileNotFoundException("Unable to find rules script", RulesScriptFileName);
}
FileInfo file = new FileInfo(RulesScriptFileName);
DateTime lastModified = file.LastWriteTime;
if (lastModified == RulesLastModified)
{
// No need to load the same rules twice.
return;
}
string rulesScript = File.ReadAllText(RulesScriptFileName);
Assembly compiledAssembly = CSScript.LoadCode(rulesScript, null, true);
#Interface = compiledAssembly.CreateInstance(ClassToInstantiate).AlignToInterface<T>();
RulesLastModified = lastModified;
}
}
}
This will take an interface of type T, compile a .cs file into an assembly, instantiate a class of a given type, and align that instantiated class to the T interface. Basically, you just have to make sure the instantiated class implements that interface. I use properties to setup and access everything, like so:
private RulesEngine<IRulesEngine> rulesEngine;
public RulesEngine<IRulesEngine> RulesEngine
{
get
{
if (null == rulesEngine)
{
string rulesPath = Path.Combine(Application.StartupPath, "Rules.cs");
rulesEngine = new RulesEngine<IRulesEngine>(rulesPath, typeof(Rules).FullName);
}
return rulesEngine;
}
}
public IRulesEngine RulesEngineInterface
{
get { return RulesEngine.Interface; }
}
For your example, you want to call Run(), so I'd make an interface that defines the Run() method, like this:
public interface ITestRunner
{
void Run();
}
Then make a class that implements it, like this:
public class TestRunner : ITestRunner
{
public void Run()
{
// implementation goes here
}
}
Change the name of RulesEngine to something like TestHarness, and set your properties:
private TestHarness<ITestRunner> testHarness;
public TestHarness<ITestRunner> TestHarness
{
get
{
if (null == testHarness)
{
string sourcePath = Path.Combine(Application.StartupPath, "TestRunner.cs");
testHarness = new TestHarness<ITestRunner>(sourcePath , typeof(TestRunner).FullName);
}
return testHarness;
}
}
public ITestRunner TestHarnessInterface
{
get { return TestHarness.Interface; }
}
Then, anywhere you want to call it, you can just run:
ITestRunner testRunner = TestHarnessInterface;
if (null != testRunner)
{
testRunner.Run();
}
It would probably work great for a plugin system, but my code as-is is limited to loading and running one file, since all of our rules are in one C# source file. I would think it'd be pretty easy to modify it to just pass in the type/source file for each one you wanted to run, though. You'd just have to move the code from the getter into a method that took those two parameters.
Also, use your IRunnable in place of ITestRunner.
You will need to use reflection to get the type "TestRunner". Use the Assembly.GetType method.
class Program
{
static void Main(string[] args)
{
Assembly assembly = Assembly.LoadFile(#"C:\dyn.dll");
Type type = assembly.GetType("TestRunner");
var obj = (TestRunner)Activator.CreateInstance(type);
obj.Run();
}
}
When you build your assembly, you can call AssemblyBuilder.SetEntryPoint, and then get it back from the Assembly.EntryPoint property to invoke it.
Keep in mind you'll want to use this signature, and note that it doesn't have to be named Main:
static void Run(string[] args)

Categories

Resources