    ГЛАВА    30 

    Содружество Delphi и C++ 

    Object Pascal и C++ — языки с очень схожими возможностями. У этих 
компиляторов множество одинаковых характеристик, включая принципы размещения 
объектов в памяти и использование виртуальных таблиц (Virtual table; VT в C++ 
и VMT — в Delphi). В этой главе рассказывается об использовании Delphi и C++ в 
одной связке. 
    Так почему вы хотите работать с двумя языками? Ответ прост: вас привлекает 
перспектива максимального использования имеющихся исходных текстов и широкий 
диапазон доступных возможностей. Примите во внимание огромное количество 
разработанных на C++ программ, широчайшие возможности Delphi no использованию 
баз данных, богатый набор СОМ-объектов и возможность работать с Web. Перевод 
мегабайтов исходных текстов на C++ в Delphi — работа весьма трудоемкая и не 
имеющая смысла, ведь можно использовать тексты программ без перевода. 

    ЗаМеТКУ 
    Вы наверняка внимательно изучили главу 25, "Программирование 
DLL: введение", и 26,   "Программирование DLL: сложный материал", и имеете опыт 
программирования как на Object Pascal, так и на Borland C++. Чтобы избежать ареста 
"языковой полицией" C++. в примерах на C++ используются простейшие типы, 
директивы и описания. Например, я применяю stdcall вместо WINAPI в описаниях функций. 

    Приготовление коктейля 

    Существует несколько способ совместного использования Pascal и C++, и у 
каждого из них свои ограничения. В каждом случае следует выбирать путь, 
наиболее подходящий для конкретной ситуации. 
    
    ——— Совместное использование классов Delphi и C++ 
    Здесь вы узнаете об использовании скомпилированного кода в Delphi и C++. 

    ——— Виртуальные таблицы при совместном использовании классов 
    Изучите концепцию взаимодействия с объектами, определение которых 
размещено во внешнем по отношению к вашему ЕХЕмодуле. 

701


    Отдельные программы 

    Один из способов использования двух языков — скомпилировать тексты на 
каждом из них в отдельные выполняемые модули, а затем вызвать из модуля, 
созданного на одном языке, модуль, созданный на другом, с помощью метода 
СгеateProces5. Связь между модулями и управление общими данными можно 
реализовать через некий "промежуточный слоТ*'благодаря возможностям 
операционной системы. Например, передачу данных можно организовать как 
передачу сообщения wm_CopyData (глава 32, "Катание на санках"). Другой способ— 
использовать объекты отображаемых файлов {file-mapping}. Данные пишутся на 
диск одним модулем, а считываются другим. 
    Такой путь, 'Возможно, не самый лучший, однако он быстро реализуется и 
