Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Command Line Written in C#

0.00/5 (No votes)
18 Jul 2003 1  
The article is about how to use command lines written in C#.

Introduction

Actually I am mostly a C++ developer, but I found that C# with .NET framework is a perfect tool for my frequent auxiliary jobs like, log files and data dump files analyses and other similar tasks. Thus, I have written a number of console utilities in C#. It is quite normal that each Main() function of these utilities does command line parsing, arguments validation and conversion into proper types, decides which method has to be called and then calls this method with prepared parameter values.

But I am tired of composing each command line rules and of writing a boring code that follows these rules. And I am tired of updating these rules and corresponding code, every time after adding or changing utility abilities. I have found a solution. From this time onwards, I prefer to write a direct call to a method with necessary parameter values just in a command line. For example: test.exe "Job1(1, Math.Sqrt(3.14), new MyClass())" or "Job2(@\"C:\Temp\", true)" where Job1() and Job2() are existing public methods. I do not write a line of code to parse command lines in my utilities any more. Thanks C# and .NET!

What can you do with the Accessor class?

  • You can evaluate C# string expression or execute a method text at runtime; note that these expressions and methods can freely access all existing public members in the main assembly (or in any other additional, if specified);
  • My proposal is to apply this ability to evaluate or execute a command line in the Main()! As you have access to public members of the main assembly, you can invoke any method that was designed for that. A command line becomes a real part of your C# program and it is written also in C#.
  • Although I have started the article with words about console applications, you can apply this technique for Win Forms as well.

Your benefits if the Main() evaluates a command line

  • If you change or extend the program functionality, then you do not change Main() any more. You can freely call new or changed methods just from a command line.
  • You can use this effectively for debugging. Do you have a just created method not yet debugged? Do you want to invoke it immediately? Just write the call to this method in the command line with some parameter values and run debugging.
  • You can use this effectively for testing. You can call for testing, all public methods of your program just from a command line. The simplest way is a batch file with multiple runs of your program with different command lines, read: different calls to different methods.

A word about safety

This approach is not a toy for a final user! It is not a good idea to give a customer the ability to call any public method in your program from a command line. This approach is mostly for software developers themselves, just to eliminate a lot of simple but very boring and non-creative work.

Nevertheless, there are different quite safe ways to keep and use this ability in a released application. You can design your application so that your main assembly has only the methods intended for calling from a command line. These methods themselves have access to the other application assemblies, but a source written in command line has to be referenced to the main assembly, only in order to access the methods designed for that. A command line source compilation will fail on attempting to access something else which is not allowed. Of course it is up to a developer how to protect his application from dummies or hackers.

The code

The code below has just to be added to your code to make it all possible. It defines the class Accessor and also contains examples of how it works (namespace Test, see 3 examples in Test.Example.Main()). You have to define the EXAMPLE constant or you may just replace it with true to enable the example code. It seems I have nothing to say here any more, the code and comments say the rest. Note that Accessor can also be used as expressions evaluator and methods executor.

using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System;

// The example illustrates main purposes of Accessor:

// 1) Evaluation of a string expression

// 2) Execution of a method's code

// 3) Execution of a command line written in C#

#if EXAMPLE
namespace Test
{
    // This class is used to show that:

    // -  we can freely create an instance of MyClass in our string source,

    //    it means that we can create any other public class of this

    //    assembly and access its public members;

    // -  we can access even the protected MyMethod() in our string source,

    //    it means that we can also access any protected members of

    //    a public not sealed class of this assembly.

    public class MyClass
    {
        public MyClass()
        {
            Console.WriteLine("Hello from Test.MyClass()");
        }
        protected int MyMethod()
        {
            Console.WriteLine("Hello from Test.MyClass.MyMethod()");
            return 12345;
        }
    }

    // Main class

    public class Example
    {
        // Suppose Job1() and Job2() implement this program functionality.

        // Then we can just write command lines like:

        //      test.exe "Job1(0, 0.0, null)"

        //      test.exe "Job1(1, Math.Sqrt(3.14), new MyClass())"

        //      test.exe "Job2(@\"C:\Temp\", true)"

        //      test.exe "Job2(@\"C:\Documents and Settings\" , false)"

        // Note: just try these command lines

        public int Job1(int arg1, double arg2, MyClass arg3)
        {
            Console.WriteLine
                ("Hello from Job1() with {0}, {1}, {2}", arg1, arg2, arg3);
            return 0;
        }
        public int Job2(string arg1, bool arg2)
        {
            Console.WriteLine
                ("Hello from Job2() with {0}, {1}", arg1, arg2);
            return 0;
        }

        // Here there are 3 examples of 3 main purposes of Accessor

