Как создать нативное приложение для Windows, используя Native API

Предыдущая тема Следующая тема Перейти вниз

Как создать нативное приложение для Windows, используя Native API

Сообщение  Замабувараев в Пн Апр 13, 2015 12:42 pm

Теория, мать склероза


Нативные приложения — это программы, предназначенные для выполнения на операционных системах Windows семейства NT (NT/2000/XP/2003/Vista/7), способные запускаться на раннем этапе загрузки Windows, до окна входа в систему и даже до запуска каких-либо подсистем Windows. Синий экран при загрузке Windows XP, в котором, например, происходит проверка диска и есть тот самый режим. Нативные приложения используют только Native API, они могут использовать только функции, экспортируемые из библиотеки ntdll.dll. Для них недоступны функции WinAPI.


Синий экран Windows XP — Синий экран Windows XP.


Серый экран Windows Server 2003 Серый экран Windows Server 2003


Чёрный экран Windows Vista Чёрный экран Windows Vista


Чёрный экран Windows 7 Чёрный экран Windows 7

Native приложения запускаются на экране, который возникает до появления окна входа в систему. Примером native приложения является приложение chkdsk, которое запускается перед входом в Windows, если предварительно была запущена проверка системного раздела на ошибки и отложена до перезагрузки. Приложение работает, выводя сообщения экран, а затем происходит обычный запуск Windows.

Что нужно знать?

В PE-заголовке EXE-файла есть специальное поле, означающее подсистему, в которой выполняется приложение. У native приложений в это поле установлено специальное значение, означающее, что EXE не требует подсистемы. У обычных приложений ставится значение, соответствующее подсистемам "Windows GUI" или "Windows console". Native приложения не запускаются в обычном режиме работы Windows. При попытке запустить программу Windows выдаёт сообщение "Приложение нельзя запустить в режиме Win32".



Нативные приложения используют Native API. Оно частично документировано в MSDN для использования при написании драйверов. Но документированы не все функции. Информацию по остальным нужно брать из неофициальных источников. Например, на сайте http://undocumented.ntinternals.net/ или из исходных кодов операционной системы ReactOS.

Функции в ntdll.dll имеют префиксы Zw и Nt, а также некоторые другие. Видно, что у Zw и Nt функции дублируются названия. На самом деле это одни и те же функции. Если искать в сети пример использования какой-либо функции, стоит поискать сначала с одним префиксом, потом с другим, иначе можно что-то упустить. Почему у них разные префиксы - отдельная история, для программирования native приложений существенной роли не играет.

Практика, мать шизофрении


Придётся забыть про всю библиотеку времени выполнения, так как она внутри себя использует WinAPI. Потому что на момент исполнения нативного приложения в системе запущен только один процесс — smss.exe, ни один файл ещё не открыт и библиотеки WinAPI, такие как kernel32.dll, user32.dll, не загружены. Можно использовать только библиотеку ntdll.dll.
Не будет работать динамическая память, включая динамические массивы, Allocate, New и прочее — это всё работает на куче и использует WinAPI.
Не будут работать встроенные файловые операции.
Строки могут быть представлены исключительно как WString фиксированного размера.
И прочее, что основано на библиотеке времени выполнения.

Наша программа будет очень проста. Она будет запускаться, выводить на экран строку «Hello, World!» и завершаться.
Начну с исходных кодов заголовочного файла "native.bi".

Код:


' Файл native.bi

#ifndef unicode
#define unicode
#endif
#include once "windows.bi"

Type _LSA_UNICODE_STRING
  Dim Length As USHORT
  Dim MaximumLength As USHORT
  Dim Buffer As PWSTR
End Type

Type LSA_UNICODE_STRING As _LSA_UNICODE_STRING
Type PLSA_UNICODE_STRING As _LSA_UNICODE_STRING Ptr
Type UNICODE_STRING As _LSA_UNICODE_STRING
Type PUNICODE_STRING As _LSA_UNICODE_STRING Ptr

