Класс CSharpCodeProvider. Возможности примененияАннотация: Данная статья не описание разработки конкретной программы, а пособие по динамическому созданию одной программы из другой.
4. Литература Класс CSharpCodeProvider обычно рассматривают в аспекте динамической компиляции и загрузки кода, когда требуется внесение изменений в функциональность приложений без их перекомпиляции. Как правило, это разработка программ, способных обрабатывать скрипты (и не только написанные на С#, но и скрипты JS, VB). Однако, куда более интересной, является возможность использования класса для формирования полнофункциональных приложений по шаблонам с динамически задаваемыми на этапе компиляции характеристиками. Параграф 1. Постановка задачи и разработка шаблона программы (программа Slave)Создадим приложение, которое позволяет создавать фотоальбомы, оформленные как Windows программы. Причем, каждый фотоальбом должен иметь свою иконку для exe файла, свою иконку для приложения, заставку при старте и ряд других настроек. Иначе, будем создавать программы Master - для создания программ-фотоальбомов и Slave - собственно шаблон программы для фотоальбомов. На начальном этапе создадим простейший прообраз будущей программы фотоальбома (шаблон Slave программы). Сформируем проект Windows решения с именем SlideMakerX (X - текущий рабочий номер). Поместим на форму контролы Button, TextBox и PictureBox (Рис.1.). В дальнейшем мы изменим набор контролов, но на первоначальном этапе они нам необходимы для понимания процессов создания файлов решения и принятых кодовых конструкций, которые могут быть обработаны классом CSharpCodeProvider.
Рис.1. Проект решения 1 Начнем вносить изменения в структуру проекта решения:
Рис.2. Добавление изображений в ресурс Приведем код файла Form1.cs, который получился у нас в результате объединения трех файлов. Код в конструкторе будет пояснен ниже. using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.IO; using System.Resources; ////////////////////////////////////////////////////////////////// //Эта часть перенесена из файла AssemblyInfo.cs ////////////////////////////////////////////////////////////////// using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; [assembly: AssemblyTitle("SlideMaker2")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("wlad")] [assembly: AssemblyProduct("SlideMaker2")] [assembly: AssemblyCopyright("Copyright c wlad 2007")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: Guid("503656d7-b907-4d31-a09b-45a3016f0d4f")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] namespace SlideMaker2 { ////////////////////////////////////////////////////////////////// //Класс partial Form1 (файл Form1.cs) ////////////////////////////////////////////////////////////////// public partial class Form1 : Form { public Form1() { InitializeComponent(); ///////////////////////////////////////////////////////////////// //Пояснение этой части кода в конструкторе будет дано ниже ///////////////////////////////////////////////////////////////// string[] sRes = System.Reflection.Assembly. GetExecutingAssembly().GetManifestResourceNames(); int viI = -1; int viJ = -1; for (int i = 0; i < sRes.Length; i++) { textBox1.Text += sRes[i].ToString() + Environment.NewLine; if (sRes[i].ToUpper().Contains("A1")) { viI = i; } if (sRes[i].ToUpper().Contains("A0")) { viJ = i; } } if (viI != -1) { using (Stream theResource = System.Reflection.Assembly. GetExecutingAssembly().GetManifestResourceStream(sRes[viI])) { this.Icon = new Icon(theResource); } } else { Icon = Properties.Resources.a1; } if (viJ != -1) { using (Stream theResource = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(sRes[viJ])) { pictureBox1.Image = Image.FromStream(theResource); } } else { pictureBox1.Image = Properties.Resources.a0; } } ////////////////////////////////////////////////////////////////// //Для показа того, что можно что-то делать в обработчиках событий ////////////////////////////////////////////////////////////////// private void button1_Click(object sender, EventArgs e) { Text = "Hello World C#"; } } ////////////////////////////////////////////////////////////////// //Это часть кода из файла Programm.cs ////////////////////////////////////////////////////////////////// static class Program { [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } } } Откомпилируем и выполним решение. Мы увидим, что наше Windows приложение имеет все то, что мы от него потребовали на первом этапе (Рис.3.).
Рис.3. Результат выполнения кода Поясним код приведенный в конструкторе: 1. Следующая конструкция позволяет нам получить имена всех ресурсов, которые есть в приложении в виде массива строк. string[] sRes = System.Reflection.Assembly. GetExecutingAssembly().GetManifestResourceNames(); 2. Не смотря на наличие нескольких файлов, так или иначе связанных с ресурсами, в приложении есть только один общий ресурс, а все остальные файлы только обеспечивают взаимодействие приложения с ресурсом: SlideMaker2.Properties.Resources.resources 3. В файле Resources.Designer.cs можно видеть механизм доступа к объектам ресурса в пространстве SlideMaker2.Properties.Resources: internal static System.Drawing.Bitmap a0 { get { object obj = ResourceManager.GetObject("a0", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } } internal static System.Drawing.Icon a1 { get { object obj = ResourceManager.GetObject("a1", resourceCulture); return ((System.Drawing.Icon)(obj)); } } 4. В приведенном примере, отображение иконки и заставки выполнено с использованием приведенных механизмов доступа: Icon = Properties.Resources.a1; pictureBox1.Image = Properties.Resources.a0; 5. Остальной код в конструкторе рассчитан на то, что у нас в приложении после компиляции будут ресурсы, содержащие имена a0 и a1 (см.далее). Параграф 2. Программная компиляция шаблона программы (программа Master)Создадим новое приложение, которое будет выполнять роль программы-Master, компилировать программный код программы Slave (собственно альбома) и создавать готовый ее exe файл. Сформируем обычный проект Windows решения с именем CreateProgramm (Рис.4.). Поместим на форму три контрола Button, два контрола PictureBox , контролы TextBox, Label, FileListBox и OpenFileDialog.
Рис.4. Проект решения 2 В дальнейшем мы изменим набор контролов в соответствии с новыми потребностями. Далее, создадим обработчики нажатия кнопок и запишем базовый код, который пока не включает собственно компиляцию Slave кода: public partial class Form1 : Form { string sPathIcon = string.Empty; string sBackGround = string.Empty; string sWorkDirectory = string.Empty; public Form1() { InitializeComponent(); //Рабочая директория проекта sWorkDirectory=Directory.GetCurrentDirectory(); } //Выбор иконки для приложения Slave программы private void button2_Click(object sender, EventArgs e) { openFileDialog1.FileName = ""; openFileDialog1.Filter = "ico файлы (*.ico)|*.ico"; if (openFileDialog1.ShowDialog() == DialogResult.OK) { sPathIcon = openFileDialog1.FileName; pictureBox1.Image = Image.FromFile(sPathIcon); File.Copy(sPathIcon, sWorkDirectory + @"\Programm\a1.ico",true); } } //Выбор заставки для Slave программы private void button3_Click(object sender, EventArgs e) { openFileDialog1.FileName = ""; openFileDialog1.Filter = "jpg файлы (*.jpg)|*.jpg|jpeg файлы (*.jpeg)|*.jpeg"; if (openFileDialog1.ShowDialog() == DialogResult.OK) { sBackGround = openFileDialog1.FileName; pictureBox2.Image = Image.FromFile(sBackGround); pictureBox2.Image.Save(sWorkDirectory + @"\Programm\a0.jpg", System.Drawing.Imaging.ImageFormat.Jpeg); } } //В button1_Click будем выполнять динамическую компиляцию private void button1_Click(object sender, EventArgs e) { //Требование задать иконку, заставку и имя программы if (textBox2.Text.Trim() == "") { MessageBox.Show("Не задано имя альбома. Введите его в соответствующем поле!", "Имя", MessageBoxButtons.OK, MessageBoxIcon.Question); return; } if (sPathIcon == string.Empty || sBackGround == string.Empty) { MessageBox.Show("Не выбрана иконка или заставка для приложения.", @"Иконка\заставка", MessageBoxButtons.OK, MessageBoxIcon.Question); return; } //Далее пойдет код динамической компиляции .............................. } Данный код не представляет интереса. Он всего лишь позволяет выбрать и сохранить требуемые параметры. Для сохранения параметров создадим папку Programm в рабочей директории данного приложения (у меня на период отладки это C:\SamplesC#\CreateProgramm\bin\Debug\Programm\). Отметим также, что мы можем выбирать рисунок для заставки любого формата. О работе с различными форматами рисунков Вы можете посмотреть здесь. Примерный вид программы показан на Рис.5.
Рис.5. Выбор параметров Все, что нам понадобится для компиляции от кода Slave программы - это два файла Form1.cs и Form1.Designer.cs, которые мы скопируем в созданную папку Programm. На начало компиляции там будут, как отмечено выше, и файлы иконки и заставки. Рассмотрим код выполняющий компиляцию (все пояснения приведены в коде): //Прежде всего пространства имен, которые необходимы: using System; using System.Collections.Generic; using System.CodeDom.Compiler; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using Microsoft.CSharp; using System.IO; ...... //А этот код добавляем в обработчик button1_Click //Создаем экземпляр компилятора C# кода using (CSharpCodeProvider code = new CSharpCodeProvider()) { //Создаем экземпляр класса параметров для компилятора CompilerParameters compileparam = new CompilerParameters(); //Добавляем основные опции компилятора: target - целевой файл - winexe; //win32icon - наличие иконки для exe файла (не приложения!!), в качестве иконки //выберем тот же файл, что мы собирались сделать иконкой приложения sPathIcon; //res - ресурсы приложения - мы добавили выбранные нами файлы a0.jpg и a1.ico compileparam.CompilerOptions = "/target:winexe" + " " + "/win32icon:" + "\"" + sPathIcon + "\" " + "/res:" + "\"" + sWorkDirectory + @"\Programm\a0.jpg" + "\" " + "/res:" + "\"" + sWorkDirectory + @"\Programm\a1.ico" + "\" "; //Параметр GenerateExecutable определяет тип создаваемого приложения //false - DLL, true - exe файл compileparam.GenerateExecutable = true; //Полезно для отладки compileparam.IncludeDebugInformation = false; //Имя выходного файла compileparam.OutputAssembly = @"c:\"+textBox2.Text.Trim()+".exe"; //Сохранять ли на диске созданный файл или выполнить в памяти compileparam.GenerateInMemory = false; //Добавление библиотек для компиляции, которые не являются //обязательными для Slave программы compileparam.ReferencedAssemblies.Add("System.dll"); compileparam.ReferencedAssemblies.Add("System.Data.dll"); compileparam.ReferencedAssemblies.Add("System.Deployment.dll"); compileparam.ReferencedAssemblies.Add("System.Drawing.dll"); compileparam.ReferencedAssemblies.Add("System.Windows.Forms.dll"); compileparam.ReferencedAssemblies.Add("System.Xml.dll"); //О том как работать с предупреждениями compileparam.TreatWarningsAsErrors = false; //И собственно компиляция CompilerResults compilerresults = code.CompileAssemblyFromFile(compileparam, new string [] { sWorkDirectory + @"\Programm\Form1.cs", sWorkDirectory + @"\Programm\Form1.Designer.cs" }); //Осталось посмотреть ошибки, если они были if (compilerresults.Errors.Count > 0) { Text = "Error: "+compilerresults.Errors[0].ErrorText; if(compilerresults.Errors.Count > 1) Text += ";" + compilerresults.Errors[0].ErrorText; } else { Text = "The compilation is executed successfully"; } } Найдем скомпилированный файл C:\a1.exe и исследуем его:
Отметим, что возможно загрузить файлы Form1.cs и Form1.Designer.cs в TextBox, то вместо конструкции new string[] можно использовать textBox1.Text (на этом основывается возможность выполнения скриптов из приложений), то есть: //Данный код CompilerResults compilerresults = code.CompileAssemblyFromFile(compileparam, new string [] { sWorkDirectory + @"\Programm\Form1.cs", sWorkDirectory + @"\Programm\Form1.Designer.cs" }); //Равноценен коду CompilerResults compilerresults = code.CompileAssemblyFromFile(compileparam, terextBox1.Text); Параграф 3. О возможности использования коллекции EmbeddedResources для добавления ресурсовВ приведенном выше коде мы определили каждый ресурс отдельно при задании опций компиляции (CompilerOptions). Это не совсем удобно. Другим путем является использование свойства класса CompilerParameters EmbeddedResources, которое дает возможность получить StringCollection ресурсов и, используя метод Add класса StringCollection, добавить пути к каждому ресурсу отдельно. При последующей компиляции ресурс становится частью тела целевого ехе файла. Внесем изменения в программу Master. Установим свойства MultiSelect контрола openFileDialog1 и MultiLine контрола filelistBox1 в true и добавим контрол Button. В обработчике нажатия этой кнопки создадим код формирования списка фотографий. Кроме того объявим переменную viCountNumPhoto для учета количества фотографий, помещенных в список. private void button4_Click(object sender, EventArgs e) { openFileDialog1.FileName = ""; openFileDialog1.Filter = "jpg файлы (*.jpg)|*.jpg|jpeg файлы (*.jpeg)|*.jpeg"; if(viCountNumPhoto == 0) filelistBox1.Items.Clear(); if (openFileDialog1.ShowDialog()==DialogResult.OK) { for (int i = 0; i < openFileDialog1.FileNames.Length; i++) { viCountNumPhoto++; filelistBox1.Items.Add(openFileDialog1.FileNames[i]); } } } При неудачном формировании списка можно предусмотреть кнопку его очистки и ряд других сервисных функций. Можно предусмотреть загрузку фото и картинок в разных форматах и преобразование форматов и т.д. и т.п. Иначе, на данном этапе открывается широкое поле для творчества, но, так как это не является нашей целью, останавливаться на этом не будем. И так, на данный момент в filelistBox1 унас список файлов, которые мы должны добавить к тем, что уже добавляются в ресурс. Эту задачу выполнит следующий код, помещенный в обработчик нажатия кнопки 1: //Копируем файлы с оригинальными именами for (int i = 1; i <= filelistBox1.Items.Count; i++) { File.Copy((string)filelistBox1.Items[i-1], sWorkDirectory + @"\Programm\b" + Convert.ToString(i) + ".jpg", true); } //Добавряем список в ресурс for (int i = 1; i <= filelistBox1.Items.Count; i++) { compileparam.EmbeddedResources.Add(sWorkDirectory + @"\Programm\b" + Convert.ToString(i) + ".jpg"); } Выполнив код на данном этапе мы можем убедиться после запуска программы Slave, что все ресурсы доступны в программе (Рис.7.).
Рис.7. Добавление файлов фотографий в ресурс Как обеспечить доступ к ресурсу, мы уже знаем, поэтому далее приводим только код вывода фото в PictureBox1 программы Slave. private int viNumPhoto=1; ..... private void button1_Click(object sender, EventArgs e) { Text = "Просмотр фото"; try { pictureBox1.Image = Image.FromStream(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream ("b" + Convert.ToString(viNumPhoto) + ".jpg")); textBox1.Text = "b" + Convert.ToString(viNumPhoto) + ".jpg"; viNumPhoto++; } catch (Exception) { textBox1.Text = "На новый круг"; viNumPhoto=1; } } Вновь перенесем файлы Form1.cs и Form1.Designer.cs в директорию Programm и выполним программу Master. В созданной мастером программе мы теперь можем просматривать те фото, которые помещены в ресурс, нажимая кнопку 1. Параграф 4. О возможности передачи параметров между программами Master/SlaveРасширим поставленную задачу. Пусть нам требуется задавать и отображать имя альбома и менять фон панели программы. На данном этапе можно потребовать много других настроек, нас же интересует сам принцип выполнения передачи параметров. Передачу параметров будем выполнять, как и ранее, через коллекцию EmbeddedResources (ресурсы) с использованием XML файла. XML файл будет включать данные класса параметров настройки для Slave программы или точнее сериализованное XML представление класса настроек. Для выполнения задачи:
Если откомпилировать и выполнить программу Master, то в созданной программе Slave мы найдем ресурс progsettings.xml (Рис.9.).
Рис.9. Результат выполнения кода Осталось забрать из ресурса настройки. Для этого, в программе Slave:
Результат работы показан на Рис.10.
Рис.10. Программы Master/Slave На этом мы заканчиваем рассмотрение темы. В отношении технологии взаимодействия программ Master/Slave при динамической компиляции Slave программы, приведенный подход является универсальным. Аналогично можно создавать музыкальные и другие альбомы и любые приложения, функциональность которых требуется несколько изменить по отношению к функциональности базового шаблона. Задача будет заключаться лишь в том, как добавить в ресурс, как извлечь из ресурса данные, как преобразовать их в требуемый формат и как использовать. Литература:Молчанов Владислав 5.10.2007г. Еcли Вы пришли с поискового сервера - посетите мою главную страничкуНа главной странице Вы найдете программы комплекса Veles - программы для автолюбителей, программы из раздела графика - программы для работы с фото, сделанными цифровым фотоаппаратом, программу Bricks - игрушку для детей и взрослых, программу записную книжку, программу TellMe - говорящий Русско-Английский разговорник - программу для тех, кто собирается погостить за бугром или повысить свои знания в английском, теоретический материал по программированию в среде Borland C++ Builder, C# (Windows приложения и ASP.Net Web сайты). |