Одной из самых роскошных тем для программистов, балующихся изобретением велосипедов, является написание собственных языков, интерпретаторов и компиляторов. Действительно, программа, способная создавать или исполнять другие программы, инстинктивно вселяет благоговейный трепет в сердца кодеров — потому что сложно, объемно, но безумно увлекательно.
Большинство начинают с собственных интерпретаторов, которые представляют собой в общем виде огромный свитч команд в цикле. Интересно, вольготно, но муторно и весьма медленно. Хочется чего-то более шустрого, чтобы JIT'ить умело и, желательно, само следило за памятью.
Отличным решением данной проблемы является выбор .NET в качестве целевой платформы. Оставим лексический разбор на следующий раз, а сегодня давайте попробуем сделать простейшую программу, которая создает работающий исполняемый экзешник:
Программа будет требовать имя, и выводить в консоли Hello, %username%.
Для создания экзешника существует много способов, например:
Трансляция в C#-код и вызов csc.exe: просто, но неспортивно
Генерация IL-кода в текстовой форме и компиляция ilasm.exe: неудобно по причине необходимости писать руками огромный манифест
Генерация сборки напрямую с помощью Reflection или Cecil
Как раз последний вариант я и выбрал. К сожалению, я не знаю, в чем для данной задачи Cecil превосходит Reflection, но мне попался пример именно на Cecil, поэтому именно его я и разберу.
Mono.Cecil — это библиотека, позволяющая работать со сборкой как с массивом байтов. C ее помощью можно как создавать свои собственные сборки, так и ковыряться и модифицировать уже существующие. Она предоставляет широкий спектр классов, которыми (обычно) удобно пользоваться.
Предмет беседы
Вот, собственно, готовый код (без описания класса, формы и всего прочего, кроме собственно метода-генератора):
using Mono.Cecil;
using Mono.Cecil.Cil;
public void Compile(string str)
{
// создаем библиотеку и задаем ее название, версию и тип: консольное приложение
var name = new AssemblyNameDefinition("SuperGreeterBinary", new Version(1, 0, 0, 0));
var asm = AssemblyDefinition.CreateAssembly(name, "greeter.exe", ModuleKind.Console);
// импортируем в библиотеку типы string и void
asm.MainModule.Import(typeof(String));
var void_import = asm.MainModule.Import(typeof(void));
// создаем метод Main, статический, приватный, возвращающий void
var method = new MethodDefinition("Main", MethodAttributes.Static | MethodAttributes.Private | MethodAttributes.HideBySig, void_import);
Read more:
Habrahabr.ru Posted via email from .NET Info