В данном разделе кратко описывается программирование в среде Windows и Win32, и описывается, как можно перенести в эти среды программы. Такой перенос позволит компилировать ваши программы в 16- и 32-разрядной версии Windows.
32-разрядные инструментальные средства Borland C++ обеспечивают создание 32-разрядных файлов .OBJ и .EXE в формате переносимых выполняемых файлов PE. Это формат выполняемого файла для программ Win32 и Windows NT.
Win32 - это расширение операционной системы Windows 3.1, обеспечивающее поддержку разработки и выполнения 32-разрядных выполняемых файлов Windows. Win32 - это набор DLL, отображающих вызовы 32-разрядного прикладного программного интерфейса (API) в соответствующие 16-разрядные вызовы, использующие виртуальный драйвер устройства (VxD) для работы с памятью и содержащие обновленные функции API. Эти DLL и VxD обеспечивают прозрачный режим работы.
Чтобы обеспечить компиляцию и выполнение своего кода под Win32, вам следует:
API Win32 расширяют большинство существующих 16-битовых API Windows до 32-битовых. Сюда добавлены и новые вызовы API, совместимые с Windows NT. API Win32 - это подмножество API Win32 для Windows NT. API Win32 состоят из 16-битовых вызовов, преобразованных и допускающих вызов в 32-разрядной среде, и 32-битовых вызовов API, реализуемых в 16-разрядной среде Windows.
Если выполняемые вызовы Win32 любой из функций API Win32 в Win32 не поддерживаются, на этапе выполнения возвращаются соответствующие коды ошибки. Если вы пишете приложения, совпадающие с API Win32 и использующие соглашения по переносимости, ваше приложение должно обладать переносимостью между 16- и 32-разрядной средой Windows.
В данном разделе обсуждаются конструкции (введенные в Windows 3.1), обеспечивающие переносимость кода Windows. Существующий 16-разрядный код Windows можно переносить с минимальными изменениями в Win32 и Windows NT. Большинство изменений предусматривают подстановку вместо старых новых макрокоманд и типов и замену специфических 16-разрядных вызовов API аналогичными API Win32. После внесения этих изменений ваш программный код сможет компилироваться и выполняться в 16-разрядной и 32-разрядной среде Windows.
Чтобы облегчить создание переносимого кода, предусмотрена переменная среду этапа компиляции STRICT. Windows 3.1 поддерживает определение STRICT в windows.h. Например, если не определена переменная STRICT, то передача HWND функции, требующей HDC, не приведет к выводу компилятором предупреждающего сообщения. Если вы определите STRICT, то получите ошибку компиляции.
Использование STRICT позволяет:
STRICT обладает обратной совместимостью с Windows 3.0, то есть ее можно использовать для создания приложений, работающих в Windows 3.0. Определение STRICT поможет вам находить и корректировать несовместимость типов при переносе программ в 32-разрядную среду и поможет обеспечить переносимость между 16- и 32-разрядной Windows.
Чтобы вы могли изменить свою программу в соответствии со STRICT, предусмотрены новые типы, константы и макрокоманды
Типы и константы | Описание |
---|---|
CALLBACK | Используется вместо FAR PASCAL в подпрограммах обратного вызова (например, оконных и диалоговых процедурах). |
LPARAM | Описывает все 32-разрядные полиморфические параметры. |
LPCSTR | То же, что LPSTR, но используется для доступ- ных только по чтению строковых указателей. |
LRESULT | Описывает все 32-разрядные возвращаемые значения. |
UINT | Переносимый беззнаковый целочисленный тип, размер которого определяется целевой средой. (В Windows 3.1 представляет 16-битовое значение, а в Win32 - 32-битовое.) |
WINAPI | Используется вместо FAR PASCAL для описаний API. Если вы пишете DLL с экспортируемыми точками входа API, то можете использовать ее для описаний API. |
WPARAM | Описывает 16-битовые полиморфические параметры. |
Макрокоманда | Описание |
---|---|
RELDOFFSET(тип, поле) | Вычисляет смещение поля в структуре. "Тип" - это тип структуры, а "поле" - это имя поля. |
WAKELP(селект,смещ) | Воспринимая селектор и смещение, создает FAR VOID*. |
WAKELPARAM(мин,макс) | Из двух 16-битовых значений создает LPARAM. |
WAKELRESULT(мин,макс) | Из двух 16-битовых значений создает LRESULT. |
OFFSETOF(указ) | Выделяет из дальнего указателя смещение и возвращает UINT. |
SELECTOROF(указ) | Выделяет из дальнего указателя селектор и возвращает UINT. |
Описатели | Значение |
---|---|
HACCEL | Описатель таблицы акселератора. |
HDRVR | Описатель драйвера (Windows 3.1). |
HDWP | Описатель DeferWindowPost(). |
HFILE | Описатель файла. |
HGDIOBJ | Общий описатель объекта GDI. |
HGLOBAL | Глобальный описатель. |
HINSTANCE | Описатель экземпляра. |
HLOCAL | Локальный описатель. |
HMETAFILE | Описатель метафайла. |
HMODULE | Описатель модуля. |
HPSRC | Описатель ресурса. |
HTASK | Описатель задачи. |
Чтобы сделать ваше приложение согласованным со STRICT, нужно:
Перечислим некоторые рекомендации, которые могут оказаться полезными при преобразовании вашего программного кода в соответствии со STRICT:
BOOL CALLBACK DlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); DLGPROC lpfnDlg; lpfnDlg=(DLGPROC)MakeProcInstance(DlgProc, hinst); ... FreeProcInstance((FARPROC)lpfnDlg);
HBRUSH hbr; hbr = (HBRUSH)(UINR) SendMessage(hwnd WM_CTLCOLOR, ..., ...);
HWND hwmd; int id; hwnd = CreateWindow("Button", "Ok", BS_PUSBUTTON, x, y, cx, cy, hwndParent, (HMENU)id, // здесь требуется приведение типа hinst, NULL);
Тип UINT создан и широко используется в API для создания типа данных, переносимого с Windows 3.x. UINT определяется как
typedef undigned int UINT;
UINT необходим из-за различий в размерах int между 16-разрядной Windows и Win32. Для 16-разрядной Windows int - это 16-битовое беззнаковое целое, а для Win32 - 32-битовое беззнаковое целое. Для описания целочисленных объектов, которые при компиляции 32-разрядных приложений предполагается расширить с 16 до 32 бит используйте UINT.
Тип WORD определяется следующим образом:
typedef unsigned short WORD;
WORD описывает 16-битовое значение и в Windows, и в Word32. Используйте этот тип для создания объектов, которые остаются 16-битовыми на обеих платформах. Поскольку описатели Win32 расширены до 32 бит, они больше не могут иметь тип WORD.
Макрокоманда WINAPI, которая находится в файле windows.h, определяет соглашения по вызову. WINAPI дает в результате соответствующее соглашение по вызову, применяемое на целевой платформе. WINAPI следует использовать вместо FAR PASCAL. Эта макрокоманда позволяет задать альтернативные соглашения по вызову. В настоящее время Win32 использует __stdcall. Фундаментальный тип unsigned изменен на более переносимый UINT.
В своей функции обратного вызова используйте соглашения по вызову CALLBACK, заменяющие FAR PASCAL.
В 32-битовом коде Windows вам требуется изменить способ распаковки данных сообщения из lParam и wParam. В Win32 wParam вырастает в размере с 16 до 32 бит, в то время как lParam сохраняет размер 32 бита. Так как lParam в 16-разрядной Windows часто содержит описатель и другое значение, а в Win32 описатель увеличивается до 32 бит, необходима новая схема упаковки wParam и lParam.
В качестве примера сообщения, на которое влияют изменения в размере параметра, можно привести WM_COMMAND. В Windows 3.x wParam содержит 16-битовый идентификатор, а lParam - 16-битовый описатель окна и 16-битовую команду.
В Win32 lParam содержит только 32-битовый описатель окна. 16-битовая команда перемещается из lParam в младшие 16 бит wParam, а старшие 16 бит wParam содержат идентификатор. Эта новая схема означает, что вам нужно изменить способ извлечения информации из этих параметров, для чего используются обработчики сообщений.
Обработчик сообщений предоставляет переносимый способ извлечения сообщений из wParam и lParam. В зависимости от вашей среды (16-битовой Windows или Win32) обработчики сообщений используют разные методы выделения из сообщений данных. Использование макрокоманд обработки сообщений обеспечит функционирования кода выделения данных из сообщения на любой из двух платформ.
Для вызова функций файлового ввода-вывода DOS Windows 3.0 предусматривает функцию API DOS3Call. Эта и другие функции INT 21H заменены в Win32 соответствующими 32-разрядными вызовами.
Функция 21H | Операция DOS | Эквивалент API Win32 |
---|---|---|
OEH | Выбор диска. | SetCurrentDirectory |
19H | Получение текущего диска. | GetCurrentDirectory |
2AH | Получение даты. | GetDateAndTime |
2BH | Установка даты. SetDateAndTime | |
2CH | Получение времени. | GetDateAndTime |
3DH | Установка времени. | SetDateAndTime |
36H | Получение свободного | GetDiskFreeSpace пространства на диске. |
39H | Создание каталога. | CreateFile |
3AH | Удаление каталога. | RemoveDirectory |
3BH | Установка текущего каталога. | SetCurrentDirectory |
3CH | Создание описателя. | CreateFile |
3DH | Открытие описателя. | CreateFile |
3EH | Закрытие описателя. | CloseHandle |
3FH | Чтение описателя. | ReadFile |
40H | Запись описателя. | WriteFile |
41H | Удаление файла. | DeleteFile |
42H | Получение атрибутов файла. | SetFilePointer |
43H | Получение атрибутов файла. | GetAttributesFile |
43H | Установка атрибутов файла. | SetAttributesFile |
47H | Получение текущего каталога. | GetCurrentDirectory |
4EH | Поиск первого файла. | FindFirstFile |
4FH | Поиск следующего файла. | FindNextFile |
56H | Изменение записи каталога. | MoveFile |
57H | Получение даты/времени файла. | GetDateAndTimeFile |
57H | Установка даты/времени файла. | SetDateAndTimeFile |
59H | Получение расширенной ошибки. | GetLastError |
5AH | Создание уникального файла. | GetTempFileName |
5BH | Создание нового файла. | CreateFile |
5CH | Блокировка файла. | LockFile |
5CH | Разблокировка файла. | UnlockFile |
67H | Установка описателя счетчика. | SetHandleCount |
В данном разделе описываются некоторые наиболее общие ошибки и предупреждения, обнаруживаемые компилятором.
Call to function имя_функции with no prototype
(Вызов функции без прототипа)
Данное предупреждение означает, что функция используется перед ее прототипизацией или описанием. Это предупреждение может выводиться также, когда функция, не воспринимающая аргументов, не описана прототипом с void.
Conversion may lose signifigant digits
(Преобразование может привести к потере значимых цифр)
Это предупреждение является результатом преобразования компилятором значения, например, LONG в int. Оно сообщает, что вы можете потерять информацию. Если вы уверены, что таких проблем не возникает, то с помощью соответствующего явного приведения типа к меньшему размеру это предупреждение можно подавить.
Function should return a value
(Функция должна возвращать значение)
Функция, описанная как возвращающая значение, значения не возвращает. В старом коде Си, отличном от стандарта ANSI, такое предупреждение является общим для функций, не возвращающих значения и не имеющих типа возврата:
foo(i) int i; { ... }
Описанные таким образом функции интерпретируются компилятором, как возвращающие int. Если функция ничего не возвращает, ее следует описать так:
void foo(int i) { ... }
Lvalue required
(Требуется именующее значение)
Type mismatch in parameter
(Несовпадение типа в параметре)
Эти ошибки указывают, что вы пытаетесь передать тип, отличный от указателя, там, где требуется указатель. При определении STRICT описателей, а также LRESULT, WPARAM и LPARAM внутренним образом описываются как указатели, поэтому попытка передачи в качестве указателя int, WORD или LONG дает в результате подобные ошибки.
Эти ошибки следует исправлять путем описания отличных от указателя значения, используемых при передаче параметров или присваивании. В случае специальных констант, таких как (HWND)1, вам следует пользоваться новыми макрокомандами (такими как HWND_BOTTOM). Ошибки несоответствия типов следует подавлять в исключительных случаях (так как часто это может дать в результате некорректный код).
Non-portable conversion
(Не переносимое преобразование)
Вы приводите указатель или описатель ближнего типа к 32-битовому значению, такому как LRESULT, LPARAM, LONG или DWORD. Это предупреждение практически всегда указывает на ошибку, так как старшие 16 бит значения будут содержать ненулевое значение. Помещая в старшие 16 бит значение текущего сегмента данных, компилятор сначала конвертирует 16-битовый ближний указатель к 32-битовому дальнему указателю.
Чтобы избежать этого предупреждения и обеспечить размещение в старших 16 битах 0, нужно привести тип описателя к UINT:
HWND hwnd; LRESULT result = (LRESULT)(UINT)hwnd;
В тех случаях, когда вы хотите, чтобы 32-битовое значение содержало указатель FAR, можно избежать предупреждения путем явного приведения типа к дальнему указателю:
char near* pch; LPARAM lparam = (LPARAM)(LPSTR)pch;
Not an allowed type
(Не является допустимым типом)
Это сообщение об ошибке обычно выводится в результате попытки разыменования указателя void. Обычно это бывает при непосредственном использовании значения-указателя, возвращаемого GlobalLock или LocalLock. Чтобы решить данную проблему, перед использованием указателя присвойте возвращаемое значение переменной соответствующего типа (используя при необходимости приведение типа).
Size of the type is unknown or zero
(Размер типа неизвестен или равен нулю)
Вы пытаетесь с помощью + или += изменить значение пустого указателя. Это ошибка обычно появляется в результате того, что отдельные функции Windows (например, GlobalLock или LocalLock) возвращающие указатели произвольных типов, определены для возврата void FAR* вместо LPSTR. Чтобы решить эту проблему, присвойте значение void* описанной соответствующим образом переменной (приведя при необходимости тип):
BYTE FAR* lpb = (BYTE FAR*)GlobalLock(h); lpb += sizeof(DWORD);
Type mismatch in redeclaration of имя_параметра
(Несовпадение типа при повторном описании параметра)
В вашей программе имеется несогласованное описание переменной, параметра или функции. Используя API Win32, вы можете внести в свой исходный код изменения, которые сделают программу более переносимой.
Для построения приложения Win32 вы должны использовать соответствующие инструментальные средства, параметры, библиотеки и код запуска. В следующей таблице перечислены параметры компилятора и компоновщика, библиотеки и код запуска, необходимые для компоновки и получения файлов .DLL и .EXE.
Параметры Параметры Библиотеки Код Создаваемый BCC32 TLINK запуска файл -W, -WE /Tpe cw32.lib c0w32.obj GUI.EXE import32.lib -WD, -WDE /Tpd cw32.lib c0x32.obj GUI.DLL imprtw32.lib -WC /Tpe/ap cx32.lib c0d32.obj Console.EXE import32.lib -WCD, -WCDE /Tpd/ap cx32.lib c0d32.obj Console.DLL imprtw32.lib
Использование в приложениях библиотек динамической компоновки DLL позволяет уменьшить объем файла .EXE, экономит системную память и обеспечивает большую гибкость при изменении и расширении приложений. DLL - это библиотека выполняемых модулей, содержащая функции или ресурсы, используемые приложениями или другими DLL. DLL не имеет основной функцией, которая обычно служит точкой входа в основную программу. DLL содержит несколько точек входа, по одной на каждую экспортируемую функцию. При загрузке DLL операционной системой она (одна ее копия) может совместно использоваться несколькими приложениями. Для полного понимания DLL полезно понимать, чем отличаются статическая и динамическая компоновка.
Когда приложение использует функцию из статически компонуемой библиотеки (например, библиотеки исполняющей системы Си), копия этой функции связывается с вашим приложением на этапе компоновки (компоновщиком TLINK). Два одновременно работающих приложения, использующих одну и ту же функцию, будут иметь собственную копию данной функции. Эффективней было бы совместно использовать единственную ее копию. Такую возможность дают динамически компонуемые библиотеки. При динамической компоновке внешние ссылки разрешаются на этапе выполнения.
Когда программа использует функцию из DLL, эта функция не компонуется с файлом .EXE. При динамической компоновке используется другой метод. Во-первых, на этапе компоновке TLINK связывает с вашим файлом .EXE записи импорта (содержащие информацию о DLL и адресе процедуры). Это временно разрешает в вашей программе внешние ссылки на функции DLL. Данные записи импорта обеспечиваются файлами определения модуля или библиотеками импорта. На этапе выполнения информация записи импорта используется для поиска и привязки к вашей программе функции DLL.
Благодаря динамической компоновке ваши приложения имеют меньший размер, так как с ними не компонуется код функций. А поскольку код DLL и ресурсы совместно используются несколькими приложениями, это экономит системную память.
DLL создаются аналогично файлам .EXE: компилируются файлы исходного кода, затем выполняется компоновка объектных файлов. Однако, DLL не имеют функции main и компонуются по другому. Ниже рассказывается, как следует писать DLL.
LibMain, DllEntryPoint и WEP
В качестве основной точки входа для DLL должны предусматриваться функция LibMain (для 16-разрядных программ) или DllEntryPoint (для 32-разрядных). В случае 32-разрядных программ Windows вызывает DllEntryPoint при каждой загрузке или выгрузке DLL, при каждом присоединении или отсоединении от DLL дополнительных процессов или при создании/уничтожении внутри процесса нити.
Инициализация DLL практически целиком зависит от функции конкретной DLL и может включать в себя следующие основные задачи:
Код инициализации выполняется только для первого приложения, использующего DLL. Код запуска DLL автоматически инициализирует локальную динамически распределяемую область памяти, поэтому в LibMain не нужно включать соответствующий код. Функции LibMain передаются следующие параметры:
int FAR PASCAL LibMain (HINSTANCE hInstance, WORD wDataSeg; WORD cbHeapSize, LPSTR lpSmdLine)
hInstance - это описатель экземпляра DLL.
wDataSeg - это значение регистра сегмента данных (DS).
cbHeapSize - размер локальной динамически распределяемой области памяти, заданной для DLL в файле определения модуля.
lpCmdLine - это дальний указатель командной строки, задан ной при загрузке DLL. Он почти всегда нулевой, так как DLL обычно загружается автоматически и без параметров.
LibMain обычно возвращает значение 0 (успешная инициализация) или 1 (неуспешная инициализация). В последнем случае Windows выгружает DLL из памяти.
Точкой выхода для 16-битовой DLL является функция WEP (Windows Exit Procedure). Эта функция для DLL не обязательна, так как библиотеки исполняющей системы Borland C++ предусматривают ее по умолчанию, но для выполнения какой-либо очистки перед выгрузкой DLL из памяти вы можете указать свою собственную функцию.
В Borland С++ WEP экспортировать не требуется. Borland С++ определяет свою собственную WEP, которая вызывает вашу WEP (если она определена), а затем выполняет очистку системы. WEP имеет следующий прототип:
int FAR PASCAL WEP (int nParameter)
где nParameter - это WEP_SYSTEMEXIT или WEP_FREE_DLL. WEP_SYSTEMEXIT указывает на завершение работы Windows, а WEP_FREE_DLL только на выгрузку DLL. В случае успешного выполнения WEP возвращает 1. По этому значению Windows в настоящее время не выполняет никаких действий.
Чтобы сделать функции DLL доступными для других приложений (.EXE или других DLL), имена функций должны экспортироваться. Чтобы использовать экспортированные функции, нужно импортировать их мена. Экспорт функций выполняется двумя способами:
Чтобы функция могла импортироваться другим приложением или DLL, она должна описываться как экспортируемая из DLL. Вы должны также указать компоновщику, что хотите импортировать эти функции. Это можно сделать тремя способами:
Функции DLL не компонуются непосредственно с приложением Windows. Они вызываются на этапе выполнения. Это означает, что такие функции должны иметь дальний тип вызова (так как DLL будет иметь другой сегмент кода). Используемые функцией DLL данные также должны иметь дальний тип.
Чтобы функцию можно было использовать в приложении, она должна также компилироваться как доступная для экспорта и затем экспортироваться. Для этого вы можете скомпилировать DLL так, чтобы все функции в ней были экспортируемыми (параметр -WD), и указать перед ними ключевое слово _export.
Если вы компилируете DLL с большой моделью памяти (дальний код, дальне данные), то вам не нужно явным образом определять в DLL дальний тип функции или ее данных.
Чтобы использовать в DLL классы, класс требуется экспортировать из DLL и импортировать в файл .EXE. Для этого можно использовать условное макроопределение. Например, в файл заголовка можно включить следующее:
#if defined (BUILD_DLL) #define _MY_CLASS _export #elif defined(USE_DLL) #define _MY_CLASS _import #else #define _MY_CLASS #endifВ своих определения определите классы следующим образом:
class _MY_CLASS class { ... };При построении DLL определите BUILD_DLL (например, с помощью параметра -D). Макрокоманда _MY_CLASS будет расширяться в _import. Определите _MY_DLL при построении файла .EXE, который будет использовать DLL. Макрокоманда _MY_CLASS будет расширяться в _import.
С помощью функций DLL все приложения, использующие DLL, могут обращаться к глобальным данным DLL. В 16-разрядных DLL функция будет использовать одни и те же данные, независимо от вызывающего ее приложения (в 32-разрядных DLL данные являются частными данными процесса). Если вы ходите защитить в 16-разрядной DLL глобальные данные и ограничить их использование единственным приложением, эту защиту нужно написать самостоятельно. Сама DLL не имеет такого механизма. Если вам нужно, чтобы данные были частными для данного вызывающего DLL приложения, их нужно распределить и работать с ними вручную. Статические данные в 16-разрядной DLL являются глобальными для всех вызывающих приложений.
Ниже перечислены типичные формы командной строки, позволяющие использовать DLL-версии библиотек исполняющей системы Borland и описанные ниже библиотеки классов.
Для 16-битовой компиляции и компоновки с использованием DLL-версии библиотеки исполняющей системы дайте команду:
bcc -c -D_RTLDLL -ml source.cpp tlink -C -Twe c0w1 source, source, , import crtldll
Обратите внимание на использование макрокоманды _RTLDLL и переключателя командной строки -ml. Для 32-битовой версии используются команды:
bcc -c -D_RTLDLL source.cpp tlink32 -Tpe -ap c0x32 source, source, , import32 cw32i
Для 16-битовой компиляции и компоновки с использованием DLL-версии библиотеки класса дайте команды:
bcc -c -D_BIDSDLL -ml source.cpp tlink -C -Twe c0w1 source, source, , import bidsi crtldll
Для 32-битовой компиляции и компоновки с использованием DLL-версии библиотеки класса дайте команды:
bcc32 -c -D_BIDSDLL -ml source.cpp tlink32 -Tpe -ap c0x32 source, source, , import32 bidsfi cw32i
Встроенный ассемблер позволяет включать в ваши программы на языке Си и С++ операторы ассемблера. Инструкции встроенного ассемблера компилируются и ассемблируются с вашей программой, и вам не потребуется писать отдельные модули.
Чтобы включить в код Си/С++ инструкции ассемблера, используйте ключевое слово asm и следующий формат:
asm код_операции операнды;где "код_операции" - допустимая инструкция процессора 80х86, "операнды" содержат операнды (операнд), допустимые для указанной операции (константы, переменные и метки). Концом оператора asm является символ ; или новая строка. После точки с запятой на той же строке может размещаться новый оператор asm, но на следующей строке оператор продолжаться не может. Для включения нескольких операторов asm их можно заключить в фигурные скобки (первая скобка должна быть на той же строке, что и asm):
asm { pop ax; pop ds iret }
Точки с запятой для комментария здесь не используются (как в TASM). При комментировании таких операторов применяйте стандартные комментарии Си. Ассемблерная часть оператора копируется непосредственно в вывод и включаются в операторы языка ассемблера, которые Borland С++ генерирует для инструкций Си и С++. Все идентификаторы Си заменяются на соответствующие эквиваленты ассемблера. Каждый оператор asm рассматривается как оператор Си.
Оператор asm может использоваться в функции или как внешнее описание вне функции. Оператор asm, размещенные внутри функции, помещаются в сегмент кода, а операторы asm вне функции - в сегмент данных.
В операторах asm вы можете использовать любой идентификатор Си, включая динамические локальные переменные, регистровые переменные и параметры функции. Borland С++ автоматически преобразует эти символы в соответствующие операнды ассемблера и добавляет к именам идентификаторов символы подчеркивания.
В общем случае вы можете использовать идентификатор Си в любой позиции, где допускается операнд адреса. Там, где допустим регистровый операнд, вы можете использовать регистровую переменную. Если ассемблер обнаруживает идентификатор при анализе операторов инструкции встроенного ассемблера, то он ищет идентификатор в таблице идентификаторов Си. Имена регистров 80х86 из поиска исключаются. Имена регистров можно задавать символами в верхнем регистре.
При программировании вам не нужно заботиться о точных смещениях локальных переменных - использование имени переменной предусматривает корректные смещения. Однако, может оказаться необходимым включить в инструкции ассемблера WORD PTR, BYTE PTR или другие переопределения размера. Для косвенных инструкций вызова типа FAR или LES необходимо переопределение DWORD PTR.
В операторах встроенного ассемблера вы можете ссылаться на элементы структуры (используя формат "переменная.элемент_структуры"). При этом вы работаете с переменными и можете сохранять и получать значения элементов структур. Однако, можно также ссылаться на элемент структуры непосредственно по имени (без имени переменно). Такая ссылка имеет форму числовой константы. При этом константы приравниваются смещению (в байтах) от начала структуры, содержащей данный элемент. Рассмотрим фрагмент программы:
struct myStruct { int a_a; int a_b; int a_c; } myA; myfumc() { ... asm {mov ax, WORD PTR myA.a_b mov bx, WORD PTR myA.a_c } ... }
Этот фрагмент описывает структурный тип с именем myStruct с тремя элементами a_a, a_b, a_c. Первый оператор встроенного ассемблера перемещает значение, содержащееся в myA.a_b, в регистр AX. Второй перемещает значение по адресу [di]+offset(a_c) в регистр BX (он берет адрес, записанный в DI, и добавляет его к смещению a_c от начала myStruct). Оператор ассемблера даст следующий код:
mov ax, DGROUP : myA+2 mov bx, [di+4]
Таким образом, если вы загружаете регистр (такой как DI) адресом структуры типа meStruct, то можете использовать имена элементов структуры для непосредственной ссылки на них. Имя элемента структуры можно использовать в любой позиции, где вы операторе языка ассемблера допускается числовая константа.
Элементу структуры должна предшествовать точка. Она указывает, что это имя элемента структуры, а не обычный идентификатор Си. В выводе ассебмлера имена элементов структуры заменяются соответствующими числовым смещением элемента, а информация о типе не сохраняется. Такие элементы структуры могут использоваться в операторах ассебмлера в качестве констант этапа компиляции.
Однако есть одно ограничение: если две используемые во встроенном ассебмлере структуры имеют элемент с одинаковыми именем, их нужно сделать различными. Укажите между точкой и именем элемента тип структуры в скобках (как в случае приведения типа), например:
asm mov bx.[di].(struct tm)tm_hour
Во встроенном ассемблере вы можете использовать любые условные и безусловные инструкции перехода и циклов. Эти инструкции допускаются только внутри функции. Поскольку метки в операторах asm определить нельзя, инструкции перехода должны использовать метки goto языка Си. Если метка находится слишком далеко, переход не будет автоматически преобразовываться в дальний переход. Поэтому работать с условными переходами нужно аккуратно. Для проверки переходов можно использовать параметр -B. Прямые переходы дальнего типа генерировать нельзя. Допускаются косвенные переходы. Чтобы использовать косвенные переходы, применяйте в качестве операнда инструкции перехода имя регистра.
Существует два способа, с помощью которых Borland C++ может обрабатывать в вашем коде Си или С++ операторы встроенного ассемблера. Во-первых, Borland С++ может преобразовывать код Cи или C++ непосредственно в код ассебмлера, а затем передавать его TASM для генерации файла .OBJ. Во-вторых, Borland С++ для включения ваших операторов ассебмлера непосредственно в поток инструкций компилятора может использовать встроенный ассебмлер (BASM).
Для обработки операторов ассебмлера, встроенных в программы Си и С++ можно указать параметр компилятора -B. По умолчанию -B вызывает TASM или TASM32. Это можно переопределить с помощью параметр -Exxx, где xxx - другой ассебмлер. Если указывается параметр -B, компилятор сначала генерирует файл ассебмлера, а затем вызывает TASM. Если включить в исходные код программы оператор #pragma inline, то TASM будет вызываться и без параметра -B.
16-разрядный компилятор Borland С++ использует BASM, что позволяет ассемблировать встроенный код ассебмлера самому компилятору, а не TASM. Этот внутренний ассебмлер компилятора делает все то же, что и TASM, но имеет следующие ограничения:
Так как BASM не является полным ассебмлером, некоторые конструкции этого языка он не воспринимает. В этом случае Borland С++ будет выводить сообщение об ошибке. При этом у вас есть две возможности: упростить программный код встроенного ассебмлера или использовать параметр -B для вызова TASM. Однако TASM может не идентифицировать место ошибки, так как номер строки исходного кода теряется.
В качестве операторов встроенного ассемблирования допускается включать любые коды операций 80х86. Существует четыре класса команд, позволяемых компилятором Borland C++:
Отметим, что компилятор допускает задания любых операндов, даже если они ошибочны или не разрешены Ассемблером. Точный формат операндов не может быть принудительно установлен компилятором.
Ниже приводится полный перечень мнемонических имен кодов операций, которые могут быть использованы в операторах встроенного ассемблирования:
При использовании средства встроенного ассемблирования в подпрограммах, эмулирующих операции с плавающей точкой (параметр BCC -f), коды операции, помеченные **, не поддерживаются.
При использовании в операторах встроенного ассемблирования мнемонических команд процессора 80186 необходимо включать параметр командной строки -1. Тогда компилятор включит в генерируемый им Ассемблерный код соответствующие операторы, в результате чего Турбо Ассемблер будет ожидать появление данных мнемонических имен. При использовании предыдущих версий ассемблера эти мнемонические имена могут не поддерживаться.
Помимо кодов операций, приведенных выше, возможно использование следующих строковых команд, как в исходном виде, так и с префиксами циклического выполнения. Строковые инструкции BASM cmps insw movsb outsw stos smpsb lods movsw scas stosb smpsw lodsb scasb stosw lodsw outsb scasw insb movs
Инструкции перехода рассматриваются отдельно. Поскольку метка не может быть включена в саму команду, переходы выполняются к меткам Си (см. выше раздел "Использование команд перехода и меток"). В следующей таблице перечислены допустимые инструкции перехода: Инструкции перехода ja jge jnc jns loop jae jl jne jnz loope jb jle jng jo loopne jbe jmp jnge jp loopnz jc jna jnl jpe loopz jcxz jnae jnle jpo je jnb jno js jg jnbe jnp jz
В операторах встроенного ассемблирования Borland C++ допустимы следующие директивы: db dd dw extrn
Назад | Содержание | Вперед