Если вам требует в JavaScript преобразовать массив из 4-х байтов в число с плавающей точкой единичной точности (float), то можно воспользоваться следующей функцией:
1 2 3 4 5 6 7 8
functionbytesToFloat(bytes) { const buffer = newArrayBuffer(4); const view = newDataView(buffer, 0 , 4); // для ClearScript нужен конструктор со всеми параметрами for (let i = 0; i < bytes.length; i += 1) { view.setUint8(i, bytes[i]); } return view.getFloat32(0); }
Обращу внимание на конструкцию вида:
1
const view = newDataView(buffer, 0 , 4);
В теории, при инициализации объекта класса DataView через конструктор должен создаваться view того же размера, что и buffer. Но на практике применения конструктора DataView в ClearScript (см. предыдущий пост с объяснениями что такое ClearScript) выяснилось, что требуется использовать перегрузку конструктора DataView с указанием всех параметров, иначе ClearScript порождает ошибку выполнения с сообщением о том, что view создаётся меньшего размера, чем требуется для buffer.
Были времена, когда обработку скриптов в .NET реализовывали проекты IronPython и IronRuby. IronRuby уже умер, IronPython ещё жив, но надо признать, что не набрал популярности несмотря на солидный возраст. Если вам требуется использовать сценарии в своём .NET приложении обратите внимание на ClearScript.
ClearScript - это библиотека, которая с лёгкостью добавит сценарии на JavaScript (через V8 и JScript) или VBScript в ваше .NET приложение. ClearScript поддерживает несколько видов движков: Google’s V8, Microsoft’s JScript и VBScript. Посредством ClearScript можно запускать JavaScript сценарии как в старом CommonJS, так и в новой ES6 стандарте, причем сценарии будут работать как в Windows, Linux, так и macOS. Количество поддержанных фишек для разных движков несколько различается, но V8 содержит их в максимальном количестве. ClearScript доступен в виде NuGet пакетов для соответствующих платформ.
Для работы с ClearScript требуется подключить следующие пространства имён:
1 2 3
using Microsoft.ClearScript; using Microsoft.ClearScript.JavaScript; using Microsoft.ClearScript.V8;
Создать и инициализировать движок скриптов (если не требуется возможность отладки, то V8ScriptEngineFlags.EnableDebugging убрать, оставив просто new() ):
Запуск вычислений в сценарии на JS приводится ниже:
1 2 3 4 5
var result = Engine.Evaluate( new DocumentInfo {Category = ModuleCategory.Standard}, $"{_jsModuleContent}{PdpFunctionName}({JsonSerializer.Serialize(initialDevice)})" ); var resultDevice = JsonSerializer.Deserialize<Device>(result.ToString());
Category = ModuleCategory.Standard означает, что текст сценария написан на ES6 (const, let, import, etc). Category = ModuleCategory.CommonJS - старый формат JS, понимающий только var и require. Из примера видно, что текстовый аргумент представляет из себя содержимое сценария и вызов JS функции с передачей ей в качестве аргумента сериализованного объекта JSON. В качестве результата возвращается экземпляр класса Object, который требуется десериализовать к нужному классу с помощью JsonSerializer.Deserialize. Таким образом, с помощью загрузки и исполнения JavaScript сценария можно динамически управлять выполняющимися инструкциями функции PdpFunctionName!
Удивительно, что можно подгружать классы .NET в движок ClearScript (в том числе даты, дженерики и LINQ) и использовать их для вычислений:
using (var engine = new V8ScriptEngine()) // создание движка { engine.AddHostType("Console", typeof(Console)); // прокинуть тип в движок engine.Execute("Console.WriteLine('{0} is an interesting number.', Math.PI)"); // 3.14159265358979 is an interesting number.
engine.AddHostObject("random", new Random()); // прокинуть объект в движок engine.Execute("Console.WriteLine(random.NextDouble())"); // 0.715555223503874
// создаем хост-объект прямо из скрипта engine.Execute(@" birthday = new lib.System.DateTime(2007, 5, 22); Console.WriteLine(birthday.ToLongDateString()); "); // Tuesday, May 22, 2007
// используем дженерик класс словаря прямо из скрипта engine.Execute(@" Dictionary = lib.System.Collections.Generic.Dictionary; dict = new Dictionary(lib.System.String, lib.System.Int32); dict.Add('foo', 123); ");
engine.AddHostObject("host", new HostFunctions()); // вызываем хост-метод с out параметром engine.Execute(@" intVar = host.newVar(lib.System.Int32); found = dict.TryGetValue('foo', intVar.out); Console.WriteLine('{0} {1}', found, intVar); "); // True 123
Разрабатывая Windows-службу, работающую с МФУ и принтерами по HTTP, я использовал класс HttpClient. Данный класс является потоко-безопасным и не требует создания многочисленных экземпляров: достаточно один раз создать HttpClient, чтобы он обсужил все требующиеся запросы (в том числе многопоточные):
1
privatestaticreadonly HttpClient Client = new HttpClient();
или если не требуется, чтобы использовались Cookies:
Данный подход избавит от проблемной утилизации (Dispose) экземпляров HttpClient и потенциального исчерпания свободных портов в системе (port exhausting), связанного с проблемной утилизацией.
Всё бы хорошо, но мною была обнаружена проблема разделяемого общего состояния эксземпляра класса HttpClient, когда мне потребовалось кроме сообщений ещё и отправлять Http Headers с аутентификацией и служебной информацией.
Оказалось, что мною используемый подход:
1 2 3 4 5 6 7
Client.DefaultRequestHeaders.Clear(); // doesn't work for concurrency Client.DefaultRequestHeaders.Add("Authorization", token); // doesn't work for concurrency var response = await Client.GetAsync(requestUri); response.EnsureSuccessStatusCode();
var result = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<UserProfile>(result);
некореектно работает в многопоточной среде, т.к. имеет разделяемое общее состояние хэдеров Http Headers.
Исследование проблемы натолкнуло меня на отличную статью с решением данной проблемы: Concurrency with HttpClient
Решение заключается в отказе от внутреннего разделяемого общего состояния HttpClient’а, а именно от Default Header. Следует дописать метод расширения, который делает то же самое, что и вышеобозначенный код, но без разделяемого общего состояния:
Простой SNMP сервер непрерывно слушает порт (161 для симулятора МФУ) и проверяет присутствие пришедшей по UDP команды OID в словаре dictionary. В качестве ключа dictionary содержит OID команду, а в качестве значения - ответ МФУ на неё. Словарь dictionary формируется при инициализации приложения из appSettings.json. При наличии ключа отправляется ответ отправителю запроса.
Обратите внимание на метод string ConvertBytesToStringOid(byte[] oidBytes), преобразующий пришедшие байты OID команды в текстовое значение к которому мы привыкли (вида 1.3.6.1.2.1.43.5.1.1.17.1). Именно оно используется для поиска в словаре dictionary.
publicstaticclassSnmpServer { publicstaticvoidStartListening(Dictionary<string, string> dictionary, int port) { while (true) { var receiver = new UdpClient(port); // UdpClient для получения данных IPEndPoint remoteIp = null; // адрес входящего подключения
try { while (true) { var data = receiver.Receive(ref remoteIp); // получаем данные int requestIdBytesLength = data[16]; var requestIdBytes = newbyte[requestIdBytesLength]; Buffer.BlockCopy(data, 17, requestIdBytes, 0, requestIdBytesLength); int inputOidLength = data[32]; var inputOidBytes = newbyte[inputOidLength]; Buffer.BlockCopy(data, 33, inputOidBytes, 0, inputOidLength); var inputOid = ConvertBytesToStringOid(inputOidBytes); if (dictionary.TryGetValue(inputOid, outvar oidValue)) { var sender = new UdpClient(remoteIp.Address.ToString(), remoteIp.Port); var oidValueBytes = Encoding.ASCII.GetBytes(oidValue); var response = GenerateOidBytesResponse( requestIdBytes, inputOidBytes, oidValueBytes ); sender.Send(response, response.Length); // отправка sender.Close(); sender.Dispose(); } } } catch (Exception) { receiver.Close(); } } }
privatestaticbyte[] GenerateOidBytesResponse( byte[] requestIdBytes, byte[] inputOidBytes, byte[] oidValueBytes ) { var result1 = new List<byte>(oidValueBytes); result1.Insert(0, (byte)oidValueBytes.Length); result1.Insert(0, 0x04); var result2 = new List<byte>(inputOidBytes); result2.AddRange(result1); result2.Insert(0, (byte)inputOidBytes.Length); result2.Insert(0, 0x06); result2.Insert(0, (byte)result2.Count); result2.Insert(0, 0x30); result2.Insert(0, (byte)result2.Count); result2.Insert(0, 0x30); Magic210(result2); Magic210(result2);
var result3 = new List<byte>(requestIdBytes); result3.AddRange(result2); result3.Insert(0, (byte)requestIdBytes.Length); result3.Insert(0, 0x02); result3.Insert(0, (byte)result3.Count); result3.Insert(0, 0xa2);
Давайте взглянем на структуру SNMP ответа get-response с помощью Wireshark:
Пакет начинается с байта 0x30 и содержит в нашем примере всего 59 байтов. Второй байт 0x39 показывает длину последующего массива отправляемых байтов (0x3916 = 5710). Пятый байт 0x00 показывает номер версии version-1, за которым следует указатель community и байт его длины 0x06 (6 байт). Значение community начинается в нашем случае с 8-го байта и идёт до 13 байта включительно (0x70 0x75 0x62 0x6C 0x69 0x63). Дальше следуют байты данных самого get-response: открывающий байт 0xA2, длина последующих байтов 0x2C (44 байта), открывающий байт идентификатора запроса 0x02 и длина request-id 0x04. Значение request-id начинается с 18 байта и идёт в нашем случае до 21 байта (0x0B 0x5E 0xA0 0x42), что составляет значение 190750786. Величина request-id соответствует величине request-id пришедшего в SNMP запросе get-request. Далее 24-м байтом идёт error-status, 27-м байтом идёт error-index, а 28-м байтом идёт открывающий байт 0x30 и длина последующих байтов 0x1E (30 байтов). 30-м байтом идёт открывающий байт 0x30 и длина последующих байтов 0x1С (28 байтов). Затем опять повторяется OID, пришедший в запросе get-request, а именно байт длины 0x0B (11 байтов) и их значение: 0x2B 0x06 0x01 0x02 0x01 0x2B 0x05 0x01 0x01 0x11 0x01, что составляет значение 1.3.6.1.2.1.43.5.1.1.17.1. Далее 45-м байтом идёт байт 0x04, что означает что далее последует значение в OctetString и 46-м байтом следует длина последующего значения 0x0D (13 байтов): 0x41 0x37 0x39 0x38 0x30 0x32 0x37 0x35 0x34 0x31 0x32 0x34 0x36, что является серийным номером МФУ А798027541246.
Давайте взглянем на структуру SNMP запроса get-request с помощью Wireshark:
Пакет начинается с байта 0x30 и содержит в нашем примере всего 46 байтов. Второй байт 0x2C показывает длину последующего массива пришедших байтов (0x2C16 = 4410). Пятый байт 0x00 показывает номер версии version-1, за которым следует указатель community и байт его длины 0x06 (6 байт). Значение community начинается в нашем случае с 8-го байта и идёт до 13 байта включительно (0x70 0x75 0x62 0x6C 0x69 0x63). Дальше следуют байты данных самого get-request: открывающий байт 0xA0, длина последующих байтов 0x1F (31 байт), открывающий байт идентификатора запроса 0x02 и длина request-id 0x04. Значение request-id начинается с 18 байта и идёт в нашем случае до 21 байта (0x0B 0x5E 0xA0 0x42), что составляет значение 190750786. Аналогичная величина request-id должна присутствовать в SNMP ответе get-response. Далее 24-м байтом идёт error-status, 27-м байтом идёт error-index, а 33-м байтом идёт длина переданного OID 0x0B (11 байтов): 0x2B 0x06 0x01 0x02 0x01 0x2B 0x05 0x01 0x01 0x11 0x01, что составляет значение 1.3.6.1.2.1.43.5.1.1.17.1. Именно на это значение OID отвечает устройство в ответ. Алгоритм того, как одиннадцать вышеперечисленных байтов превращаются в вышеуказанный OID можно найти в статье Простой SNMP сервер на C#. SNMP пакет завершается байтами 0x05 0x00.
Традиционно книга Дэвида Томаса и Эндрю Ханта “Программист-прагматик. Ваш путь к мастерству” (David Thomas, Andrew Hunt - The Pragmatic Programmer) относится к разряду MUST HAVE/READ и не спроста. Колоссальный опыт авторов с великолепным стилем изложения материала (советы, задачи и упражнения) делают данную книгу столько важной в карьере разработчика. Не могу сказать, что прочёл книгу на одном дыхании. Некоторые темы давались труднее других и требовали времени на переваривание. Могу рекомендовать данную книгу как новичкам, так и опытным программистам - все смогут найти полезное в ней по своему уровню.
Также хотел отметить, что авторы не просто сконцентрировались на коде, но и на многих аспектах жизни разработчика, включая нравственные вопросы: кто будет пользоваться данным кодом и приближает ли ваш код наш мир к тому образу будущего, который бы вы хотели видеть? (У вас же есть такой образ, правда?)
Хотел бы вкратце резюмировать советы авторов, которые раскрываются примерами в основном содержимом книги:
Заботьтесь о своем ремесле
Думайте о своей работе
У вас есть свобода выбора
Предлагайте варианты разрешения затруднений, а не оправдания и извинения
Нельзя жить с разбитыми окнами
Будьте катализатором перемен
Не забывайте об общей картине
Включайте в требования к системе вопрос о её качестве
Регулярно инвестируйте в свой багаж знаний
Критически анализируйте то, что вы читаете и слышите
Родной язык - это просто ещё один язык программирования
Важно не только то, что вы говорите, но и как вы это говорите
Создавая документацию, не фиксируйте её навечно
Удачное проектное решение легче изменить, чем неудачное
DRY - Don’t Repeat Yourself (не повторяйся)
Упрощайте повторное использование исходного кода
Исключайте взаимное влияние несвязанных компонентов системы
Окончательных решений не существует
Остерегайтесь увлекаться новомодными веяниями
Пользуйтесь методом трассирующих пуль для отыскания целей
Создавайте прототипы для обучения
Программируйте близко к предметной области
Выполняйте оценку, чтобы исключить неожиданности
Повторно уточняйте график выполнения работ по мере написания кода
Храните свои знания в виде простого текста
Используйте всю мощь командных оболочек
Стремитесь свободно владеть редактором
Всегда пользуйтесь системой контроля версий
Устраните затруднение, а не вините в нём других
Не паникуй!
Вылавливающий ошибку тест должен предшествовать её исправлению
Внимательно читайте сообщения об ошибке, каким бы отвратительным оно ни было
Системный вызов select работает нормально
Не предполагайте, а доказывайте
Изучите язык манипулирования текстом
Написать идеальную программу нельзя
Проектируйте по контракту
Пользуйтесь досрочным аварийным завершением программы
Пользуйтесь утверждениями, чтобы предотвратить невозможное
Завершайте то, что начали
Действуйте локально
Всегда предпринимайте небольшие шаги
Избегайте предсказания будущего
Развязанный код проще изменить
Указывай, а не спрашивай
Не связывайте вызовы методов в цепочку
Избегайте глобальных данных
Если данные настолько важны, чтобы быть глобальными, заключите их в оболочку API
Программирование имеет отношение к коду, а программы - к данным
Не накапливайте состояние, а передавайте его по кругу
Не платите налог на наследование
Выражать полиморфизм предпочтительнее с помощью интерфейсов
Делегируйте полномочия службам: отношение СОДЕРЖИТ превосходит отношение ЯВЛЯЕТСЯ
Пользуйтесь миксинами для совместного использования функциональных возможностей
Параметризуйте своё приложение, используя внешнюю конфигурацию
Анализируйте последовательность выполняемых действий с целью повышения параллельности
Общее состояние - неверное состояние
Случайные отказы часто вызваны осложнениями, возникающими в связи с параллельностью
Пользуйтесь акторами, чтобы достичь параллельности без общего состояния
Пользуйтесь классными досками для координации потока выполнения
Прислушивайтесь к своим внутренним чувствам
Не программируйте по совпадению
Оценивайте порядок производительности своих алгоритмов
Проверяйте свои оценки
Выполняйте рефакторинг кода как можно раньше и чаще
Тестирование предназначено не для выявления программных ошибок
Тест - первый пользователь вашего кода
Стройте комплексно, а не сверху вниз или снизу вверх
Проектируйте с учетом тестирования
Тестируйте свои программы сами, иначе их будут тестировать пользователи
Пользуйтесь тестами на основе свойств для проверки правильности ваших предположений
Не усложняйте код и минимизируйте поверхность атак
Устанавливайте обновления системы безопасности незамедлительно
Именуйте правильно; а по мере надобности переименовывайте
Никто точно не знает, чего он хочет
Программисты помогают людям понять, чего они хотят на самом деле
Требования изучаются с помощью обратной связи
Работайте с пользователем, чтобы мыслить как пользователь
Бизнес-правила - это метаданные
Пользуйтесь словарем терминов проекта
Вместо того, чтобы мыслить нешаблонно, находите шаблон
Не вникайте в исходный код в одиночку
Гибкость - это не существительное, а порядок выполнения действий
Поддерживайте небольшие, устойчивые команды
Планируйте и воплощайте в жизнь пополнение багажа знаний
Организовывайте полнофункциональные команды
Делайте то, что пригодно, а не то, что модно
Выпускайте программное обеспечение, когда оно требуется пользователям
Пользуйтесь контролем версий, чтобы проводить сборки, тесты и выпуски
Текстируйте как можно раньше, чаще и автоматически
Программирование нельзя считать завершенным до тех пор, пока не пройдут все тесты
Пользуйтесь саботажем для проверки своих тестов
Проверяйте покрытие тестами состояний, а не исходного кода
Обнаруживайте ошибки единожды
Не пользуйтесь ручными процедурами
Доставляйте пользователям не просто код, а удовольствие
Подписывайте свою работу
Прежде всего, не нанесите вред
Не потакайте всякой шушере
Это ваша жизнь. Делитесь ею, празднуйте и стройте её. И ЖЕЛАЕМ ПОЛУЧИТЬ ОТ ЭТОГО УДОВОЛЬСТВИЕ!
Некоторые советы кажутся очевидными, некоторые - интригующе-непонятными, но будучи вырванными из контекста они мало что дают для понимания без прочтения самой книги. Прочтите - не пожалеете!
Для редактирования и компиляции NSIS скриптов есть замечательная программа Venis_IX.
Для шаблонной генерации скрипта установщика в программе присутствует Venis Install Wizard:
В основном окне можно создать / открыть скрипт (расширение .nsi), отредактировать его и откомпилировать. Результат компиляции отображается в консольном окне Compile Results. В случае успеха будет показан конечный размер установщика (Total size:) в байтах, как показано на изображении ниже:
В случае ошибки в консоли будет указана причина и строка на которой ошибка случилась.
К сожалению в программе нет привычного разработчикам дебаггера, поэтому отладка скрипта, как правило, осуществляется при его выполнении через всплывающие окна MessageBox или вывод в консоль с помощью DetailPrint.
Также в программе есть великолепная и подробная справка по языку, командам и макросам NSIS (NSIS User Manual):
Встроенных функций (StrCmp, IntCmp, IfErrors, Goto …)
1 2 3 4 5 6 7 8 9
StrCmp$0'some value'0 +3 MessageBoxMB_OK'$$0 is some value' Goto done StrCmp$0'some other value'0 +3 MessageBoxMB_OK'$$0 is some other value' Goto done # else MessageBoxMB_OK'$$0 is "$0"' done:
удобной библиотеки LogicLib, превращающей логические операции встроенных функций в некое подобие высокоуровневой библиотеки
1 2 3 4 5 6 7 8 9
${If}$0 == 'some value' MessageBoxMB_OK'$$0 is some value' ${ElseIf}$0 == 'some other value' MessageBoxMB_OK'$$0 is some other value' ${Else} MessageBoxMB_OK'$$0 is "$0"' ${EndIf}
${Switch}, ${If}, ${While}, ${For} etc.
Комментариев ; # /**/
Переноса на следующую строку \
Объявления и использования переменных
1 2
Var BLA ;Declare the variable StrCpy$BLA"123";Now you can use the variable $BLA
Section "Uninstall" Delete$INSTDIR\Uninst.exe ; delete self (see explanation below why this works) Delete$INSTDIR\myApp.exe RMDir$INSTDIR DeleteRegKeyHKLM SOFTWARE\myApp SectionEnd
Плагинов Plug-in DLLs
1 2 3 4 5 6 7 8 9 10 11 12 13
SimpleSC::ExistsService"$paramServiceName" Pop$0; returns an errorcode if the service doesn?t exists (<>0)/service exists (0) ${If}$0 == 0 Call smartRemover Abort ${EndIf}
Если вам однажды потребуется изготовить установщик для Windows, то обратите внимание на NSIS (Nullsoft Scriptable Install System). NSIS - это профессиональная система с открытым исходным кодом, компактная и гибкая. У неё есть свои минусы (низкоуровневый синтаксис языка скриптов NSIS, включающий работу с регистрами, макросы и т.п.), но плюсы, как правило, их перевешивают.
К основным плюсам можно отнести:
сверх компактный размер (оверхэд самого NSIS всего 34 KB)
совместимость с основными версиями ОС: Windows 95, Windows 98, Windows ME, Windows NT, Windows 2000, Windows XP, Windows Server 2003, Windows Vista, Windows Sever 2008, Windows 7, Windows Server 2008R2, Windows 8, Windows Server 2012, Windows 8.1, Windows Server 2012R2, Windows Server 2016 и Windows 10
большое количество плагинов, позволяющих реализовать почти все задумки
безоплатность
стабильность
многоязычность (в одном установщике до 60-ти языков)
создание пользовательских диалоговых окон
создание собственных плагинов (C, C++, Delphi)
Плагины включают в себя взаимодействие с БД, реестром, папками и файлами, переменными окружения, Windows службами, IIS, командной строкой, Powershell, Win32 API вызовы, перезагрузку системы, интернет соединения, создание ярлыков, деинсталляторов и многое многое другое.
Взаимодействие с NSIS основано на скриптах (на собственном языке программирования, базовый синтаксис которого описан в посте), которые собираются в Windows установщик. NSIS до сих пор поддерживается (последний релиз NSIS 3.06.1 от 31 июля 2020), имеет большое сообщество и массу туториалов.