Статьи Королевства Дельфи

       

Компиляция


После того, как исходный код создан, требуется его откомпилировать. Компилятор dcc32 замечательно подходит для этой роли - он очень быстрый, качественный и объединяет в себе все, что необходимо для построения exe-файлов, dll-библиотек и пакетов. Размер файла dcc32.exe (версия 12.0, из Delphi 5) всего 545 Кб, ранние версии имеют еще меньший размер. К нему нужно добавить только три файла - rlink32.dll, sysinit.dcu и system.dcu (это минимум). Компилятор и указанные файлы можно разместить в подкаталоге прикладной программы, например, bin. Генерировать текст целесообразно в подкаталоге компилятора, например, bin\pas, чтобы использовать короткие пути файлов и не засорять каталог компилятора.

Для вызова dcc32.exe в библиотеке DccUsing определена функция ExecDcc32. Она устанавливает текущий каталог, создает файл для перехвата ошибок компиляции, вызывает компилятор, дожидается завершения компиляции и определяет наличие ошибок.

function ExecDcc32(const aDccDir, aOptions, aProjectPath, aErrorPath: String; aCheckPaths: Boolean = False): Boolean;

Функция принимает аргументы: aDccDir - каталог, в котором находится компилятор Dcc32, aOptions - опции компилятора (рекомендации по их использованию смотрите в файле DccUsing.pas), aProjectPath - путь файла проекта (обычно dpr), aErrorPath - путь файла, куда будут направляться сообщения об ошибках компиляции. Необязательный аргумент aCheckPaths позволяет разрешить или запретить контроль наличия каталога и файла dcc32.exe. Функция возвращает True, если компиляция была успешной и False в противном случае. Предупреждения (hints и warnings) ошибками не считаются - их выводом можно управлять с помощью опций -H и -W. Опуская детали, рассмотрим немного подробнее эту функцию:

// сохранение текущего каталога и установка нового CurDir := GetCurrentDir; if not SetCurrentDir(DccDir) then raise Exception.Create(SCantChangeDir + DccDir); try hStdOut := INVALID_HANDLE_VALUE; try // установки атрибутов безопасности with SecurAtt do begin nLength := SizeOf(SecurAtt); lpSecurityDescriptor := nil; // разрешить наследование дочернему процессу bInheritHandle := BOOL(True); end; // создание файла, в который будут направляться ошибки hStdOut := CreateFile(PChar(aErrorPath), GENERIC_WRITE, 0, @SecurAtt, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if hStdOut = INVALID_HANDLE_VALUE then raise Exception.Create(SCantCreateFile + aErrorPath); // заполнение структуры, специфицирующей создание процесса ZeroMemory(@StartupInfo, SizeOf(StartupInfo)); with StartupInfo do begin cb := SizeOf(StartupInfo); // скрывать окно компилятора и наследовать потоки ввода-вывода dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES; wShowWindow := SW_HIDE; hStdOutput := hStdOut; end; // создать и стартовать процесс компилятора s := 'dcc32.exe ' + aOptions + ' ' + aProjectPath; if not CreateProcess('dcc32.exe', PChar(s), @SecurAtt, @SecurAtt, BOOL(True), 0, nil, PChar(DccDir), StartupInfo, ProcessInfo) then raise Exception.Create(SCantCreateProcess + 'dcc32.exe'); // ждать завершение компиляции неопределенное время WaitForSingleObject(ProcessInfo.hProcess, INFINITE); // получить результат компиляции ResultCode := 0; GetExitCodeProcess(ProcessInfo.hProcess, ResultCode); result := ResultCode = 0; finally // закрыть файл ошибок if hStdOut <> INVALID_HANDLE_VALUE then CloseHandle(hStdOut); end; finally // восстановить прежний каталог по умолчанию SetCurrentDir(CurDir); end;


Установка каталога компилятора, как текущего, позволяет не заботиться о мелочах, связанных с назначением путей. Компилятор направляет сообщения об ошибках в стандартный файл вывода. Для его перехвата создаем свой файл, дескриптор которого передаем компилятору. Для того, чтобы процесс компилятора мог наследовать дескриптор открытого файла, устанавливаем его атрибут наследования. При заполнении структуры StartupInfo указываем, что окно компилятора должно быть скрытым и порождаемый процесс должен наследовать стандартные потоки ввода-вывода. Атрибуты безопасности, передаваемые функции создания процесса, нужны для правильной работы в NT, в Windows 95-98 их можно было бы опустить. Функция CreateProcess сохраняет параметры процесса в структуре ProcessInfo - мы используем дескриптор процесса, чтобы передать его функции ожидания системного события - в данном случае, завершения процесса. С помощью GetExitCodeProcess получаем значение, которое возвращает компилятор. Если компиляция была успешной, то возвращается 0, иначе - ненулевое значение. Операции закрытия файла ошибок и восстановления предыдущего каталога произойдут независимо от возможных исключительных ситуаций по ходу функции ExecDcc32.
Компилятору, вместе с исходным файлом (файлами), нужно также передать файл проекта (dpr) и уточнить в опциях, что же будет результатом компиляции. Возможных вариантов много - GUI или консольное приложение, dll, пакет, ActiveX (наверное, есть еще варианты). Выбор вида компиляции связан со спецификой задачи, требованиями пользователя и вкусами разработчика. К этому вопросу я еще раз вернусь в разделе Исполнение кода.
Содержание раздела