Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Теллес М. - Borland C++ Builder. Библиотека программиста - 1998

.pdf
Скачиваний:
774
Добавлен:
13.08.2013
Размер:
4.35 Mб
Скачать

Borland C++ Builder (+CD). Библиотека программиста 251

Рис. 12.1. Форма CBuilder для приложения MFC

После создания формы вам надо определить, каким образом форма будет создаваться в приложении MFC. Здесь начинается второй этап нашего процесса создание оберточной функции (wrapper function) для доступа к форме из приложения MFC.

Создание оберточной функции

Одной из проблем, возникающих при работе в разных средах разработки, является проблема преобразования имен (name mangling) компилятором C++. Как и следовало ожидать, компиляторы Microsoft Visual C++ и Borland C++Builder после преобразования имен возвращают разные имена для одних и тех же функций, что сильно затрудняет их объединение. К счастью, язык C++ предоставляет способ, позволяющий обойти эту проблему, — можно отменить преобразование имен функций и объектов, определяемых в системе.

Если вы поместите блок кода внутрь выражения extern "C", компилятор не будет производить преобразование имен в этом блоке. Глядя на синтаксис этого выражения, может показаться, что оно исключает возможность применения кода C++ внутри блока, но это не так. В блоке, определенном выражением extern "C", вы можете применять любой синтаксис (даже расширения CBuilder), и тем не менее все будет работать должным образом.

Рассмотрим, к примеру, следующий блок кода:

void CreateAForm(long nWhichForm);

Если вы пропустите этот код через компилятор Microsoft Visual C++, скорее всего в результирующем объектном файле вы получите имя типа _CreateAForm@4. Насколько я понимаю, подобное представление используется для функций, имеющих аргумент типа long. А вот зачем компилятор прибавляет символ подчеркива ния (_) в начало имени функции, я понятия не имею похоже, это уходит корнями в глубокое прошлое.

Если вы мыслите примерно так же, как и я, вас не должно волновать, почему компилятор делает то, что он делает. Все, что вам надо от компилятора, — это чтобы он выдавал работающий код, который вы могли бы включить в свое приложение. Итак, проблема состоит в том, чтобы заставить два компилятора выдавать коды в сопоставимом друг с другом формате. Для этой цели и используется выражение extern "C".

Возвратимся к нашей строке и перепишем ее теперь следующим образом:

extern "C"

Borland C++ Builder (+CD). Библиотека программиста 252

{

void CreateAForm(long nWhichForm);

}

Теперь и после компиляции имя функции будет «CreateAForm» — без преобразований, связанных с аргументами и символа подчеркивания в начале имени. Оба компилятора знают, как работать с таким синтаксисом, так что для них это идеальный способ общения.

Предвосхищу ваш вопрос. Не существует никакой возможности напрямую «общаться» с объектом

VCL CBuilder из приложения на Microsoft Visual C++. Visual C++ не сможет переварить все расширения синтаксиса, используемые в VCL для описания объектов (__property, __fastcall и т. п.), так что напрямую связать их никак не получится. Соответственно, вам придется использовать посредническую функцию для выполнения задачи.

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

Ниже приведен код оберточной функции, который надо добавить в программу (будущую DLL), то есть в файл project1.cpp:

extern "C" void WINAPI __declspec(dllexport) ShowForm(void)

{

Form1 = new TForm1((TComponent *)NULL);

Form1->Show();

}

Первое, на что следует обратить внимание в этом коде, — это на определение функции. О разделе extern "C" мы только что поговорили. Далее функция не возвращает никаких значений, об этом говорит употребленное здесь выражение void. Раздел WINAPI свидетельствует о том, что мы имеем дело с обычной функцией Windows.

А вот раздел __delspec (dllexport) — это весьма важное добавление, которое вам придется использовать во всех функциях, которые вы собираетесь вызывать из какой-либо внешней программы. Это выражение означает, что функция должна быть автоматически экспортирована из DLL и доступна для любой вызывающей программы. Если вы не определите свою функцию как экспортируемую, ни одна из вызывающих программ не будет ее видеть, а следовательно, и приложение MFC не сможет ее вызвать.

