Приклад з CBuilder до MFC

У цьому прикладі ми крок за кроком пройдемо весь процес завантаження форми 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,

т. п.), так що безпосередньо пов'язати їх ніяк не вийде. Відповідно, вам доведеться використовувати посередницьку функцію для виконання завдання.

У першому прикладі ми розглянемо лише виведення даних на екран, не беручи до уваги випадки, коли в програму, що викликає, повинні повертатися будь-які значення. Уявимо, що це просто екран заставки або щось таке. З цієї причини ми створимо обгорткову функцію, яка відображатиме форму на екрані, а потім повертатиме управління 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

; Тут розміщуються імена, що експортуються

У цьому описі вам слід зробити деякі заміни, спираючись на те, що:

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

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