На этом шаге мы рассмотрим использование объектных модулей.
Перейдем теперь к вопросу о подсоединении других объектных модулей и библиотек во второй стадии трансляции. Отметим, что, сколько бы ни подсоединялось объектных модулей, один объектный модуль является главным. Смысл этого весьма прост: именно с этого модуля начинается исполнение программы. На этом различие между модулями заканчивается. Условимся далее, что главный модуль всегда в начале сегмента кода будет содержать метку START, ее мы указываем после директивы END - транслятор должен знать точку входа программы, чтобы указать ее в заголовке загружаемого модуля.
Обычно во второстепенные модули помещаются процедуры, которые будут вызываться из основного и других модулей. Рассмотрим такой модуль (PROG2_1.ASM), процедура которого PROC1 будет вызываться из основного модуля:
.386P ;Модуль PROG2_1.ASM. ;Плоская модель памяти. .MODEL FLAT, STDCALL PUBLIC PROC1 _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' PROC1 PROC MOV EAX, 1000 RET PROC1 ENDP _TEXT ENDS END
Прежде всего, обратите внимание на то, что после директивы END не указана никакая метка. Ясно, что это не главный модуль, процедуры его будут вызываться из других модулей.
Далее. Процедура, которая будет вызываться, должна быть объявлена как PUBLIC. В этом случае это имя будет сохранено в объектном модуле и далее может быть связано с вызовами из других модулей.
Итак, выполняем команду:
ML /coff /с PROG2_1.ASM
А теперь проведем маленькое исследование. Просмотрим объектный модуль с помощью какой-нибудь простой программы просмотра, например той, что есть у программы Far. И что же мы обнаружим: вместо имени PROC1 мы увидим имя _PROC1@0.
Рис.1. Просмотр объектного файла
Поясним полученные результаты. Во-первых, подчеркивание спереди отражает стандарт ANSI, предписывающий всем внешним именам (доступным нескольким модулям) автоматически добавлять символ подчеркивания. Здесь ассемблер будет действовать автоматически, и у нас по этому поводу не будет никаких забот.
Сложнее с припиской @0. Что она означает? На самом деле все просто: цифра после знака @ определяет количество байт, которые необходимо передать в стек в виде параметров при вызове процедуры. В данном случае ассемблер понял так, что наша процедура параметров не требует. Сделано это для удобства использования директивы INVOKE.
Приведем текст основного модуля PROG2_2.ASM, который содержит вызов процедуры из модуля PROG2_1.ASM:
.386P ;Плоская модель памяти. .MODEL FLAT, STDCALL ;Прототип внешней процедуры. EXTERN PROC1@0:NEAR ;Сегмент данных. _DATA SEGMENT DWORD PUBLIC USE32 'DATA' _DATA ENDS ;Сегмент кода. _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: CALL PROC1@0 RET ;Выход. _TEXT ENDS END START
Как вы понимаете, процедура, вызываемая из другого модуля, объявляется как EXTERN. Далее, вместо имени PROC1 нам приходится использовать имя PROC1@0. Здесь пока ничего нельзя сделать. Может возникнуть вопрос о типе NEAR. Дело в том, что в операционной системе MS-DOS тип NEAR означал, что вызов процедуры (или безусловный переход) будет происходить в пределах одного сегмента. Тип FAR означал, что процедура (или переход) будет вызываться из другого сегмента. В операционной системе Windows реализована так называемая плоская модель памяти, когда всю память можно рассматривать как один большой сегмент. И здесь логично использовать тип NEAR.
Выполним команду:
ML /coff /с PROG2_2.ASM
В результате получим объектный модуль PROG2_2.OBJ. Теперь можно объединить модули и получить загружаемую программу PROG2_2.EXE:
LINK /SUBSYSTEM:WINDOWS PROG2_2.OBJ PROG2_1.OBJ
При объединении нескольких модулей первым должен идти главный, а остальные - в произвольном порядке.
На следующем шаге мы рассмотрим директиву INVOKE.