Создание файла DEF

Возможно , вы помните, что при разговоре о DLL в CBuilder мы останавливали свое внимание на программе implib, которая используется для создания библиотек импорта (import libraries) для DLL. Когда эти библиотеки импорта присоединены к программе, функции из DLL ведут себя так же, как если бы они были просто частью исходного файла этой программы. При динамической

Borland C++ Builder (+CD). Библиотека программиста 253

загрузке вы сначала загружаете DLL, потом находите функцию в DLL и уже после этого вызываете функцию косвенным вызовом (как мы это делали в предыдущей главе). При статической загрузке процесс выглядит подругому. Когда вы статически загружаете DLL посредством библиотеки импорта, Windows сама выполняет работу по нахождению DLL, загрузке ее в память и преобразованию ваших обращений к функциям в косвенные вызовы функций. Библиотеки импорта это менее гибкое образование, чем статические библиотеки. Однажды присоединив библиотеку импорта, вы уже привязываетесь к конкретному воплощению функции и уже ничего не можете сделать с ней во время исполнения. С другой стороны, библиотеки импорта чудесно подходят для обновления версий. Вы можете поставлять пользователям обновленные версии DLL, и если вы ничего не меняли в описаниях функций, то пользователь сможет использовать более новые версии, ничего не меняя в своих программах.

Ознакомившись со столь радостным описанием библиотек импорта, вы, возможно, решите, что наша работа в основном выполнена и все, что нам осталось сделать, — это при помощи программы implib создать библиотеку импорта для нашей DLL, загрузить ее в приложение на Visual C++ с MFC и вызвать функцию. Вы, безусловно, не правы. У меня складывается впечатление, что у создателей компиляторов есть свой тайный кодекс чести, и создание компилятора, позволяющего программисту сделать что-нибудь настолько просто, в корне противоречит самому духу этого кодекса. Так что теперь нам надо проделать некую достаточно прямолинейную, но тем не менее малопонятную процедуру, для того чтобы наша DLL заработала с MFC.

Вы не можете использовать программу implib, поскольку библиотеки, создаваемые этой программой, не совместимы с Visual C++. Несмотря на то что, как я уже упоминал, DLL — она и в Африке DLL, библиотека это все же набор объектных файлов. Для того чтобы код был совместим, форматы объектных файлов также должны быть совместимы. Надо ли говорить, что форматы объектных файлов CBuilder и Microsoft Visual C++ сильно различаются, так что библиотека импорта не сможет быть корректно загружена. Что нам надо сделать, так это создать библиотеку импорта в Visual C++, которая была бы совместима с этой системой.

«Нет проблем, — скажете вы, — просто запускаем версию implib для Visual C++ — и ключик у нас в кармане». Извините, но не все так просто. Фирма Microsoft по каким-то причинам перестала поставлять программу implib со своими средствами разработки. Она все же предоставляет в наше распоряжение метод для создания библиотеки импорта, но он требует несколько больших усилий. Давайте рассмотрим шаги, которые нам необходимо предпринять для того, чтобы выполнить эту работу.

Первое, что понадобится вам для создания библиотеки импорта, — это файл описания модуля (DEF). Как вам сразу бы сказал любой программист, поработав ший с Windows несколько лет, этот файл представляет собой описание DLL, которое включает в себя список экспортируемых функций. Вместо того чтобы заставлять вас разбираться в синтаксисе файла DEF (а он очень похож на древнескандинав ские руны), я предоставлю в ваше распоряжение шаблон файла DEF, который вы сможете без труда заполнить своими данными. Этот шаблон (с описаниями того, что следует заменить), выглядит следующим образом:

; project1.def : Описывает параметры модуля для DLL LIBRARY $$ProjectName$$