Declare Sub RtlInitUnicodeString Alias "RtlInitUnicodeString"(ByRef s As UNICODE_STRING, ByRef s2 As WString)
Declare Sub NtDisplayString Alias "NtDisplayString"(ByRef s As UNICODE_STRING)


Здесь объявлены две подпрограммы. Первая подпрограмма RtlInitUnicodeString преобразует юникодную строку в тип UNICODE_STRING. Вторая NtDisplayString отображает строку UNICODE_STRING на экране (аналог функции WriteConsole из WinAPI). Обязательно сохраняем файлы в юникоде, для этого замечательно подходит кодировка UTF-8 с меткой BOM. (Не спрашивайте, зачем столько разных переименовай _LSA_UNICODE_STRING в разные типы, так решила сама корпорация Microsoft.)

Теперь сам файл "native.bas"

Код:


' Файл native.bas
#include once "native.bi"

Const HelloWorld = "Hello, world!"
Common Shared sHello As UNICODE_STRING

Public Sub Main Alias "Main"()
    RtlInitUnicodeString(sHello, HelloWorld)
    NtDisplayString(sHello)
End Sub


Так как мы отказались от runtime, то нам придётся самостоятельно компилировать ассемблерный код в объектные файлы и собирать их. Поэтому здесь создана собственная точка входа в программу, которую мы назвали как Main. Потом мы укажем её компоновщику.

Для оптимизации ассемблерного кода и выкидывания оттуда лишнего создадим несложный VB‐скрипт "RemoveLines.vbs". Он принимает в качестве параметра имя файла с ассемблерным листингом и удаляет оттуда ненужные строки.

Код:


Option Explicit
' Прочитать файл построчно. Если встретим строку балигн 16, то будем настороже
' Читаем следующую строку, если она попадает в список запрещённых слов
' то ставим флаг, чтобы строку в файл не писать
' далее не пишем строки в файл, пока не встретится ret что?нибудь
Dim objFSO
Dim strFileName
Dim objArgs
Set objFSO = CreateObject("Scripting.FileSystemObject")
' Получить параметр программы
Set objArgs = WScript.Arguments
For Each strFileName In objArgs
    REM WScript.Echo strFileName
    ' Открыть файл на чтение, прочитать до конца, закрыть
    Dim strFileNameWithoutExt
    strFileNameWithoutExt = LCase(objFSO.GetBaseName(strFileName))
    Dim objTS
    Set objTS = objFSO.OpenTextFile(strFileName)
    Dim strLines
    strLines = objTS.ReadAll
    REM WScript.Echo strLines
    objTS.Close
    Set objTS = Nothing
    ' Открыть снова на запись
    Set objTS = objFSO.CreateTextFile(strFileName)
    ' Разбить строку на массив
    ' Пройтись по массиву
    Dim blnSkipLines
    blnSkipLines = False
    Dim astrLines
    astrLines = Split(strLines, vbCrLf)
    Dim i
    For i = 0 To UBound(astrLines)
        Select Case astrLines(i)
            Case ".balign 16"
                ' Начало, нужно быть готовым
                Select Case astrLines(i + 1)
                    Case "_GetCurrentFiber:"
                        ' Ставим флаг, что строку нужно пропустить
                        blnSkipLines = True
                    Case "_InterlockedCompareExchange64@20:"
                        ' Ставим флаг, что строку нужно пропустить
                        blnSkipLines = True
                    Case "_IN6_IS_ADDR_UNSPECIFIED:"
                        ' Ставим флаг, что строку нужно пропустить
                        blnSkipLines = True
                    Case "_IN6_IS_ADDR_LOOPBACK:"
                        ' Ставим флаг, что строку нужно пропустить
                        blnSkipLines = True
                    Case "_IN6_IS_ADDR_MULTICAST:"
                        ' Ставим флаг, что строку нужно пропустить
                        blnSkipLines = True
                    Case "_IN6_SET_ADDR_UNSPECIFIED:"
                        ' Ставим флаг, что строку нужно пропустить
                        blnSkipLines = True
                    Case "_IN6_SET_ADDR_LOOPBACK:"
                        ' Ставим флаг, что строку нужно пропустить
                        blnSkipLines = True
                    Case "_fb_ctor__" & strFileNameWithoutExt & ":"
                        ' Ставим флаг, что строку нужно пропустить
                        blnSkipLines = True
                    Case Else
                        ' Записываем
                        objTS.WriteLine(astrLines(i))
                End Select
            Case ".section .ctors"
                If astrLines(i + 1) = ".int _fb_ctor__" & strFileNameWithoutExt Then
                    i = i + 2
                End If
            Case "ret", "ret 20"
                If blnSkipLines Then
                    '  Снимаем флаг и пропускаем эту строку
                    blnSkipLines = False
                Else
                    objTS.WriteLine(astrLines(i))
                End If
            Case Else
                ' Пишем строку, только если разрешено
                If Not blnSkipLines Then
                    objTS.WriteLine(astrLines(i))
                End If
        End Select
    Next
    ' Закрыть файл
    objTS.Close
    Set objTS = Nothing
