Главная » C++, C++ Builder » Пример: из CBuilder в MFC

0

В этом примере мы шаг за шагом пройдем весь процесс загрузки формы CBuilder в приложение MFC — от начала до конца. Я покажу вам все ловушки, которые возникнут на пути такого преобразования, а также научу некоторым уловкам, которые облегчат вам жизнь.

Первое, что надо сделать для того, чтобы загрузить форму CBuilder в приложение MFC, — это создать в CBuilder DLL, содержащую эту форму. Не самое сложное задание, не правда ли? Просто создайте новое приложение в CBuilder, выбрав команду меню File д New, и в качестве типа создаваемого приложения укажите DLL. CBuilder автоматически сгенерирует полный скелет DLL, включая функцию для точки входа в DLL и файл сборки (make-файл). Если вы привыкли работать в других системах, вас это должно приятно удивить — система сделала за  вас  всю  рутинную работу.

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

На рис. 12.1 показана форма, которую мы будем использовать в своем приложении.  Как  вы видите, она не представляет собой ничего особенного. Я надеюсь, что к этому моменту вы достаточно хорошо освоили CBuilder для того, чтобы создать форму  любого типа. Правда,  в данном примере наше внимание будет сконцентрировано в основном на том, что происходит, так сказать, за кулисами, а не на интерфейсе нашего приложения.

Рис. 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"

{

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 ведут себя так же, как если бы они были просто частью исходного файла этой программы. При динамической

загрузке вы сначала загружаете 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$$

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

·    $$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

В данной распечатке результатов  работы программы 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

не поддерживается данной платформой. Это сообщение появилось из-за того, что мы не определили значение 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.

Откройте класс 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.

Источник: Теллес М. – Borland C++ Builder. Библиотека программиста – 1998

По теме:

  • Комментарии