При сборке программы ассемблер создает из файла программы и других файлов, которые он включает, один модуль - объектный файл с расширением .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, которой передается имя компонента:
.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 ведет себя как директива 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