» Разработка → Кроссплатформенное использование классов .Net из неуправляемого кода. Или аналог IDispatch на Linux
» Разработка → Кроссплатформенное использование классов .Net в 1С через Native ВК. Или замена COM на Linux
» Кроссплатформенное использование классов .Net в 1С через Native ВК. Или замена COM на Linux II
» Асинхронное программирование в 1С через .Net Native ВК
1С, Linux, Excel, Word, OpenXML,ADO и Net Core
На данный момент в .Net Core 2 варианта динамической компиляции
1. Это аналог CodeDom Microsoft.CodeAnalysis.CSharp.CSharpCompilation
2. Roslyn Scripting Api. Примеры здесь
var compilation = Microsoft.CodeAnalysis.CSharp.CSharpCompilation.Create("a") .WithOptions(new Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions(Microsoft.CodeAnalysis.OutputKind.DynamicallyLinkedLibrary)) .AddReferences( Microsoft.CodeAnalysis.MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location)) .AddSyntaxTrees(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.ParseText( @" using System; public class C { public C(){} public string M() { return ""Hello Roslyn.""; } }")); var fileName = @"d:\NetStandart\TestCoreNetApp\src\TestCoreNetApp\bin\Debug\netcoreapp1.0\a.dll"; compilation.Emit(fileName); var a = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(fileName); Type тип = a.GetType("C"); var obj = Activator.CreateInstance(тип); var res = тип.GetMethod("M").Invoke(obj, null); Console.WriteLine(res.ToString());
Этот подход хорош когда нужно обновить библиотеку. Но при этом создается DLL со всеми вытекающими.
Второй способ мне нравится больше. Возьмем на примере получения делегата.
string words = "надо заменить все первые буквы в словах на заглавные"; string pattern = @"\w+"; var scr = Microsoft.CodeAnalysis.Scripting.ScriptOptions.Default; var mscorlib = Assembly.Load(System.Runtime.Loader.AssemblyLoadContext.GetAssemblyName(@"c:\Users\Smirnov_SA\.nuget\packages\Microsoft.NETCore.Portable.Compatibility\1.0.1\ref\netcore50\mscorlib.dll")); scr =scr.WithReferences(mscorlib, typeof(MulticastDelegate).GetTypeInfo().Assembly, typeof(System.Runtime.CompilerServices.IStrongBox).GetTypeInfo().Assembly, typeof(MatchEvaluator).GetTypeInfo().Assembly, typeof(Regex).GetTypeInfo().Assembly) .WithImports("System", "System.Text.RegularExpressions"); string КодДелегата = @"return (MatchEvaluator)((match) => { string x = match.Value; // If the first char is lower case... if (char.IsLower(x[0])) { // Capitalize it. return char.ToUpper(x[0]) + x.Substring(1, x.Length - 1); } return x; });"; var result = Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript.EvaluateAsync(КодДелегата, scr).Result; MatchEvaluator evaluator = (MatchEvaluator)result; Console.WriteLine(Regex.Replace(words, pattern, evaluator));
Обязательно нужно указать ссылки на:
mscorlib.dll
System.Private.CoreLib.ni.dll
System.Runtime.dll
Для компиляции используются следующие библиотеки:
«Microsoft.CodeAnalysis.CSharp»: «2.0.0-beta3»,
«Microsoft.CodeAnalysis.CSharp.Scripting»: «2.0.0-beta3»,
«Microsoft.CodeAnalysis.Scripting.Common»: «2.0.0-beta3»,
Microsoft.CodeAnalysis.Scripting
По динамической компиляции. В свое время работал с запчастями для автомобилей. А там куча поставщиков и клиентов. При этом прайсы на миллионы позиций. И каждый горазд давать данные в своем формате. Было проще для каждого клиента создавать код записывать в справочник и использовать через Выполнить или Вычислить. Правда при работе с миллионными прайсами этот подход тормозил.
Так или иначе приходилось писать DLL и работать через COM.
C помощью динамической компиляции можно хранить текст кода и применять его в зависимости от условий. В том числе динамически формировать по условиям, а скомпилированный делегат можно кэшировать для повторного использования.
Скорость компиляции достаточно высокая (кроме первого раза секунд 5).
Перейдем к 1С. Так воторй алгоритм на 1С выглядит так
стр = "return (MatchEvaluator)((match) => |{ | string x = match.Value; |// If the first char is lower case... |if (char.IsLower(x[0])) |{ |// Capitalize it. |return char.ToUpper(x[0]) + x.Substring(1, x.Length - 1); |} |return x; | |});"; текст = "надо заменить все первые буквы в словах на заглавные"; Образец = "\w+"; // MatchEvaluator evaluator = (MatchEvaluator)ПолучитьДелегат(); ScriptOptions=ъТип("Microsoft.CodeAnalysis.Scripting.ScriptOptions","Microsoft.CodeAnalysis.Scripting"); CSharpScript=ъТип("Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript","Microsoft.CodeAnalysis.CSharp.Scripting"); scr = ъ(ScriptOptions.Default); mscorlibСборка = ъ(Врап.Сборка("mscorlib.dll",Истина)); Private_CoreLibСборка=ъ(Врап.Сборка("System.Private.CoreLib.ni",Истина)); System_RuntimeСборка=ъ(Врап.Сборка("System.Runtime",Истина)); RegularExpressionsСборка=ъ(Врап.Сборка("System.Text.RegularExpressions",Истина)); Regex=ъ(RegularExpressionsСборка.GetType("System.Text.RegularExpressions.Regex")); scr =ъ(scr.WithReferences(mscorlibСборка.ПолучитьСсылку(), Private_CoreLibСборка.ПолучитьСсылку(), System_RuntimeСборка.ПолучитьСсылку(), RegularExpressionsСборка.ПолучитьСсылку())); scr =ъ(scr.WithImports("System", "System.Text.RegularExpressions")); evaluator = ъ(ъ(CSharpScript.EvaluateAsync(стр, scr.ПолучитьСсылку())).Result); Сообщить(Regex.Replace(текст, Образец, evaluator.ПолучитьСсылку()));
В общем ничего особенного. Получили сборки, дали ссылки на них скомпилировали, вызвали.
Перейдем к более сложным примерам. Так в предыдущей статье я показывал примеры использования DocumentFormat.OpenXml на примере чтения Excel и Word.
Там была проблема в скорости из-за приведения строки к объекту через функцию ъ и сама скорость вызова из 1С в 5 раз медленнее её родного кода.
Поэтому вынесем большую часть кода в .Net. Аналогичный вариант есть на большом .Net
.Net в 1С. На примере использования HTTPClient,AngleSharp. Удобный парсинг сайтов с помощью библиотеки AngleSharp, в том числе с авторизацией аля JQuery с использованием CSS селекторов. Динамическая компиляция (пример в конце статьи).
Создадим класс и скопируем его в макет. Суть класса прочитать данные ячеек и сгруппировать их по номеру строки.
public class ДанныеЯчейки { public string ИмяЯчейки; public string ИмяКолонки; public int НомСтроки; public string ЗначениеЯчейки; } public class ExcelReader { static Regex ШаблонКолонки = new Regex("[A-Za-z]+"); OpenXmlElementList ОбщиеСтроки; void ЗаписатьДанныеАдреса(string адрес, ДанныеЯчейки данныеЯчейки) { данныеЯчейки.ИмяЯчейки = адрес; var match = ШаблонКолонки.Match(адрес); var ИмяКолонки = match.Value; var НомерСтроки = int.Parse(адрес.Substring(ИмяКолонки.Length)); данныеЯчейки.ИмяКолонки = ИмяКолонки; данныеЯчейки.НомСтроки = НомерСтроки; } void ДобавитьДанныеОбЯчейке(List<ДанныеЯчейки> ДанныеЭселя, Cell cell) { var адрес = cell.CellReference.InnerText; var text = cell.CellValue?.Text; var DataType = cell.DataType; string res = text; if (DataType != null && DataType == CellValues.SharedString) { int ssid = int.Parse(text); res = ОбщиеСтроки[ssid].InnerText; } if (res == null) return; var result = new ДанныеЯчейки(); ЗаписатьДанныеАдреса(адрес, result); result.ЗначениеЯчейки = res; ДанныеЭселя.Add(result); } public List<ДанныеЯчейки> ReadExcel(string fileName) { List<ДанныеЯчейки> ДанныеЭселя = new List<ДанныеЯчейки>(); using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { using (SpreadsheetDocument doc = SpreadsheetDocument.Open(fs, false)) { var workbookPart = doc.WorkbookPart; // Строки хранятся отдельно // В ячейках хранится индекс var pt = workbookPart.GetPartsOfType<SharedStringTablePart>(); var sstpart = pt.First(); var sst = sstpart.SharedStringTable; ОбщиеСтроки = sst.ChildElements; var workbook = workbookPart.Workbook; // Получим список страниц var sheet = workbook.Descendants<Sheet>().First(); var worksheetPart = (DocumentFormat.OpenXml.Packaging.WorksheetPart)workbookPart.GetPartById(sheet.Id); var worksheet = worksheetPart.Worksheet; var cells = worksheet.Descendants<Cell>(); // One way: go through each cell in the sheet foreach (Cell cell in cells) { ДобавитьДанныеОбЯчейке(ДанныеЭселя, cell); } } } return ДанныеЭселя; } static string НайтиИмяПоследнейКолонки(List<ДанныеЯчейки> Тз) { var рез = ""; var ДлинаРез = 0; foreach (var стрТз in Тз) { var Стр = стрТз.ИмяКолонки; var СтрДл = Стр.Length; if (СтрДл > ДлинаРез) { ДлинаРез = СтрДл; рез = Стр; } else if (СтрДл == ДлинаРез && string.Compare(Стр, рез, true) > 0) рез = Стр; } return рез; } public static object ПолучитьДанныеЭкселя(string fileName) { var res = new ExcelReader(); var Данные = res.ReadExcel(fileName); var данныеЭкселя = Данные.GroupBy(ё => ё.НомСтроки).Select(ъ => new { НомСтроки = ъ.Key, Ячейки = ъ.ToArray() }).OrderBy(ь => ь.НомСтроки); var ИмяПоследнейКолонки = НайтиИмяПоследнейКолонки(Данные); return new { ДанныеЁкселя = данныеЭкселя.ToList(), ИмяПоследнейКолонки = ИмяПоследнейКолонки }; } } return new Func<string, object>(ExcelReader.ПолучитьДанныеЭкселя);
Мы описали класс чтения и возвращаем ссылку на делегат принимающий путь к файлу и возвращающий анонимный класс. Все равно в 1С мы будем работать с ним через рефлексию. Обратите внимание, что здесь описаны 2 класса.
Теперь вызовем этот код из 1С.
ScriptOptions=ъТип("Microsoft.CodeAnalysis.Scripting.ScriptOptions","Microsoft.CodeAnalysis.Scripting"); CSharpScript=ъТип("Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript","Microsoft.CodeAnalysis.CSharp.Scripting"); scr = ъ(ScriptOptions.Default); mscorlibСборка = ъ(Врап.Сборка("mscorlib.dll",Истина)); Private_CoreLibСборка=ъ(Врап.Сборка("System.Private.CoreLib.ni",Истина)); System_RuntimeСборка=ъ(Врап.Сборка("System.Runtime",Истина)); RegularExpressionsСборка=ъ(Врап.Сборка("System.Text.RegularExpressions",Истина)); OpenXmlСбока=ъ(Врап.Сборка("DocumentFormat.OpenXml.dll")); LinqСбока=ъ(Врап.Сборка("System.Linq", истина)); FileSystemСбока=ъ(Врап.Сборка("System.IO.FileSystem", истина)); Regex=ъ(RegularExpressionsСборка.GetType("System.Text.RegularExpressions.Regex")); scr =ъ(scr.WithReferences(mscorlibСборка.ПолучитьСсылку(), Private_CoreLibСборка.ПолучитьСсылку(), System_RuntimeСборка.ПолучитьСсылку(), RegularExpressionsСборка.ПолучитьСсылку(),OpenXmlСбока.ПолучитьСсылку(),LinqСбока.ПолучитьСсылку(),FileSystemСбока.ПолучитьСсылку())); scr =ъ(scr.WithImports("System", "System.Collections.Generic", "System.Linq", "System.IO", "DocumentFormat.OpenXml", "DocumentFormat.OpenXml.Packaging", "DocumentFormat.OpenXml.Spreadsheet", "System.Text.RegularExpressions")); Текст=ПолучитьМакет("ТекстКлассаЧтенияЕксель").ПолучитьТекст(); Делегат = ъ(ъ(CSharpScript.EvaluateAsync(Текст, scr.ПолучитьСсылку())).Result); Данные= ъ(Делегат.DynamicInvoke(ИмяФайла)); Сообщить(Данные.ИмяПоследнейКолонки); рез=новый ТаблицаЗначений; ПоследняяКолонка=Данные.ИмяПоследнейКолонки; СоздатьКолонки(рез.Колонки,ПоследняяКолонка); Колонки=рез.Колонки; Тз=ъ(Данные.ДанныеЁкселя); Тз=ъ(Врап.ПолучитьЭнумератор(Тз.ПолучитьСсылку())); сч=1; // Получили сгруппированные данные по строкам Пока Тз.MoveNext() Цикл стрТз= ъ(Тз.Current); НомСтроки=стрТз.НомСтроки; Пока сч<НомСтроки Цикл сч=сч+1; рез.Добавить(); КонецЦикла; сч=сч+1; стр=рез.Добавить(); // получим ячейки по строке ТзГр=ъ(стрТз.Ячейки); ТзГр=ъ(Врап.ПолучитьЭнумератор(ТзГр.ПолучитьСсылку())); Пока ТзГр.MoveNext() Цикл стрТзГр= ъ(ТзГр.Current); ИмяКолонки=стрТзГр.ИмяКолонки; ЗначениеЯчейки=стрТзГр.ЗначениеЯчейки; // Можно конечно получить индекс зная смещение символа 64 относительно 1 и 26 разрядную систему // но найдем колонку по имени и её индекс Колонка=Колонки.Найти(ИмяКолонки); стр.Установить(Колонки.Индекс(Колонка),ЗначениеЯчейки); КонецЦикла; КонецЦикла;
Теперь скорость обработки Экселя значительно увеличилась, а затраты на компиляцию соизмеримы с затратами на чтения файлов.
Посмотрим процесс чтения Word. Не мудрствуя лукаво я взял готовый класс здесь. Тем более там все на англицком.
// Исходники можно скачать здесь //https://code.msdn.microsoft.com/office/CSOpenXmlGetPlainText-554918c3/sourcecode?fileId=71592&pathId=851860130 public class GetWordPlainText : IDisposable { // Specify whether the instance is disposed. private bool disposed = false; // The word package private WordprocessingDocument package = null; /// <summary> /// Get the file name /// </summary> private string FileName = string.Empty; /// <summary> /// Initialize the WordPlainTextManager instance /// </summary> /// <param name="filepath"></param> public GetWordPlainText(string filepath) { this.FileName = filepath; if (string.IsNullOrEmpty(filepath) || !File.Exists(filepath)) { throw new Exception("The file is invalid. Please select an existing file again"); } this.package = WordprocessingDocument.Open(filepath, true); } /// <summary> /// Read Word Document /// </summary> /// <returns>Plain Text in document </returns> public string ReadWordDocument() { StringBuilder sb = new StringBuilder(); OpenXmlElement element = package.MainDocumentPart.Document.Body; if (element == null) { return string.Empty; } sb.Append(GetPlainText(element)); return sb.ToString(); } /// <summary> /// Read Plain Text in all XmlElements of word document /// </summary> /// <param name="element">XmlElement in document</param> /// <returns>Plain Text in XmlElement</returns> public string GetPlainText(OpenXmlElement element) { StringBuilder PlainTextInWord = new StringBuilder(); foreach (OpenXmlElement section in element.Elements()) { switch (section.LocalName) { // Text case "t": PlainTextInWord.Append(section.InnerText); break; case "cr": // Carriage return case "br": // Page break PlainTextInWord.Append(Environment.NewLine); break; // Tab case "tab": PlainTextInWord.Append("\t"); break; // Paragraph case "p": PlainTextInWord.Append(GetPlainText(section)); PlainTextInWord.AppendLine(Environment.NewLine); break; default: PlainTextInWord.Append(GetPlainText(section)); break; } } return PlainTextInWord.ToString(); } #region IDisposable interface public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { // Protect from being called multiple times. if (disposed) { return; } if (disposing) { // Clean up all managed resources. if (this.package != null) { this.package.Dispose(); } } disposed = true; } #endregion public static string GetText(string FileName) { using (var pt = new GetWordPlainText(FileName)) { return pt.ReadWordDocument(); } } } return new Func<string,string>(GetWordPlainText.GetText);
Но вернемся к Руслишу:
ScriptOptions=ъТип("Microsoft.CodeAnalysis.Scripting.ScriptOptions","Microsoft.CodeAnalysis.Scripting"); CSharpScript=ъТип("Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript","Microsoft.CodeAnalysis.CSharp.Scripting"); scr = ъ(ScriptOptions.Default); mscorlibСборка = ъ(Врап.Сборка("mscorlib.dll",Истина)); Private_CoreLibСборка=ъ(Врап.Сборка("System.Private.CoreLib.ni",Истина)); System_RuntimeСборка=ъ(Врап.Сборка("System.Runtime",Истина)); RegularExpressionsСборка=ъ(Врап.Сборка("System.Text.RegularExpressions",Истина)); OpenXmlСбока=ъ(Врап.Сборка("DocumentFormat.OpenXml.dll")); LinqСбока=ъ(Врап.Сборка("System.Linq", истина)); FileSystemСбока=ъ(Врап.Сборка("System.IO.FileSystem", истина)); Regex=ъ(RegularExpressionsСборка.GetType("System.Text.RegularExpressions.Regex")); scr =ъ(scr.WithReferences(mscorlibСборка.ПолучитьСсылку(), Private_CoreLibСборка.ПолучитьСсылку(), System_RuntimeСборка.ПолучитьСсылку(),OpenXmlСбока.ПолучитьСсылку(),FileSystemСбока.ПолучитьСсылку())); scr =ъ(scr.WithImports("System", "System.Text", "System.IO", "DocumentFormat.OpenXml", "DocumentFormat.OpenXml.Packaging")); Текст=ПолучитьМакет("ТекстЧтенияВорд").ПолучитьТекст(); Делегат = ъ(ъ(CSharpScript.EvaluateAsync(Текст, scr.ПолучитьСсылку())).Result); стр = Делегат.DynamicInvoke(ИмяФайла); Текст=Новый ТекстовыйДокумент; Текст.ДобавитьСтроку(стр); Текст.Показать();
Основная задача указать ссылки на используемые сборки и пространство имен.
В следующей статье я сделаю динамическое создание обертки над объектами использующих события по аналогии с
.NET(C#) для 1С. Динамическая компиляция класса обертки для использования .Net событий в 1С через ДобавитьОбработчик или ОбработкаВнешнегоСобытия

