Внешние компоненты в 1С 8.2
Введение
Эта статья дает представление о работе внешних компонент в системе «1С: Предприятие».
Будет показан процесс разработки внешней компоненты для системы «1С: Предприятие» версии 8.2, работающей под управлением ОС семейства Windows с файловым вариантом работы. Такой вариант работы используется в большинстве решений, предназначенных для предприятий малого бизнеса. ВК будет реализована на языке программирования C++.
Внешние компоненты «1C: Предприятие»
Структура ВК
Внешняя компонента системы «1С: Предприятие» представлена в виде DLL-библиотеки. В коде библиотеки описывается класс-наследник IComponentBase. В создаваемом классе должны быть определены методы, отвечающие за реализацию функций внешней компоненты. Более подробно переопределяемые методы будут описаны ниже по ходу изложения материала.
Запуск демонстрационной ВК
Компиляция
Демонстрационная ВК расположена на диске подписки ИТС в каталоге «/VNCOMP82/example/NativeAPI».
Для сборки демонстрационной ВК будем использовать Microsoft Visual Studio 2008. Другие версии данного продукта не поддерживают используемый формат проекта Visual Studio. 
Открываем проект AddInNative. В настройках проекта подключаем каталог с заголовочными файлами, необходимыми для сборки проекта. По умолчанию они располагаются на диске ИТС в каталоге /VNCOMP82/include.
Результатом сборки является файл /bind/AddInNative.dll. Это и есть скомпилированная библиотека для подключения к конфигурации 1С.
Подключение ВК к конфигурации 1С
Создадим пустую конфигурацию 1С.
Ниже приведен код модуля управляемого приложения.
Если при запуске конфигурации 1С не было сообщено об ошибке, то ВК была успешно подключена.
В результате выполнения приведенного кода в глобальной видимости конфигурации появляется объект ДемоКомп, имеющий свойства и методы, которые определены в коде внешней компоненты.
Демонстрация заложенного функционала
Результат запуска конфигурации приведен на изображении
На панель «Сообщения» выведены результаты вызовов методов ДемоКомп.Выключить() и Демо.Комп.Включить(). Последующие строки на той же панели содержат результаты обработки полученных от ВК сообщений — Источник, Событие и Данные соответственно.
Произвольное имя внешней компоненты
Задача: Изменить имя внешней компоненты на произвольное.
В предыдущем разделе использовался идентификатор AddInNativeExtension, смысл которого не был пояснен. В данном случае AddInNativeExtension — это наименование расширения.
В коде ВК определен метод RegisterExtensionAs, возвращающий системе «1С: Предприятие» имя, которое необходимо для последующей регистрации ВК в системе. Рекомендуется указывать идентификатор, который в известной мере раскрывает суть внешней компоненты.
Приведем полный код метода RegisterExtensionAs с измененным наименованием расширения:
В приведенном примере имя ВК изменено на SomeName. Тогда при подключении ВК необходимо указывать новое имя:
Расширение списка свойств ВК
Полное описание методов, включая список параметров подробно описан в документации, поставляемой на диске ИТС.
Рассмотрим реализацию приведенных методов класса CAddInNative.
В демонстрационной ВК определены 2 свойства: Включен и ЕстьТаймер (IsEnabled и IsTimerPresent).
В глобальной области видимости кода библиотеки определено два массива:
которые хранят русское и английское названия свойств. В заголовочном файле AddInNative.h определяется перечисление:
Перечисление Props будет иметь вид:
Для значительного упрощения кода будем использовать STL C++. В частности, для работы со строками WCHAR, подключим библиотеку wstring.
Для сохранения значения метода Тест, определим в классе CAddInNative в области видимости private поле:
Для передачи строковых параметров между «1С: Предприятие» и внешней компонентов используется менеджер памяти «1С: Предприятие». Рассмотрим его работу подробнее. Для выделения и освобождения памяти соответственно используются функции AllocMemory и FreeMemory, определенные в файле ImemoryManager.h. При необходимости передать системе «1С: Предприятие» строковый параметр, внешняя компонента должна выделить под нее память вызовом функции AllocMemory. Ее прототип выглядит следующим образом:
где pMemory — адрес указателя, в который будет помещен адрес выделенного участка памяти,
ulCountByte — размер выделяемого участка памяти.
Пример выделения памяти под строку:
Для удобства работы с строковыми типами данными опишем функцию wstring_to_p. Она получает в качестве параметра wstring-строку. Результатом функции является заполненная структура tVariant. Код функции:
Тогда соответствующая секция case оператора switch метода GetPropVal примет вид:
Для реализации второго свойства определим поле класса CaddInNative
в котором будем сохранять тип последнего переданного значения. Для этого в метод CaddInNative::SetPropVal добавим команду:
Теперь при запросе чтения значения второго свойства будем возвращать значение last_type, чего требует обозначенное задание.
Проверим работоспособность произведенных изменений.
Для этого приведем внешний вид конфигурации 1С к виду:
В результате запуска получим последовательность сообщений:
3
Вася
Петя
22
Второе и третье сообщения являются результатом чтения свойства, установленного на предыдущем шаге. Первое и второе сообщения содержат код типа последнего установленного свойства. 3 соответствует целочисленному значению, 22 — строковому. Соответствие типов и их кодов устанавливается в файле types.h, который находится на диске ИТС.
Расширение списка методов
Отредактируем функцию GetNProps, чтобы она возвращала количество параметров метода «Тест»:
Внесем изменения в функцию CAddInNative::GetParamDefValue:
Благодаря добавленной строке
в случае отсутствия одного или нескольких аргументов соответствующие параметры будут иметь пустое значение (VTYPE_EMPTY). Если необходимо наличие значения по умолчанию для параметра, следует задать его в секции eMethTest оператора switch функции CAddInNative::GetParamDefValue.
Так как метод «Тест» может возвращать значение, необходимо внести изменения в код функции HasRetVal:
И добавим исполняемый код метода в функцию CallAsFunc:
Скомпилируем компоненту и приведем код конфигурации к виду:
После запуска конфигурации получим сообщение: «Привет, Мир!», что говорит о том, что метод отработал успешно.
Таймер
Операционная система будет посылать сообщение WM_TIMER в программу с интервалом указанным в аргументе nElapse (в миллисекундах). В последнем параметре можно указать функцию, которая будет выполняться при каждом срабатывании таймера. Заголовок этой функции должен выглядеть так (имя может быть любым):
Рассмотрим реализацию таймера в демонстрационной ВК.
Так как мы рассматриваем процесс разработки внешней компоненты для ОС семейства Windows, не будем рассматривать реализацию таймера в других операционных системах. Для ОС GNU/Linux, в частности, реализация будет отличаться синтаксисом функции SetTimer и TimerProc.
В исполняемом коде вызывается метод SetTimer, в который передается функция MyTimerProc:
Идентефикатор созданного таймера помещается в переменную m_uiTimer, чтобы в последствии его можно было отключить.
Функция MyTimerProc выглядит следующим образом:
Суть функции сводится к тому, что вызывается метод ExternalEvent, который посылает сообщение системе «1С: Предприятие».
Для расширения функционала метода СтартТаймер произведем следующие действия:
Модифицируем код метода GetNParams так, чтобы он для метода eMethStartTimer возвращал значение 1:
Приведем код метода CallAsProc к виду:
Теперь проверим работоспособность. Для этого в модуле управляемого приложения конфигурации напишем код:
После запуска конфигурации в программу будут поступать сообщения с интервалом в 2 секунды, что говорит о корректной работе таймера.
Взаимодействие с системой «1С: Предприятие»
Для взаимодействия между внешней компонентой и системой «1С: Предприятие» используются методы класса IAddInDefBase, описанного в файле AddInDefBase.h. Перечислим наиболее часто используемые:
Генерация сообщения об ошибке
wcode, scode — коды ошибки (список кодов ошибок с описанием можно найти на диске ИТС)
source — источник ошибки
descr — описание ошибки
Отправка сообщения системе «1С: Предприятие»
wszSource — источник сообщения
wszMessage — текст сообщения
wszData — передаваемые данные
Перехват сообщения осуществляется процедурой ОбработкаВнешнегоСобытия
Регистрация внешней компоненты в системе «1С: Предприятие»
wszProfileName — имя компоненты.
Этих методов достаточно для полноценного взаимодействия ВК и 1С. Для получения данных внешней компонентой от системы «1С: Предприятие» и наоборот внешняя компонента отправляет специальное сообщение, которое в свою очередь перехватывается системой «1С» и, при необходимости вызывает методы внешней компоненты для обратной передачи данных.
Тип данных tVariant
При обмене данными между внешней компонентой и системой «1С: Предприятие» используется тип данных tVariant. Он описан в файле types.h, который можно найти на диске с ИТС:
Технология внешних компонентов
Технология внешних компонентов позволяет создавать программы (внешние компоненты), которые будут динамически подключаться и тесно взаимодействовать с системой 1С:Предприятие 8, расширяя ее возможности. Данная технология позволяет подключать к системе 1С:Предприятие 8 различное торговое оборудование: сканеры штрих-кодов, принтеры этикеток и т. д.
Native API
Для создания внешних компонентов используется технология Native API — собственный интерфейс системного программирования 1С:Предприятия 8. Она поддерживает операционные системы Windows и Linux, и дает возможность создавать внешние компоненты, работающие как под одной, так и под другой операционной системой. Компоненты, созданные по технологии Native API, могут быть подключены в толстом клиенте, в тонком клиенте, в веб-клиенте, внешнем соединении и в сервере приложений.
Расширение встроенного языка
Внешние компоненты позволяют расширять встроенный язык новыми объектами. Структуры механизмов внешних компонент максимально приближены к внутренним структурам системы 1С:Предприятие 8, что повышает эффективность работы.
Вызов процедуры обработки событий, контролируемых внешней компонентой
Внешняя компонента может порождать события, обрабатываемые в предопределенной процедуре языка ОбработкаВнешнегоСобытия. Это позволяет подключать к системе 1С:Предприятие 8 сканеры и другие устройства, требующие асинхронного обмена данными.
Добавление страницы свойств в параметры «1С:Предприятие 8»
Внешние компоненты могут добавлять свои страницы свойств в диалог параметров системы 1С:Предприятие 8. Таким образом, торговое оборудование может включаться в систему и управляться стандартным для системы 1С:Предприятие 8 способом.
Сохранение параметров компоненты через механизм сохранения параметров «1С:Предприятие 8»
При сохранении своих параметров внешняя компонента может использовать механизмы системы 1С:Предприятие 8.
Доступ к строке состояния
При работе состояние внешней компоненты может отражаться в панели состояния системы 1С:Предприятие 8.
Шелл для синего экрана: Изучаем программирование на Native API на примере шелла
Содержание статьи
Если программе проверки диска требуется исправить ошибки системного раздела, который не может быть отключен во время работы Windows, после перезагрузки программа запускается до открытия окна логина, отображая белые буквы на синем экране. Это — особый режим работы Windows, в котором еще не работает подсистема Win32, зато есть полный доступ к файлам и реестру.
Загрузочный экран
В Windows XP такой режим работы выглядит как синий экран с логотипом в верхнем правом углу, в Windows 2003 цвет этого экрана серый, в Vista и Windows 7 — черный. Самое частое приложение, которое ты можешь наблюдать работающим в этом режиме, это программа проверки диска. Обычно диск проверяется консольной программой chkdsk.exe. Но в загрузочном экране стартует вовсе не оно, как можно было бы подумать, а autochk.exe — приложение, написанное на чистом Native API. Только такие программы способны запуститься до загрузки подсистемы Win32.
Неплохо было бы написать шелл, командную строку для экспериментов, чтобы побродить по системе еще до ее полной загрузки, с возможностью редактировать файлы и реестр. Я загорелся этой идеей и занялся разработкой такого шелла. Первая версия программы была написана с использованием библиотеки ZenWinX, позже я изучил исходный код шелла NCLI из проекта TinyKRNL и решил строить свой собственный шелл на его основе. От использования ZenWinX я отказался, но часть исходного кода оттуда, связанная с обработкой клавиатурных комбинаций, перекочевала в новую версию, чтобы правильнее, чем в NCLI, реализовать работу с клавиатурой. В частности, NCLI даже не поддерживал переключение регистра символов по клавише Shift. К набору команд, доступных в NCLI, добавились команды, написанные мной. Так получилась программа, которую я назвал Native Shell.
Программирование
Программы, которые могут запускаться из «синего экрана», — это native-приложения, то есть такие приложения, которым доступны только функции NT Native API, экспортируемые библиотекой ntdll.dll.
Это набор функций, выполняющихся в режиме ядра, которые можно вызывать из пользовательского режима. Библиотека экспортирует два набора функций — с одинаковыми названиями, но разными префиксами: «Zw» и «Nt». Функции с префиксом «Zw» предназначены для прямого вызова из режима ядра (например, из драйверов), а функции с префиксом Nt — из пользовательского режима (из приложений). При вызове функции с префиксом «Nt» в итоге выполняется тот же код, что и при использовании префикса Zw, но при этом перед исполнением кода параметры функции проходят дополнительную проверку, так как с точки зрения системы пользовательский режим — это ненадежный источник входных данных.
Вообще, при программировании Native-приложений следует иметь в виду, что почти никаких готовых или заданных по умолчанию возможностей в этом режиме нет, разве что вывод букв на экран реализуется сравнительно просто. Все остальные операции, будь то ввод с клавиатуры или установка текущего рабочего каталога, не говоря уж об остальном, реализуется непосредственно самой программой.
Native API непривычен и не слишком удобен для использования, но другие API недоступны. Если в программировании с использованием WinAPI для какого-то действия требуется одна-две функции, то в Native API, возможно, потребуется пять-шесть. Это и не удивительно, ведь каждый вызов WinAPI внутри реализуется как раз вызовом нескольких функций из ntdll.dll.
Настройка проекта
Для написания собственного native-приложения понадобится документация, среда разработки и заголовочные файлы Native API. Функции NT Native API частично документированы в MSDN. Однако там описано далеко не все, что имеется в ntdll, так что информацию следует по возможности искать в других источниках.
В качестве среды разработки может выступить любой редактор кода, а компиляция native-приложения будет производиться утилитой build из Windows Driver Kit, так что его придется установить себе на машину. WDK используется для разработки драйверов, но способен также компилировать native-приложения.
Проект приложения в WDK выглядит как каталог, где лежат файлы исходного кода, рядом с которыми расположен файл конфигурации проекта под именем SOURCES. Так может выглядеть простой nativeпроект:
TARGETNAME=native
TARGETTYPE=PROGRAM
UMTYPE=nt
INCLUDES=$(DDK_INC_PATH)
SOURCES=native.c
PRECOMPILED_INCLUDE=precomp.h
Сборка программы осуществляется командой build, набранной в командной строке Build Environment WDK. Перед сборкой следует скачать заголовочные файлы NDK (Native NT Toolkit), так как стандартных хидеров из WDK недостаточно, чтобы использовать все существующие функции Native API. Каталог ndk следует распаковать в папку, содержащую заголовочные файлы WDK, и прописать к ней путь в файле bin/setenv.bat (в строке «include=»).
Обработка команд
Шелл загрузочного экрана должен вести себя точно так же, как и шелл для любой другой среды, то есть воспринимать команды, набранные с клавиатуры, и выводить на экран результаты исполнения команд в текстовом виде. Чтобы шелл мог воспринимать ввод, он должен самостоятельно получить скан-коды с клавиатуры и преобразовать их в коды символов.
Для чтения с клавиатуры необходимо с помощью функции NtCreateFile открыть устройство клавиатуры как файл. Имя файла при этом будет выглядеть как «DeviceKeyboardClass0».
Параллельно нужно создать событие (объект ядра типа Event), которое будет использоваться для ожидания ввода символов.
Чтение с клавиатуры осуществляется функцией NtReadFile, которой в параметрах переданы хэндл клавиатуры и хэндл события.
IO_STATUS_BLOCK Iosb;
LARGE_INTEGER ByteOffset = 0;
NTSTATUS Status;
RtlZeroMemory(&Iosb, sizeof(Iosb));
Status = NtReadFile(hDriver, hEvent, NULL, NULL, &Iosb, Buffer, *BufferSize, &ByteOffset, NULL);
Следует проанализировать возвращаемое значение функции и при необходимости подождать наступления события с помощью
NtWaitForSingleObject.
if (Status == STATUS_PENDING)
<
Status = NtWaitForSingleObject(hEvent, TRUE, NULL);
>
NtReadFile вернет данные в виде структуры KEYBOARD_INPUT_DATA.
Эта структура имеет следующий формат:
typedef struct _KEYBOARD_INPUT_DATA
<
USHORT UnitId;
USHORT MakeCode;
USHORT Flags;
USHORT Reserved;
ULONG ExtraInformation;
> KEYBOARD_INPUT_DATA, *PKEYBOARD_INPUT_DATA;
Поле MakeCode содержит сканкод нажатой клавиши, а поле Flags — необходимую дополнительную информацию о том, были ли нажаты одновременно Shift, Ctrl или что-то еще. Шелл должен содержать таблицу, из которой по сканкоду и флагам можно выбрать конкретный символ, соответствующий определенному сочетанию клавиш. Полученный символ можно возвратить из собственного аналога стандартной функции getch. Из символов можно складывать строки, а строки — обрабатывать как команды. Среди команд, к слову, следует обязательно предусмотреть команду завершения работы шелла, чтобы Windows могла загружаться в свое обычное состояние. Работа Native-приложения завершается вызовом функции NtTerminateProcess(NtCurrentProcess(), 0);
Вывод текста на экран чрезвычайно прост, он заключается в помещении в строку типа UNICODE_STRING какого-либо текста и последующем вызове функции NtDisplayString:
UNICODE_STRING unic;
RtlInitUnicodeString(&unic, L»Hello, world!n»);
NtDisplayString(&unic);
Функция поддерживает два управляющих символа — возврат каретки «r» и перевод строки «n».
Операции с файлами
Самые элементарные операции для командной строки — это отображение текущего каталога, перемещение между ними и операции над файлами. В native-режиме при всех операциях с файлами система ничего не знает о концепции «текущего каталога». Во всех файловых функциях требуется передавать полный путь, формат которого, к тому же, отличается от привычного. Существует функция, помогающая хранить значение текущего каталога, но сцеплять это значение с именем файла нужно самостоятельно.
Функции для установки и получения текущего каталога определены так:
NTSYSAPI ULONG NTAPI RtlGetCurrentDirectory_U(
ULONG MaximumLength,
PWSTR Buffer
);
NTSYSAPI NTSTATUS NTAPI RtlSetCurrentDirectory_U(
IN PUNICODE_STRING name
);
NTSYSAPI BOOLEAN NTAPI RtlDosPathNameToNtPathName_U(
IN PCWSTR DosPathName,
OUT PUNICODE_STRING NtPathName,
OUT PCWSTR *NtFileNamePart,
OUT CURDIR *DirectoryInfo
);
Целесообразно сохранять путь в DOS-формате. Его можно показывать пользователю без изменений, если он хочет увидеть текущий каталог. При каждой файловой операции придется формировать полный NTпуть к файлу вызовом соответствующей функции или прямой склейкой пути с префиксом.
Одна из необходимых возможностей шелла — это вывод листинга каталога. Чтобы его вывести, программа должна получить список файлов и каталогов текущей директории. Прежде всего, надо открыть каталог функцией NtCreateFile с опцией FILE_LIST_DIRECTORY и указанием флага FILE_DIRECTORY_FILE. Полученный хэндл скармливается функции NtQueryDirectoryFile, которой передается константа FileBothDirectoryInformation и указатель на буфер данных типа FILE_ BOTH_DIR_INFORMATION. Структура этого типа позволяет узнать о файлах и каталогах все их важные параметры: имя, атрибуты, размер и время создания.
typedef struct _FILE_BOTH_DIR_INFORMATION
<
ULONG NextEntryOffset;
ULONG FileIndex;
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG FileAttributes;
ULONG FileNameLength;
ULONG EaSize;
CCHAR ShortNameLength;
WCHAR ShortName[12];
WCHAR FileName[1];
> FILE_BOTH_DIR_INFORMATION, *PFILE_BOTH_DIR_INFORMATION;
При вызове функции NtQueryDirectoryFile можно использовать параметр ReturnSingleEntry = TRUE, тогда за один вызов функции в буфер будет помещена только одна структура FILE_BOTH_DIR_ INFORMATION, а вызвать функцию в цикле придется столько раз, сколько файлов в каталоге. При установке того же параметра в FALSE функция будет вызвана всего один раз, а в буфере окажется массив структур. Перемещаться по нему можно, сдвигая указатель на структуру по смещению, указанному в поле NextEntryOffset. У последнего элемента массива значение этого поля будет NULL.
Функции для стандартных файловых операций, таких как чтение из файла, запись в файл и удаление файла, документированы в MSDN. Их названия NtReadFile, NtWriteFile, NtDeleteFile соответственно, а использование мало чем отличается от привычных функций WinAPI.
Чтобы скопировать файл, нужно просто прочитать его из одного места и записать копию в другом. А вот переименование файла — более комплексная операция, поэтому стоит рассмотреть ее подробнее.
Переименование файла
Существует функция NtSetInformationFile, которая может производить множество различных операций над файлом. Нас интересует операция переименования. Прототип функции выглядит так:
NTSYSCALLAPI NTSTATUS NTAPI NtSetInformationFile(
IN HANDLE FileHandle,
IN PIO_STATUS_BLOCK IoStatusBlock,
IN PVOID FileInformation,
IN ULONG Length,
IN FILE_INFORMATION_CLASS FileInformationClass
);
В параметре FileInformationClass передается константа
FileRenameInformation, означающая операцию переименования. В сочетании с этой константой функция получает в параметре FileInformation указатель на структуру FILE_RENAME_INFORMATION.
typedef struct _FILE_RENAME_INFORMATION
<
BOOLEAN ReplaceIfExists;
HANDLE RootDirectory;
ULONG FileNameLength;
WCHAR FileName[1];
> FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION;
Структура FILE_RENAME_INFORMATION имеет переменную длину, зависящую от длины нового имени файла. Нужно выделить для структуры достаточное количество памяти. Предположим, у тебя есть буфер NewFileName с новым именем файла и его размер в переменной FileNameSize.
PFILE_RENAME_INFORMATION FileRenameInfo;
FileRenameInfo = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(FILE_RENAME_INFORMATION) + FileNameSize);
После выделения памяти следует скопировать буфер NewFileName в поле структуры FileName и проинициализировать другие ее поля. Поле ReplaceIfExists определяет, заменять ли существующий файл, если его имя совпадает с новым именем файла при переименовании. В параметре RootDirectory может содержаться хэндл другой директории, в которой должен оказаться файл после перемещения.
Проще оставить это поле равным NULL, ведь для перемещения файла в другой каталог достаточно указать в поле FileName полный путь к новому расположению файла в NT-формате. Если осуществляется переименование файла, а не перемещение, в FileName должно быть только имя файла. После инициализации структуры остается только вызвать функцию для осуществления операции:
Размер буфера FileRenameInfo нельзя считать равным sizeof(FILE_ RENAME_INFORMATION), ведь в определении структуры не учтена изменчивая длина поля FileName. Поэтому в четвертом параметре Length следует передать длину структуры, к которой прибавлен размер строки FileName.
Реестр
Для операций с реестром используются документированные в MSDN функции, названия которых оканчиваются на «-Key», например, для чтения из реестра используется NtQueryValueKey.
На уровне Native API реестр выглядит немного не так, как в Win32. Вместо нескольких корневых псевдоключей HKEY_XXX используется единственный ключ «REGISTRY» с двумя подключами «USER» и « MACHINE». Эти два ключа соответствуют «HKEY_USERS» и «HKEY_ LOCAL_MACHINE». Эквивалента ключу «HKEY_CURRENT_USER» нет, ветки разных пользователей следует искать в «USER». Ключу «HKEY_ CLASSES_ROOT» соответствуют разные ветви реестра, располагающиеся как в ветке «USER», так и в «MACHINE». Еще одно отличие от WinAPI в том, что работая с реестром, мы оперируем обычным типом HANDLE, а не специальным типом HKEY.
Дэниэл Мэдден еще в 2006 году написал программу с открытым исходным кодом под названием NtRegEdit — аналог стандартного редактора реестра (regedit.exe). NtRegEdit использует для доступа к реестру только функции Native API, поэтому код из программы можно перенести в свое собственное native-приложение.
В библиотеке ZenWinX также присутствует код, использующий функции реестра. Например, функция winx_register_boot_exec_command умеет, как видно из названия, прописывать команду, выполняющуюся при запуске, то есть выполнять запись в ключ реестра BootExecute.
Библиотека ntreg корейского программиста rodream содержит набор функций для работы с реестром — достаточно просто подключить к своему проекту файлы ntreg.c и ntreg.h, и программа может в него читать и писать. В этой библиотеке отсутствует функция вывода списка ключей и значений из заданной ветки реестра, но, к счастью, ее несложно написать самостоятельно.
Чтобы узнать, какие подключи есть у какого-либо ключа, используется функция NtEnumerateKey.
NTSYSCALLAPI NTSTATUS NTAPI NtEnumerateKey(
IN HANDLE KeyHandle,
IN ULONG Index,
IN KEY_INFORMATION_CLASS KeyInformationClass,
OUT PVOID KeyInformation,
IN ULONG Length,
OUT PULONG ResultLength
);
Имена подключей будут браться из указателя на структуру KEY_ NODE_INFORMATION. В параметр KeyInformationClass записываем константу KeyNodeInformation, в параметр KeyInformation помещаем указатель на структуру. Код для получения всех подключей в итоге будет выглядеть следующим образом:
ULONG ResultLength, i = 0;
char buf[BUFFER_SIZE];
PKEY_NODE_INFORMATION pki = (PKEY_NODE_INFORMATION)buf;
while (STATUS_SUCCESS == NtEnumerateKey(hKey, i++, KeyNodeInformation, pki, BUFFER_SIZE, &ResultLength))
<
;
>
Внутри этого цикла очередное имя подключа доступно как строка WCHAR pki->Name, ее можно выводить на экран или сохранять в какой-нибудь внутренний список. Похожим образом можно получить список всех значений, содержащихся в ключе реестра, только используется другая функция NtEnumerateValueKey с константой KeyValueBasicInformation, а результат оказывается в структуре KEY_ VALUE_BASIC_INFORMATION.
pbi = (PKEY_VALUE_BASIC_INFORMATION)buf;
while (STATUS_SUCCESS == NtEnumerateValueKey(hKey, i++, KeyValueBasicInformation, pbi, BUFFER_SIZE, &ResultLength))
<
;
>
Имя находится в строке pbi->Name, а тип значения (REG_SZ, REG_DWORD или другой) определяется в pbi->Type.
Запуск процессов
Неплохо иметь в шелле возможность запускать другие процессы. Это сразу расширяет применяемость программы, ведь если программа может запускать процессы, ее функциональность уже не ограничена операциями, зашитыми в ее код. Дальнейшее расширение доступных действий в native-режиме можно осуществлять разработкой новых программ. Да и запускать native-приложения, поставляемые с операционной системой, тоже можно. В загрузочном режиме невозможен запуск Win32-приложений, так как процессы подсистемы Win32 при создании требуют уведомления CSRSS о новом процессе (а он еще неактивен). Поэтому подавляющее большинство утилит Windows запуститься не смогут, за исключением лишь немногих программ, таких как autochk.exe, autofmt.exe (аналоги Win32-утилит chkdsk.exe и format.exe для проверки и форматирования диска), srdelayed.exe (программа отложенных операций с файлами).
Чтобы запустить из native-программы другую такую же программу, используется функция RtlCreateUserProcess. Ей передаются параметры запускаемого процесса в виде структуры типа RTL_USER_PROCESS_ PARAMETERS, которая инициализируется специальной функцией RtlCreateProcessParameters. Именно в эту структуру помещают полный путь к исполняемому файлу в NT-формате, название для отображения в списке процессов и командную строку приложения. После запуска функция помещает параметры процесса в заранее приготовленный буфер RTL_USER_PROCESS_INFORMATION. Оттуда берется тред потока и передается в функцию NtResumeThread, чтобы поток начал выполняться. С этого момента новый процесс запущен.
Перед запуском процесса неплохо бы отключаться от обработки клавиатуры, то есть закрыть ее хэндл, а также хэндл обработки ее событий. Это позволит вновь запущенному приложению обрабатывать клавиатуру самостоятельно, без дублирования обработки в шелле. Восстанавливать контроль над клавиатурой можно после завершения запущенного процесса. Чтобы дождаться завершения процесса, нужно всего лишь извлечь его хэндл из поля ProcessHandle структуры RTL_USER_PROCESS_INFORMATION и передать его в функцию NtWaitForSingleObject, которая приостановит выполнение текущего процесса до завершения запущенного.
Для проверки возможности запуска процессов можно запустить autochk.exe так, чтобы запустилась проверка системного диска. Для этого следует в RtlCreateUserProcess передать следующие строки:
Native-приложения — это самый низкий уровень взаимодействия приложения с системой в пользовательском режиме. Режим native-загрузки сочетает в себе почти неограниченный доступ к потрохам системы с возможностью выполнять различные действия в интерактивном режиме. Я уверен, что освоив написание программ на чистом Native API, ты найдешь для них множество интересных применений.
Ключи запуска
Автозапуск native-приложений задается в ветке реестра HKEY_LOCAL_ MACHINECurrentControlSetControlSession Manager. Здесь есть два ключа, позволяющих запустить приложение на этапе загрузки системы. Обычно присутствует только один из них, BootExecute. Это мультистроковый параметр, содержащий строку «autocheck autochk *». После нее можно добавить свою команду запуска. Например, можно поместить native.exe в папку %systemroot% system32, а в BootExecute прописать строку «native». В результате native.exe запустится сразу после autochk.exe при запуске системы. Здесь же можно указать командную строку процесса, например «native some-command».
Второй ключ реестра, через который возможен запуск, носит название SetupExecute и полностью аналогичен BootExecute. Разница между ними в том, что запуск из этих ключей происходит на разных этапах инициализации системы. На этапе запуска из SetupExecute в системе уже создан файл подкачки и инициализированы переменные среды, а на этапе BootExecute еще нет.



