Блог → Углубимся в историю: Borland Kylix 3 и C++. Часть 2

Но давайте вернёмся к главному новшеству в Kylix 3 - поддержке C++ и соответствующему IDE. Предлагаю сосредоточиться на возможностях (а заодно - и недостатках) среды программирования C++. Поскольку наборы компонентов одинаковы для обеих IDE, речь о новых компонентах пойдёт в разделе, посвященном Delphi Language. Возможности C++, в общем, те же, что и у Borland C++ Builder последних версий. Те же расширения языка, те же "прагмы". Вообще спору нет - по сравнению с Delphi IDE, среда C++ выглядела более профессионально: предоставлялась возможность выбирать между режимами компиляции Debug и Release и контролировать параметры оптимизации. В каком-то смысле даже странно, что возможности управления оптимизацией в одной IDE были шире чем в другой, ведь ядро компилятора практически аналогично в обеих IDE. Наверное у Borland'а были какие-то соображения на этот счёт.

Из новшеств следует отметить и функцию предварительной компиляции заголовочных файлов для ускорения процесса сборки приложений. Хотя предварительную компиляцию заголовков, появившуюся еще в Borland C++ Builder 3.0, трудно назвать новинкой, в документации эта функция относится к категории New Features, и для мира Linux IDE это было действительно так! Поэтому специально для тех, кто не сталкивался раньше с предварительной компиляцией, остановимся на этой технологии подробнее.



Существенно более низкая скорость компиляции проектов на C++ по сравнению с программами, написанными на "чистом Си", связана, отчасти, с гораздо более сложным анализом заголовочных файлов, содержащих объявления классов C++, который приходится выполнять компилятору. Для решения этой проблемы разработчики Borland и предложили использовать предварительную компиляцию заголовочных файлов (под компиляцией в данном случае подразумевается построение таблиц символов, т.е. symbol tables). Выигрыш в скорости основан на том, что заголовочные файлы, как правило - самая консервативная часть проекта, а заголовки стандартных библиотек разработчиками программ вообще не модифицируются. Таким образом, выполнив компиляцию заголовков один раз, можно существенно ускорить процесс сборки приложений.

Практически же эта нехитрая технология была реализована следующим образом. Для каждого набора директив #include, встречающегося в исходных файлах проекта, создаётся отображение (image), содержащее скомпилированные заголовки в той последовательности, в которой они включены в исходный файл. Все отображения, созданные для данного проекта, сохраняются в специальном файле. Совместное использование одного отображения скомпилированных заголовков несколькими модулями возможно только в том случае, если заголовки включены в исходные файлы в одной и той же последовательности. Поскольку секции исходных файлов, содержащие директивы #include, как правило, совпадают лишь частично, была введена специальная директива #pragma hdrstop. Если данная директива встречается в исходном файле, предварительная компиляция выполняется только для заголовочных файлов, включённых перед этой директивой. Благодаря директиве #pragma hdrstop исходные файлы

#include
#include
#include
#pragma hdrstop
#include "unitl.h"


и

#include
#include
#include
#pragma hdrstop
#include "unit2.h"


могут использовать одно и то же отображение заголовков. Режим предварительной компиляции заголовков управляется опциями раздела Precompiled Headers вкладки Compiler окна Project Options (по умолчанию режим включен). На этой вкладке можно также указать имя файла, в котором будут храниться скомпилированные заголовки для данного проекта (в противном случае компилятор сам выберет это имя). При желании вы можете включить в число предварительно компилируемых заголовков и те заголовочные файлы, что были созданы в рамках вашего проекта, если, конечно, вы уверены в их "незыблемости". Не следует включать в число предварительно компилируемых заголовочных файлов те, на которые ссылаются другие предварительно компилируемые заголовки. Директивы включения таких файлов следует размещать после директивы #pragma hdrstop (для этой цели может служить и опция Stop After раздела Precompiled Headers).

Теперь расскажу и о главном разочаровании, постигшем меня при знакомстве с C++ IDE. Разочарование связано с тем, что формат классов в Borland С++ отличается от формата GNU С++. Речь идёт о формате хранения классов в объектных файлах (*.o). При этом отличается не только формат измененных имен (mangled names), но и формат вызова методов (в GNU C++ это cdecl, а в Borland C++ - fastcall). При этом формат самих объектных файлов, генерируемых компилятором C++, полностью соответствует стандарту GCC. Kylix 3 даже был настолько любезен, что вставлял в объектные файлы таблицу символов, которую можно просматривать командой nm. Несовместимость форматов хранения информации о классах приводит к тому, что в Kylix невозможно использовать библиотеки Linux, экспортирующие интерфейсы C++ (например, непосредственно обращаться к библиотеке Qt library).

Необходимость в библиотеке-контейнере для библиотеки libqt вызвана не столько несовместимостью форматов хранения классов, сколько отсутствием обратной совместимости двоичных интерфейсов (и интерфейсов программирования) разных версий самой Qt library. В такой ситуации коммерческому компилятору с закрытым исходным кодом, который нельзя компилировать специально для каждого дистрибутива, все равно необходима "своя" версия библиотеки libqt.

