Все вопросы связанные с программированием. Языки программирования. Средства разработки.
Ответить
Аватара пользователя
tAZAR
Не в сети
Модератор
Модератор
Сообщения: 567
Зарегистрирован: Ср июн 23, 2004 8:13
Откуда: г.Шахты

Кому полезное и интересное, а кому - и не очень )

Сообщение tAZAR »

Буду выкладывать периодически здесь более или менее занятные дотнетчикам (и не только) вещи. Учитывая 2х годовалый опыт работы на C# и использование мной его как основного языка- с него и начну. Вдруг, кому полезно будет.

Создание "движка" для выполния псевдо-скрипта, назовем его CSharp Script
Программист рано или поздно сталкивается с проблемой интеграции в свои приложения скриптов как инструмента расширения функционала. Многие используют jscript, vbscript, ms scripting host в общем, также lua и т.д.
Необходимость разработки трехзвенки с гибким и легкорасширяемым сервером, просто модульной платформы или (упоси бог) учетки с БЛ на клиенте ведет к попыткам взаимодействовать с подобными скриптами, или созданию механизма плагинов. Имея уже сравнительно большой опыт разработки систем, активно использующих механизм плагинов, я решил добавить возможность создавать сценарии для своего сервера в виде именно скриптов (псевдо-скриптов на самом деле) на родном для платформы языке (C#) без вкручивания костылей на msc или других скриптах.
Учитывая наличие пронстранств имен System.CodeDom.Compiler и Microsoft.CSharp мы имеем возможность как компилировать исходный код, так и выполнять его, загружать в домены приложения и т.д.
Прелюдия:
Это помогут сделать классы CSharpCodeProvider, CompilerParameters и CompilerResults.
Итак, создаем новый проект (моя версия FrameWork >=2.0), "Windows exe".
Я назвал класс, занимающийся отработкой "команд" препоцессора CSharpScriptHost. Наш хост немного расширяет возможности C# - я добавил туда директивы (парсингу синтаксиса время не уделялось, будет желание - можно добавить)
#include - подключение к файлу скрипта дополнительных исходных файлов для компиляции, имя файла пишется без кавычек.
#inline - вставка в исходный код содержимого указанного файла, имя файла без кавычек
#reference - подключение дополнительных сборок, объекты и пространства имен которых будут использованы в скрипте

Реализация класса CSharpScriptHost (сильно не пинайте - с архитектурой не заморачивался, и есть поле для оптимизации):

Код: Выделить всё

using System;
using System.CodeDom.Compiler;
using System.IO;
using System.Reflection;
using System.Text;
using System.Windows.Forms;
using Microsoft.CSharp;
using System.Collections.Generic;

namespace CSharpScript
{
    public class CSharpScriptHost
    {
        /// <summary>
        /// Создает объект CSharpScriptHost
        /// </summary>
        public CSharpScriptHost() {
            Parameters = new Dictionary<string, object>();
        }

        public Dictionary<string, object> Parameters;

        /// <summary>
        /// Показывать окно ожидания в процессе сборки?
        /// </summary>
        public bool ShowWindow { get; set; }

        /// <summary>
        /// Выполняет файл, указанный в filename
        /// </summary>
        /// <param name="filename">Имя файла</param>
        /// <returns></returns>
        public bool ExecuteScriptFromFile(string filename)
        {
            return ExecuteScript(GetFile(filename));
        }
        /// <summary>
        /// Загружает текстовый файл в строковую переменную
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>
        private string GetFile( string filename) {
            string code;
            try {
                StreamReader reader = new StreamReader( filename );
                code = reader.ReadToEnd();
                reader.Close();
                reader.Dispose();
                reader = null;
            }
            catch ( FileNotFoundException fnf ) {
                throw new CSharpScriptHostException( fnf.Message );
            }
            return code;
        }
        private List<string> references;


        /// <summary>
        /// Обработка кода до сборки, формирование списка требующихся дополнительных сборок и выполнение 
        /// команд препроцессора
        /// </summary>
        /// <param name="code">Код скрипта</param>
        /// <returns></returns>
        private string ParseCode(string code) {
            StringBuilder resCode = new StringBuilder();
            foreach ( string line in code.Split( '\n') ) {
                string cmd = line.Trim();
                string[] asm = line.Replace( ";", String.Empty ).Trim().Split( new char[] { ' ', '\t' }, 2 );
                if ( cmd.StartsWith( "#reference" ) ) {
                    if ( asm.Length > 1 )
                        references.Add( asm[1].Trim() );
                    else
                        throw new CSharpScriptHostException( "#reference: Ожидается имя сборки." );
                    continue;
                }
                 
                if ( cmd.StartsWith( "#include" ) ) {
                    if(asm.Length > 1){
                        string file = GetFile( asm[1] );
                        string inccode = ParseCode( file );
                        resultCode.Add( inccode);
                    }
                    else
                        throw new CSharpScriptHostException( "#include: Ожидается имя файла." );
                    continue;
                }
                if ( cmd.StartsWith( "#inline" ) ) {
                    if ( asm.Length > 1 ) {
                        string file = GetFile( asm[1] );
                        resCode.Append(ParseCode( file));
                    }
                    else
                        throw new CSharpScriptHostException( "#inline: Ожидается имя файла." );
                    continue;
                }

                resCode.Append(line);
            }
            return resCode.ToString();
        }
        private List<string> resultCode;

        /// <summary>
        /// Выполняет скрипт
        /// </summary>
        /// <param name="code">Код скрипта</param>
        /// <returns></returns>
        public bool ExecuteScript(string code)
        {
            references = new List<string>();
            resultCode = new List<string>();
            
            FormWait frmWait = new FormWait();
            if ( ShowWindow ) {
                frmWait.Show();
                frmWait.Update();
            }

            resultCode.Add(ParseCode( code ));
            
            CSharpCodeProvider provider = new CSharpCodeProvider();
            string filename = Path.GetTempFileName();
            CompilerParameters parameters = new CompilerParameters();
            parameters.GenerateInMemory = true;
            parameters.GenerateExecutable = false;

            parameters.OutputAssembly = filename;
            foreach ( string asm in references ) {
                parameters.ReferencedAssemblies.Add( asm );
            }
            foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
                if(parameters.ReferencedAssemblies.IndexOf(asm.ManifestModule.Name) <0 )
                    parameters.ReferencedAssemblies.Add(asm.Location);

            CompilerResults result = provider.CompileAssemblyFromSource(parameters, resultCode.ToArray());
            frmWait.Close();
            frmWait.Dispose();
            frmWait = null;
            StringBuilder errorInfo = new StringBuilder();
            if(result.Errors.Count > 0)
                errorInfo.Append("При сборке возникли ошибки:\n");
            foreach (CompilerError error in result.Errors)
            {
                if (error.IsWarning)
                    continue;
                errorInfo.Append(error.ToString());
                errorInfo.Append("\n");
            }
            if (!string.IsNullOrEmpty(errorInfo.ToString()))
            {
                throw new CSharpScriptHostException( errorInfo.ToString());
            }
            else
            {
                if(File.Exists(filename))
                    File.Delete(filename);
                Type t = result.CompiledAssembly.GetType( "ScriptMain.ScriptMain" );
                MethodInfo m = null;
                if ( t != null ) {
                    m = t.GetMethod( "Main" );
                    if ( m != null ) {
                        m.Invoke( null, new object[] {Parameters } );
                    }
                    else
                        throw new CSharpScriptHostException( "Метод static void Main(Dictionary<string,object>) не найден!" );

                }
                else
                    throw new CSharpScriptHostException( "Не объявлен класс ScriptMain.ScriptMain!" );
                }
               
                return true;
            }
    }
}

В классе используется CSharpScriptHostException для выбрасывания исключений. Код предельно прост:

Код: Выделить всё

using System;

namespace CSharpScript {
    public class CSharpScriptHostException:Exception {
        public CSharpScriptHostException( string message ) : base( message ) { }
    }
}
Естественно, желательно доработать эту ситуацию и добавить выбрасывание FileNotFoundException, или сделать CSharpScriptHostFileNotFoundException.. Это уже мелочи.
В результате мы получили класс, умеющий компилировать и выполнять код из "псевдо-скрипта" из обычной строковой переменной. Откуда возьмется код в этой переменной - нам абсолютно не важно. Также он умеет загружать код из файла или нескольких файлов с использованием директив.
Обратите внимание, что хост добавляет к параметрам компилятора ссылки на все сборки, которые использует основной домен приложения. Это говорит о том, что вы можете свободно использовать в скрипте объекты и, например, поля, статических классов приложения - достаточно только указать соответствующий using.
Для чего делалось "Windows exe" ?
Привожу простой код startup-класса (там для гламура нужно лишние юзинги вычистить):

Код: Выделить всё

using System;
using System.CodeDom.Compiler;
using System.IO;
using System.Reflection;
using System.Text;
using System.Windows.Forms;
using Microsoft.CSharp;
using System.Collections.Generic;

namespace CSharpScript
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main(string[] args)
        {
            CSharpScriptHost scriptHost = new CSharpScriptHost();
            scriptHost.ShowWindow = true;
            for ( int i = 0; i < args.Length; i++ )
                scriptHost.Parameters.Add( String.Format( "arg_{0}", i.ToString() ), args[i] );
            if (args.Length < 1)
            {
                MessageBox.Show("Не указано имя файла.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            try {
                scriptHost.ExecuteScriptFromFile( args[0] );
            } 
            catch ( CSharpScriptHostException ex ) {
                MessageBox.Show( ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error );
            }
        }
    }
}
Все. Регистрируем соответствие любого расширения получившемуся в результате сборки проекта exeшнику - и мы можем "исполнять" скрипты путем обычного их запуска как приложения в ОС.
Для того, чтобы использовать объект CSharpScriptHost в своем приложении - просто добавляем референс на получившуюся сборку, и вперед, расширяйте функционал без пересборки проекта, шифруйте исходные файлы "скриптов" как угодно если боитесь открытого кода, или этого не позволяет политика фирмы.. В общем, полная свобода действий. Наслаждайтесь!