CODE SHARED EXECUTE READ DATA READ WRITE DESCRIPTION '$$Description$$' EXPORTS

; Здесь располагаются экспортируемые имена $$FunctionName$$ @$$FunctionNumber$$

Borland C++ Builder (+CD). Библиотека программиста 254

В этом описании вам следует сделать некоторые замены, опираясь на то, что:

·$$ProjectName$$ имя проекта. Как правило, это будет то же самое имя, под которым вы сохраняли проект в CBuilder.

·$$Description$$ написанное на нормальном человеческом языке описание проекта, из которого можно узнать, почему была создана данная DLL и для чего она используется.

·$$FunctionName$$ имя функции, которую вы хотите экспортировать. Существует одно место в шаблоне для каждой функции, которую вы хотите описать как экспортируемую в DLL.

·$$FunctionNumber$$ простой порядковый номер (от 1 до количества функций в DLL). Этот номер используется для того, чтобы ускорить загрузку функции из DLL во время исполнения.

Нам для нашей программы надо знать список экспортируемых функций в DLL. Как правило, вы знакомы с этим списком, поскольку сами же и создавали эту DLL, но иногда может возникнуть ситуация, когда вам придется работать с DLL, написанной не вами, в которой не будут определены экспортируемые функции. Также может возникнуть момент, когда вы захотите узнать, какие же еще функции, кроме указанных в документации, экспортирует DLL (надо сказать, что подобное желание приходит весьма часто). Для того чтобы осуществить задуманное, вам придется использовать еще одну поставляемую с Visual C++ программу — dumpbin.

Чтобы получить список экспортируемых функций в DLL, используйте следующий синтаксис запуска программы dumpbin:

dumpbin /EXPORTS filename.dll

Я надеюсь, вы уже догадались, что здесь filename.dll — это имя той DLL, для которой вы хотите узнать список экспортируемых функций. Если мы запустим программу dumpbin для файла project1.dll, созданного нами в CBuilder, то увидим следующее:

dumpbin /EXPORTS project1.dll

Microsoft (r) COFF Binary File Dumper Version 4.20.6164 Copyright (c) Microsoft Corp 1992-1996. All rights reserved. Dump of file project1.dll

File type: DLL

Section contains the following Exports for D:\matt\CBDLL\Project1.dll

0 characteristics

0 time date stamp Wed Dec 31 19:00:00 1969

0.00 version

1 ordinal base

3 number of functions

3 number of names ordinal hint name

1 0 ShowForm (0000119C)

2 1 _ DebugHook (000313FC)

3 2 _ Exception Class (000347E0) Summary

9000 .data

1000 .edata

2000 .idata

4000 .reloc

5000 .rsrc

F000 .text

1000 .tls

Borland C++ Builder (+CD). Библиотека программиста 255

В данной распечатке результатов работы программы dumpbin содержится довольно много интересной информации, но нас действительно волнуют только некоторые значения. Наиболее важен для нас блок, выделенный подсвечиванием, в котором показаны экспортируемые функции с номерами, которые нам надо внести в файл DEF. Функции _DebugHook и _ExceptionClass нас не волнуют они используются самой системой CBuilder. А вот созданная нами в DLL функция ShowForm нас как раз интересует. Теперь, используя полученную информацию, мы можем доопределить файл DEF для нашей DLL:

; project1.def : Описывает параметры модуля для DLL LIBRARY "PROJECT1"

CODE SHARED EXECUTE READ DATA READ WRITE

DESCRIPTION 'DLL для использования BCB в MFC' EXPORTS

; Здесь располагаются экспортируемые имена

ShowForm @1

Создание библиотеки импорта

Мы начали разговор с работы с библиотеками импорта, так что вы можете удивиться, почему нам пришлось сначала создавать этот непонятный файл DEF. Все на самом деле просто для того чтобы создать библиотеку импорта, которую можно было бы использовать в Visual C++, надо сначала определить файл DEF, содержащий информацию о DLL.

