Search code examples
c#dll-injection

Replace usage of production classes in compiled code with simulated classes


So I have production code, compiled against production dlls. They access the real architecture of the system.

I'm working on a simulator that will simulate this architecture. All the classes in the simulation are named the same and belong to the same namespaces as the production classes.

People can use my simulation dlls to do a rough draft test of their code.

However, if they call existing production business logic that was compiled against the production classes, it will load the existing production dlls and use the real architecture.

If people want to use my simulation, but call existing business logic, I'd have to use some kind of injection to overwrite the dll that loads the production classes.

Is this possible?

For an example:

I have a dll called Production.dll In it there is a class like so.

namespace Production
{
    public class A { public void Do(); }
}

Now, I have a dll called Simulation.dll with the same class and code.

Someone wrote a program called DoA.exe

public static class Program
{
    public static Main()
    {
        var a = new Production.A();
        a.Do();
    }
}

I want to make DoA.exe load my simulation dll, but I may not be able to remove Production.dll from its search path. How can I force it to use Simulation.dll instead.


Solution

  • I think I understand better your problem. While I think my original solution is cleaner, this is how to do it "dirty".

    Assuming your class schema is like this (simplified):

    // assembly: Production.dll (no dependencies)
    namespace Production {
      public class Test {
        public void Do() {
          Console.Out.WriteLine("Production");
        }
      }
    }
    
    // assembly: Simulation.dll (no dependencies)
    namespace Production {
      public class Test {
        public void Do() {
          Console.Out.WriteLine("Simulation");
        }
      }
    }
    
    // assembly: Usage.dll (dependency on Production.dll)
    namespace Usage {
      public class TestUsage {
        public void Do() {
          new Production.Test().Do();
        }
      }
    }
    

    And finally code that will perform the override:

    // Console application ConsoleApplication.exe
    // dependency on Production.dll, Usage.dll and Simulation.dll
    namespace ConsoleApplication {
      internal class AssemblyResolver : MarshalByRefObject {
        static internal void Register(AppDomain domain) {
          var resolver = domain.CreateInstanceFromAndUnwrap(
            Assembly.GetExecutingAssembly().Location,
                typeof(AssemblyResolver).FullName) as AssemblyResolver;
    
          resolver.RegisterDomain(domain);
        }
    
        private void RegisterDomain(AppDomain domain) {
          domain.AssemblyResolve += ResolveAssembly;
        }
    
        private Assembly ResolveAssembly(object sender, ResolveEventArgs args) {
          var assemblyName = new AssemblyName(args.Name);
          string name = assemblyName.Name;
          // comment out line below and you'll load "Production" instead
          if (name == "Production") {
            name = "Simulation";
          }
          var fileNames = new[] { name + ".dll", name + ".exe" };
          foreach (string fileName in fileNames) {
            var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
            if (File.Exists(path)) {
              return Assembly.Load(File.ReadAllBytes(path));
            }
          }
    
          return null;
        }
      }
    
      class Program {
        static void Main(string[] args) {
          var domain = AppDomain.CreateDomain("Doable", null, new AppDomainSetup {
            DisallowApplicationBaseProbing = true
          });
          AssemblyResolver.Register(domain);
          domain.DoCallBack(() => {
            // writes out "Simulation"
            new Usage.TestUsage().Do();
          });
        }
      }
    }