Разбиение программы на модули и подключение внешних модулей

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

При сборке программы ассемблер создает из файла программы и других файлов, которые он включает, один модуль - объектный файл с расширением .obj. Затем компоновщик берет несколько объектных файлов (созданных MASM или другими компиляторами) и объединяет их в один исполняемый файл (файл .exe). Это позволяет выносить в отдельные файлы различные компоненты программы и компилировать их независимо дург от друга и при необходимости подключать в другие программы. Например, мы можем определить процедуру в отдельном файле, скомпилировать ее в объектный файл и потом подключать при необходимости в другие программы. Причем подобным образом мы можем подключать объектные файлы, скомпилированный из кода на C/C++ и других языков программирования.

Для подключения компонентов из внешних модулей MASM имеет две директивы: extern и externdef:

extern идентификатор:тип
externdef идентификатор:тип

После директивы идет идентификатор - название процедуры/функции или какого-то объекта. И через двоеточие указывается тип идентифкатора. Можно использовать следующие типы:

  • proc (указывает, что идентификатор - название процедуры/функции или метки)

  • любые встроенные типы MASM (например, byte, word, dword, qword, oword и т.д.)

  • любые пользовательские типы (например, имя структуры)

  • имя константы

Рассмотрим на примере. Пусть у нас есть файл sum.asm, в котором определена простейшая процедура sum:

.code
; через RCX и RDX передаются параметры
; через RAX возвращается результат
sum proc
    mov rax, rcx
    add rax, rdx
    ret
sum endp
end

Данная процедура определена в секции .code. Она принимает два числа через регистры RCX и RDX и результат их сложения помещает в регистр RAX.

Пусть в той же папке располагается главный файл программы main.asm со следующим кодом:

extern sum:proc     ; подключаем внешнюю процедуру sum
.code
main proc
    mov rcx, 6
    mov rdx, 1
    call sum
    ret
main endp
end

Здесь первой строкой кода подключаем внешнюю процедуру sum, для чего директиве extern передается имя процедуры (sum) и тип proc. Благодаря этому при в процедуре main можно использовать процедуру sum для вычисления суммы чисел.

Чтобы скомпилировать файл программы, нам надо оба файла - и sum.asm, и main.asm скомпилировать в объектные файлы и затем их объединить в исполняемый файл. В MASM для этого можно применить команду:

ml64 main.asm sum.asm /link /entry:main

Данная команда сначала сгенерирует два объектных файла - sum.obj и main.obj и затем скомпонует их в один файл main.exe.

Также можно выполнить компиляцию по отдености. Например, сначала скомпилируем файл sum.asm с помощью команды:

ml64 /c sum.asm

В результате будет создан объектный файл sum.obj.

Затем для компиляции главного файла main.asm и сборки всей программы выполним команду:

ml64 main.asm sum.obj /link /entry:main

Подобным образом вместо sum.obj можно подключать любые другие объектые файлы, в том числе сгенерированные по коду на C/C++.

Но стоит отметить, что объектные файлы влияют на итоговый размер программы: при компоновке исполняемого файла комновщик MASM берет весь код из объектного файла и добавляет его в выходной исполняемый файл. То есть есть объектный файл содержит 100 процедур, но вы используете в программе только одну из них, то все равно код всех 100 процедур будет добавляться в итоговый исполняемый файл.

public

Чтобы компонент был виден вне своего модуля, при его определении применяется директива public, которой передается имя компонента:

.code
public sum  ; функция sum общедоступна
sum proc
    mov rax, rcx
    add rax, rdx
    ret
sum endp
end

Подключение переменных

Как и процедуры, можно подключать и другие компоненты из внешних модулей, например, переменные. Пусть у нас есть следующий файл numbers.asm:

.data
public num1
num1 dword 32

public num2
num2 word 16

public num3
num3 byte 8

end

Для примера здесь определены три переменных, причем для каждой из них применяется директива public, чтобы переменная была видна вне файла.

В файле main.asm подключим и используем эти переменные:

extern num1:dword, num2:word, num3:byte
.code
main proc
    mov eax, num1
    ret
main endp
end

При подключении внешних компонентов мы можем перечислить их через запятую после директивы extern. Хотя также мы могли бы их определить по отдельности:

extern num1:dword
extern num2:word
extern num3:byte

externdef

Директива externdef ведет себя как директива extern, когда подключаемый компонент отсутствует в исходном файле, и ведет себя как директива public, когда компонент определен в исходном файле. При этом в одном исходном файле может быть несколько объявлений externdef для одного и того же символа. Например:

externdef sum:proc
.code
sum proc
    mov rax, rcx
    add rax, rdx
    ret
sum endp

main proc
    mov rdx, 1
    mov rcx, 2
    call sum
    ret
main endp
end

В данном случае функция sum определена непосредственно в текущем файле, поэтому применение директивы externdef фактически будет означать, что к процедуре sum применяется директива public, и процедура sum поэтому видна вне своего модуля.

Какой в этом смысл? Дело в том, что если проект содержит множество файлов на ассемблере, и мы захотим во все эти файлы подключить все используемые функции, то мы можем выделить все внешние подключения в один заголовочный файл и подключать его во все файлы с помощью директивы include. И чтобы избежать ошибки, когда какой-то компонент будет подключаться в тот же файл, где он и определен, как раз и применяется директива externdef

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850