Взаимодействие с WinAPI

Последнее обновление: 05.08.2023

Для обращения к некоторым возможностям операционной системы Windows предоставляет некоторый функционал в виде функций WinAPI, которые можно вызывать в том числе и из программы на ассемблере ARM64. Для работы с WinAPI требуется подключить в программу одну и из библиотек Windows, которые содержать необходимые функции.

Вывод на консоль

Так, за работу с файлами отвечает библиотека kernel32.lib. Рассмотрим на примере записи в файл, а точнее на его частном случае - выводе на консоль. Для записи в файл применяется функция WriteFile, которая имеет следующее определение на языке C++:

BOOL WriteFile(
  [in]                HANDLE       hFile,
  [in]                LPCVOID      lpBuffer,
  [in]                DWORD        nNumberOfBytesToWrite,
  [out, optional]     LPDWORD      lpNumberOfBytesWritten,
  [in, out, optional] LPOVERLAPPED lpOverlapped
);

WriteFile имеет следующие параметры:

  • hFile: дескриптор файла, передается через регистр X0

  • lpBuffer: адрес буфера для записи, передается через регистр X1

  • nNumberOfBytesToWrite: размер буфера, передается через регистр Х2

  • lpNumberOfBytesWritten: адрес переменной типа dword для получения количества байтов, записанных в файл. Равно размеру буфера, если операция записи прошла успешно. Передается через регистр Х3

  • lpOverlapped: обычно имеет значение NULL (0), передается через регистр Х4

После выполнения функция WriteFile возвращает ненулевое значение, если запись прошла успешно, и ноль, если произошла ошибка.

Но чтобы записать данные в файл, нам необходим дескриптор этого файла. В зависимости от того, куда мы хотим записать, отличается способ получения дескриптора. В данном же случае мы будем выводить данные на консоль, и для этого нам потребуется встроенная функция GetStdHandle

HANDLE WINAPI GetStdHandle(
  _In_ DWORD nStdHandle
);

Она принимает один параметр - число, которое указывает, для какого устройства надо получить дескриптор. Этому параметру можно передать следующие значения:

  • -10: устройство стандартного ввода (для ввода с консоли)

  • -11: устройство стандартного вывода (для вывода на консоль)

  • -12: для вывода ошибок на консоль

В качестве результата функция возвращает дескриптор консоли.

Используем функцию WriteFile для записи (вывода) данных на консоль, а точнее в стандартный поток вывода:

    global __main
    import WriteFile        ; импортируем функцию WriteFile
    import GetStdHandle     ; импортируем функцию GetStdHandle
    area main, CODE