Это может показаться несколько странным, но как только вы осознаете, что же на самом деле представляет собой библиотека импорта, все сразу же станет на свои места. Библиотека импорта

это некая разновидность библиотеки, содержащая в себе «адреса» функций DLL и некий код, который сообщает Windows, что

функции надо грузить прямо из DLL, не используя библиотеку импорта. После изучения этого механизма становится понятно, что все это придумано для того, чтобы можно было создать библиотеку импорта для DLL, не используя саму DLL. Так придумала фирма Microsoft, а зачем откуда мне знать?

Для того чтобы создать библиотеку импорта для DLL, имея файл DEF, следует воспользоваться программой lib, поставляемой с Visual C++. По сравнению с программой tlib, поставляемой с CBuilder, программа lib гораздо более сложна. Как вы, возможно, помните, программа tlib просто создает некий архив объектных файлов, который можно подключить к приложению, а программа lib умеет делать гораздо больше вещей.

Перейдите в каталог, содержащий файл DEF для нашей DLL, и запустите программу lib со следующими параметрами (это позволит вам создать библиотеку импорта для DLL):

lib /DEF:project1.dll

Microsoft (r) 32-Bit Library Manager Version 4.20.6164 Copyright (c) Microsoft Corp 1992-1996. All rights reserved

LIB: warning LNK4068: /MACHINE not specified; defaulting to IX86 project1.dll : warning LNK4017: MZP statement not supported for the target platform; ignored

Creating library project1.lib and object project1.exp

Вы ничего не потеряете, проигнорировав предупреждение программы о том, что выражение MZP

Borland C++ Builder (+CD). Библиотека программиста 256

не поддерживается данной платформой. Это сообщение появилось из-за того, что мы не определили значение MACHINE для нашей библиотеки. По умолчанию программа lib устанавливает архитектуру машин Intel, так что, поскольку пока CBuilder работает только под Windows на платформе Intel, нас это вполне устраивает.

По окончании работы lib (а работает она всего лишь одну-две секунды) найдите в своем каталоге файл с именем, совпадающим с именем файла DEF и расширением LIB. Это и есть наша пресловутая библиотека импорта для DLL.

Замечание

Одной из проблем при косвенном создании библиотеки импорта является то, что если вы допустили ошибку в файле DEF, библиотека не сможет быть подключена корректно. Если компоновщик выдает вам сообщения о странных ошибках, проверьте файл DEF.

Итак, теперь у нас есть DLL и есть библиотека импорта, которую мы можем использовать в приложениях на Visual C++. Так что теперь нам надо создать приложение MFC и вызвать функцию. Давайте этим и займемся.

Приложение MFC

Я надеюсь, что вы уже умеете создавать приложения в Visual C++ при помощи Мастера приложений. Если нет, почитайте что-нибудь по этой системе эта книга о CBuilder, а не о

Visual C++.

Итак, при помощи Мастера приложений создайте новое приложение, назовите его MFCToBCB и назначьте ему однодокументный (SDI) тип интерфейса. Остальные значения, задаваемые по умолчанию, нас не интересуют нам они не понадобятся.

Если вы используете версию Visual C++ 4.x, выберите команду Project д Insert Files Into the Project

и найдите только что созданную нами библиотеку импорта. Добавьте ее в проект и нажмите кнопку OK. Теперь при сборке проекта Visual C++ подключит в него библиотеку импорта для нашей DLL. Надо сказать, что данный пример не будет работать в версиях Visual C++ ниже четвертой, поскольку вы не можете подключить 32-разрядную библиотеку к 16-разрядному приложению.

Следующее, что нам надо сделать, — это вызвать функцию. Для начала добавьте в главное меню вашего приложения новый пункт с заголовком Особо, а в него включите подпункт Вызвать форму C++Builder. Полная структура меню приложения показана на рис. 12.2.

