ПЕРЕЛІК ДИСЦИПЛІН:
  • Адміністративне право
  • Арбітражний процес
  • Архітектура
  • Астрологія
  • Астрономія
  • Банківська справа
  • Безпека життєдіяльності
  • Біографії
  • Біологія
  • Біологія і хімія
  • Ботаніка та сільське гос-во
  • Бухгалтерський облік і аудит
  • Валютні відносини
  • Ветеринарія
  • Військова кафедра
  • Географія
  • Геодезія
  • Геологія
  • Етика
  • Держава і право
  • Цивільне право і процес
  • Діловодство
  • Гроші та кредит
  • Природничі науки
  • Журналістика
  • Екологія
  • Видавнича справа та поліграфія
  • Інвестиції
  • Іноземна мова
  • Інформатика
  • Інформатика, програмування
  • Юрист по наследству
  • Історичні особистості
  • Історія
  • Історія техніки
  • Кибернетика
  • Комунікації і зв'язок
  • Комп'ютерні науки
  • Косметологія
  • Короткий зміст творів
  • Криміналістика
  • Кримінологія
  • Криптология
  • Кулінарія
  • Культура і мистецтво
  • Культурологія
  • Російська література
  • Література і російська мова
  • Логіка
  • Логістика
  • Маркетинг
  • Математика
  • Медицина, здоров'я
  • Медичні науки
  • Міжнародне публічне право
  • Міжнародне приватне право
  • Міжнародні відносини
  • Менеджмент
  • Металургія
  • Москвоведение
  • Мовознавство
  • Музика
  • Муніципальне право
  • Податки, оподаткування
  •  
    Бесплатные рефераты
     

     

     

     

     

     

         
     
    Створення в середовищі Borland C + + Builder dll, сумісної з Visual C ++
         

     

    Інформатика, програмування

    Створення в середовищі Borland C + + Builder dll, сумісної з Visual C + +

    Роман Мананников

    Проблеми взаємодії

    Складність використання dll, створеної за допомогою Borland C + + Builder (далі BCB), у проектах, що розробляються в середовищах Microsoft, обумовлена трьома основними проблемами. По-перше, Borland і Microsoft дотримуються різних угод щодо найменування (naming convention) функції в dll. Залежно від того, як оголошена експортована функція, її ім'я може бути доповнено компілятором певними символами. Так, при використанні такої угоди про виклик (calling convention), як __cdecl, BCB перед ім'ям функції додає символ підкреслення. Visual C + + (далі VC), в свою чергу, при експорті функції як __stdcall додасть до її імені крім підкреслення також інформацію про список аргументів (символ @ плюс розмір списку аргументів в байтах).        

    ПРИМІТКА   

    Використання угоди __stdcall   означає, що викликається, сама видалить з стека свої аргументи.   Угода __cdecl, навпаки, зобов'язує очищати стек зухвалу функцію.   Оголошення функції як __cdecl приведе до деякого (незначного)   збільшення розміру кінцевого файлу, що виконується, оскільки кожен раз після   виклику цієї функції потрібно код з очищення стека, з іншого боку, саме   з-за очищення стека викликає функцією допускається передача змінного   числа параметрів. В стек параметри і в тому, і в іншому випадку поміщаються   справа наліво.     

    У таблиці 1 наведені можливі варіанти найменувань для експортованої функції MyFunction, оголошеної в такий спосіб:        

    extern   "C" void __declspec (dllexport) MyFunction (int   Param);     

    в залежності від угоди про виклик () і компілятора.        

    Угода про виклик         

    VC + +         

    C + + Builder             

    __stdcall         

    _MyFunction @ 4         

    MyFunction             

    __cdecl         

    MyFunction         

    _MyFunction     

    Таблиця 1. Найменування функцій залежно від угоди про виклик і компілятора.

    По-друге, об'єктні двійкові файли (. obj і. lib), створювані BCB, несумісні з об'єктними файлами VC, і, отже, не можуть бути прилінкованими до VC-проекту. Це означає, що при бажанні використовувати неявне зв'язування (linking) c dll необхідно якимсь чином створити. lib-файл (бібліотеку імпорту) формату, якого дотримується Microsoft.        

    ПРИМІТКА   

    Слід зазначити, що до появи   32-розрядної версії Visual C + + 1.0 компілятори Microsoft використовували   специфікацію Intel OMF (Object Module Format - формат об'єктного модуля). Всі   наступні компілятори від Microsoft створюють об'єктні файли у форматі COFF   (Common Object File Format - стандартний формат об'єктного файлу). Основний   конкурент Microsoft на ринку компіляторів - Borland - вирішила відмовитися від   формату об'єктних файлів COFF і продовжує дотримуватися формату OMF Intel.   Звідси і несумісність двійкових об'єктних файлів.     

    По-третє, класи та функції-методи класів, експортуються з BCB dll, не можуть бути використані в проекті на VC. Причина цього криється в тому, що компілятори спотворюють (mangle) імена як звичайних функцій, так і функцій-методів класу (не плутайте з різними угодами про найменуваннях). Спотворення вноситься для підтримки поліморфізму, тобто для того, щоб розрізняти функції з однаковим ім'ям, але різними наборами переданих їм параметрів. Якщо для звичайних функцій викривлення можна уникнути, використовуючи перед визначенням функції директиву extern "С" (але при цьому, по-перше, на передній план виходить перша проблема - різні угоди про найменування функцій в dll, а по-друге, з двох і більше функцій з однаковим ім'ям директиву extern "З" можна використовувати тільки для однієї з них, Інакше виникнуть помилки при компіляції), то для функцій-методів класу спотворення імені неминучі. Компілятори Borland і Microsoft, як ви вже, мабуть, здогадалися, використовують різні схеми внесення спотворень. У результаті VC-додатки просто не бачать класи та методи класів, експортуються бібліотеками, скомпільованих в BCB.        

    ПРИМІТКА   

    Від редакції: Зокрема,   різновидами поліморфізму часу компіляції є перевантаження (ad-hoc   поліморфізм) та шаблони функцій (параметричний поліморфізм).     

    Ці три проблеми ускладнюють використання BCB dll з додатків, створених на VC, але все-таки це можливо. Нижче описані три способи створення dll сумісної з VC і подальшого успішного використання цієї dll.

    Алгоритми створення VC-сумісної dll і її використання

    Два з описаних в цьому розділі алгоритмів застосовують неявне зв'язування з dll, один - явну завантаження dll. Опишемо спочатку самий простий спосіб - використання BCB dll з проекту VC за допомогою її явною завантаження в процесі виконання програми.

    Алгоритм з явною завантаженням dll

    Застосовуючи цю техніку, нам не доведеться створювати сумісні з VC бібліотеки імпорту (. lib). Замість цього додасться ряд дій із завантаження і вивантаження dll в програмі, її використовує.

    Створимо BCB dll (New -> DLL Wizard -> C + + -> Use VCL -> OK), експортує для простоти всього дві функції. Одна з функцій буде обчислювати суму двох чисел і не буде використовувати VCL-класи, а інша буде створювати вікно і виводити в VCL-компонент TStringGrid елементи масиву, переданого в якості одного з аргументів.        

    ПРИМІТКА   

    Оскільки дії, здійснювані   функціями, в нашому випадку абсолютно не важливі, дані приклади не несуть   смислового навантаження, однак варто звернути увагу на функцію   ViewStringGridWnd, яка показує, що всередині самої dll використовувати   VCL-класи можна без будь-яких обмежень.     

    Лістинг 1 - компілятор Borland C + + Builder 5

    ExplicitDll.h        

    # ifndef _EXPLICITDLL_   

    # define _EXPLICITDLL_   

    extern "C"   

    (   

    int __declspec (dllexport)   __cdecl SumFunc (int a, int b);   

    HWND __declspec (dllexport)   __stdcall ViewStringGridWnd (int Count, double * Values);   

    )   

    # endif     

    Ключове слово __declspec з атрибутом dllexport позначає функцію як продукцію, що експортується, ім'я функції додається до таблиці експорту dll. Таблиця експорту будь-якого PE-файлу (. Exe або. Dll) складається з трьох масивів: масиву імен функцій (а точніше, масиву покажчиків на рядки, що містять імена функцій), масиву порядкових номерів функцій та масиву відносних віртуальних адрес (RVA) функцій. Масив імен функцій впорядкований в алфавітному порядку, він відповідає масив порядкових номерів функцій. Порядковий номер після деяких перетворень перетворюється на індекс елементу з масиву відносних віртуальних адрес функцій. При експорті функції на ім'я має місце наступна послідовність дій: за відомим імені функції визначається її індекс у масиві імен функцій, далі за отриманим індексу із масиву порядкових номерів визначається порядковий номер функції, потім з порядкового номера, з урахуванням базового порядкового номера експорту функцій для даного PE-файлу, обчислюється індекс, за яким з масиву адрес витягується шуканий RVA функції. Крім експорту на ім'я можливий експорт функцій з їх номером (ordinal). У цьому випадку послідовність дій для отримання індексу елемента з масиву відносних віртуальних адрес зводиться тільки до перетворення порядкового номера функції. Для експорту функцій за номером використовується. def-файл з секцією EXPORTS, де за кожній функцією буде закріплений порядковий номер. При цьому в тексті самої dll функції як експортуються НЕ позначаються. Детальніше про таблицю експорту можна прочитати в статті за адресою http://www. rsdn.ru/article/baseserv/pe_coff.xml.

    ExplicitDll.cpp        

    # include   

    # include   

    # include "ExplicitDll.h"      

    int __cdecl SumFunc (int a, int b)   

    (   

    return a + b;   

    )      

    HWND __stdcall ViewStringGridWnd (int Count, double * Values)   

    (   

    try   

    (   

    // створюємо VCL-форму, на якій буде   відображений StringGrid,   

    // і ставимо її основні параметри   

    TForm * GridForm = new TForm ((TComponent   *) NULL);   

    GridForm-> Caption = "Grid Form";   

    GridForm-> Width = 300;   

    GridForm-> Height = 300;      

    // створюємо компонент StringGrid і   встановлюємо його розміри   

    TStringGrid * Grid = new   TStringGrid (GridForm);   

    Grid-> ColCount = Count + 1;   

    Grid-> RowCount = Count + 1;      

    // заповнюємо StringGrid значеннями   

    if (Values! = NULL)   

    for (int i = 0; i   

    Grid-> Cells [i + 1] [i + 1] =   Values [i];      

    // задаємо параметри відображення StringGrid в батьківському вікні   

    Grid-> Parent = GridForm;   

    Grid-> Align = alClient;   

    // показуємо VCL-форму   

    GridForm-> Show ();      

    // повертаємо Хендлі VCL-вікна клієнтського   додатку,   

    // щоб воно могло це вікно при необхідності   закрити   

    return GridForm-> Handle;   

    )   

    catch (...)   

    (   

    return NULL;   

    )   

    )      

    # pragma argsused   

    int WINAPI DllEntryPoint (HINSTANCE hinst, unsigned long reason, void *   lpReserved)   

    (   

    return 1;   

    )     

    Проаналізуємо сформовані компілятором найменування експортованих функцій. Скориставшись утилітою impdef.exe, що поставляється разом з C + + Builder (знаходиться в каталозі $ (BCB) Bin, синтаксис командного рядка - impdef.exe ExplicitDll.def ExplicitDll.dll), отримаємо Наступне. def-файл

    ExplicitDll.def        

    LIBRARY EXPLICITDLL.DLL      

    EXPORTS   

    ViewStringGridWnd @ 1;   ViewStringGridWnd   

    _SumFunc @ 2; _SumFunc   

    ___CPPdebugHook @ 3; ___CPPdebugHook     

    Оскільки в даному прикладі експортована функція ViewStringGridWnd використовує угоду __stdcall, її ім'я залишилося незмінним (див. таблицю 1), отже, для виклику цієї функції VC-додаток скористається ім'ям ViewStringGridWnd (наприклад, при виклику GetProcAddress), а от для виклику функції SumFunc використовувати доведеться ім'я _SumFunc. Очевидно, що здійснювати виклик функції, користуючись її зміненим ім'ям, незручно само по собі, а тим більше, якщо dll пише один програміст, а працює з нею інший. Для того, щоб при використанні __cdecl-угоди експортовані функції можна було використовувати з їх справжніми іменами (без символів підкреслення), необхідно про це подбати заздалегідь, тобто на етапі створення самої dll. Для цього створюється. Def-файл (це можна зробити в будь-якому текстовому редакторі), в якому визначається секція EXPORTS, що містить псевдонім (alias) для кожної експортованої __cdecl-функції. У нашому випадку він буде виглядати в такий чином

    ExplicitDllAlias.def        

    EXPORTS   

    ; VC funcname = BCB funcname   

    SumFunc = _SumFunc     

    Тобто, у функції, що експортується як _SumFunc, буде псевдонім SumFunc, який ми виключно для зручності робимо ідентичним оригінального імені цієї функції в коді (хоча псевдонім може бути яким завгодно).

    Створений. def-файл додається (Project -> Add to Project) до проекту dll. Після компіляції, проаналізувавши dll c допомогою impdef.exe, отримаємо наступне

    ExplicitDll.def        

    libRARY EXPLICITDLL.DLL      

    EXPORTS   

    SumFunc @ 4; SumFunc   

    ViewStringGridWnd @ 2;   ViewStringGridWnd   

    _SumFunc @ 1; _SumFunc   

    ___CPPdebugHook @ 3;   ___CPPdebugHook     

    Маємо на одну експортовану функцію більше, але при це реальна кількість функцій в dll залишилося незмінним, а функція з ім'ям SumFunc (функція-псевдонім) є посиланням на свій оригінал, тобто на функцію, що експортується під ім'ям _SumFunc.        

    ПРИМІТКА   

    Більш правильним буде сказати, що   функція-псевдонім просто додається до таблиці експорту dll: її ім'я SumFunc   додається у масив імен функцій, а в масив порядкових номерів додається   присвоєний їй порядковий номер. Однак відповідний функції-псевдонімом   RVA в масиві відносних віртуальних адрес буде дорівнює RVA функції з   ім'ям _SumFunc. Переконатися в цьому можна послідовно викликаючи   GetProcAddress для імен функцій SumFunc і _SumFunc і аналізуючи повертається   адреса (можна, звісно, скористатися різними програмами,   що дозволяють переглянути вміст файлу, що виконується). В обох випадках   адреса функції буде однаковий.     

    Таким чином, за допомогою. def-файла псевдонімів при експорті функцій, визначених як __cdecl, ми позбавляє користувачів від необхідності виконання функцій по їх зміненим іменах, хоча і така можливість залишається.        

    ПОПЕРЕДЖЕННЯ   

    Оскільки __stdcall-і __cdecl-функції   по-різному працюють зі стеком, не намагайтеся з клієнтського застосування   викликати __stdcall-функції як __cdecl, і навпаки, інакше стек буде   пошкоджений, і подальше виконання програми буде неможливо.     

    В результаті викладеного вище ми отримали 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, оскільки решта коду генерується середовищем розробки.

    Лістинг 2 - Компілятор Visual C + + 6.0

    UsingExplicitDLLDlg.cpp        

    // код, що генерується середовищем   розробки   

    ...      

    // Хендлі тестованої DLL   

    HINSTANCE hDll = NULL;      

    // тип покажчика на функцію   ViewStringGridWnd   

    typedef HWND (__stdcall * ViewStringGridWndProcAddr) (int Count,   double * Values);      

    // Хендлі вікна з VCL-компонентом StringGrid   

    HWND hGrid = NULL;      

    // тип покажчика на функцію   SumFunc   

    typedef int (__cdecl * SumFuncProcAddr) (int a, int b);      

    // код, що генерується середовищем   розробки   

    ...      

    // обробник натискання   кнопки SumFunc   

    void   CUsingExplicitDLLDlg:: OnSumFunc ()   

    (   

    // покажчик на функцію SumFunc   

    SumFuncProcAddr ProcAddr =   NULL;   

    if (hDll! = NULL)   

    (   

    // отримання адреси функції   

    ProcAddr = (SumFuncProcAddr)   GetProcAddress (hDll, "SumFunc");   

    if (ProcAddr! = NULL)   

    (   

    // виклик функції   

    int result = (ProcAddr) (5, 6);   

    // відображення результату в заголовку діалогу   

    char str [10];   

    this-> SetWindowText (itoa (result, str   , 10 ));   

    )   

    )   

    )      

    // обробник натискання кнопки   ViewStringGridWnd   

    void CUsingExplicitDLLDlg:: OnViewStringGridWnd ()   

    (   

    // покажчик на функцію ViewStringGridWnd   

    ViewStringGridWndProcAddr   ProcAddr = NULL;   

    if (hDll! = NULL)   

    (   

    // отримання адреси функції   

    ProcAddr = (ViewStringGridWndProcAddr)   GetProcAddress (hDll,   

    "ViewStringGridWnd ");   

    if (ProcAddr! = NULL)   

    (   

    // ініціалізація аргументів   

    const int count = 5;   

    double Values [count] = (2.14, 3.56, 6.8, 8, 5.6564);      

    // закриваємо раніше створене вікно, щоб вони   не плодити   

    if (hGrid! = NULL)   

    :: SendMessage (hGrid, WM_CLOSE,   0, 0);   

    // виклик функції   

    hGrid = (ProcAddr) (count,   Values);   

    )   

    )   

    )      

    // обробник події вікна WM_DESTROY   

    void CUsingExplicitDLLDlg:: OnDestroy ()   

    (   

    CDialog:: OnDestroy ();   

      

    // закриваємо вікно з компонентом StringGrid,   якщо воно було створено   

    if (hGrid! = NULL)   

    :: SendMessage (hGrid, WM_CLOSE,   0, 0);   

    // вивантаження dll з пам'яті   

    FreeLibrary (hDll);   

    )      

    // обробник події вікна WM_CREATE   

    int CUsingExplicitDLLDlg:: OnCreate (LPCREATESTRUCT lpCreateStruct)   

    (   

    if   (CDialog:: OnCreate (lpCreateStruct) == -1)   

    return -1;   

      

    // завантаження dll в пам'ять   

    hDll =   LoadLibrary ( "ExplicitDll.dll ");   

      

    return 0;   

    )     

    Явна завантаження dll має як переваги, так і недоліки. У нашому випадку великим плюсом є те, що явна завантаження позбавляє від якого б то не було взаємодії з вихідним кодом dll, в Зокрема немає необхідності підключати заголовкові. h-файл з оголошеннями функцій. Клієнтський додаток компілюється і працює незалежно від використовуваної dll, а випадки невдалої завантаження бібліотеки або невдалого отримання адреси функції завжди можна обіграти так, щоб вони не вплинули на подальше виконання основної програми.        

    ПРИМІТКА   

    Слід зазначити, що використання   експортованих unmanaged-функцій з керованого коду (managed code) в. NET   здійснюється виключно за допомогою явної завантаження dll. До процесу   виклику функції в цьому випадку крім стандартних кроків (таких як завантаження dll   в пам'ять за допомогою LoadLibrary, отримання адреси необхідної функції за допомогою   GetProcAddress і безпосередньо виклик), додається також процес маршалинга   (marshaling), тобто процес перетворення типів даних. NET у їх аналоги в   традиційному двійковому коді (при проштовхуванні аргументів у стек) і назад   (при аналізі значення, що повертається). Для вказівки, що метод імпортується   з dll, використовується атрибут DllImport, параметри якого містять   інформацію, необхідну для виклику LoadLibrary і GetProcAddress.     

    Таким чином, для виклику експортується функції з dll, скомпільованих в BCB, необхідно виконати наступну послідовність действійя:

    Оголосити експортовані функції або як __cdecl, або як__stdcall. Якщо використовується тільки угода __stdcall, пропускаємо пункт 3.

    Помістити оголошення функцій в блок extern "С". Чи не експортувати класи та функції-члени класів, оскільки це все одно не вдасться.

    Якщо експортуються функції з угодою про виклик __cdecl, щось додати до проекту. def-файл з псевдонімами для кожної такої функції.

    відкомпілювати dll.

    Створити клієнтський (тобто використовує BCB бібліотеку) VC-проект.

    Копіювати створену BCB dll в папку з клієнтським VC-додатком.

    Завантажити dll з клієнтського застосування в пам'ять при допомоги LoadLibrary.

    Отримати адресу потрібної функції за допомогою GetProcAddress і привласнити його вказівником на функцію.

    Викликати функцію за допомогою покажчика на неї.

    Після закінчення використання вивантажити dll з пам'яті з допомогою FreeLibrary.

    Алгоритм з неявним зв'язуванням для експорту (імпорту) __cdecl-функцій

    Як випливає з назви розділу, даний спосіб призначений для експорту (а на стороні клієнта - для імпорту) функцій з __cdecl-угодою про виклик. Щоб скористатися неявним зв'язуванням, перш за все, необхідно створити об'єктний. lib-файл (бібліотеку імпорту), що містить посилання на dll і перелік що знаходяться в dll функцій. Даний об'єктний файл можна створити по. def-файлу експорту бібліотеки за допомогою утиліти lib.exe. При цьому отриманий. lib-файл буде у потрібному нам форматі COFF, оскільки компілятор VC дотримується саме цієї специфікації (утиліта lib.exe поставляється разом з VC і вміє створювати бібліотеки імпорту тільки за. def-файлу). Готовий . lib-файл прілінковивается до клієнтського проекту.

    При неявному зв'язуванні програма не підозрює, що використовує dll, тому функції, які викликаються з динамічної бібліотеки, як і будь-які інші, повинні бути оголошені в тексті клієнтської програми. Для оголошення функцій скористаємося вихідним заголовки BCB dll, але функції в ньому повинні бути помічені вже не як __declspec (dllexport), а як __declspec (dllimport), тобто як імпортовані ззовні, оскільки по відношенню до клієнтського додатку ці функції є саме імпортованими.

    Оригінальний текст dll цього разу буде виглядати наступним чином:

    Лістинг 3 - компілятор Borland C + + Builder 5

    ImplicitLinking_cdecl.h        

    # ifndef _IMPLICITDLL_   

    # define _IMPLICITDLL_   

    // якщо   макрос-ідентифікатор _DLLEXPORT_ було визначено раніше,   

    // то макрос _DECLARATOR_   позначити функцію як продукцію, що експортується,   

    // інакше   функція буде позначена як імпортована.   

    // Дана конструкція з   директив препроцесора дозволяє   

    // скористатися   заголовки бібліотеки як на етапі   

    // створення DLL, так і на   етапі її використання, а саме, при   

    // неявному зв'язування.   

    # ifdef _DLLEXPORT_   

    # define _DECLARATOR_   __declspec (dllexport)   

    # else   

    # define _DECLARATOR_   __declspec (dllimport)   

    # endif      

    extern "C"   

    (   

    int _DECLARATOR_ __cdecl   SumFunc (int a, int b);   

    HWND _DECLARATOR_ __cdecl   ViewStringGridWnd (int Count, double * Values);   

    )   

    # endif     

    ImplicitLinking_cdecl.cpp        

    # include   

    # include      

    // визначення _DLLEXPORT_,   щоб замість макросу _DECLARATOR_   

    // в заголовки   було підставлено __declspec (dllexport),   

    // і функції були оголошені   як експортуються   

    # define _DLLEXPORT_   

    # include "ImplicitLinking_cdecl.h"      

    int __cdecl SumFunc (int a, int b)   

    (//тіло функції таке ж   як у попередньому розділі   

    )      

    HWND __cdecl ViewStringGridWnd (int Count, double * Values)   

    (//тіло функції таке ж   як у попередньому розділі   

    )      

    # pragma argsused   

    int WINAPI DllEntryPoint (HINSTANCE hinst,   

    unsigned long reason,   

    void * lpReserved)   

    (   

    return 1;   

    )     

    Основна що виникає при цьому проблема полягає в те, що, згідно з таблицею 1, функції з __cdecl-угодою про виклик будуть експортуватися із символом підкреслення, отже,. lib-файл, створений за. def-файлу експорту бібліотеки, буде містити змінені назви функцій. З іншого сторони, по-перше, компілятор VC буде очікувати незмінених найменувань __cdecl-функцій, тому що сам VC, експортуючи функції з __cdecl-угодою про виклику, нічого до їх найменуванню не додає, а по-друге, заголовки BCB dll, що підключається до клієнтського додатку, містить оголошення функцій з їх реальними (без символу підкреслення) іменами. У результаті цього, якщо в тексті клієнтського застосування зустрінеться хоча б один виклик нашій функції, то VC при зв'язуванні спробує знайти опис цієї продукції, що імпортується функції в доданої до проекту бібліотеці імпорту (. lib-файлі), з тим, щоб додати відповідний запис у таблицю імпорту програми. Але через невідповідність імен функцій у заголовну і об'єктному файлах лінковщік, природно, в . lib-файлі нічого не знайде, про що не сповільнить видати повідомлення (наприклад, таке - error LNK2001: unresolved external symbol __imp__SumFunc).        

    ПРИМІТКА   

    Таблиця імпорту будь-якого PE-файлу   містить масив структур IMAGE_IMPORT_DESCRIPTOR. Кожна така структура   відповідає одній з dll, з якою неявно пов'язаний PE-файл. Структура   IMAGE_IMPORT_DESCRIPTOR серед інших полів містить поле з RVA   рядка-найменування dll, якої вона відповідає, і два поля з RVA масивів   подвійних слів, призначених для зберігання інформації про імпортованих   функціях. При запуску програми завантажувач PE-файлів заповнює один з цих   масивів (так звану таблицю адрес імпорту) адресами імпортованих   функцій, завантаживши перед цим dll, в якій ці функції знаходяться. Адреса   імпортованої функції обчислюється як сума адреси, за якою була   завантажена експортує цю функцію dll, і зміщення (RVA) самої функції   щодо початку dll.     

    Описану вище проблему невідповідності заголовну і об'єктного файлів можна вирішити двома способами - знову скористатися розглянутим у попередньому розділі. def-файлом з псевдонімами або використовувати в заголовки нашої бібліотеки директиву препроцесора # define.

    Використання псевдонімів

    Слідуючи цим способом, створюємо і додаємо до проекту BCB dll наступний. Def-файл:

    ImplicitLinkingAliases.def        

    EXPORTS   

    ; MSVC name = Borland name   

    SumFunc = _SumFunc   

    ViewStringGridWnd   = _ViewStringGridWnd     

    Після компіляції наша dll буде експортувати функції

    ImplicitLinking_cdecl.def        

    libRARY IMPLICITLINKING_CDECL.DLL      

    EXPORTS   

    SumFunc @ 4; SumFunc   

    ViewStringGridWnd @ 5;   ViewStringGridWnd   

    _SumFunc @ 1; _SumFunc   

    _ViewStringGridWnd @ 2;   _ViewStringGridWnd   

    ___CPPdebugHook @ 3; ___CPPdebugHook     

    Таким чином, до таблиці експорту dll додаються функції-псевдоніми, імена яких відповідають функціям, оголошеним заголовки нашої бібліотеки. Для повної відповідності (хоча цього можна і не робити) видалимо з ImplicitLinking_cdecl.def згадки про всі сторонніх для програми-клієнта функції, тому що заголовки містить оголошення тільки двох функцій. В результаті отримаємо. Def-файл готовий для створення з нього об'єктного. lib-файла:

    ImplicitLinking_cdecl.def        

    libRARY IMPLICITLINKING_CDECL.DLL      

    EXPORTS   

    SumFunc @ 4; SumFunc   

    ViewStringGridWnd @ 5; ViewStringGridWnd             

    ПРИМІТКА   

    В єдиному статті,   яку мені вдалося знайти з даної теми (на сайті 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.       

    За допомогою утиліти lib.exe створимо необхідний для неявного зв'язування. lib-файл у форматі COFF, для цього в командному рядку наберемо        

    lib.exe   / def: ImplicitLinking_cdecl.def     

    або        

    lib.exe   / def: ImplicitLinking_cdecl.def/out: ImplicitLinking_cdecl.lib     

    Отриманий. lib-файл додамо до проекту VC-клієнта (Project -> Add To Project -> Files ...).

    Використання директиви препроцесора # define

    Тепер розглянемо спосіб, що дозволяє домогтися однакових назв функцій у заголовну і об'єктних (. lib) файлах з допомогою директиви # define. Перепишемо заголовки нашої BCB-бібліотеки наступним чином

    Лістинг 4 - компілятор Borland C + + Builder 5

    ImplicitLinking_cdecl.h        

    # ifndef _IMPLICITDLL_   

    # define _IMPLICITDLL_   

    # ifdef _DLLEXPORT_   

    # define _DECLARATOR_   __declspec (dllexport)   

    # else   

    # define _DECLARATOR_   __declspec (dllimport)   

    # endif      

    extern "C"   

    (   

    // при компіляції в VC до оригінальних   найменувань   

    // функцій додадуться символи підкреслення,   таким чином   

    // імена оголошуються функцій співпадуть з їхньою   іменами в таблиці   

    // експорту DLL і, отже,. lib-файлі   

    # ifdef _MSC_VER   

    # define SumFunc _SumFunc   

    # define ViewStringGridWnd   _ViewStringGridWnd   

    # endif   

    int _DECLARATOR_ __cdecl   SumFunc (int a, int b);   

    HWND _DECLARATOR_ __cdecl   ViewStringGridWnd (int Count, double * Values);   

    )   

    # endif     

    При компіляції клієнтського VC-додатки в підключеному до проекту заголовки dll (ImplicitLinking_cdecl.h) до найменування кожної функції за допомогою директив # define додається символ підкреслення (макрос _MSC_VER визначається компілятором VC за замовчуванням). Оскільки з BCB dll __cdecl-функції експортуються в такий же спосіб, тобто з додаванням символу підкреслення, то встановлюється відповідність імен експортуються та оголошених функцій. Макроси # define поширюють свій вплив і на весь наступний код додатку, що дозволяє в тексті програми при виклику імпортованої функції користуватися її оригінальним ім'ям, яке при компіляції буде доповнено необхідним магічним символом підкреслення. Таким чином, ми йдемо на поводу у фірми Borland і в клієнтському додатку завуальовано використовуємо для виконання функцій з нашої dll імена, змінені компілятором BCB. Саме необхідність використання змінених імен (хай і не у відкриту завдяки define-трюку), на мій погляд, є істотним недоліком цього способу, так як, наприклад, при бажанні явно (див. розділ "Алгоритм з явною Завантаження та dll ") використовувати dll доведеться оперувати зміненими іменами функцій. Не розвиваючи далі цю тему, скажу, що якщо BCB dll створюється з чітким наміром використовувати її в VC-додатках, то краще додавати до проекту бібліотеки. def-файл зі зручними для користувачів іменами-псевдонімами функцій.

    До переваг цього способу (define-трюку) можна віднести його простоту і, як би це не суперечило сказаному в попередньому абзаці, відсутність необхідності додавати до таблиці експорту dll псевдоніми функцій. Незважаючи на всі зручності використання псевдонімів, таблиця експорту (а отже, і сама dll) при цьому збільшується в розмірах. Та й створення . def-файла псевдонімів при великій кількості функцій не додає приємних емоцій.

    Після компіляції dll за допомогою impdef.exe отримуємо . def-файл експорту, з якого утилітою lib.exe створюємо Об'єктовий. lib-файл і додаємо його до клієнтського VC-проекту.

    Лістинг клієнтського застосування, код якого в даному випадку не залежить від способу вирішення проблеми невідповідності найменувань функцій у заголовну і об'єктному файлах бібліотеки, представлений нижче. Як і в попередньому розділі, це діалогове вікно з двома кнопками. Що нас цікавить код зосереджений у обробника подій натискання кнопок діалогу.

    Лістинг 5 - Компілятор Visual C + + 6.0

    UsingImplicitLinking_cdeclDlg.cpp        

    // код, що генерується середовищем   розробки   

    ...      

    // Хендлі вікна з   VCL-компонентом StringGrid   

    HWND hGrid = NULL;   

    // підключаємо заголовкові   файл бібліотеки   

    # include   "ImplicitLinking_cdecl.h"      

    // код, що генерується середовищем   розробки   

    ...      

    void   CUsingImplicitLinkng_cdeclDlg:: OnSumFunc ()   

    (   

    // викликаємо функцію SumFunc з dll   

    int res = SumFunc (5, 9);      

    // виводимо результат в заголовок діалогового вікна   

    char str [10];   

    this-> SetWindowText (itoa (res, str, 10));   

    )      

    void CUsingImplicitLinkng_cdeclDlg:: OnViewStringGridWnd ()   

    (   

    // ініціалізація аргументів   

    const int count = 5;   

    double Values [count] = (2.14,   3.56, 6.8, 8, 5.6564);   

    // закриваємо раніше створене вікно, щоб вони не «плодилися»   

    if (hGrid! = NULL)   

    :: SendMessage (hGrid, WM_CLOSE,   0, 0);   

    // викликаємо функцію   ViewStringGridWnd з dll   

    hGrid =   ViewStringGridWnd (count, Values);   

    )      

    void CUsingImplicitLinkng_cdeclDlg:: OnDestroy ()   

    (   

    CDialog:: OnDestroy ();   

      

    // закриваємо вікно з компонентом StringGrid, якщо воно було створено   

    if (hGrid! = NULL)   

    :: SendMessage (hGrid, WM_CLOSE,   0,0);   

    )     

    Основною перевагою неявної завантаження dll є саме неявность використання dll з боку клієнтського додатку. Іншими словами, додаток, викликаючи функції, не підозрює, що вони можуть перебувати десь в зовнішньому модулі. Результатом є спрощення коду програми. До недоліків слід віднести той факт, що dll знаходиться в пам'яті протягом всього роботи програми, неявно її використовує. Завантаження dll здійснюється за завантаженні програми - завантажувач PE-файлів, переглядаючи кожну запис в таблиці імпорту програми, завантажує відповідну цього запису dll. Отже, якщо використовуваних бібліотек багато, завантаження основної програми може затягнутися. У разі відсутності неявно використовується dll додаток взагалі не запуститься.

    Підсумковий алгоритм з неявним зв'язуванням для експорту (імпорту) __cdecl-функцій складається з наступної послідовності дій (див. також Демонстраційний проект):

    1. Оголосити експортовані функції як __cdecl.

    2. Помістити оголошення функцій в блок extern "С", при це не експортувати класи та функції-члени класів.

    3. В заголовки для можливості його подальшого використання на стороні клієнта вставити:        

    # ifdef _DLLEXPORT_   

    # define _DECLARATOR_   __declspec (dllexport)   

    # else   

    # define _DECLARATOR_   __declspec (dllimport)   

    # endif     

    і додати макрос _DECLARATOR_ до оголошення кожній функції, наприклад,        

    int   _DECLARATOR_ __cdecl SumFunc (int a, int b);     

    4. Далі або створити і додати до проекту. Def-файл з псевдонімами для кожної функції, або додати в заголовки бібліотеки наступне:        

    # ifdef _MSC_VER   

    # define FuncName1 _FuncName1   

    # define FuncName2 _FuncName2   

    # define FuncNameN _FuncNameN   

    # endif     

    Якщо використовувався # define-трюк, то пункт 7 потрібно буде пропустити.

    5. Скомпілювати BCB dll.

    6. За допомогою impdef.exe створити. Def-файл з найменуваннями експортованих функцій.

    7. Якщо в пункті 4 скористалися псевдонімами, видалити з. def-файла експорту невживані найменування функцій, залишивши тільки псевдоніми.

    8. Створити клієнтський VC-проект.

    9. З. Def-файла експорту бібліотеки за допомогою утиліти lib.exe створити об'єктний. lib-файл формату COFF і додати його до клієнтського VC-додатку.

    10. Копіювати BCB dll і її заголовки в папку з клієнтським VC-проектом.

    11. У клієнтському додатку підключити заголовкові файл dll.

    12. Викликати в тілі програми необхідні функції, не замислюючись над тим, що вони розташовані у зовнішній dll.

    Алгоритм з неявним зв'язуванням для експорту (імпорту) __stdcall-функцій

    Як уже згадувалося вище, утиліта 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, цей розділ присвячується.

    Вихідний код BCB dll залишився таким же, як у попередньому розділі (див. Лістинг 3), тільки ключове слово __cdecl скрізь необхідно замінити ключовим словом __stdcall.

    Відомо, що при створенні 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:

    Лістинг 6 - Компілятор Visual C + + 6.0

    ImplicitLinking_stdcallDummy.h        

    # ifdef _DLLEXPORT_   

    # define _DECLARATOR_ __declspec (dllexport)   

    # else   

    # define _DECLARATOR_   __declspec (dllimport)   

    # endif      

    extern "C"   

    (   

    int _DECLARATOR_ __stdcall   SumFunc (int a, int b);   

    HWND _DECLARATOR_ __stdcall   ViewStringGridWnd (int Count, double * Values);   

    )     

    ImplicitLinking_stdcallDummy.cpp        

    # define _DLLEXPORT_   

    # include   

    # include "ImplicitLinking_stdcallDummy.h"      

    int __stdcall SumFunc (int a, int b)   

    (   

    return 0;   

    )      

    HWND __stdcall ViewStringGridWnd (int Count, double * Values)   

    (   

    return NULL;   

    )     

    Згідно таблиці 1, VC експортує __stdcall-функції, додаючи до їх найменуванню інформацію про список аргументів і символ підкреслення. Отже, в об'єктних. Lib-файлі будуть імена, відмінні від оригінальних імен функцій, оголошених в заголовки, і тим більше відмінні від найменувань функцій, що експортуються з BCB dll, тому що __stdcall-функції компілятор BCB експортує без змін. Позбавлятися цієї невідповідності будемо знову за допомогою. def-файла. Для нашого прикладу він буде таким:

    DummyDef.def        

    libRARY ImplicitLinking_stdcall.dll      

    EXPORTS   

    SumFunc   

    ViewStringGridWnd     

    Рядок з ім'ям бібліотеки (LIBRARY) в. def-файлі не обов'язкова, але якщо вона є, то ім'я, вказане в ній, в точності має збігатися з іменами помилковою і вихідної dll. Додаємо. Def-файл до VC-проекту, перекомпіліруем і отримуємо помилкову dll і необхідну нам бібліотеку імпорту, містить коректне опис експортованих __stdcall-функцій. . lib-файл, дістався у спадок від неправдивої dll, повинен додаватися (прілінковиваться) до будь-якому VC-проекту, який збирається використовувати нашу вихідну BCB dll.

    Приклад VC-додатки, імпортуючого __stdcall-функції, такий же, як і в попередньому розділі (див. Лістинг 5). Чи не забудьте в прикладі підключити (# include) потрібний заголовки BCB dll і додати до проекту потрібну бібліотеку імпорту.

    Алгоритм з неявним зв'язуванням для експорту (імпорту) __stdcall-функцій (див. також Демонстраційний проект, ImplicitLinkingDll_stdcall.zip):

    Оголосити експортовані функції як __stdcall.

    Помістити оголошення функцій в блок extern "С". Чи не експортувати класи та функції-члени класів.

    скомпілювати BCB dll.

    Оскільки створити коректну бібліотеку імпорту з допомогою утиліти lib.exe не вдається, створити помилкову VC dll, яка містить такий же набір функцій, як і початкова BCB dll.

    Перевірити ідентичність назв помилкової dll і dll вихідної, назви повинні співпасти.

    Якщо для помилкової бібліотеки використовуються вихідні тексти BCB dll, то уд

         
     
         
    Реферат Банк
     
    Рефераты
     
    Бесплатные рефераты
     

     

     

     

     

     

     

     
     
     
      Все права защищены. Reff.net.ua - українські реферати ! DMCA.com Protection Status