Grid-> Cells [i + 1] [i + 1] =
Values [i]; p>
// задаємо параметри відображення StringGrid в батьківському вікні p>
Grid-> Parent = GridForm; p>
Grid-> Align = alClient; p>
// показуємо VCL-форму p>
GridForm-> Show (); p>
// повертаємо Хендлі VCL-вікна клієнтського
додатку, p>
// щоб воно могло це вікно при необхідності
закрити p>
return GridForm-> Handle; p>
) p>
catch (...) p>
( p>
return NULL; p>
) p>
) p>
# pragma argsused p>
int WINAPI DllEntryPoint (HINSTANCE hinst, unsigned long reason, void *
lpReserved) p>
( p>
return 1; p>
) p>
Проаналізуємо сформовані компілятором
найменування експортованих функцій. Скориставшись утилітою impdef.exe,
що поставляється разом з C + + Builder (знаходиться в каталозі $ (BCB) Bin, синтаксис
командного рядка - impdef.exe ExplicitDll.def ExplicitDll.dll), отримаємо
Наступне. def-файл p>
ExplicitDll.def p>
LIBRARY EXPLICITDLL.DLL p>
EXPORTS p>
ViewStringGridWnd @ 1;
ViewStringGridWnd p>
_SumFunc @ 2; _SumFunc p>
___CPPdebugHook @ 3; ___CPPdebugHook p>
Оскільки в даному прикладі експортована функція
ViewStringGridWnd використовує угоду __stdcall, її ім'я залишилося незмінним
(див. таблицю 1), отже, для виклику цієї функції VC-додаток
скористається ім'ям ViewStringGridWnd (наприклад, при виклику GetProcAddress), а
от для виклику функції SumFunc використовувати доведеться ім'я _SumFunc. Очевидно,
що здійснювати виклик функції, користуючись її зміненим ім'ям, незручно само
по собі, а тим більше, якщо dll пише один програміст, а працює з нею інший.
Для того, щоб при використанні __cdecl-угоди експортовані функції
можна було використовувати з їх справжніми іменами (без символів підкреслення),
необхідно про це подбати заздалегідь, тобто на етапі створення самої dll.
Для цього створюється. Def-файл (це можна зробити в будь-якому текстовому редакторі),
в якому визначається секція EXPORTS, що містить псевдонім (alias) для кожної
експортованої __cdecl-функції. У нашому випадку він буде виглядати в такий
чином p>
ExplicitDllAlias.def p>
EXPORTS p>
; VC funcname = BCB funcname p>
SumFunc = _SumFunc p>
Тобто, у функції, що експортується як _SumFunc, буде
псевдонім SumFunc, який ми виключно для зручності робимо ідентичним
оригінального імені цієї функції в коді (хоча псевдонім може бути яким
завгодно). p>
Створений. def-файл додається (Project -> Add to
Project) до проекту dll. Після компіляції, проаналізувавши dll c допомогою
impdef.exe, отримаємо наступне p>
ExplicitDll.def p>
libRARY EXPLICITDLL.DLL p>
EXPORTS p>
SumFunc @ 4; SumFunc p>
ViewStringGridWnd @ 2;
ViewStringGridWnd p>
_SumFunc @ 1; _SumFunc p>
___CPPdebugHook @ 3;
___CPPdebugHook P>
Маємо на одну експортовану функцію більше, але при
це реальна кількість функцій в dll залишилося незмінним, а функція з ім'ям
SumFunc (функція-псевдонім) є посиланням на свій оригінал, тобто на
функцію, що експортується під ім'ям _SumFunc. p>
ПРИМІТКА p>
Більш правильним буде сказати, що
функція-псевдонім просто додається до таблиці експорту dll: її ім'я SumFunc
додається у масив імен функцій, а в масив порядкових номерів додається
присвоєний їй порядковий номер. Однак відповідний функції-псевдонімом
RVA в масиві відносних віртуальних адрес буде дорівнює RVA функції з
ім'ям _SumFunc. Переконатися в цьому можна послідовно викликаючи
GetProcAddress для імен функцій SumFunc і _SumFunc і аналізуючи повертається
адреса (можна, звісно, скористатися різними програмами,
що дозволяють переглянути вміст файлу, що виконується). В обох випадках
адреса функції буде однаковий. p>
Таким чином, за допомогою. def-файла псевдонімів при
експорті функцій, визначених як __cdecl, ми позбавляє користувачів від
необхідності виконання функцій по їх зміненим іменах, хоча і така можливість
залишається. p>
ПОПЕРЕДЖЕННЯ p>
Оскільки __stdcall-і __cdecl-функції
по-різному працюють зі стеком, не намагайтеся з клієнтського застосування
викликати __stdcall-функції як __cdecl, і навпаки, інакше стек буде
пошкоджений, і подальше виконання програми буде неможливо. p>
В результаті викладеного вище ми отримали dll,
експортує функції з іменами SumFunc і ViewStringGridWnd. При цьому їх
назви не залежать від того, яке угоду про виклик використовувалося при
оголошення цих функцій. Тепер розглянемо приклад використання нашої dll в
додатку VC. Створимо в середовищі Visual C + + 6.0 (або Visual C + + 7.0) просте
MFC-додаток, що буде являти собою звичайне діалогове вікно (File
-> New -> MFC AppWizard (exe) -> Dialog based -> Finish). Додамо до
початкового діалогу дві кнопки: кнопку "SumFunc" і кнопку "ViewStringGridWnd".
Потім для кожної кнопки створимо обробник події BN_CLICKED: OnSumFunc () і
OnViewStringGridWnd () відповідно. Нам також знадобляться обробники
повідомлень для подій форми WM_CREATE і WM_DESTROY. Повний робочий код цього
програми знаходиться в прикладах до статті, тут же буде приведена тільки
частину, що демонструє роботу з нашою dll, оскільки решта коду
генерується середовищем розробки. p>
Лістинг 2 - Компілятор Visual C + + 6.0
p>
UsingExplicitDLLDlg.cpp p>
// код, що генерується середовищем
розробки p>
... p>
// Хендлі тестованої DLL p>
HINSTANCE hDll = NULL; p>
// тип покажчика на функцію
ViewStringGridWnd p>
typedef HWND (__stdcall * ViewStringGridWndProcAddr) (int Count,
double * Values); p>
// Хендлі вікна з VCL-компонентом StringGrid p>
HWND hGrid = NULL; p>
// тип покажчика на функцію
SumFunc p>
typedef int (__cdecl * SumFuncProcAddr) (int a, int b); p>
// код, що генерується середовищем
розробки p>
... p>
// обробник натискання
кнопки SumFunc p>
void
CUsingExplicitDLLDlg:: OnSumFunc () p>
( p>
// покажчик на функцію SumFunc p>
SumFuncProcAddr ProcAddr =
NULL; p>
if (hDll! = NULL) p>
( p>
// отримання адреси функції p>
ProcAddr = (SumFuncProcAddr)
GetProcAddress (hDll, "SumFunc"); p>
if (ProcAddr! = NULL) p>
( p>
// виклик функції p>
int result = (ProcAddr) (5, 6); p>
// відображення результату в заголовку діалогу p>
char str [10]; p>
this-> SetWindowText (itoa (result, str
, 10 )); p>
) p>
) p>
) p>
// обробник натискання кнопки
ViewStringGridWnd p>
void CUsingExplicitDLLDlg:: OnViewStringGridWnd () p>
( p>
// покажчик на функцію ViewStringGridWnd p>
ViewStringGridWndProcAddr
ProcAddr = NULL; p>
if (hDll! = NULL) p>
( p>
// отримання адреси функції p>
ProcAddr = (ViewStringGridWndProcAddr)
GetProcAddress (hDll, p>
"ViewStringGridWnd "); p>
if (ProcAddr! = NULL) p>
( p>
// ініціалізація аргументів p>
const int count = 5; p>
double Values [count] = (2.14, 3.56, 6.8, 8, 5.6564); p>
// закриваємо раніше створене вікно, щоб вони
не плодити p>
if (hGrid! = NULL) p>
:: SendMessage (hGrid, WM_CLOSE,
0, 0); p>
// виклик функції p>
hGrid = (ProcAddr) (count,
Values); p>
) p>
) p>
) p>
// обробник події вікна WM_DESTROY p>
void CUsingExplicitDLLDlg:: OnDestroy () p>
( p>
CDialog:: OnDestroy (); p>
p>
// закриваємо вікно з компонентом StringGrid,
якщо воно було створено p>
if (hGrid! = NULL) p>
:: SendMessage (hGrid, WM_CLOSE,
0, 0); p>
// вивантаження dll з пам'яті p>
FreeLibrary (hDll); p>
) p>
// обробник події вікна WM_CREATE p>
int CUsingExplicitDLLDlg:: OnCreate (LPCREATESTRUCT lpCreateStruct) p>
( p>
if
(CDialog:: OnCreate (lpCreateStruct) == -1) p>
return -1; p>
p>
// завантаження dll в пам'ять p>
hDll =
LoadLibrary ( "ExplicitDll.dll "); p>
p>
return 0; p>
) p>
Явна завантаження dll має як переваги, так і
недоліки. У нашому випадку великим плюсом є те, що явна завантаження
позбавляє від якого б то не було взаємодії з вихідним кодом dll, в
Зокрема немає необхідності підключати заголовкові. h-файл з оголошеннями
функцій. Клієнтський додаток компілюється і працює незалежно від
використовуваної dll, а випадки невдалої завантаження бібліотеки або невдалого
отримання адреси функції завжди можна обіграти так, щоб вони не вплинули на
подальше виконання основної програми. p>
ПРИМІТКА p>
Слід зазначити, що використання
експортованих unmanaged-функцій з керованого коду (managed code) в. NET
здійснюється виключно за допомогою явної завантаження dll. До процесу
виклику функції в цьому випадку крім стандартних кроків (таких як завантаження dll
в пам'ять за допомогою LoadLibrary, отримання адреси необхідної функції за допомогою
GetProcAddress і безпосередньо виклик), додається також процес маршалинга
(marshaling), тобто процес перетворення типів даних. NET у їх аналоги в
традиційному двійковому коді (при проштовхуванні аргументів у стек) і назад
(при аналізі значення, що повертається). Для вказівки, що метод імпортується
з dll, використовується атрибут DllImport, параметри якого містять
інформацію, необхідну для виклику LoadLibrary і GetProcAddress. p>
Таким чином, для виклику експортується функції з
dll, скомпільованих в BCB, необхідно виконати наступну послідовність
действійя: p>
Оголосити експортовані функції або як __cdecl, або
як__stdcall. Якщо використовується тільки угода __stdcall, пропускаємо пункт
3. p>
Помістити оголошення функцій в блок extern "С". Чи не
експортувати класи та функції-члени класів, оскільки це все одно не
вдасться. p>
Якщо експортуються функції з угодою про виклик
__cdecl, щось додати до проекту. def-файл з псевдонімами для кожної такої
функції. p>
відкомпілювати dll. p>
Створити клієнтський (тобто використовує BCB
бібліотеку) VC-проект. p>
Копіювати створену BCB dll в папку з клієнтським
VC-додатком. p>
Завантажити dll з клієнтського застосування в пам'ять при
допомоги LoadLibrary. p>
Отримати адресу потрібної функції за допомогою GetProcAddress
і привласнити його вказівником на функцію. p>
Викликати функцію за допомогою покажчика на неї. p>
Після закінчення використання вивантажити dll з пам'яті з
допомогою FreeLibrary. p>
Алгоритм з неявним зв'язуванням для експорту (імпорту)
__cdecl-функцій
p>
Як випливає з назви розділу, даний спосіб
призначений для експорту (а на стороні клієнта - для імпорту) функцій з
__cdecl-угодою про виклик. Щоб скористатися неявним зв'язуванням, перш
за все, необхідно створити об'єктний. lib-файл (бібліотеку імпорту), що містить
посилання на dll і перелік що знаходяться в dll функцій. Даний об'єктний файл можна
створити по. def-файлу експорту бібліотеки за допомогою утиліти lib.exe. При цьому
отриманий. lib-файл буде у потрібному нам форматі COFF, оскільки компілятор VC
дотримується саме цієї специфікації (утиліта lib.exe поставляється разом
з VC і вміє створювати бібліотеки імпорту тільки за. def-файлу). Готовий
. lib-файл прілінковивается до клієнтського проекту. p>
При неявному зв'язуванні програма не підозрює, що
використовує dll, тому функції, які викликаються з динамічної бібліотеки, як і
будь-які інші, повинні бути оголошені в тексті клієнтської програми. Для
оголошення функцій скористаємося вихідним заголовки BCB dll, але
функції в ньому повинні бути помічені вже не як __declspec (dllexport), а як
__declspec (dllimport), тобто як імпортовані ззовні, оскільки по відношенню
до клієнтського додатку ці функції є саме імпортованими. p>
Оригінальний текст dll цього разу буде виглядати
наступним чином: p>
Лістинг 3 - компілятор Borland C + +
Builder 5
p>
ImplicitLinking_cdecl.h p>
# ifndef _IMPLICITDLL_ p>
# define _IMPLICITDLL_ p>
// якщо
макрос-ідентифікатор _DLLEXPORT_ було визначено раніше, p>
// то макрос _DECLARATOR_
позначити функцію як продукцію, що експортується, p>
// інакше
функція буде позначена як імпортована. p>
// Дана конструкція з
директив препроцесора дозволяє p>
// скористатися
заголовки бібліотеки як на етапі p>
// створення DLL, так і на
етапі її використання, а саме, при p>
// неявному зв'язування. p>
# ifdef _DLLEXPORT_ p>
# define _DECLARATOR_
__declspec (dllexport) p>
# else p>
# define _DECLARATOR_
__declspec (dllimport) p>
# endif p>
extern "C" p>
( p>
int _DECLARATOR_ __cdecl
SumFunc (int a, int b); p>
HWND _DECLARATOR_ __cdecl
ViewStringGridWnd (int Count, double * Values); p>
) p>
# endif p>
ImplicitLinking_cdecl.cpp p>
# include p>
# include p>
// визначення _DLLEXPORT_,
щоб замість макросу _DECLARATOR_ p>
// в заголовки
було підставлено __declspec (dllexport), p>
// і функції були оголошені
як експортуються p>
# define _DLLEXPORT_ p>
# include "ImplicitLinking_cdecl.h" p>
int __cdecl SumFunc (int a, int b) p>
(//тіло функції таке ж
як у попередньому розділі p>
) p>
HWND __cdecl ViewStringGridWnd (int Count, double * Values) p>
(//тіло функції таке ж
як у попередньому розділі p>
) p>
# pragma argsused p>
int WINAPI DllEntryPoint (HINSTANCE hinst, p>
unsigned long reason, p>
void * lpReserved) p>
( p>
return 1; p>
) p>
Основна що виникає при цьому проблема полягає в
те, що, згідно з таблицею 1, функції з __cdecl-угодою про виклик будуть експортуватися
із символом підкреслення, отже,. lib-файл, створений за. def-файлу
експорту бібліотеки, буде містити змінені назви функцій. З іншого
сторони, по-перше, компілятор VC буде очікувати незмінених найменувань
__cdecl-функцій, тому що сам VC, експортуючи функції з __cdecl-угодою про
виклику, нічого до їх найменуванню не додає, а по-друге, заголовки
BCB dll, що підключається до клієнтського додатку, містить оголошення функцій з
їх реальними (без символу підкреслення) іменами. У результаті цього, якщо в
тексті клієнтського застосування зустрінеться хоча б один виклик нашій функції, то
VC при зв'язуванні спробує знайти опис цієї продукції, що імпортується функції в
доданої до проекту бібліотеці імпорту (. lib-файлі), з тим, щоб додати
відповідний запис у таблицю імпорту програми. Але через невідповідність
імен функцій у заголовну і об'єктному файлах лінковщік, природно, в
. lib-файлі нічого не знайде, про що не сповільнить видати повідомлення (наприклад,
таке - error LNK2001: unresolved external symbol __imp__SumFunc). p>
ПРИМІТКА p>
Таблиця імпорту будь-якого PE-файлу
містить масив структур IMAGE_IMPORT_DESCRIPTOR. Кожна така структура
відповідає одній з dll, з якою неявно пов'язаний PE-файл. Структура
IMAGE_IMPORT_DESCRIPTOR серед інших полів містить поле з RVA
рядка-найменування dll, якої вона відповідає, і два поля з RVA масивів
подвійних слів, призначених для зберігання інформації про імпортованих
функціях. При запуску програми завантажувач PE-файлів заповнює один з цих
масивів (так звану таблицю адрес імпорту) адресами імпортованих
функцій, завантаживши перед цим dll, в якій ці функції знаходяться. Адреса
імпортованої функції обчислюється як сума адреси, за якою була
завантажена експортує цю функцію dll, і зміщення (RVA) самої функції
щодо початку dll. p>
Описану вище проблему невідповідності заголовну і
об'єктного файлів можна вирішити двома способами - знову скористатися
розглянутим у попередньому розділі. def-файлом з псевдонімами або використовувати
в заголовки нашої бібліотеки директиву препроцесора # define. p>
Використання псевдонімів
p>
Слідуючи цим способом, створюємо і додаємо до проекту
BCB dll наступний. Def-файл: p>
ImplicitLinkingAliases.def
p>
EXPORTS p>
; MSVC name = Borland name p>
SumFunc = _SumFunc p>
ViewStringGridWnd
= _ViewStringGridWnd P>
Після компіляції наша dll буде експортувати функції p>
ImplicitLinking_cdecl.def p>
libRARY IMPLICITLINKING_CDECL.DLL p>
EXPORTS p>
SumFunc @ 4; SumFunc p>
ViewStringGridWnd @ 5;
ViewStringGridWnd p>
_SumFunc @ 1; _SumFunc p>
_ViewStringGridWnd @ 2;
_ViewStringGridWnd P>
___CPPdebugHook @ 3; ___CPPdebugHook p>
Таким чином, до таблиці експорту dll додаються
функції-псевдоніми, імена яких відповідають функціям, оголошеним
заголовки нашої бібліотеки. Для повної відповідності (хоча цього можна
і не робити) видалимо з ImplicitLinking_cdecl.def згадки про всі
сторонніх для програми-клієнта функції, тому що заголовки містить
оголошення тільки двох функцій. В результаті отримаємо. Def-файл готовий для
створення з нього об'єктного. lib-файла: p>
ImplicitLinking_cdecl.def p>
libRARY IMPLICITLINKING_CDECL.DLL p>
EXPORTS p>
SumFunc @ 4; SumFunc p>
ViewStringGridWnd @ 5; ViewStringGridWnd p>
ПРИМІТКА p>
В єдиному статті,
яку мені вдалося знайти з даної теми (на сайті bcbdev.com),
рекомендувалося, крім видалення з. def-файла сторонніх функцій, замінити
найменування секції EXPORTS на IMPORTS. Робити цього не випливає з тієї
простої причини, що утиліта lib.exe (принаймні, що поставляється з 6-ої і
7-ий Visual Studio) секцію IMPORTS не підтримує, тому ігнорує всі
наступні опису функцій і створює порожній. lib-файл. Утиліта lib.exe
знаходиться в каталозі $ (VC) Bin, але запустити її звичайно з першого разу не
вдається, оскільки для роботи їй потрібно бібліотека mspdb60.dll (для
lib.exe, що поставляється з Visual Studio 7 - mspdb70.dll). mspdb60.dll лежить в
папці $ (Microsoft Visual Studio) CommonMSDev98Bin, а mspdb70.dll - в папці
$ (Microsoft Visual Studio. NET) Common7IDE. P>
За допомогою утиліти lib.exe створимо необхідний для
неявного зв'язування. lib-файл у форматі COFF, для цього в командному рядку
наберемо p>
lib.exe
/ def: ImplicitLinking_cdecl.def p>
або p>
lib.exe
/ def: ImplicitLinking_cdecl.def/out: ImplicitLinking_cdecl.lib p>
Отриманий. lib-файл додамо до проекту VC-клієнта (Project -> Add
To Project -> Files ...). P>
Використання директиви препроцесора # define
p>
Тепер розглянемо спосіб, що дозволяє домогтися
однакових назв функцій у заголовну і об'єктних (. lib) файлах з допомогою
директиви # define. Перепишемо заголовки нашої BCB-бібліотеки наступним
чином p>
Лістинг 4 - компілятор Borland C + + Builder 5
p>
ImplicitLinking_cdecl.h p>
# ifndef _IMPLICITDLL_ p>
# define _IMPLICITDLL_ p>
# ifdef _DLLEXPORT_ p>
# define _DECLARATOR_
__declspec (dllexport) p>
# else p>
# define _DECLARATOR_
__declspec (dllimport) p>
# endif p>
extern "C" p>
( p>
// при компіляції в VC до оригінальних
найменувань p>
// функцій додадуться символи підкреслення,
таким чином p>
// імена оголошуються функцій співпадуть з їхньою
іменами в таблиці p>
// експорту DLL і, отже,. lib-файлі p>
# ifdef _MSC_VER p>
# define SumFunc _SumFunc p>
# define ViewStringGridWnd
_ViewStringGridWnd P>
# endif p>
int _DECLARATOR_ __cdecl
SumFunc (int a, int b); p>
HWND _DECLARATOR_ __cdecl
ViewStringGridWnd (int Count, double * Values); p>
) p>
# endif p>
При компіляції клієнтського VC-додатки в підключеному
до проекту заголовки dll (ImplicitLinking_cdecl.h) до найменування
кожної функції за допомогою директив # define додається символ підкреслення
(макрос _MSC_VER визначається компілятором VC за замовчуванням). Оскільки з BCB
dll __cdecl-функції експортуються в такий же спосіб, тобто з додаванням
символу підкреслення, то встановлюється відповідність імен експортуються та
оголошених функцій. Макроси # define поширюють свій вплив і на весь
наступний код додатку, що дозволяє в тексті програми при виклику
імпортованої функції користуватися її оригінальним ім'ям, яке при
компіляції буде доповнено необхідним магічним символом підкреслення. Таким
чином, ми йдемо на поводу у фірми Borland і в клієнтському додатку завуальовано
використовуємо для виконання функцій з нашої dll імена, змінені компілятором BCB.
Саме необхідність використання змінених імен (хай і не у відкриту
завдяки define-трюку), на мій погляд, є істотним недоліком цього
способу, так як, наприклад, при бажанні явно (див. розділ "Алгоритм з явною
Завантаження та dll ") використовувати dll доведеться оперувати зміненими іменами
функцій. Не розвиваючи далі цю тему, скажу, що якщо BCB dll створюється з
чітким наміром використовувати її в VC-додатках, то краще додавати до
проекту бібліотеки. def-файл зі зручними для користувачів іменами-псевдонімами
функцій. p>
До переваг цього способу (define-трюку) можна
віднести його простоту і, як би це не суперечило сказаному в попередньому
абзаці, відсутність необхідності додавати до таблиці експорту dll псевдоніми
функцій. Незважаючи на всі зручності використання псевдонімів, таблиця експорту
(а отже, і сама dll) при цьому збільшується в розмірах. Та й створення
. def-файла псевдонімів при великій кількості функцій не додає приємних
емоцій. p>
Після компіляції dll за допомогою impdef.exe отримуємо
. def-файл експорту, з якого утилітою lib.exe створюємо Об'єктовий. lib-файл і
додаємо його до клієнтського VC-проекту. p>
Лістинг клієнтського застосування, код якого в даному
випадку не залежить від способу вирішення проблеми невідповідності найменувань
функцій у заголовну і об'єктному файлах бібліотеки, представлений нижче. Як і в
попередньому розділі, це діалогове вікно з двома кнопками. Що нас цікавить код
зосереджений у обробника подій натискання кнопок діалогу. p>
Лістинг 5 - Компілятор Visual C + + 6.0
p>
UsingImplicitLinking_cdeclDlg.cpp
p>
// код, що генерується середовищем
розробки p>
... p>
// Хендлі вікна з
VCL-компонентом StringGrid p>
HWND hGrid = NULL; p>
// підключаємо заголовкові
файл бібліотеки p>
# include
"ImplicitLinking_cdecl.h" p>
// код, що генерується середовищем
розробки p>
... p>
void
CUsingImplicitLinkng_cdeclDlg:: OnSumFunc () p>
( p>
// викликаємо функцію SumFunc з dll p>
int res = SumFunc (5, 9); p>
// виводимо результат в заголовок діалогового вікна p>
char str [10]; p>
this-> SetWindowText (itoa (res, str, 10)); p>
) p>
void CUsingImplicitLinkng_cdeclDlg:: OnViewStringGridWnd () p>
( p>
// ініціалізація аргументів p>
const int count = 5; p>
double Values [count] = (2.14,
3.56, 6.8, 8, 5.6564); p>
// закриваємо раніше створене вікно, щоб вони не «плодилися» p>
if (hGrid! = NULL) p>
:: SendMessage (hGrid, WM_CLOSE,
0, 0); p>
// викликаємо функцію
ViewStringGridWnd з dll p>
hGrid =
ViewStringGridWnd (count, Values); p>
) p>
void CUsingImplicitLinkng_cdeclDlg:: OnDestroy () p>
( p>
CDialog:: OnDestroy (); p>
p>
// закриваємо вікно з компонентом StringGrid, якщо воно було створено p>
if (hGrid! = NULL) p>
:: SendMessage (hGrid, WM_CLOSE,
0,0); p>
) p>
Основною перевагою неявної завантаження dll є
саме неявность використання dll з боку клієнтського додатку. Іншими
словами, додаток, викликаючи функції, не підозрює, що вони можуть перебувати
десь в зовнішньому модулі. Результатом є спрощення коду програми. До
недоліків слід віднести той факт, що dll знаходиться в пам'яті протягом всього
роботи програми, неявно її використовує. Завантаження dll здійснюється за
завантаженні програми - завантажувач PE-файлів, переглядаючи кожну запис в таблиці
імпорту програми, завантажує відповідну цього запису dll. Отже,
якщо використовуваних бібліотек багато, завантаження основної програми може
затягнутися. У разі відсутності неявно використовується dll додаток взагалі не
запуститься. p>
Підсумковий алгоритм з неявним зв'язуванням для експорту
(імпорту) __cdecl-функцій складається з наступної послідовності дій (див.
також Демонстраційний проект): p>
1. Оголосити експортовані функції як __cdecl. p>
2. Помістити оголошення функцій в блок extern "С", при
це не експортувати класи та функції-члени класів. p>
3. В заголовки для можливості його подальшого
використання на стороні клієнта вставити: p>
# ifdef _DLLEXPORT_ p>
# define _DECLARATOR_
__declspec (dllexport) p>
# else p>
# define _DECLARATOR_
__declspec (dllimport) p>
# endif p>
і додати макрос _DECLARATOR_ до оголошення кожній
функції, наприклад, p>
int
_DECLARATOR_ __cdecl SumFunc (int a, int b); p>
4. Далі або створити і додати до проекту. Def-файл з
псевдонімами для кожної функції, або додати в заголовки бібліотеки
наступне: p>
# ifdef _MSC_VER p>
# define FuncName1 _FuncName1 p>
# define FuncName2 _FuncName2 p>
# define FuncNameN _FuncNameN p>
# endif p>
Якщо використовувався # define-трюк, то пункт 7 потрібно
буде пропустити. p>
5. Скомпілювати BCB dll. p>
6. За допомогою impdef.exe створити. Def-файл з
найменуваннями експортованих функцій. p>
7. Якщо в пункті 4 скористалися псевдонімами,
видалити з. def-файла експорту невживані найменування функцій, залишивши
тільки псевдоніми. p>
8. Створити клієнтський VC-проект. p>
9. З. Def-файла експорту бібліотеки за допомогою
утиліти lib.exe створити об'єктний. lib-файл формату COFF і додати його до
клієнтського VC-додатку. p>
10. Копіювати BCB dll і її заголовки в папку
з клієнтським VC-проектом. p>
11. У клієнтському додатку підключити заголовкові
файл dll. p>
12. Викликати в тілі програми необхідні функції, не
замислюючись над тим, що вони розташовані у зовнішній dll. p>
Алгоритм з неявним зв'язуванням для експорту (імпорту)
__stdcall-функцій
p>
Як уже згадувалося вище, утиліта lib.exe може
створювати бібліотеку імпорту тільки з. def-файла експорту, при чому lib.exe при
це ніяк не взаємодіє з самої dll. Однак. Def-файл не містить ніякої
інформації, касаемой угод про виклик, яких дотримуються експортуються
функції. Отже, і lib.exe, працюючи виключно с. Def-файлом, не
зможе вловити, що має справу з __stdcall-функціями, і, як результат, не
зможе в. lib-файлі відобразити функції згідно з Microsoft-угоди про
найменування для __stdcall-функцій. Таким чином, враховуючи з попереднього
розділу, що для __cdecl-функцій lib.exe генерує цілком працездатний
. lib-файл, приходимо до наступного висновку: утиліта lib.exe не здатна
генерувати бібліотеки імпорту для dll, що експортують __stdcall-функції.
Людям, які побажали або вимушеним (а після прочитання цього розділу думаю тільки
вимушеним) використовувати BCB dll з __stdcall-функціями в VC, цей розділ
присвячується. p>
Вихідний код BCB dll залишився таким же, як у
попередньому розділі (див. Лістинг 3), тільки ключове слово __cdecl скрізь
необхідно замінити ключовим словом __stdcall. p>
Відомо, що при створенні VC dll разом з нею Середа
генерує. lib-файл (бібліотеку імпорту), який представлений, природно, в
потрібному нам форматі COFF, і в якому коректно будуть відображатися
__stdcall-функції. Тому створимо (File -> New ... -> Win32
Dynamic-Link Library -> OK -> An empty DLL project -> Finish) помилкову (dummy) VC dll, яка буде
експортувати той же набір функцій, що й BCB dll. Реалізація функцій у помилкової
dll абсолютно не важлива, важливі виключно їх найменування. Крім однакових
найменувань експортованих функцій упомилковою і вихідної бібліотек повинні
збігатися імена, оскільки. lib-файли містять найменування dll. Можна
скористатися вихідними текстами BCBdll, скопіювавши. h-и. cpp-файли у
директорію до помилкової dll, потім додавши їх до проекту (Project -> Add To
Project -> Files ...) і видаливши тіла всіх функцій. Якщо функція повертає
значення, то залишаємо оператор return і повертаємо відповідно до типу все,
що завгодно (можна 0, NULL і т.д.). Оскільки тіла функцій будуть порожніми,
більшу частину директив # include з підключаються заголовки також
можна видалити. У результаті отримаємо відповідно до нашого прикладу наступний код помилкової
dll: p>
Лістинг 6 - Компілятор Visual C + + 6.0
p>
ImplicitLinking_stdcallDummy.h
p>
# ifdef _DLLEXPORT_ p>
# define _DECLARATOR_ __declspec (dllexport) p>
# else p>
# define _DECLARATOR_
__declspec (dllimport) p>
# endif p>
extern "C" p>
( p>
int _DECLARATOR_ __stdcall
SumFunc (int a, int b); p>
HWND _DECLARATOR_ __stdcall
ViewStringGridWnd (int Count, double * Values); p>
) p>
ImplicitLinking_stdcallDummy.cpp p>
# define _DLLEXPORT_ p>
# include p>
# include "ImplicitLinking_stdcallDummy.h" p>
int __stdcall SumFunc (int a, int b) p>
( p>
return 0; p>
) p>
HWND __stdcall ViewStringGridWnd (int Count, double * Values) p>
( p>
return NULL; p>
) p>
Згідно таблиці 1, VC експортує __stdcall-функції,
додаючи до їх найменуванню інформацію про список аргументів і символ
підкреслення. Отже, в об'єктних. Lib-файлі будуть імена, відмінні від
оригінальних імен функцій, оголошених в заголовки, і тим більше
відмінні від найменувань функцій, що експортуються з BCB dll, тому що
__stdcall-функції компілятор BCB експортує без змін. Позбавлятися
цієї невідповідності будемо знову за допомогою. def-файла. Для нашого прикладу він
буде таким: p>
DummyDef.def p>
libRARY ImplicitLinking_stdcall.dll p>
EXPORTS p>
SumFunc p>
ViewStringGridWnd p>
Рядок з ім'ям бібліотеки (LIBRARY) в. def-файлі не
обов'язкова, але якщо вона є, то ім'я, вказане в ній, в точності має
збігатися з іменами помилковою і вихідної dll. Додаємо. Def-файл до VC-проекту,
перекомпіліруем і отримуємо помилкову dll і необхідну нам бібліотеку імпорту,
містить коректне опис експортованих __stdcall-функцій. . lib-файл,
дістався у спадок від неправдивої dll, повинен додаватися (прілінковиваться) до
будь-якому VC-проекту, який збирається використовувати нашу вихідну BCB dll. p>
Приклад VC-додатки, імпортуючого
__stdcall-функції, такий же, як і в попередньому розділі (див. Лістинг 5). Чи не
забудьте в прикладі підключити (# include) потрібний заголовки BCB dll і
додати до проекту потрібну бібліотеку імпорту. p>
Алгоритм з неявним зв'язуванням для експорту (імпорту)
__stdcall-функцій (див. також Демонстраційний проект,
ImplicitLinkingDll_stdcall.zip): p>
Оголосити експортовані функції як __stdcall. p>
Помістити оголошення функцій в блок extern "С". Чи не
експортувати класи та функції-члени класів. p>
скомпілювати BCB dll. p>
Оскільки створити коректну бібліотеку імпорту з
допомогою утиліти lib.exe не вдається, створити помилкову VC dll, яка містить
такий же набір функцій, як і початкова BCB dll. p>
Перевірити ідентичність назв помилкової dll і dll
вихідної, назви повинні співпасти. p>
Якщо для помилкової бібліотеки використовуються вихідні
тексти BCB dll, то уд