Рис. 12.2. Структура меню приложения MFCToBCB

Определив новый пункт меню, вызовите Мастер классов и определите обработчик для этого пункта в классе CMainFrame. Дайте новому методу имя OnBuilderForm этот метод будет использоваться приложением для отображения формы CBuilder.

Borland C++ Builder (+CD). Библиотека программиста 257

Откройте класс CMainFrame на редактирование в редакторе IDE Visual C++.

В начало исходного файла mainfrm.cpp добавьте строки кода, представляющие прототип нашей функции, — этот прототип потребуется компилятору для того, чтобы сгенерировать код, способный вызывать функцию из DLL: extern "C"

{

void __declspec(dllexport) ShowForm(void);

}

Не забудьте, что и здесь нам надо заключить описание прототипа функции внутрь выражения extern "C", в противном случае компоновщик выдаст нам сообщение об ошибке.

Еще один интересный аспект приведенного выше описания использование модификатора __declspec(dllexport). Когда этот модификатор используется в DLL, он означает, что функция DLL должна быть экспортируемой. А когда этот модификатор применяется в приложении, он автоматически преобразуется в выражение, означаю

щее, что эта функция должна быть импортирована из DLL. Такой вот пример удачного использования макроса.

Вызов функции для отображения формы

Последний этап нашего процесса вызов функции. Добавьте в код метода OnBuilderForm следующие строки:

void CMainFrame::OnBuilderForm()

{

ShowForm();

}

Вот и все, что надо сделать для того, чтобы вызвать метод и отобразить форму на экране. Теперь компилятор скомпилирует весь код, а компоновщик присоединит библиотеку импорта для DLL; таким образом, приложение будет собрано воедино.

При запуске приложения Windows автоматически загрузит DLL для формы CBuilder. Когда вы вызовете метод DLL ShowForm, библиотека импорта осуществит косвенный вызов функции DLL, и форма будет отображена на экране.

Если вы мне не верите, попробуйте сами: создайте приложение Visual C++ и, предварительно убедившись в том, что нужная нам DLL находится в том же каталоге, что и исполняемый файл приложения, запустите программу. Выберите пункт меню Вызвать форму C++Builder и убедитесь, что форма действительно отображает ся на экране. Итак, нам удалось присоединить форму

CBuilder к приложению MFC.

Взаимодействие Visual C++ и формы CBuilder

В первом примере этой главы мы изучили основные моменты, позволяющие использовать формы CBuilder в приложениях MFC. Это, конечно, очень важно, но есть более важные задачи, чем простой вывод формы на экран. В нашем теперешнем примере мы научимся осуществлять взаимодействие между MFC и формами CBuilder. Мы создадим простенькую форму, что-то вроде листка из записной книжки, в которую пользователь сможет занести адрес какого-нибудь своего

Borland C++ Builder (+CD). Библиотека программиста 258

знакомого. Приложение MFC создаст объект, который будет передан в эту форму, где будет использован для того, чтобы передать информацию из формы в приложение.

Для написания этого приложения нам опять потребуется создать в CBuilder DLL, содержащую форму для введения адресов. Выполните это самостоятель но. Форма, которую вы должны создать, показана на рис. 12.3. Когда вы закончите с этим, мы перейдем к следующему этапу добавлению данных в приложение MFC.

Рис. 12.3. Форма для ввода адреса

Первое, что нам надо сделать в приложении, — это добавить оберточную функцию для DLL, которая позволит нам создать форму. Давайте сначала посмотрим на код, а потом обсудим некоторые интересные моменты, которые в нем встретятся:

extern "C"