__main
    stp fp, lr, [sp, #-16]! ; Сохраняем регистры FR и LR в стек

    mov x0, #-11  ; Аргумент для GetStdHandle - STD_OUTPUT
    bl GetStdHandle ; вызываем функцию GetStdHandle

    ; После вызова GetStdHandle Х0 хранит дескриптор консоли
    ; Устанавливаем остальные параметры
    
    ldr x1, =message        ; Второй параметр - строка для вывода
    mov x2, len             ; Третий параметр - длина строки
    ldr x3, =bytesWritten   ; Четвертый параметр - количество записанных байтов
    mov x4, #0              ; Пятый параметр

    bl WriteFile            ; вызываем функцию WriteFile

    ldp fp, lr, [sp], #16    ; Сохраняем регистры FR и LR в стек
    ret 
    area DATA         
message dcb "Hello METANIT.COM!\n"  ; строка для вывода
len equ 19      ; размер строки
bytesWritten dcd 16  ; для получения количество записанных байтов
    end

Для использования функций их надо импортировать с помощью директивы import:

import WriteFile        ; импортируем функцию WriteFile
import GetStdHandle     ; импортируем функцию GetStdHandle

Вместо import можно было бы использовать синонимичную директиву extern

extern WriteFile        ; импортируем функцию WriteFile
extern GetStdHandle     ; импортируем функцию GetStdHandle

Передача параметров в функции и возвращение результатов применяет те же соглашения, что действуют в целом в ARM64 на других платформах: первые 8 целочисленных параметров передаются через регистры Х0-Х7, а числа с плавающей точкой - через регистры V0-V7. Девятый и последующие параметры передаются через стек. Результат функции помещается в регистр Х0.

Здесь функции GetStdHandle в качестве параметра через регистр X0 передается значение -11, которое указывает, что надо получить дескриптор стандартного вывода.

Вызов функций WinAPI выполняется также как и вызов любых других функций - с помощью инструкции bl

mov x0, #-11  ; Аргумент для GetStdHandle - STD_OUTPUT
bl GetStdHandle ; вызываем функцию GetStdHandle

Полученный через регистр X0 дескриптор будет передаваться первому параметру функции WriteFile. Через регистр X1 передается адрес выводимой строки - переменной message, а через регистр X2 - длина строки - константа len. В регистр Х3 загружаем адрес переменной bytesWritten, через которую получим реальное число записанных байтов.

ldr x1, =message        ; Второй параметр - строка для вывода
mov x2, len             ; Третий параметр - длина строки
ldr x3, =bytesWritten   ; Четвертый параметр - количество записанных байтов
mov x4, #0              ; Пятый параметр
bl WriteFile            ; вызываем функцию WriteFile

Поскольку функции изменяют состояние регистра LR/X30, то перед вызовом функций необходимо его сохранить в стек, а после выполнения функций восстановить из стека

stp fp, lr, [sp, #-16]! ; Сохраняем регистры FR и LR в стек
; .....................
ldp fp, lr, [sp], #16    ; Сохраняем регистры FR и LR в стек

Поскольку эти функции располагаются в библиотеке kernel32.lib, то эту библиотеку необходимо передать компоновщику при компоновке программы. Допустим, исходный код рограммы определен в файле hello.asm, то процесс компиляции/компоновки и вызов программы будет выглядеть следующим образом :

c:\arm64>armasm64 hello.asm -o hello.obj
Microsoft (R) ARM Macro Assembler Version 14.36.32537.0 for 64 bits
Copyright (C) Microsoft Corporation.  All rights reserved.


c:\arm64>link hello.obj kernel32.lib /entry:__main /subsystem:console
Microsoft (R) Incremental Linker Version 14.36.32537.0
Copyright (C) Microsoft Corporation.  All rights reserved.


c:\arm64>hello
Hello METANIT.COM!

c:\arm64>

Графическое приложение

Рассмотрим еще один примере взаимодействия с WinAPI на примере создания простейшего графического приложения на примере диалогового окна. Для создания графического приложения в Windows применяется библиотека user32.lib, а непосредственно для создания диалогового окна в WinAPI можно использовать функцию MessageBoxA, которая имеет следующее определение на языке C++:

int MessageBoxA(
  [in, optional] HWND   hWnd,
  [in, optional] LPCSTR lpText,
  [in, optional] LPCSTR lpCaption,
  [in]           UINT   uType
);

Первый параметр - hWnd представляет дескриптор родительского окна. Если родительское окно не применяется, то этот параметр имеет значение NULL. Второй параметр представляет текст отображаемого в окне сообщения, а третий параметр - текст заголовка окна. Четвертый параметр - числовое значение, которое указывает, какие кнопки будут использоваться на окне.

Рассмотрим создание графического окна на примере. Пусть у нас есть файл hello.asm со следующим кодом:

    global __main
    extern MessageBoxA     ; импортируем функцию MessageBoxA
    area main, CODE
__main
    stp fp, lr, [sp, #-16]! ; Сохраняем регистры FR и LR в стек

    mov x0, #0              ; HWin = NULL
    ldr x1, =message        ; отображаемое сообщение
    ldr x2, =win_title      ; Заголовок диалогового окна
    mov x3, #0              ; 0 - кнопка "OK"

    bl MessageBoxA            ; вызываем функцию MessageBoxA

    ldp fp, lr, [sp], #16    ; Восстанавливаем регистры FR и LR из стека
    ret 
    area DATA         
message dcb "Hello METANIT.COM!",0     ; сообщение в окне
win_title dcb "Window ARM", 0          ; заголовок окна
    end

Вначале импортируем внешнюю функцию MessageBoxA:

extern MessageBoxA     ; импортируем функцию MessageBoxA

Поскольку родительское окно не используется, то для первого параметра в регистр X0 передаем значение 0:

mov x0, #0

Второй и третий параметры, которые передаются соответственно через регистры X1 и Х2, принимают в качестве теста сообщения и заголовка адреса переменных message и win_title:

ldr x1, =message        ; отображаемое сообщение
ldr x2, =win_title      ; Заголовок диалогового окна

В качестве кнопки последнему параметру через регистр Х3 передаем число 0. Это значит, что окно будет иметь одну кнопку "OK":

mov x3, #0              ; 0 - кнопка "OK"

Можно передать и другие значения. Например, число 1 устанавливает кнопки OK и Cancel, а число 4 - кнопки "Yes" и "No". Полный список значений можно посмотреть в документации.

При компоновке программные компоновщику надо передать библиотеку user32.lib, а также параметр /subsystem:windows. Данный параметр указывает, что будет создаваться графическое приложение. Так, скомпилируем приложение с помощью следующей команды:

armasm64 hello.asm -o hello.obj
link hello.obj user32.lib /entry:__main /subsystem:windows

В итоге после запуска скомпилированного приложения мы увидим следующее окно:

Графическое приложение на ассемблере в Windows
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850