Розробляючи програми на C++ ми маємо можливість повторно використовувати уже написаний код організований у бібліотеки. Бібліотека — це уже готовий, скомпільовай, об’єктний код, який можна підключати до своєї програми. Бібліотеки бувають статичними та динамічними.
Зміст
- 1. Статичні бібліотеки
- 2. Динамічні бібліотеки
- 3. Проект calc
- 4. Створення бібліотеки
- 5. Використання бібліотеки
- 6. Автоматизація процесу компіляції
- 7. Рефакторинг проекту
1. Статичні бібліотеки
Статична бібліотека, на етапі компіляції вбудовується у виконуваний файл. Схематично це можна зобразити таким малюнком:
При такому підході, кожна наша програма, яка використовує статичну бібліотеку, має свою, особисту її копію.
2. Динамічні бібліотеки
При динамічній лінковці у виконуваний файл вбудовується не вся бібліотека, а лише таблиця посилань цієї бібліотеки. Схематично це зображається наступним малюнком:
Більш детально про бібліотеки можна прочитати тут чи тут. Ми ж, сконцентруємося більше на практиці і послідовно розглянемо сам процес створення та використання бібліотек.
3. Проект calc
Давайте, створимо невеликий проект на прикладі якого будемо описувати процес. Це буде простий консольний калькулятор, який буде вміти додавати, віднімати, множити та ділити два числа. Числа і операції будемо передавати параметрами командної стрічки. При цьому, реалізацію самих операцій, у вигляді методів, ми винесемо у бібліотеку, а в основній програмі будемо використовувати ці операції. Отже, почнемо з написання коду бібліотеки.
4. Створення бібліотеки
Створимо два файла calc_library.h:
і lib.cpp:
4.1. Створення статичної бібліотеки
Тепер, маючи код нашої бібліотеки, ми можемо скомпілювати його і почнемо ми зі статичної бібліотеки.
Для того, щоб отримати статичну бібліотеку послідовно виконаємо наступні команди:
$ g++ --std=c++11 -Wall -c calc_library.cpp -o calc_library_static.o $ ar rcs libcalc_library.a calc_library_static.o |
|
g++ --std=c++11 -Wall -c calc_library.cpp -o calc_library_static.o ar rcs libcalc_library.a calc_library_static.o |
Перша команда створює об’єктний файл, а друга — запаковує цей файл у статичну бібліотеку. По своїй природі статична бібліотека є звичайним архівом, тому ми використовуємо команду архівації ar. Власне, саме тому розширення статичної бібліотеки .a походить від слова archive (архів).
В результаті, ми отримаємо готову до використання статичну бібліотеку libcalc_library.a.
4.2. Створення динамічної бібліотеки
Тепер створимо динамічну бібліотеку. Для цього послідовно виконаємо команди:
$ g++ --std=c++11 -Wall -fpic -c calc_library.cpp -o calc_library_dynamic.o $ g++ -shared calc_library_dynamic.o -o libcalc_library.so |
|
g++ --std=c++11 -Wall -c calc_library.cpp -o calc_library_dynamic.o g++ -shared calc_library_dynamic.o -o libcalc_library.dll |
Як бачите, уся відмінність у командах це розширення результуючого файла. Для Windows динамічна бібліотека має розширення .dll
(dynamic linked library — динамічно з’єднувана бібліотека), а для *NIX подібних систем динамічна бібліотека має розширення .so
(shared object — розділюваний об’єкт).
5. Використання бібліотеки
Для того щоб скористатися щойно створеними бібліотеками, ми напишемо програму калькулятор у файлі main.cpp. Код програми наступний:
Зверніть увагу, що тут ми нігде не оголошуємо і не описуємо методи add(), minus(), div() і mult(). Тому, якщо ми попробуємо скомпілювати цю програму уже відомою нам командою:
$ g++ --std=c++11 -Wall min.cpp -o calc
то компілятор видасть помилку, оскільки він не знайде цих методів. Ми повинні явно вказати компілятору бібліотеку в якій знаходиться реалізація цих методів.
5.1. Використання статичної бібліотеки
Для того щоб скомпілювати нашу програму, потрібно використати додаткові ключі команди компіляції, а саме:
$ g++ --std=c++11 -Wall -L . -static main.cpp -l calc_library -o calc_static |
|
g++ --std=c++11 -Wall -L . -static main.cpp -l calc_library -o calc_static.exe |
- Ключом -L ми задаємо шлях де компілятор повинен шукати нашу бібліотеку. Оскільки, ми вказали відносний шлях
.
, то компілятор буде шукати бібліотеку у поточному каталозі; - Ключ -l (ел маленьке) вказує компілятору, яку бібліотеку потрібно підключити. Зверніть увагу, що ми опустили префікс lib перед назвою бібліотеки;
- Ключ -static вказує, що компіляція буде статичною.
Слід зауважити, що і після ключа -L, і після ключа -l (ел маленьке) можна опустити пропуск, це ніяк не вплине на компіляцію. Тобто, ми з успіхом можемо запустити таку команду:
g++ --std=c++11 -Wall -L. -static main.cpp -lcalc_library -o calc_static
Також, варто сказати, що, якщо нам потрібно зазначити декілька шляхів чи бібліотек, то на кожен шлях чи бібліотеку потрібен свій ключ -L, чи -l (ел маленьке), відповідно.
Нарешті, ми можемо запустити програму і подивитися на її результати:
$ ./calc_static 10 + 20 $ ./calc_static 10 - 20 $ ./calc_static 10 \* 20 $ ./calc_static 10 / 20 |
|
calc_static 10 + 20 calc_static 10 - 20 calc_static 10 * 20 — не працює calc_static 10 / 20 |
5.1. Використання динамічної бібліотеки
Для того, щоб скомпілювати нашу програму з використанням динамічної бібліотеки, потрібно запустити таку ж команду як і у випадку статичної бібліотеки тільки без ключа -static, тобто:
$ g++ --std=c++11 -Wall -L./ main.cpp -lcalc_library -o calc_dynamic |
|
g++ --std=c++11 -Wall -L./ main.cpp -lcalc_library -o calc_dynamic.exe |
В результаті, ми отримаємо виконуваний файл. Запустимо його на виконання:
$ ./calc_dynamic 10 + 20 |
|
calc_dynamic 10 + 20 |
Тепер, коли ми маємо дві програми, статичної і динамічної лінковки, зверніть увагу на їх розмір. Програма скомпільована як статична набагато більша у розмірі за динамічно скомпільовану програму. Це тому, що при статичній лінковці бібліотека вбудовується у виконуваний файл, а при динамічній лінковці — цього не відбувається.
6. Автоматизація процесу компіляції
Щоб автоматизувати процес компіляції, ми напишемо Makefile. В цьому Makefile’і ми опишемо 6 цілей. Коротко розглянемо кожну з них:
- Ціль static_library — має залежність від файла calc_library.cpp і створює статичну бібліотеку libcalc_library.a;
- Ціль dynamic_library — має залежність від файла calc_library.cpp і створює динамічну бібліотеку libcalc_library.so чи calc_library.dll, залежно від системи;
- Ціль static — залежить від статичної бібліотеки libcalc_library.a і створює статично злінкований виконуваний файл;
- Ціль dynamic — залежить від динамічної бібліотеки libcalc_library.so і створює динамічно злінкований виконуваний файл;
- Ціль all — створює і статичний, і динамічний виконувані файли.
- Ціль clean — видаляє усі скомпільовані файли.
І, власне, сам Makefile:
|
|
|
Тепер, ми можемо компілювати нашу програму одною командою:
$ make |
|
mingw32-make |
7. Рефакторинг проекту
Ми завжди стараємося огранізовувати файлову структуру проекту таким чином, щоб усі файли не були в одному каталозі. Наприклад, файли заголовків, ми розміщуємо у каталозі inc (inclide), файли з кодом — у каталозі src (source), тимчасові, проміжні, об’єктні файли — у папці obj (object), скомпільовані файли бібліотек — у папці lib, а результуючі, виконувальні файли — у папці bin (binary). Отже, займемося цим.
Створимо ці папки у каталозі проекту і перенесемо відповідні файли у призначені для них місця. Файлова структура проекту набуде такого вигляду:
Гарно, акуратно.
При таких змінах наш Makefile став неробочим і потребує внесення деяких змін. Зміни, в основному, пов’язані із шляхами до файлів. Також, в деяких командах потрібно явно вказати компілятору шлях де містяться файли заголовків. Робиться це за допомогою ключа -I. Ми не робили цього дотепер, оскільки наш файл заголовку calc_library.h розміщувався в одній директорії із файлом реалізації calc_library.cpp. Відредагуємо Makefile наступним чином:
|
|
|
Успіхів!
Немає коментарів:
Дописати коментар