views:

6573

answers:

5

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

+3  A: 

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();
    }
}
William Edmondson
Isn't this missing a step, in which you get the appropriate `MethodInfo` from the type and call `Invoke`? (I understood the original question as specifying the caller not knowing anything about the Type in question.)
Jeff Sternal
You're missing one thing. You have to cast obj to type TestRunner. var obj = (TestRunner)Activator.CreateInstance(type);
BFree
It sounds like Tyndall is actually building this dll in an earlier step. This implementation assumes he knows that the method Run() already exists and knows that it has not parameters. If these are indeed unknown then he would need to do a little deeper reflection
William Edmondson
Good call BFree. Changes made...
William Edmondson
hmmm. TestRunner is a class inside my dynamic written code. So this static code in your example can not resolve TestRunner. It has no idea what it is.
tyndall
How much control do you have over the code generation?
William Edmondson
+2  A: 

http://www.codeproject.com/KB/cs/DynLoadClassInvokeMethod.aspx

Nescio
good stuff. thanks +1
tyndall
+7  A: 

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();
Jeff Sternal
+1 It worked using the type.invokeMember line. Should I use that method or keep trying to do something with the interface? I'd rather not have to even worry about putting that in the dynamically built code.
tyndall
Hmm, doesn't the second block of code work for you? Does your calling assembly have access to the IRunnable type?
Jeff Sternal
Second block does work. Calling assembly doesn't really know about IRunnable. So I guess I'll stick with second method. Slight follow up. When I regen the code and then redo dyn.dll I can't seem to replace it because its in use. Anything like a Assembly.UnloadType or something to allow me to replace the .dll? Or should I jsut do it "in memory"? thoughts? thanks
tyndall
Guess I don't know the proper way to do the "in memory" thing if that is the best solution.
tyndall
I don't recall the specifics (and I'm heading away from my computer for a while), but I believe an Assembly can only be loaded once per AppDomain - so you'll either have to create new AppDomains for each Assembly instance (and Load the Assemblies into those) or you'll have to restart your application before you can compile a new version of the Assembly.
Jeff Sternal
There's a detailed discussion of hosting multiple versions of a dynamically compiled assembly in AppDomains here: http://blogs.msdn.com/jasonz/archive/2004/05/31/145105.aspx. It's too complex to go into in a comment, but it sounds like it will require some rearchitecting of your application. As another alternative, you might consider using different names for your generated assemblies.
Jeff Sternal
A: 

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)
280Z28
What is AssemblyBuilder? I was trying CodeDomProvider and then "provider.CompileAssemblyFromSource"
tyndall
+1  A: 

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.

Chris Doggett
what is the @Interface? very cool ideas here. need to fully digest this. +1
tyndall
very interesting I didn't realize the C# parser had to look one character pass the @ to see if it was part of a variable name or a @"" string.
tyndall
Thanks. The @ before the variable name is used when the variable name is a keyword. You can't name a variable "class", "interface", "new", etc... But you can if you prepend an @. Probably doesn't matter in my case with a capital "I", but it was originally an internal variable with a getter and setter before I converted it to an auto-property.
Chris Doggett
Thats right. I forgot about the @ thing. How would you handle the question I had to Jeff Sternal about the "in memory thing"? I guess my big issue now is I can build the dynamic .dll and load it, but I can only do it once. Don't know how to "unload" the assembly. Is it possible to create another AppDomain load the assembly in that space, use it, and then take down this second AppDomain. Rinse. Repeat.?
tyndall
There's no way to unload the assembly unless you use a second AppDomain. I'm not sure how CS-Script does it internally, but the part of my rules engine that I stripped out is a FileSystemWatcher that automatically runs LoadRules() again whenever the file changes. We edit the rules, push them out to the users, whose client overwrites that file, the FileSystemWatcher notices the changes, and recompiles and reloads the DLL by writing another file in the temp directory. When the client starts up, it clears out that directory before the first dynamic compile, so we don't have a ton of leftovers.
Chris Doggett