Search code examples
c#.netwpfcode-generation

how to access to a method inside current namespace when using a compiled C# programmatically?


after reviewing all answers and question about compiling c# code programmatically, I choose this method:

CompileCSCAtRuntime

using System;
using System.CodeDom.Compiler;
using System.Reflection;
using Microsoft.CSharp;
using AA.UI.WPF.WND;

namespace AA.UI.WPF.COMMON
{
    public static class CompileCSCAtRuntime
    {
        public static void HelloWorld()
        {
            string code = @"
                using System;
                using System.Windows;
                using System.Windows.Forms;
                using System.Reflection;
                namespace AA.UI.WPF.COMMON
                {
                    public class Program
                    {
                        public static void Main()
                        {
                            var aassembly = Assembly.LoadFrom(@""Path_To_Assembly"");
                            Type CompileCSCAtRuntime = aassembly.GetType(""AA.UI.WPF.COMMON.CompileCSCAtRuntime"");
                            Type Login = aassembly.GetType(""AA.UI.WPF.WND.Login"");
                            MethodInfo AccessLogin = CompileCSCAtRuntime.GetMethod(""AccessLogin"");
                            dynamic L = AccessLogin.Invoke(null, null);
                            L.ShowMessage(""hi"");
                        }
                    }
                }
            ";
            CSharpCodeProvider provider = new CSharpCodeProvider();
            CompilerParameters parameters = new CompilerParameters();
            string[] refr = {
                "System",
                "System.Windows",
                "System.Windows.Forms",
                "System.Drawing",
                "Microsoft.CSharp",
                "System.Core",
                "System.Data"};
            foreach (string r in refr)
                parameters.ReferencedAssemblies.Add($"{r}.dll");
            parameters.ReferencedAssemblies.Add($"Path_To_Assembly");
            parameters.GenerateInMemory = true;
            parameters.GenerateExecutable = true;
            CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
            if (results.Errors.Count > 0)
            {
                MessageBox.Show(
                    results.Errors
                        .Cast<CompilerError>()
                        .Select(error => error.ErrorText)
                        .Aggregate((s, s1) => s += $";\r\n\r\n{s1}"));
                return;
            }
            Assembly assembly = results.CompiledAssembly;
            Type program = assembly.GetType("AA.UI.WPF.COMMON.Program");
            MethodInfo main = program.GetMethod("Main");
            main.Invoke(null, null);            
        }
        public static Login AccessLogin()
        {
            return Login.Instance;
        }
    }
}

and Login

using System;
using AA.UI.WPF.COMMON;

namespace AA.UI.WPF.WND
{
    public partial class Login 
    {
        internal static Login Instance => _instance ?? (_instance = new Login());
        private static Login _instance = null;
        public Login()
        {
            InitializeComponent();
            if (_instance == null) _instance = this;
        }
        internal void ShowMessage(string msg)
        {
            MessageBox.Show(msg);
        }
    }

update

it's work fine if I don't use reflection.

before the edit, I asked how can I can access a method outside of dynamically compiled c# code, I'm satisfied by @BionicCode answers. thank him. see comments.

your answer is completely true. since you post the first answer I have said you this is true. I use dynamic type as you said.

but now the last thing, I can't access the private or internal method, RuntimeBinderException: 'AA.UI.WPF.WND.Login.ShowMessage(string)' is inaccessible due to its protection level. I think it's normal

goal

my goal is to inject some code as a string and run these like other normal code without reflection because it's too complicated and access to other classes, types, etc... inside current namespace FD.UI.WPF. if you know another easier way, please provide.


Solution

  • Like always, it would be very interesting to know the error message you get. I assume you are getting a compiler error?

    But generally you should be able to execute methods from other assemblies.

    Based on your code, you have to add the proper references to the CompilerParameters, which is the assembly that contains the CompileCSCAtRuntime type you wish to reference e.g., AA.UI.WPF.COMMON.dll (I don't know the real assembly name at this point):

    CompilerParameters parameters = new CompilerParameters();
    parameters.ReferencedAssemblies.Add("System.dll"); 
    parameters.ReferencedAssemblies.Add("System.Windows.dll");
    parameters.ReferencedAssemblies.Add("AA.UI.WPF.COMMON.dll");
    

    Now you can use the following code (note the added using import):

    string code = @"
          using System;
          using System.Windows;
          using AA.UI.WPF.COMMON;
    
          namespace AA.UI.WPF.COMMON
          {
            public class Program
            {
              public static void Main()
              {
                CompileCSCAtRuntime.ShowMessage(""hello"");
              }
            }
          }";
    

    To add more flexibility, you have to dynamically load the assembly that contains the type and then execute the method using MethodInfo (reflection). This way you don't need to add any assembly references or using imports to the compiled code:

    string code = @"
          using System;
          using System.Windows;
          using System.Reflection;
    
          namespace AA.UI.WPF.COMMON
          {
            public class Program
            {
              public static void Main()
              {
                var assembly = Assembly.LoadFrom(@""Path_To_Assembly"");
                Type type = assembly.GetType(""AA.UI.WPF.COMMON.CompileCSCAtRuntime"");
                MethodInfo method = type.GetMethod(""ShowMessage"");
                method.Invoke(null, new[] { ""hello"" });
              }
            }
          }";
    

    You can use this snippet to output or log compiler errors:

    CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
    if (results.Errors.Count > 0)
    {
      MessageBox.Show(
        results.Errors
          .Cast<CompilerError>()
          .Select(error => error.ErrorText)
          .Aggregate((result, currentValue) => result += $";{Environment.NewLine}{currentValue}"));
    }
    

    Addressing your update

    Now, that the context has changed, you have to adopt your code to this new scenario. First of all dynamic is not required here. You can use var or object instead, which has significantly less overhead (even when considering boxing scenarios). When using reflection, you should try to be as efficient as possible, because reflection itself is already expensive.

    You are currently invoking Login.ShowMessage using the instance that was returned by

    dynamic L = AccessLogin.Invoke(null, null);
    L.ShowMessage(""hi"");
    

    When using the instance, you are bound to the compiled access rules, which say that Login.ShowMessage is internal (you know that internal restricts access to assembly scope).
    Since your dynamically compiled code is compiled into a new dynamic assembly, the caller's scope (the dynamic code) does no longer satisfy this access constraint.

    To get around this visibility constraints, you have to access and invoke non-public members using reflection. To get the MemberInfo of any non-public member you always have to specify the appropriate BindingFlags combinations:

    string code = @"
            using System;
            using System.Windows;
            using System.Reflection;
            namespace AA.UI.WPF.COMMON
            {
              public class Program
              {
                public static void Main()
                {
                  var assembly = Assembly.LoadFrom(@""Path_To_Assembly"");
                  Type compileCSCAtRuntimeType = assembly.GetType(""AA.UI.WPF.COMMON.CompileCSCAtRuntime"");
                  MethodInfo accessLoginMethod = compileCSCAtRuntimeType.GetMethod(""AccessLogin"");
    
                  object resultInstance = accessLoginMethod.Invoke(null, null);
                  Type resultType = resultInstance.GetType();
                  MethodInfo resultMethod = resultType.GetMethod(""ShowMessage"", 
                    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
                  resultMethod.Invoke(resultInstance, new [] { ""hi"" });
                }
              }
            }";