Приступаем к динамической компиляции. Потребуется информация о пути к системным библиотекам.
//путь к используемому фреймворку
static string frmPath = @"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2\";
static string keyConsole = ""; //ввод с клавиатуры
static object dynClassInstance = null; //объект типа скрипт
static System.Threading.Thread my_thread = null; //текущий поток со скриптом
readonly static List<System.Threading.Thread> lstThr = new List<Thread>(); //список всех потоков со скриптами
Добавляем пространства имен
//for dynamo
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.Reflection;
using System.Threading;
Добавляем метод Process.
//компилировать и выполнить скрипт из окна "комманд"
public static void Process(string s)
{
//сборки
string[] includeAssemblies = { "MathPanel.exe",
frmPath + "System.dll",
frmPath + "System.Xaml.dll",
frmPath + "WindowsBase.dll",
frmPath + "PresentationFramework.dll",
frmPath + "PresentationCore.dll"
, frmPath + "System.Drawing.dll"
, frmPath + "System.Net.dll"
, frmPath + "System.Net.Http.dll"
};
//пространства имен
string[] includeNamespaces = { "MathPanel", "MathPanelExt", "System.Net.Sockets" };
keyConsole = "";
CompileDynamo(s, null, includeNamespaces, includeAssemblies);
//создаем новый поток
try
{
if (dynClassInstance != null)
{
my_thread = new System.Threading.Thread(new System.Threading.ThreadStart(() => {
Type type = dynClassInstance.GetType();
MethodInfo methodInfo = type.GetMethod("Execute");
try
{
methodInfo.Invoke(dynClassInstance, null);
Dynamo.Console("Скрипт выполнен.");
}
catch (Exception yyy) { Dynamo.Console(yyy.ToString()); }
//my_thread = null;
}));
my_thread.Start();
lstThr.Add(my_thread);
}
}
catch (Exception xxx) { Dynamo.Console(xxx.ToString()); }
}
И вспомогательный метод CompileDynamo
//создать объект типа скрипт
static object CompileDynamo(string code, Type outType = null, string[] includeNamespaces = null, string[] includeAssemblies = null)
{
StringBuilder namespaces = null;
object methodResult = null;
dynClassInstance = null;
using (CSharpCodeProvider codeProvider = new CSharpCodeProvider())
{
//ICodeCompiler codeCompiler = codeProvider.CreateCompiler();//obsolete!
CompilerParameters compileParams = new CompilerParameters
{
CompilerOptions = "/t:library",
GenerateInMemory = true
};
int ipos = code.IndexOf("///[DLL]");
if (ipos > 0)
{
int ipos2 = code.IndexOf("[/DLL]", ipos);
if (ipos2 > 0)
{
compileParams.ReferencedAssemblies.Add("MathPanel.exe");
string ass = code.Substring(ipos + 8, ipos2 - ipos - 8);
var arr = ass.Split(',');
foreach (string _assembly in arr)
{
compileParams.ReferencedAssemblies.Add(frmPath + _assembly.Trim());
}
}
}
else if (includeAssemblies != null && includeAssemblies.Length > 0)
{
foreach (string _assembly in includeAssemblies)
{
compileParams.ReferencedAssemblies.Add(_assembly);
}
}
if (includeNamespaces != null && includeNamespaces.Length > 0)
{
namespaces = new StringBuilder();
foreach (string _namespace in includeNamespaces)
{
namespaces.Append(string.Format("using {0};\n", _namespace));
}
}
if (code.IndexOf("namespace DynamoCode") < 0)
code = string.Format(
@"{1}
using System;
namespace DynamoCode{{
public class Script{{
public {2} Execute(){{
{3} {0};
}}
}}
}}",
code,
namespaces != null ? namespaces.ToString() : null,
outType != null ? outType.FullName : "void",
outType != null ? "return" : string.Empty
);
CompilerResults compileResult = codeProvider.CompileAssemblyFromSource(compileParams, code);
//codeCompiler.CompileAssemblyFromSource(compileParams, code);
if (compileResult.Errors.Count > 0)
{
//throw new Exception(compileResult.Errors[0].ErrorText);
Console("compile error: " + compileResult.Errors[0].ErrorText);
return "compile error: " + compileResult.Errors[0].ErrorText;
}
System.Reflection.Assembly assembly = compileResult.CompiledAssembly;
dynClassInstance = assembly.CreateInstance("DynamoCode.Script");
/*Type type = dynClassInstance.GetType();
MethodInfo methodInfo = type.GetMethod("Execute");
methodResult = methodInfo.Invoke(dynClassInstance, null);*/
}
return methodResult;
}
И меняем код обработчика кнопки
if (!bReady)
{
MessageBox.Show("Не готово!");
return;
}
Process(textBlock1.Text);
Запускаем программу, печатаем в окне команд
var hz = Math.Sqrt(3);
Dynamo.Console(hz.ToString());
Нажимаем «Выполнить», получаем 1,73205080756888 в окне сообщений.
Поясним детали. Обработчик кнопки считывает текст из окна команд и передает его в метод Process().
Process формирует список выполняемых модулей
string[] includeAssemblies
и список пространств имен
string[] includeNamespaces
Затем вызывает для компиляции кода метод CompileDynamo. В случае успеха
(dynClassInstance != null) есть истина.
Тогда создаем новый поток с делегатом, в котором запускается метод Execute из скомпилированного кода.
my_thread = new System.Threading.Thread(new System.Threading.ThreadStart(() => {
Type type = dynClassInstance.GetType();
MethodInfo methodInfo = type.GetMethod("Execute");
try
{
methodInfo.Invoke(dynClassInstance, null);
Dynamo.Console("Done");
}
catch (Exception yyy) { Dynamo.Console(yyy.ToString()); }
}));
Стартуем поток и добавляем его в список (для последующей очистки).
my_thread.Start();
lstThr.Add(my_thread);
Собственно, компиляция происходит в методе CompileDynamo. Сначала создается объект codeProvider, который предоставляет доступ к экземплярам генератора и компилятора кода C#. Ему передаются параметры компиляции: создавать библиотеку в памяти.
using (CSharpCodeProvider codeProvider = new CSharpCodeProvider())
{
CompilerParameters compileParams = new CompilerParameters
{
CompilerOptions = "/t:library",
GenerateInMemory = true
};
Фрагмент позволяет указывать список нужных библиотек в компилируемом коде.
int ipos = code.IndexOf("///[DLL]");
if (ipos > 0)
{
int ipos2 = code.IndexOf("[/DLL]", ipos);
if (ipos2 > 0)
{
compileParams.ReferencedAssemblies.Add("MathPanel.exe");
string ass = code.Substring(ipos + 8, ipos2 - ipos - 8);
var arr = ass.Split(',');
foreach (string _assembly in arr)
{
compileParams.ReferencedAssemblies.Add(frmPath + _assembly.Trim());
}
}
}
Затем формируется окончательный код для компиляции. Если поставить точку прерывания, то можно зафиксировать код, который передается компилятору.
using MathPanel;
using MathPanelExt;
using System.Net.Sockets;
using System;
namespace DynamoCode{
public class Script{
public void Execute(){
var hz = Math.Sqrt(3);
Dynamo.Console(hz.ToString());
}
}
}
Таким образом происходит «обертывание» кода из окна команд в класс DynamoCode.Script с методом Execute.
Затем даем команду компилятору
CompilerResults compileResult = codeProvider.CompileAssemblyFromSource(compileParams, code);
В случае успеха создаем экземпляр сборки
if (compileResult.Errors.Count > 0)
{
Console("compile error: " + compileResult.Errors[0].ErrorText);
return "compile error: " + compileResult.Errors[0].ErrorText;
}
System.Reflection.Assembly assembly = compileResult.CompiledAssembly;
dynClassInstance = assembly.CreateInstance("DynamoCode.Script");
Теперь можно написать что-то посложнее для выполнения. Например, генерация чисел от 1 до 20 и потом снова до 1.
//for Excel increment cell values
for(int i = 1; i <= 20; i++)
Dynamo.Console(i.ToString());
for(int i = 19; i >= 1; i--)
Dynamo.Console(i.ToString());
Копируем результат из окна сообщений и вставляем в Эксель. Удобно.
Рис.2.3. Копирование результата в Эксель
С реализацией первой кнопки «Выполнить» мы закончили. Переходим к другим кнопкам.