Что же касается интерфейсов C, то, хотя в большинстве случаев библиотеки C можно использовать непосредственно, подводные камни существуют и здесь. Несовместимость Borland C со стандартами GNU C приводит к тому, что некоторые заголовочные файлы не соответствуют синтаксису, принятому в Kylix. Это касается, например, файла /usr/include/asm/types.h, на который косвенно ссылается файл /usr/include/linux/cdrom.h. Встроенные макросы GCC signed, const, inline незнакомы Borland C. Впрочем, с заголовочными файлами GNU C library, X library, OpenGL library и другими важнейшими файлами Linux API, проблем не возникает.

В общем, можно сказать, что хотя непреодолимых препятствий на пути использования интерфейсов Linux в проектах Kylix C++ и не было, разработчики всё же могли столкнуться с увлекательной проблемой адаптации заголовочных файлов GNU C для Kylix. Иногда проблему адаптации make-файлов удается решить, вставив перед директивой #include следующий набор директив:

#ifndef _GNUC_
#define _inline_ inline
#define _signed_ signed
#define _const_ const
#endif


В качестве примера программирования на C++ в Kylix приведу исходный текст "web-сервера за пять минут", созданного на основе компонента IdHTTPServer из пакета Indy. Сервер позволяет просматривать информацию о входящих запросах при помощи компонента Memo. Программисты, имеющие опыт работы в GNU C++, могут оценить удобство работы с классом String. В форме нового Kylix-приложения поместите компоненты Memo и IdHTTPServer. Назначьте обработчики события OnShow главной формы и OnCommandGet компонента IdHTTPServer. Тексты обработчиков и констант приводятся в следующем листинге файла Unitl.cpp (привожу содержательную часть листинга после всех директив include и pragma):

/* Корневой каталог web-документов */
#define HTML_DOCS_ROOT "/var/www/html"

/* Порт сервера по умолчанию */
#define HTTP_P0RT 8811
TForml *Form1;
_fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
IdHTTPServer1->ServerSoftware =
"Indy HTTP Web server Demo";
IdHTTPServer1->DefaultPort =
HTTP_P0RT;
}

void _fastcall
TForml::FormShow(TObject *Sender)
{
IdHTTPServerl->Active = true;
}

void _fastcall
TForml::IdHTTPServerlCommandGet
(TIdPeerThread *AThread,
TIdHTTPRequestInfo *ARequestInfo,
TIdHTTPResponseInfo *AResponselnfo)
{
String FName = HTML_D0CS_ROOT +
ARequestInfo->Document;
Memo1->Lines->Add("Request: " +
ARequestInfo->RawHTTPCommand);
Memo1->Lines->Add("Referer: " +
ARequestInfo->Referer);
Memo1->Lines->Add("Client IP: * +
ARequestInfo->RemoteIP);
Memo1->Lines->Add("=========");

if {FName [FName.LengthO] == '/')
FName = FName + "index.html";
else if {DirectoryExists (FName +
'/' )) {
AResponseInfo->Redirect( ARequestInfo->Document + '/' );
return;
}

if (FileExists(FName)) {
IdHTTPServer1->ServeFile( AThread, AResponselnfo, FName );
return;
}

AResponseInfo->ResponseNo = 404;
AResponselnfo->WriteHeader();
AResponseInfo->WriteContent();
}


Во избежание появления сообщений об ошибке в момент выхода из сервера, его рекомендуется запускать в режиме root. При программировании на С придется отказаться от расширений GNU С. Последнее можно считать недоработкой, во-первых, потому, что, как говорилось выше, расширенный синтаксис GNU С используется в заголовочных файлах Linux, а во-вторых, потому что среда Borland традиционно предоставляет программисту возможность выбирать стандарт языка, включая такие варианты как K&R и Unix V. При разработке компилятора для Linux было бы логично добавить в этот список стандарт GNU С.

Отмечу еще некоторые моменты, связанные с программированием в C++ IDE:
• Как и GNU С, Borland С поддерживает директиву встраивания функций - в Borland С эта директива имеет вид inline.
• По умолчанию переменные не размещаются в регистрах даже при наличии ключевого слова register. Для того, чтобы изменить это, следует установить соответствующую опцию на вкладке Advanced Compiler.
• Для того, чтобы связать компилируемую программу с разделяемой библиотекой, необходимо либо включить библиотеку в проект (меню Project -> Add to Project), либо вставить в текст программы директиву #pragma link, например:

#pragma link
"/usr/X11R6/lib/libX11.so"


Таким же образом можно подключать и внешние объектные файлы. Команда меню Project -> Export Makefile позволяет создавать make-файлы для утилиты make и консольного компилятора BC++. Создаваемые make-файлы имеют расширение *.mak. Тут нельзя не отметить своеобразный юмор разработчиков: в начале make-файла Kylix содержится комментарий, предупреждающий редактор Emacs о том, что тот имеет дело с make-файлом. Для условной компиляции платформо-зависимых фрагментов кода в платформо-независимых проектах в среде введены специальные встроенные макросы _Windows и _linux_, а макрос _BCPLUSPLUS_ позволит отделить код, предназначенный для Kylix C++ IDE, от кода, предназначенного для GNU С (макрос BCPLUSPLUS не определен в проектах, использующих язык С). Файлы программ, использующих CLX, отличаются воистину "дельфийскими" размерами (впрочем, KDevelop едва ли уступает Kylix по этому параметру), а вот программы, написанные на чистом С, радуют весьма компактным кодом. Причём иногда даже более компактным чем, у GCC!