P.S. О защите: если вас не устраивает то, что скрипт имеет возможность равноправно взаимодействовать с кодом приложения - загружайте сборку скрипта в отдельный AppDomain и не добавляйте ссылки на сборки, да и защиту на "фильтр" подключаемых из скрипта сборок сделать - 5 минут.

В прикреленном архиве проект для MSVS и пример скрипта в папке Debug.

Если вам это пригодилось, или заинтересовало вас - я готов рассказывать о различных интересных для разработчика моментах и дальше.
Вложения
CSharpScript.zip
(67.97 КБ) 253 скачивания
346500 night eXtreme sha.ENcounter team

Fedor
Не в сети
Новичок
Новичок
Сообщения: 16
Зарегистрирован: Ср май 12, 2010 22:39

Re: Кому полезное и интересное, а кому - и не очень )

Сообщение Fedor »

Ну наконец-то появилось хоть что-то относящееся к кодингу. Спасибо.

lewov
Не в сети
Случайно забежавший
Случайно забежавший
Сообщения: 1
Зарегистрирован: Ср апр 09, 2014 10:48

Re: Кому полезное и интересное, а кому - и не очень )

Сообщение lewov »

А кто знает на каком движке стоит этот сайт? http://gigaset-shop.ru/besprovodnye-telefony/

Ответить