        static int Main(string[] args)
        {
            // Example 1. Evaluation of a string expression.

            string myExpression = "M.Sin(0.5) + M.Sqrt(2.0)";
            Console.WriteLine("\nExample 1. Evaluating:\n{0}...", 
                                                    myExpression);
            double result1 = (double)Absolute.Accessor.Evaluate(
                myExpression,
                false, // we don't need main assembly reference

                // just for fun: M instead of Math

                "using M = System.Math;", 
                null); // we don't need any class base list

            Console.WriteLine("Evaluation result: {0}", result1);

            // Example 2. Execution of a method text.

            string myMethod = @"
int Executable()
{
    Console.WriteLine(""Hello from Executable()"");
    return MyMethod();
}";
            Console.WriteLine("\nExample 2. Executing method:{0}...", 
                                                           myMethod);
            int result2 = (int)Absolute.Accessor.Execute(
                myMethod,
                true, // add main assembly reference to access MyClass

                // we can use MyClass instead of Test.MyClass

                "using Test;", 
                // MyClass's child can access protected MyMethod()

                "MyClass"); 
            Console.WriteLine("Execution result: {0}", result2);

            // Example 3.

        
            // THE AIM IS TO USE OUR PROGRAM AS A COMMAND LINE DRIVEN TOOL

            // WITHOUT WRITING A LINE OF CODE THAT PARSES ITS COMMAND LINE

            // Let the very first argument be a piece of code invoking

            // a program job and let Accessor make all parsing for us!


            // Our benefits for this example:

            // - we don't validate arguments and convert 

            //   them into required types

            // - we don't write any code to create required 

            //   MyClass instance

            // - we don't even call Job1() or Job2() methods;

            // - if (oh God!) we change Job's parametes count or types

            //   then this Main() function always remains the same

            //   (as usually we have to use updated command lines)

        
            int jobResult;
            try
            {// we have to catch possible compilation and execution errors

                // args0 is args[0] if any else some default job

                string args0 =
                    args.Length > 0 ?
                    args[0] : "Job1(1, Math.Sqrt(3.14), new MyClass())";
                Console.WriteLine
                    ("\nExample 3. Running command line:\n\"{0}\"", args0);
                jobResult = (int)Absolute.Accessor.Evaluate(
                    args0,
                    true, // access to Example and MyClass

                    "using Test;", // MyClass istead of Test.MyClass

                    "Example"); // access to Job()

                Console.WriteLine("Command line result: {0}", jobResult);
            }
            catch(Exception e)
            {// syntax errors are reported here as well as any others 

                Console.WriteLine(e.Message);
                jobResult = -1;
            }

            // result

            return jobResult;
        }
    }
}
#endif

namespace Absolute
{
    // summary: C# expessions evaluator and methods executor

    // remarks: Author: Roman Kuzmin

    public sealed class Accessor
    {
        // summary: Evaluates C# expression

        // expression: C# expression

        // mainAssembly: Add a reference to the main assembly

        // headCode: null or a code to be added at a generated source head

        // classBase: null or a generated class base list

        // assemblies: [params] null or additional assemblies

        // returns: Evaluated value as object

        public static object Evaluate(
            string expression,
            bool mainAssembly,
            string headCode,
            string classBase,
            params string[] assemblies)
        {
            string methodCode = String.Format(
                "object Expression()\n{{return {0};}}", expression);
            return Execute(
                methodCode, mainAssembly, headCode, classBase, assemblies); 
        }
        
        // summary: Executes C# method's code

        // methodCode: Complete C# method's code

        // mainAssembly: Add a reference to the main assembly

        // headCode: null or a code to be added at a generated source head

        // classBase: null or a generated class base list

        // assemblies: [params] null or additional assemblies

        // returns: null or the executed method's return value

        public static object Execute(
            string methodCode,
            bool mainAssembly,
            string headCode,
            string classBase,
            params string[] assemblies)
        {
            // extract method name

            int i1, i2 = methodCode.IndexOf('(');
            if(i2 < 1)
                throw new Exception("Accessor: syntax error: ( expected");
            for(--i2; i2 >= 0 && methodCode[i2] <= ' '; --i2);
            for(i1 = i2; i1 >= 0 && methodCode[i1] > ' '; --i1);
            string methodName = methodCode.Substring(i1 + 1, i2 - i1);

            // code builder

            StringBuilder code = new StringBuilder();

            // << default using directives

            code.Append("using System;\n");
            
            // << head code

            if(headCode != null)
                code.AppendFormat("{0}\n", headCode);

            // << class name

            code.Append("public class AbsoluteClass");

            // << clase base list

            if(classBase != null)
                code.AppendFormat(" : {0}", classBase);

            // << class body with method

            code.AppendFormat("\n{{\npublic {0}\n}}\n", methodCode);

            // compiler parameters

            CompilerParameters parameters = new CompilerParameters();
            parameters.GenerateExecutable = false;
            parameters.GenerateInMemory = true;

            // referenced asseblies

            parameters.ReferencedAssemblies.Add("system.dll");
            if(mainAssembly)
                parameters.ReferencedAssemblies.Add(
                    Process.GetCurrentProcess().MainModule.FileName);
            if(assemblies != null)
                parameters.ReferencedAssemblies.AddRange(assemblies);

            // compiler and compilation

            ICodeCompiler compiler =
                new CSharpCodeProvider().CreateCompiler();
            CompilerResults results =
                compiler.CompileAssemblyFromSource(
                parameters, code.ToString());
            
            // errors?

            if(results.Errors.HasErrors)
            {
                StringBuilder error = new StringBuilder();
                error.Append("Accessor: compilation errors:\n");
                foreach(CompilerError err in results.Errors)
                    error.AppendFormat(
                        "#{0} {1}. Line {2}.\n",
                        err.ErrorNumber, err.ErrorText, err.Line);
                error.AppendFormat("\n{0}", code);
                throw new Exception(error.ToString());
            }

            // class instance

            object classInstance =
                results.CompiledAssembly.CreateInstance("AbsoluteClass");

            // execute method

            MethodInfo info = classInstance.GetType().GetMethod(methodName);
            return info.Invoke(classInstance, null);
        }

        // summary: Private constructor makes the class 'pure static'

        private Accessor()
        {
        }
    }
}

History

  • 07/20/2003 - Original article.
  • 07/21/2003 - More detailed explanation and code comments.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here