{

void WINAPI __declspec(dllexport) CreateAddressForm(TAddress *pObject)

{

// Сначала создаем форму

TForm1 *pForm = new TForm1(NULL);

//Получаем данные от пользователя pForm->ShowModal();

//Передаем данные обратно в структуру

pObject->SetLastName( pForm->Edit1->Text.c_str()); pObject->SetFirstName( pForm->Edit2->Text.c_str()); pObject->SetAddress1( pForm->Edit3->Text.c_str()); pObject->SetAddress2( pForm->Edit4->Text.c_str()); pObject->SetCity( pForm->Edit5->Text.c_str()); pObject->SetState( pForm->Edit6->Text.c_str()); pObject->SetZipCode( pForm->Edit7->Text.c_str());

}

}; // Конец блока extern "C"

Первое, что бросается в глаза, — здесь мы опять создаем оберточную функцию, используя объявление extern "C", с тем чтобы эту функцию можно было вызвать из MFC. Но на этот раз у функции есть аргумент, представляющий объект, созданный в MFC для хранения данных, введенных пользователем в форму. Давайте посмотрим на этот объект, который, кстати, вам придется добавить в новый файл с именем Address.h. Этот файл и будет использоваться в приложении MFC для осуществления передачи данных:

Borland C++ Builder (+CD). Библиотека программиста 259

#ifndef _ADDRESS_H_ #define _ADDRESS_H_ class TAddress

{

char strLastName[80]; char strFirstName[80]; char strAddress1[80]; char strAddress2[80]; char strCity[40];

char strState[4]; char strZipCode[10]; public: TAddress(void)

{

}

~TAddress(void)

{

}

const char *GetLastName(void)

{

return strLastName;

}

const char *GetFirstName(void)

{

return strFirstName;

}

const char *GetAddress1(void)

{

return strAddress1;

}

const char *GetAddress2(void)

{

return strAddress2;

}

const char *GetCity(void)

{

return strCity;

}

const char *GetState(void)

{

return strState;

}

const char *GetZipCode(void)

{

return strZipCode;

}

void SetLastName( const char *strLast )

{

strncpy( strLastName, strLast, 80 );

}

void SetFirstName( const char *strFirst )

{

strncpy( strFirstName, strFirst, 80 );

Borland C++ Builder (+CD). Библиотека программиста 260

}

void SetAddress1( const char *strAdd1 )

{

strncpy(strAddress1, strAdd1,80);

}

void SetAddress2( const char *strAdd2 )

{

strncpy(strAddress2, strAdd2,80);

}

void SetCity( const char *strC )

{

strncpy(strCity, strCity,40);

}

void SetState( const char *strSt )

{

strncpy(strState, strSt,4);

}

void SetZipCode( const char *strZip )

{

strncpy(strZipCode, strZip, 10);

}

};

#endif

Как вы видите, это простой объект C++, предназначенный для хранения информации из нашей записной книжки. Используя стандартный объект C++, мы облегчаем задачу присоединения DLL к приложению MFC. Код в оберточной функции определяет части объекта-адреса посредством вызова функций класса, так что нам не надо волноваться о выравнивании или определении объекта. Мы доверяем

объекту хранение информации в том виде, в каком ему надо, для того чтобы она воспринималась

Visual C++ и MFC.

И последнее замечание: вам надо подключить заголовочный файл Address.h к исходному файлу project.cpp с тем, чтобы описание класса TAddress было доступно компилятору. Как вы видите, «форвардное» описание оберточной функции содержится в конце заголовочного файла для нужд приложения Visual C++.

Приложение Visual C++

Та часть работы, которая была связана с CBuilder, завершена, и теперь нам надо создать приложение Visual C++ для использования оберточной функции DLL. Итак, при помощи Мастера приложений создайте простое приложение, назовите его MfcBcbTest и выберите для него тип однодокументного интерфейса.

В класс CMainFrame добавьте обработчик для пункта меню Файл д Новый. Воспользуйтесь советом Мастера классов, назовите этот обработчик OnFileNew. Теперь в метод OnFileNew класса CMainFrame добавьте следующие строки:

void CMainFrame::OnFileNew()

{

TAddress *pAddress = new TAddress; CreateAddressForm(pAddress);

Соседние файлы в предмете Программирование на C++