Next
Set objArgs = Nothing
Set objFSO = Nothing


Все необходимые файлы готовы, осталось создать командный файл сборки проекта make.cmd:

Код:


@echo off

"%ProgramFiles%\FreeBASIC\fbc.exe" -r -lib native.bas
cscript //Nologo RemoveLines.vbs "native.asm"
"%ProgramFiles%\FreeBASIC\bin\win32\as.exe" --32 --strip-local-absolute "native.asm" -o "native.o"
"%ProgramFiles%\FreeBASIC\bin\win32\ld.exe" -m i386pe -e _Main@0 -subsystem native -s --stack 1048576,1048576 -L "%programfiles%\freebasic\lib\win32" -L "./" "native.o" -o "native.exe" -( -lntdll -)


Здесь сначала мы транслируем исходный код бейсика в ассемблерный листинг, затем пропускает этот листинг через скрипт, удаляющий лишний код, создаём объектный файл и компилируем этот объектный файл в exe. В качестве подсистемы указываем native (параметр -subsystem native) и добавляем зависимость только от библиотеки ntdll.dll (параметр -lntdll).

Чтобы native приложение запустилось при запуске Windows, надо положить его в каталог system32, а в ключ реестра HKLM\System\CurrentControlSet\Control\Session Manager\BootExecute прописать его имя файла, и аргументы, если они есть. Ключ имеет тип MULTI_SZ, может содержать несколько строк. Первой строкой там идёт Autocheck Autochk *. После неё можно прописывать свою программу. Программа, прописанная в этом ключе, имеет свойство запускаться даже в безопасном режиме Windows (safe mode), так что нужно быть осторожным. Ошибка в программе — и система не запустится. Но можно внутри приложения отслеживать факт запуска в safe mode и обрабатывать этот режим отдельно, например сделать завершение программы, если она обнаружила себя запущенной в safe mode. Однако несмотря на то, что программа запускается и может выполнять какие-то действия, в этом режиме не работает вывод на консоль. Невозможно взаимодействие с пользователем. Это следует учитывать.

Работа программы тестировалась на операционной системе ReactOS.



PS. Пример работает на 32‐битной операционной системе.


Последний раз редактировалось: zamabuvaraeu (Пн Апр 13, 2015 12:43 pm), всего редактировалось 1 раз(а) (Обоснование : Опечатки)
avatar
Замабувараев

Сообщения : 99
Дата регистрации : 2008-08-20
Возраст : 33
Откуда : Красноярск

Посмотреть профиль http://www.freebasic.su

Вернуться к началу Перейти вниз

Re: Как создать нативное приложение для Windows, используя Native API

Сообщение  trew в Пн Апр 13, 2015 4:07 pm

Почитал, спасибо, очень познавательно.

trew

Сообщения : 331
Дата регистрации : 2010-10-14

Посмотреть профиль

Вернуться к началу Перейти вниз

Предыдущая тема Следующая тема Вернуться к началу


 
Права доступа к этому форуму:
Вы не можете отвечать на сообщения