обычно не требует чреватой ошибками переделки исходных текстов модулей. 
    Можно также использовать модель Component Object Model (СОМ^писывающую 
формат объектов и связь между ними независимо от языка (подробнее об этом 
рассказывается в главе 19, "Работа с интерфейсами СОМ"). 

    OBJ-файлы 

    Delphi поддерживает объектные файлы в фермате кода Intel. Этот формат 
используется как Borland C++, так и Delphi (при установленной опции генерации 
объектных файлов; по умолчанию определены DCU-файлы). Visual C++ использует 
COFF-формат объектных файлов, и поэтому такой путь неприемлем при работе с ним. 

    DLL 

    Пожалуй, проще всего получить коктейль из Delphi и C++ с помощью библиотек 
DLL. Они могут генерироваться как Delphi, так и C++, и функции из DLL, 
написанной на одном языке, могут легко и просто вызываться модулем, написанном 
на другом. Допускается даже использование классов одного языка другим. 
    Из-за гибкости этого метода основное внимание в данной главе уделяется 
именно ему. 

    Соглашения о вызовах 

    Рассмотрим вопрос о соглашении о вызовах (calling conventions). Соглашение 
о вызовах можно считать протоколом передачи данных в подпрограмму — формат и 
последовательность передачи. Хотя C++ и Delphi поддерживают некоторые общие 
соглашения, вызовы по умолчанию у них разные. Прежде чем перейти к детальному 
рассмотрению этого вопроса, остановимся на поддерживаемых соглашениях, 
приведенных в табл. 30.1. (Следует отметить, что в различных компиляторах C++ 
могут использоваться различные соглашения о вызовах; особенно богатыми 
возможностями в этом плане обладает Watcom C++.—Прим. ред.) 

    Таблица 30.1. Общие соглашения о вызовах    ? '  " " -.'-,•;:,,,.. 

    Название	Описание C++	Описание Delphi	Используется по умолчанию 

    C-Decl	cdecl	cdecl	C++ 
    Pascal	pascal	pascal	Delphi 1.0 и Windows З.х 
    StdCall	stdcall	stdcall	Win95 и WinNT 
    SafeCall	—	safecall	Только Delphi! 
    Fast call	fastcall	register.	Delphi 2.0, 3 

    В столбце "Название" приведены имена, которые используются в этой главе. В 
столбце "Описание C++" содержатся директивы для определения соглашения о 
вызовах в C++, а в "Описание Delphi" — директивы для той же цели в Delphi в 
последнем столбце, "Используется по умолчанию", указано, каким языком или 
операционной системой данное соглашение принято по умолчанию. 
    Вы, вероятно, заметили, что в C++ и Delphi применяются различные 
соглашения о вызовах по умолчанию. Когда вы декларируете функцию, размещенную 
в другом модуле, убедитесь, что вы определили соглашение, используемое 
функцией. Расхождение в передаче параметров функции может привести к самым 
неожиданным результатам. 

    702                                       Часть VI. Сложный материал 

    В табл. 30.1 показано, что в Delphi по умолчанию установлен вызов типа 
Fast Call, а в C++— соглашение СDecl. Поскольку одно соглашение должно 
соответствовать другому, вы можете встать перед выбором, какой именно язык 
использовать. Никакой! Применяйте то. соглашение, которое принято 
в^перационной_,си^теме (а именно— StdCall); это наиболее разумное решение, 
обеспечивающее к тому же совместимость с программным обеспечением, 
разработанным другими программистами. 

    Как быть конформистом 

    Одна из проблем состоит в том, что не все языки поддерживают все 
соглашения о вызовах. Даже в тех случаях, когда поддерживаются различные 
соглашения, эта поддержка либо ограничена, либо ее реализация различна в 
разных языках (например, реализации Fast call). Проблема становится особенно 
очевидной, когда вы используете чужой код. 
    Часто разработчики создают библиотеки DLL на,яаыке_.0±+, нисколько не 
заботясь о том, что экспортируемые функции используют (как правило) соглашения 
C-Decl и что C++ искажает имена функций (mangled патент. Кстати, почему-то они 
не считают нужным документировать использование соглашений, а иногда даже не 
задумываются о том, какие именно соглашения они применяют. 
    Лично я с подозрением отношусь к таким разработкам с недокументированными 
соглашениями, особенно если использованы соглашения, отличные от StdCall. Я 
считаю, что это однозначно говорит об отношении разработчика к качеству продукта. 
    В главе 26, "Программирование DLL: сложный материал", предоставляется 
детальная информация о соглашениях о вызовах, параметрах, возвращаемых 
значениях, работе со стеком и регистрами. зэмбтку 
    Хотя все соглашения о вызовах детально описаны в упомянутой главе, 
соглашение C-Decl требует некоторых дополнительных пояснений. Если вы, 
несмотря на мои навязчивые рекомендации использовать соглашение StdCall, не 
согласны со мной, примите к сведению мои предупреждения. 
    Реализация соглашения C-Decl в Delphi ограничена хотя бы потому, что 
C-Decl чувствительно к регистрам символов. Кроме того, по умолчанию перед 
именем вставляется символ подчеркивания (_) (конечно, эта опция может быть 
отключена или изменена, но по умолчанию...). Результат? Компиляция кодов C++ и 
Delphi выполняется отлично, а при компоновке или загрузке DLL появляются 
неразрешенные ссылки (это не те, которые запрещены, а те, которые компоновщик 
не смог найти, т.е. разрешить). Предположим, что некто создал DLL, в которой 
описал функцию GetHeartRate по соглашению C-Decl. В результате она 
экспортируется как _GetHeartRate и для ее вызова в Delphi следует 
предпринимать специальные меры. 
    Необходимо помнить и об искажении имен в C++ (так называемое mangled names 
— изменение, искажение имен). Дело в том, что в C++ разрешена перегрузка 
функций (function overloading) и допустимо использование разных функций с 
одинаковыми именами. Например, функции foo (int) и foo (int, int) различить по 
имени невозможно, их реальные имена включают еще и типы принимаемых ими 
параметров. В действительности имена функций в таком случае будут выглядеть 
наподобие @foofunc$qinqminzc$v. A Delphi, к сожалению, не поддерживает таких искажений. 
    Отключить процесс искажения имени функции можно путем описания ее как 
extern "С", т.е. экспортируемые функции следует описывать примерно так: 

    extern "С" int   stdcall SomeFunction(int) 


    Применение объектных файлов для совместного использования C++ и Object Pascal 

    Созданные C++ объектные файлы могут использоваться Delphi с помощью 
директивы компилятора {$L FileName}. По умолчанию для объектных файлов 
установлено расширение OBJ. В процессе компоновки Delphi внедряет содержимое 
объектного файла в результирующий выполняемый файл, что известно как 
статическая компоновка (static link). 

    Глава 30. Содружество Delphi и C++                                    703 


    Использование объектных файлов C++ в Delphi 

    Итак, вы компонуете объектные файлы в проекте Delphi для доступа к 
содержащимся в них функциям. Просмотрите листинг 30.1, который находится на 
дополнительно распространяемой дискете. 

    ЛИСТИЖ-30.1. \UDELPHI3\CHP30\SAMP1.CPP                                     

                \ int iGlobal = 0; 
    int _stdcall SetGlobalValue (int iValue) (   int iTmp = ,iGlobal; 
    iGlobal = iValue; 
    return iTmp; 
    } 
    int _stdcall GetGlobalValue() ( return iGlobal; 
    ) 

    Здесь приведен простейший пример. Декларирована и инициализирована нулем 
глобальная переменная iGlobal. Две функции, декларированные по соглашению 
_stdcall, используются для получения значения переменной и установки нового 
значения (при этом функция возвращает предыдущее значение глобальной 
переменной). В листинге 30.2 показано, как эти 4>ункции могут применяться в Delphi. 

    Листинг 30.2. USEOBJUI . РАЗ. Использование функции C++ (фрагмент)   

    var 
    Formi: TFormI ; 
    implementation 
    ($R *.DFM) 
    ($L SAMP1} //Подключение SAMP1.0BJ! 
    function SetGlobalValue(iValue: integer): integer; stdcall; external
    function GetGlobalValue: integer; stdcall; externals
    procedure TFormI.ButtonlClick(Sender: TObject) ; 
    begin 
    SetGlobalValue(10); //Установка для переменной в OBJ-файле значения 10. 
    ShowMessage(Format('Variable in OBJ contains %d',[GetGlobalValue])); 
    end; 
    end. 

    После директивы ( $L SAMP1}, использующейся для подключения файла SAMP1. 
OBJ, идет описание функций SetGlobalValue и GetGlobalValue. Обратите внимание 
на их описание как external-функций, что сообщает компилятору о расположении 
тела этих функций во внешнем объектном файле. 
    Но почему пример настолько прост? Как насчет классов C++, вызовов 
библиотеки времени выполнения C++ (C++ Run-time library) или доступа к 
системным глобальным переменным, например к errno? Вот здесь следует обуздать 
свое воображение и вспомнить о том, что использование объектных файлов очень ограничено. 
    Вы можете получить доступ к своим подпрограммам в объектном модуле, но 
если эти подпрограммы используют подпрограммы из других модулей (например, из 
библиотеки времени выполнения или Windows API), компоновщик Delphi "поднимет 
руки вверх". Точно так ваш объектный файл не должен содержать описаний классов 
C++ (это связано с тем, что поддержка классов в C++ неявно использует другие модули). 

    704                                       Часть VI. Сложный материал 

    Если вам никак нельзя обойтись без вызовов Windows API в объектном файле, 
можете воспользоваться I принципом "нормальные герои всегда идут в обход". 
Предположим, что вы хотите вызвать функцию API "Уа MessageBox.  Используем  
описание функции  в  виде extern   "С"   int   _stdcall MessageBox (long, 
char*, char*, long) ; (т.е. как функцию, расположенную в другом модуле). При 
компоновке Delphi увидит, что это функция внешняя, и будет искать ее в других 
модулях. Идаидет, но в Delphi, а не в C++. Хотя это и решает проблему, иногда 
может возникнуть труднообнаруживаемая ошибка. 
    Ваше первое побуждение — подключить все необходимые библиотечные объектные 
файлы? Даже если это удастся, то вряд ли сработает, не говоря уже о том, что 
размеры выполняемого файла могут неоправданно увеличиться. Кроме того, Dejrlhi 
не работает с библиотечными (LIB) файлами. Вдобавок ко всему возникнет 
проблема, связанная с выполнением инициализирующего библиотечного кода... 
    Последняя неприятность — вы не получите доступа к глобальным переменным в 
объектном файле C++. Однако вы можете, как в приведенном выше примере, 
определить переменную в своем файле и функции для получения и изменения ее 
значения и использовать их вместо непосредственного доступа к переменной. 

    Использование объектных файлов Delphi в C++ 

    Так же, как и C++, Delphi может генерировать объектные файлы для их 
использования в программах на C++. Кстати, Borland C++ Builder применяет 
версию объектных файлов библиотеки Delphi 2.0 VCL (да-да!). Разработчики C++ 
добавили специальный переключатель в компилятор DCC32 (этот переключатель 
доступен только в версиях, поставляемых с C++ Builder и ВС 5.02), который 
компилирует исходный текст на Pascal в объектный файл, так же как и генерирует 
соответствующий заголовочный файл C++. 
    При использовании объектных файлов Delphi компоновщик C++ получает доступ 
к глобальным переменным Delphi. Но помните, что обратное неверно. 
    Предположим, что объектный файл Pascal декларирует следующую глобальную переменную: 

    var iGlobal: integer; 

    Получить непосредственный доступ к ней из C++ можно, объявив ее следующим образом: 

    extern int iGlobal; 

    Delphi не создает объектных файлов по умолчанию. Для этого необходимо 
включить опцию Generate Object Files во вкладке Linker диалогового окна 
Project Options, вызываемого с помощью команды Project^Options. 

    Применение DLL для совместного использования C++ и Object Pascal 

    Вероятно, простейший и наиболее логичный путь совместного применения кода 
C++ и Object Pascal — использование DLL. DLL предназначены для создания 
стандартизованных, независимых от языка библиотек с возможностью многократного 
использования кода, в том числе разными приложениями. Я не буду много говорить 
о достоинствах DLL, так как надеюсь, что к этому моменту вы уже ознакомились с 
главами 26 и 27, в которых эти вопросы освещены более подробно. 
    Итак, у вас есть три способа применения объектов Delphi и C++ с помощью DLL. 
    • Использование СОМ-сервера. 
    • Использование линейных функций, в которые "обернуты" методы 
экспортируемых классов или просто вызовы библиотечных функций. 
    • Экспортирование виртуальных интерфейсов классов. 
    Первый путь несколько выходит за рамки этой главы. Упомянем лишь о слухах, 
что в будущие версии Microsoft WinX будет включена полная поддержка СОМ. 
Рассмотрим два других способа использования "двуязычия". 

    Линейные функции 

    Линейные функции (flat functions} — это подпрограммы, экспортируемые иэ 
DLL. DLL может быть загружена ЕХЕ или другой DLL неявно с использованием 
специальных описаний или явно, с помощью функций LoadLibrary, LoadLibraryEx и 
GetProcAddress. Линейные функции обычно непосредственно содержат необходимый 
код (или вызы

    Глава 30. Содружество Delphi и C++                                    705 
    23 Использование Delphi 3. Специальное издание 

    вают другие функции с ним), что фактически сводит функции к библиотечным, 
или используются в качестве посредников к методам существующих классов. 
Представьте экспортируемую функцию Function!, которая создает объект класса 
TFoo. Экспортируемая функция Function2 просто вызывает некий метод класса, a 
Functions освобождает созданный первой функцией объект. Это и есть заключение 
класса в оболочку (wrapping) из экспортируемых функций. 

    Библиотечные подпрограммы 

    Предположим, ваша программа на C++ работает с данными, представленными в 
виде строки, разделенной запятыми. Объект Delphi TStringList легко справляется 
с такими данными, а потому хотелось бы использовать его для работы в составе 
программы на языке C++. В листинге 30.3 содержится проект DLL и экспортирующая 
функция CDSort, которая сортирует представленные таким образом данные. 

    Листинг 30.3. \UDELPHl3\CHP30\SDFMOD.DPR. Пример использования объекта TStringList 

    library SDFMod; 
    uses 
    SysUtils, 
    Dialogs, 
    Classes; 
    procedure CDSort(pStrings: PChar); stdcall; 
    begin 
    if not Assigned(pStrings) then Raise 
    EInvalidPointer.Create('Buffer is nil'); 
    with TStringList.Create do try 
    CommaText := pStrings; 
    Sorted := True; 
    StrCopy(pStrings,PChar(CommaText)) ; 
    finally 
    Free; 
    end; 
    end; 
    exports CDSort; 
    begin 
    end. 

    Функция CDSort — всего лишь простейший пример того, что можно сделать с 
помощью Delphi для дополнительного использования в программе на C++. Вы можете 
создать множество таких процедур и объединить их в одной DLL. В листинге 30.4 
показано использование описанной в предыдущем листинге функции CDSort 
программой на C++. 

    Листинг 30.4. \UDELPHI3\CHP30\USELIB. СРР. Динамическая загрузка SDFMOD. 
DLL и использование функции CDSort 

    #include <windows.h> 
    #include <string.h> 
    #define C_STRING \ "\"CString\",\"DString\",\"BString\",\"AString\"" 
    void (_stdcall *CDSort)(char*) = NULL; 
    #pragma argsused 
    int WINAPI WinMain(HINSTANCE hCurInstance, 
    HINSTANCE hPrevInstance, LPSTR IpCmdLine, int nCmdShow) ( char* pBuf = new char[256]; 


    706                                       Часть VI. Сложный материал 


    strcpy(pBuf,C_STRING) ; 
    MessageBox(0,pBuf,"Before",MB_OK) ; 
    HINSTANCE hinst = LoadLibrary("SDFMod.dll"); 
    if (hinst) { 
    (FARPROC)CDSort = GetProcAdd.r:e.ss.(.hJn.st, "CDSort") ; 
    if (CDSort) CDSort'(pBuf') ; 
    MessageBox(O.pBuf,"After",MB_OK);                                          
    } 
    delete pBuf; 
    return 1; 
    ) 

    Обратите внимание на строку void (_stdcall *CDSort) (char*) = NULL;, 
описывающую переменную с именем CDSort, которая представляет собой указатель 
на функцию. Позже в нее будет помещен адрес экспортируемой одноимённой функции 
из DLL. Обратите особое внимание на описание __stdcall, цель которого— описать 
функцию как использующую соглашение StdCall, как было сделано при создании DLL. 
    Адрес функции получаем с помощью функции GetProcAddres5, которой в 
качестве первого параметра передаем значение, полученное при вызове функции 
LoadLibrary, а в качестве второго— имя экспортируемой функции. Возвращаемое 
значение — искомый адрес экспортируемой функции в момент выполнения программы: 

    (FARPROC)CDSort = GetProcAddress(hinst,"CDSort"); 

    Если вызов GetProcAddress выполнен успешно (возвращено ненулевое 
значение), функция CDSort может быть вызвана с передачей ей в качестве 
параметра адреса буфера pBuf, данные в котором будут изменены кодом в DLL и 
затем показаны при вызове функции MessageBox. 
    Рассмотрим более сложный пример — создание и вывод формы Delphi из 
программы на C++. Экспортируемая функция может выглядеть примерно так: 

    function ShowModalForm(hMainWnd: THandle): integer; stdcall; 
    begin 
    Application.Handle := hMainWnd; 
    with TFormI.Create(Application) do 
    try 
    result := ShowModal; 
    finally Free; 
    end; 
    end; 

    Текст на C++ не сильно отличается от приведенного в листинге 30.4. 
Основное изменение касается описания указателя на функцию. 

    int (_stdcall *ShowModalForm)(HWND) = NULL; 

    Конечно же, вызов функции будет несколько отличаться от вызова CDSort. 

    if (hinst) { 
    (FARPROC)ShowModalForm = GetProcAddress(hinst, "ShowModalForm") ; 
    if (ShowModalForm) ShowModalForm(0); 

    Теперь рассмотрим другой способ работы с DLL — помещение классов в 
"функциональные" оболочки. 

    Оболочки классов 

    Предположим, вы хотите получить из Delphi доступ к экземпляру 
существующего класса C++. Для этого экспортируйте функции для создания и 
уничтожения экземпляра класса и по одной функции для каждого открытого члена 
класса. Вот как выглядит реализация сказанного в листинге 30.5. 

    Глава 30. Содружество Delphi и С+Т-                                    707 


    Листинг 30.5. \UDELPHI3\CHP30\CWRAP.CPP. Экспортируемые функции для 
доступа к классу TCharQue         , 

    #include <windows.h> 
    #include <queues.h> 
    typedef TQueue <char> TCharQue; 
    extern "C" ( 
    //Returns an instance of TCharQue 
    HANDLE _stdcall _export QueLoadQueueInst (int iSize) 
    { TCharQue* CharQue = 
    new TCharQue(( iSize ? iSize : DEFAULT_QUEUE_SIZE)); 
    return (HANDLE)CharQue; 
    } 
    //Releases an instance of TCharQue 
    void stdcall _export QueFreeQueueInst(TCharQue* CharQue) 
    ( delete CharQue; 
    } 
    void _stdcall _export QueFlush(TCharQue* CharQue) 
    { CharQue->Flush() ; 
    } 
    int stdcall _export QueIsEmpty(TCharQue* CharQue) 
    { return CharQue->IsEmpty(); 
    } 
    int _stdcall _export QueIsFull(TCharQue* CharQue) 
    ( return CharQue->IsFull(); 
    } 
    void _stdcall _export QuePut( TCharQue* CharQue, char t ) 
    { CharQue->Put(t) ; 
    ) 
    char _stdcall _export QueGet(TCharQue* CharQue) 
    ( return CharQue->Get(); 
    } 
    int   stdcall-"export QueGetItemsInContainer( 
    —                           TCharQue* CharQue) 
    ( return CharQue->GetItemsInContainer(); 
    ) } 

    Экспортируемые функции, показанные в листинге 30.5, используются в 
качестве интерфейса с членами класса. 
    Класс TQueue реализует очередь (первым пришел — первым вышел; FIFO), 
используя для установления в очередь функцию Put, а для выхода из очереди — 
функцию Get. 
    Применять этот класс полезно, например, в случаях, когда получение данных 
происходит со скоростью, большей скорости их обработки, и они должны какое-то 
время храниться в памяти (например, при работе с последовательными портами). 
    Подпрограмма QueLoadQueueInst возвращает вновь созданный экземпляр класса 
TCharQue в вызвавшую программу. Вызывающая программа может трактовать 
полученное значение как дескриптор, который следует передать назад при 
обращении к экземпляру класса. По окончании работы следует вызвать функцию 
QueFreeQueueInst, которая просто вызывает метод delete для удаления экземпляра 
класса из памяти. 
    Перейдем на другую сторону дороги и посмотрим, что происходит со стороны 
Delphi, а именно — как можно воспользоваться DLL из программы на Delphi 
(листинг 30.6). 

    , Листинг 30.6. \UDELPHI3\CHP30\CWRAPU1. PAS. Использование CWRAP. DLL 
    
    unit cwrapul; 
    interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 


    708                                       Часть VI. Сложный материал 


    StdCtrls; 
    type 
    TFormI = class(TForm) 
    Buttoni: TButton; 
    procedure ButtonlClick(Sender: TObject); 
    private 
    { Private declarations } 
    public 
    ( Public declarations } 
    end; 
    var 
    Formi: TFormI; 
    const 
    CWRAP = 'C:\BOOKS\DELPHI30\CHAPT30\CODE\CWRAP.DLL'; 
    function  QueLoadQueueInst(iSize: integer): THandle; 
    stdcall  external CWRAP; 
    procedure QueFreeQueueInst(hCharQue: THandle); 
    stdcall  external CWRAP; 
    procedure QueFlush(hCharQue: THandle); 
    stdcall  external CTRAP; 
    function  QueIs Empty(hCharQue: THandle)  longbool; 
    stdcall  external CWRAP; 
    function  QueIsFull(hCharQue: THandle): longbool; 
    stdcall  external CWRAP; 
    procedure QuePut( hCharQue: THandle; t: char ); 
    stdcall  external CWRAP; 
    function  QueGet(hCharQue: THandle): char; 
    stdcall  external CWRAP; 
    function  QueGetItemsInContainer(hCharQue: THandle)': integer; 
    stdcall  external CWRAP; 
    implementation 
    ($R *.DFM) 
    procedure TFormI.ButtonlClick(Sender: TObject); 
    var 
    hinst: THandle; 
    begin 
    hinst := QueLoadQueueInst(0) ; 
    if QueIsEmpty(hinst) then 
    ShowMessage('IsEmpty = TRUE') ; 
    QuePy±( hinst, 'H' ); 
    QuePut( hinst, 'E' ) ; 
    QuePut( hinst, 'L' ) ; 
    QuePut( hinst, 'L' ) ; 
    QuePut( hinst, '0' ) ; 
    if not QueIsEmpty(hinst) then 
    ShowMessage(IntToStr(QueGetItemsInContainer(hinst))) ; 
    ShowMessage(Format('%s%s%s%s%s', 
    [ QueGet(hinst),QueGet(hinst), QueGet(hinst),QueGet(hinst), QueGet(hinst)] 
    )) ; 
    QueFreeQueueInst(hinst); 
    end; 
    end. 


    Глава 30. Содружество Delphi и C++                                    709 

    Функции QueIsEmpty и QueIsFull в модуле C++ определены как возвращающие 
целые значения, а в модуле Delphi — как возвращающие логические значения, так 
что вы можете использовать их в логических операциях без преобразования типов 
(такое изменение типа возвращаемого значения в данном случае вполне корректно, 
так как типы LongBool в Pascal и int в C++ имеют один размер). 

    Взаимодействие с виртуальными таблицами 


    Другой способ взаимодействия кодов Delphi и C++ в DLL — сделать 
виртуальный интерфейс объектов экспортируемым. В DLL это выполняется путем 
экспортирования единственной подпрограммы, возвращающей экземпляр класса, в 
вызывающую программу. Такую операцию позв.одяют выполнить как C++, так и 
Delphi. Вызывающая программа может создаваться на любом языке, поддерживающем 
формат СОМ-объектов и загрузку и доступ к экспортируемым из DLL подпрограммам. 
    Пример в этой главе демонстрирует библиотеку DLL на C++, которая 
вызывается из ЕХЕ-модуля Delphi. В главе 26, "Программирование DLL: сложный 
материал", рассматриваются детали таких вызовов, так же как -  и обратный 
вариант вызова, использующий библиотеку DLL, написанную на Pascal. 

    Виртуальный класс в C++ 

    Выше в этой главе уже шла речь об экспортировании линейных функций для 
доступа к экземпляру класса C++ TQueue. Программа использовала для этого его 
функции-члены. В листинге 30.7 приведен основной файл проекта на C++ CClass, 
который осуществляет доступ к экземпляру класса другим путем. Эта технология 
базируется на создании класса-оболочки, все функции которого виртуальны, а 
значит, их адреса помещены в VT (виртуальную таблицу) экземпляра объекта. 
(Учтите, что одинаковое '])азм'ещ?{П1е'_адрёсов' в виртуальных таблицах Delphi 
и Borland C++ не говорит о том, что это будет справедливо для любого 
компилятора C++, особенно если принять во внимание, что способ реализации 
виртуальности не стандартизован и целиком находится во власти разработчика 
компилятора. —Прим. ред.) 

    Листинг 30.7. \UDELPHl3\CHP30\CCLASS. срр. функция GetQueue, возвращающая 
экземпляр класса TCharQueue s 

    #include <windows.h> 
    #include <queues.h> 
    #include <stdio.h> 
    #include "charque.h" 
    extern "C" TCharQueue* stdcall export GetQueue(int iMax) 
    { return new TCharQueue(iMax); 
    » 

    Экспортируемая функция GetQueue получает в качестве параметра целое число, 
определяющее максимальный размер очереди. Возвращаемое значение— указатель на 
экземпляр класса TCharQueue, который является виртуальной оболочкой для класса 
TQueue. Описание класса TCharQueue приведено в листинге 30.8. 

    Листинг 30.8. \UDELPHI3\CHP30\CHARQUE.H. Описание класса TCharQueue        
                      ; 
    #ifndef charque_h 
    #define charque h 
    #include <queues.h> 
    typedef TQueue <char> TCharQue; 
    class TCharQueue { public: 
    TCharQueue(int i) ; 
    -TCharQueue() ; 
    private: 
    TCharQue* FCharQue; 
    public: 
    virtual char _stdcall Get(); 


    710                                      Часть VI. Сложный материал 


    virtual void stdcall Put( char t ); 
    virtual int _stdcall IsEmptyO; 
    virtual int _stdcall IsFullO; 
    virtual int   stdcall GetItemsInContainer(); 
    virtual void stdcall Flush(); 
    virtual void stdcall Delete (); 
    }; 
    #endif 

    В листинге 30.8 содержится файл CharQue.h, начинающийся с описания класса 
TQueue с новым именем TCharQue. Поскольку TQueue — шаблон класса, должен быть 
определен тип данных для класса (для простоты используется тип char). 
    Класс TCharQueue очень прост, но его необходимо определить, так как он 
является тем средством передвижения, которое экспортирует функциональность класса. 
    Класс TCharQueue имеет закрытый член класса, содержащий указатель на 
экземпляр класса. Он инициализируется, как вы увидите позже, в конструкторе 
класса. Более важен в настоящий момент открытый интерфейс. Первое, на что 
следует обратить внимание, все функции — члены класса объявлены как 
виртуальные. Как уже упоминалось, цель, преследуемая таким описанием, — 
внесение адресов функций в виртуальную таблицу. Delphi в описываемом нами 
способе может получить доступ только к виртуальным методам класса. Второе, на 
что следует обратить внимание, — все функциичлены объявлены по соглашению   
stdcall. Это сделано для того, чтобы все подпрограммы были доступны независимо 
от используемого языка программирования. ~'В листинге 30.9 содержится 
реализация описываемого класса. 

    Листинг 30.9. \UDELPHI3\CHP30\CHARQUE. СРР. Реализация класса TCharQueue 

    #nclude "charque.h" 
    TCharQueue::TCharQueue(int i) 
    ( FCharQue = NULL; 
    FCharQue == ( ! i ? new TCharQue : new TCharQue(i) ); 
    if (!FCharQue) throw; 
    TCharQueue::-TCharQueue() 
    { if (FCharQue) delete FCharQue; 
    ) 
    char _stdcall TCharQueue::Get () 
    { return FCharQue->Get(); 
    } 
    void _stdcall TCharQueue::Put ( char t ) 
    ( FCharQue->Put(t) ; 
    } 
    int _stdcall TCharQueue::IsEmptyO 
    { return FCharQue->IsEmpty(); 
    } 
    
    int _stdcall TCharQueue::IsFull() 
    {return FCharQue->IsFull() ; 
    } 
    int _stdcall TCharQueue::GetItemsInContainer() 
    { return FCharQue->GetItemsInContainer() ; 
    } 
    void _stdcall TCharQueue::Flush() 
    { FCharQue->Flush(); 
    } 
    void _stdcall TCharQueue::Delete() 
    9 { ::delete this; 


    Глава 30. Содружество Delphi и C++                                   711 

    Как видите, каждый вызов метода TCharQueue просто дублирует вызов 
соответствующего метода TCharQue. Конструктор класса создает экземпляр класса 
TCharQue и сохраняет указатель на него во внутренней переменной, 
использующейся при вызовах методов. 
    Обратите внимание на реализацию освобождения экземпляра объекта из памяти, 
реализованную не как специальная функция, а как виртуальный метод. 

    Использование виртуального класса в Delphi 

    После создания CCLAS S.DLL приступим к написанию программы, которая ее 
использует. Как именно это сделать, показано в листинге 30.10. 

    Листинг 30.10. \UDELPHI3\CHP30\LOADU1. РАЗ. Использование TCharQueue 

    unit loadul; 
    interface 
    I  usea 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; 
    'type 
    TFormI = class(TForm) 
    GroupBoxl: TGroupBox; 
    Button3: TButton; 
    procedure Button3Click(Sender: TObject); 
    private 
    { Private declarations } 
    public 
    { Public declarations ) 
    end; 
    TCharQueue = class 
    public 
    function    Get: char 
    stdcall virtual abstract 
    procedure   Put( const t: char ) 
    stdcall virtual abstract 
    function    IsEmpty Longboo.. 
    stdcall virtual abstract 
    function    IsFull: Longbool 
    stdcall virtual abstract 
    function    GetItemsInContainer: integer 
    stdcall virtual abstract 
    procedure   Flush 
    stdcall virtual abstract 
    procedure   Delete 
    stdcall virtual abstract 
    end; 
    var 
    Formi: TFormI; 
    function GetQueue(iMax: integer): TCharQueue; 
    stdcall; external 'CClass'; 
    implementation 
    ($R *.DFM) 
    procedure TFormI.Button3Click(Sender: TObject); 
    begin 
    With GetQueue(5) do 
    try 


    712                                       Часть VI. Сложный материал 


    Put ( ' H ' ) ; 
    Put ( ' E ' ) ; 
    Put ( ' L' ) ; 
    Put ( • L' ) ; 
    Put ('0'); 
    if IsFulK) then 
    ShowMessage(Format('There are %d characters in Queue', 
    [GetItemsInContainerQ])) ; 
    ShowMessage( Format('%s%s%s%s%s', 
    [ GetO , 
    Get(), 
    GetO, 
    GetO , 
    Get 0 ] ) ) ; 
    if IsEmptyO then 
    ShowMessage(Format('There are %d characters in Queue', 
    [GetItemsInContainer()])); 
    finally 
    Delete; 
    end; 
    end; 
    end. 

    Последняя часть раздела интерфейса пытается неявно загрузить CCLASS . DLL 
и импортировать функцию GetQueue. function GetQueue(iMax: integer): 
TCharQueue; stdcall; external 'CClass'; 
    Конечно, для того чтобы скомпилировать этот код, необходимо, чтобы к этому 
моменту класс TCharQueue был описан, что и сделано в этом же разделе. Описание 
класса совпадает с описанием в исходном тексте на C++, но все методы описаны 
как abstract. При описании класса таким образом компилятор не будет жаловаться 
на_отсугствиетекста функций и линковщик не будет пытаться разрешить'ссыл'ки на 
них"""                                           , 
    Заметим, что при объявлении абстрактных функций имена методов не должны 
совпадать с именами функций в DLL. Дело в том, что ссылки на функции 
разрешаются не по именам, а по смещениям в виртуальной таблице. Поэтому важны 
не имена функций, а порядок их описания (порядок их обязательно должен быть 
тем же, что и в описании в DLL). Однако те же имена, что и в DLL, упростят 
написание и понимание. 
    В этом примере буквы, составляющие слово "Hello", помещаются в очередь и 
выводится окно с сообщением о количестве символов в очереди. Затем символы из 
очереди отображаются в другом окне. После этого еще в одном окне сообщается о 
количестве оставшихся после этого в очереди символов (0). 

    Таблица преобразования типов 

    В табл. 30.2 показаны общие типы данных C++ и Delphi, т.е. эти типы данных 
C++ могут использоваться без преобразования типов в Delphi. 

    \ Таблица 30.2, Типы данных Delphi и C++ 

    Delphi                      C++ 

    Целочисленные данные 
    Shortint                  short 
    Smallint                  unsigned short 
    Byte                      BYTE 
    char                      unsigned short 


    Глава 30. Содружество Delphi и C++                                   713 


    Окончание табл. 30.2 

    Delphi_______________C++________________________________________ 

    Целочисленные данные' 
    Cardinal	unsigned int 
    Integer	int 
    Word	unsigned int 
    Longint	long 
    DWORD	unsigned long 
    DWORD	UINT 

    Данные с плавающей точкой 
    Double	double 
    Extended	long double 
    Comp	unsigned long 
    Real — 
    Single	float 

    Символьные и строковые типы 
    Char	char 
    String — 
    ShortString — 
    Wchar	wchar t 
    Pchar	char*,LPSTR 

    Логические типы 
    Boolean	Любой однобайтовый: BYTE, Char, enum 
    Bool	bool 
    WordBool	unsigned int 
    LongBool	long 

    Другие типы 
    pointer	void* 
    Pinteger	int* 
    Variant — 
    Currency — 
    Set — 
    
    
    Замечание о перечислимых типах данных (enumerated): в Delphi этот тип— 
однобайтовый (unsigned byte), а в C++—двубайтовый (unsigned short). Однако в 
обоих языках есть опции для изменения представления перечислимых типов и вы 
можете указать Delphi использовать совместимый с C++ тип данных. В Delphi, в 
частности, есть две директивы компилятора для изменения размера перечислимого типа: 

    ($2),($MINENUMSIZE). 

    В Delphi константы (const) используются так же, как в C++. Пусть функция 
Foo в C++ описана так: 

    int _stdcall _export Foo(const T tVar); 

    Тогда эквивалентное описание в Delphi будет выглядеть следующим образом: 

    function Foo(const tVar: T); integer; stdcall; 


    714                                       Часть VI. Сложный материал 

    Интересно также проследить за использованием указателей на функции. В C++ 
вы можете определить пользовательский тип данных с помощью typedef, например 

    typedef int (_stdcall TMyProc)(char) ; 

    Теперь можно определить переменную этого типа TMyProc MyProc; . Ниже 
приведено эквивалентное определение типа и описание переменной на Pascal. 

    type TMyProc = function(c: PChar): integer; stdcall; 
    var MyProc: TMyProc; 

    Можно определить указатель на функцию и без специального описания типов. 
Ниже приведены такие описания в Delphi и C++ соответственно"."" 

    var MyProc: function(c: PChar): integer; stdcall; 
    int (_^stdcaTl MyProc)(char*); 


    Резюме 

    Эта глава посвящена совместному использованию Delphi и C++. В ней 
рассматривались следующие подходы. 
    • ЕХЕ вызывает другой модуль ЕХЕ. 
    • Язык 1 использует объектные файлы языка 2. 
    K Использование экспортируемых подпрограмм из DLL. 
    Я Экспортирование экземпляров объектов из DLL. 
    Взаимодействие ЕХЕ-модулей не является в строгом понимании совместным 
использованием двух языков программирования и требует решения вопросов 
взаимодействия между двумя программами. Использование объектных файлов крайне 
ограничено и сложно в реализации. 
    Оптимальным вариантом представляется использование DLL, при этом функции 
DLL должны быть созданы без искажения имен и желательно использовать 
соглашения о вызовах StdCall. 
    Как рассказывалось в этой главе, использование DLL может опираться на 
экспортирование функций и экземпляров класса. 
    Как показывает опыт, большинство программистов знают оба языка (как 
Delphi, так и C++), но как правило они знают один язык гораздо лучше, 
чем"другой. В помощь им в главе приведена таблица преобразования основных 
типов данных в языках Delphi и C++. 
    Глава 30. Содружество Delphi и C++    715 
