Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
Дмитрий Козлюк 0cd768da2d
lab04: библиотеки
2 лет назад
..
assets lab04: библиотеки 2 лет назад
README.md lab04: библиотеки 2 лет назад

README.md

Использование библиотек

Цель работы

  1. Уметь устанавливать и подключать к программе внешние библиотеки.
  2. Уметь использовать типовые элементы API библиотек.
  3. Уметь работать с параметрами командной строки программы.

Задание

Добавить возможность построения гистограммы по данным из файла в сети. Адрес файла задается аргументом командной строки программы. Если адрес не задан, читать данные со стандартного ввода, как раньше.

Пример запуска программы с загрузкой данных из файла на сайте кафедры:

lab34.exe http://uit.mpei.ru/study/courses/cs/lab03/marks.txt >marks.svg

Работу нужно вести на основе кода общего задания к ЛР № 3 в прежнем репозитарии. По этой причине во всех примерах используется lab34.exe.

Защита представляет собой демонстрацию выполненного индивидуального задания, ответы на вопросы по коду и по релевантной теории (см. контрольные вопросы).

Указания

Скачивать файл по сети будем с помощью библиотеки cURL.

Получать сведения о системе будем с помощью API операционной системы. Это тоже внешняя библиотека, но её не требуется устанавливать.

Ввод из произвольного потока

Рефакторинг (refactoring) — это изменение кода программы без изменения её функциональных характеристик (интересующего пользователя поведения).

Зачем менять код, если это не добавляет возможностей и не исправляет ошибок? Например, для того, чтобы облегчить дальнейшее внесение изменений в него. В нашем случае программа жестко завязана на то, что данные поступают со стандартного ввода. Ожидается, что они смогут поступать не только оттуда. Код нужно переписать так, чтобы он мог работать с любым входящим потоком. Тогда текущее поведение будет частным случаем нового, когда поток — стандартный ввод (cin).

Желаемая схема работы программы.

Что такое «работать с потоком ввода»? Рассмотрим любой пример работы со стандартным потоком ввода:

cin >> number_count;

Здесь есть переменная cin, которая представляет поток, и операция >> с ней. Работать с другим потоком значит использовать иную переменную вместо cin.

Что такое «работать с любым потоком», если поток — это переменная в программе? Это значит, в программе есть блок кода для ввода данных, который принимает переменную-поток и работает с ней. Но параметризованный блок кода — это же функция! Вводом данных занимается функция input_data(). Следовательно, у нее должен появится параметр — поток, из которого читать.

Input
input_data(istream& in) {
    // Прежний код, но работающий с in вместо cin.
}

Это должна быть именно ссылка, потому что нельзя скопировать поток ввода — он объективно один во всей программе. Это не может быть константная ссылка, потому что функция меняет поток: данные после вычитывания оттуда «исчезают».

Вызов функции будет выглядеть так:

auto in = input_data(cin);

Самостоятельно. Перепишите функцию input_data() и ее вызов, как предложено. Убедитесь, что поведение программы не изменилось. Сделайте коммит.

Самостоятельно. Добавьте input_data() новый параметр: bool prompt. Если он равен true, то нужно выводить в cerr подсказки, если false, то не нужно их выводить. Сделайте коммит.

Загрузка данных по сети

Установка cURL

Страница библиотеки cURL: https://curl.haxx.se/libcurl/.

На этой странице есть раздел Download со ссылкой на страницу загрузок:

Вариантов загрузки очень много, потому что cURL собирается под много операционных систем (Windows, Linux, различные Unix-системы), архитектур процессора (Intel/AMD, ARM и других) и их вариантов (32 или 64 бита). Windows находится в конце списка:

Для Windows следует выбрать ссылку с описанием binary the curl project: Странице загрузок для Windows — ссылки, чтобы скачать архив для системы нужной разрядности. Как выбрать разрядность? Это зависит не от версии Windows, а от того, собирает компилятор 32-битные или 64-битные программы. По умолчанию с CodeBlocks идет компилятор 32-битных программ mingw32, он же устанавливается по инструкции для РПОСУ. Используемый компилятор можно увидеть в Build log при сборке программы. Если там mingw64, значит, компилятор 64-битный.

В архиве находится папка curl-7.69.1-win32-mingw (версия cURL и разрядность компилятора та, которая выбрана для загрузки). Ее нужно извлечь, переименовать в curl для удобства и переместить в каталог проекта.

Самостоятельно. Занесите эту папку в .gitignore. Проверьте, что сделали это правильно, с помощью команды git status.

Из папки curl далее понадобятся:

  • bin/libcurl.dll — динамическая библиотека (libcurl-x64.dll для 64 бит);
  • lib — каталог со статическими библиотеками;
  • include — каталог с заголовочными файлами.

Примечание. Есть инструменты, которые позволяют скачивать и устанавливать библиотеки из командной строки, для Windows это Chocolatey и vcpkg. Они автоматизируют и стандартизируют то, что в рамках этой ЛР делается вручную, чтобы разобраться.

Подключение cURL к проекту

Каждая программа должна до первого вызова функций библиотеки cURL вызвать функцию curl_global_init(). Например, это можно сделать первой строкой в main():

curl_global_init(CURL_GLOBAL_ALL);

При попытке собрать программу возникнут ошибки, текст которых означает, что константа CURL_GLOBAL_ALL и функция curl_global_init() не объявлены, то есть неизвестны компилятору:

error: 'CURL_GLOBAL_ALL' was not declared in this scope
error: 'curl_global_init' was not declared in this scope

Если открыть документацию по ссылке, она начинается так:

SYNOPSIS

#include <curl/curl.h>

CURLcode curl_global_init(long flags);

«Synopsis» значит «сводка» (отсюда слово «синоптик»), это типовой раздел справки к библиотекам, в котором кратко показывается, как функция объявлена и какой заголовочный нужно подключить, чтобы ее использовать. В данном случае нужно добавить в начало main.cpp:

#include <curl/curl.h>

При компиляции все равно будет ошибка, но другая — что файл не найден:

fatal error: curl/curl.h: No such file or directory

Как известно из лекций, #include <...> ищет файл в неких «стандартных каталогах», куда папка include из архива не входит. Чтобы добавить её в список, нужно открыть диалог Project → Build options... и перейти во вкладку Search directories. Там три дочерних вкладки: Compile (пути для поиска #include <...>), Linker (пути для поиска библиотек) и Resource compiler (не требуется). Нужно нажать кнопку Add, в открывшемся диалоге указать curl/include и нажать OK (путь к папке можно также выбрать через диалог рядом с полем ввода). Каталог curl/include добавится в список, диалог можно закрыть кнопкой OK.

Внимание. Рекомендуется вводить пути и имена файлов в настройках как текст, а не выбирать их с помощью диалогов.

Ошибка сборки изменится:

undefined reference to `__imp_curl_global_init'

Обратите внимание, что это уже ошибка не компиляции, а компоновки (линкера). Исходный текст программы правильный, все заголовочные файлы найдены, появился obj/Debug/main.o. Но чтобы создать из него исполняемый файл не хватает машинного кода функции curl_global_init().

Чтобы подключить библиотеку, нужно два действия:

  1. Вернуться к Search directories, теперь на дочернюю вкладку Linker. Там нужно добавить каталог curl/lib аналогично предыдущему шагу.

  2. Указать, какую именно библиотеку подключить. Если просмотреть каталог curl/lib, там есть libcurl.a и libcurl.dll.a. Сейчас выберем libcurl.dll.a, разница будет пояснена в последующих пунктах. На вкладке Linker settings (соседняя с Search directories) нужно нажать Add и ввести libcurl.dll.a, после чего сохранить настройки и покинуть диалог нажатиями OK.

Если все сделано правильно, сборка должна проходить успешно.

Установка динамических библиотек

Перейдите в каталог bin/Debug, откройте там консоль и запустите программу:

lab34.exe

Будет ошибка (главное в ней — имя библиотеки libcurl….dll):

Дело в том, что программа собрана с использованием динамической библиотеки. В lab34.exe нет кода cURL, а только указание загрузить libcurl-x64.dll, где этот код есть, и вызвать его. Но библиотеки нет в тех каталогах, где Windows ищет библиотеки. Одним из таких каталогов является текущий. Скопируйте libcurl-x64.dll в bin/Debug и попробуйте запустить программу (libcurl.dll для 32 бит).

Самостоятельно. Убедитесь, что программа запускается. Сделайте коммит.


Врезка отсюда до горизонтальной черты не нужна для новых версий cURL. Если после размещения libcurl….dll в bin/Debug программа запускается, пропустите это дополнение.

Ошибка изменится, теперь не найдены будут libcrypto-1_1.dll и libssl-1_1.dll, но их уже нет в поставке cURL. В интернете можно узнать, что эти библиотеки из набора OpenSSL, нужного cURL для установки защищенных соединений. Откуда их взять?

Разумеется, в интернете найдется всё:

Никогда не устанавливайте библиотеки из непроверенных источников!

Чтобы установить недостающие библиотеки, нужно посетить официальный сайт, как правило, там есть ссылки для загрузки, и им можно доверять. Кроме того, даже если скачанная библиотека будет подлинной (безопасной), раскладывать отдельные библиотеки вручную ненадежно. Через OpenSSL wiki можно найти сайт с готовыми сборками.

Скачайте установочный пакет (MSI) по ссылке в первой или третьей строке в зависимости от разрядности системы:

Внимание. Разрядность cURL и OpenSSL должны совпадать.

В процессе установки:

  • При выборе Copy OpenSSL DLLs to: нужно выбрать The Windows system directory, чтобы библиотеки были доступны при запуске программы из любого каталога.
  • На последнем экране автор сборки предлагает поддержать его труд 10 $. Можно просто снять флажок и продолжить. OpenSSL распространяется бесплатно.

Статическая сборка программы

Попробуем собрать программу статически. Чтобы поменять компоновку cURL на статическую, необходимо:

  1. Вернуться в Linker settings, выбрать в списке libcurl.dll.a, нажать Edit, изменить имя на libcurl.a и нажать OK.

  2. Перейти на вкладку Compiler settings (рядом с Linker settings), выбрать дочернюю вкладку #defines. Там в текстовое поле нужно ввести CURL_STATICLIB и выйти из диалога кнопкой OK.

Примечание. Последний шаг специфичен для cURL, аналогичный шаг для других библиотек обычно требуется только на Windows.

При сборке будет множество ошибок:

curl\lib\libcurl.a(connect.o):(.text+0x1d1): undefined reference to `__imp_ntohs'
curl\lib\libcurl.a(connect.o):(.text+0x2ee): undefined reference to `__imp_getpeername'
curl\lib\libcurl.a(connect.o):(.text+0x2f8): undefined reference to `__imp_WSAGetLastError'
...

Причина в том, что код cURL использует функции из других библиотек, поэтому программа должна линковаться с ними всеми. Для этого нужно определять, из какой библиотеки недостающая функция (обычно поиском в интернете), устанавливать эту библиотеку (если она не системная), добавлять её в Linker settings и пробовать собрать снова. В рамках ЛР этого делать не нужно. На практике у зависимостей основной библиотеки могут быть другие лицензии, которые придется принимать во внимание.

Самостоятельно. Удалите все изменения, сделанные в попытках собрать программу статически, при помощи git.

Получение аргументов программы

Из ЛР № 1 известно, что программа может запускаться с аргументами, которые перечисляются после ее имени через пробел: fc /N expected.txt actual.txt. Здесь fc (fc.exe) — исполняемый файл программы, а /N, expected.txt и actual.txt — три аргумента. Перенаправление ввода-вывода не является аргументом, например, команда lab01.exe <input.txt вызывает lab01.exe без аргументов.

Получить значения аргументов позволяет следующая версия функции main():

int
main(int argc, char* argv[]) {
    ...

Примечание. В некоторых источниках можно найти int main(int argc, char** argv) — это то же самое. Все формы void main(...) являются нестандартными.

Параметр argc (argument count) показывает количество элементов в массиве argv (argument values). Начальный элемент (с индексом 0) содержит имя программы, то есть при запуске без параметров argc == 1.

Самостоятельно. Напишите в начале main() код, который в случае, если argc больше 1, распечатывает argc и argv в формате argv[0] = ... и выходит (return 0). Сделайте коммит. Проверьте на таких примерах, поясните результат:

  • lab34.exe -x --y /z w
  • lab34.exe param "with spaces"
  • lab34.exe param <marks.txt >NUL (скачайте marks.txt)

Примечание. Необязательные аргументы, которые имеют фиксированные имена (например, /N у fc), иногда называют опциями. Имена опций принято начинать с -, -- или / (только в Windows). Остальные аргументы называют позиционными.

Загрузка файла по сети

Весь код в этом пункте должен выполняться только в случае argc > 1.

Для простых сценариев cURL имеет набор easy-функций, которые позволяют скачать файл всего в несколько строк. Первой должна быть вызвана curl_easy_init():

CURL* curl = curl_easy_init();

Что за тип данных CURL*? Это так называемый дескриптор (handle). Очень часто библиотека оперирует сложными объектами, внутреннее устройство которых программе знать не нужно, но с другой стороны, программа должна сообщать функциям библиотеки, с каким объектом работать. В случае cURL таким объектом выступает сетевое соединение и связанные данные. Единственное, что можно делать с дескрипторами — передавать их функциям библиотеки и сравнивать между собой. Переменную типа CURL* нельзя разыменовать, хотя это и указатель.

Как правило, если есть функция создания дескриптора, как curl_easy_init(), есть и функция его освобождения (закрытия). Действительно, документация гласит:

This call MUST have a corresponding call to curl_easy_cleanup when the operation is complete.

Это означает, что вся работа с curl должна делаться между написанной строкой и следующим вызовом:

curl_easy_cleanup(curl);

Самостоятельно. Изучите пример (раздел EXAMPLE) документации на curl_easy_init(). Перенесите пример в программу, но вместо фиксированного адреса используйте первый аргумент командной строки (с индексом 1). Убедитесь, что программа работает — содержимое файла из сети печатается на стандартный вывод. Код печати argc и argv удалите. Сделайте коммит.

Структура программы должна стать такой:

int
main(int argc, char* argv[]) {
    if (argc > 1) {
        // работа с cURL
        return 0;
    }
    // прежний код построения гистограммы
}

Обработка ошибок

В переменную res пример сохраняет результат curl_easy_perform(). Значение res — числовой код ошибки, а если ошибок не было, CURLE_OK (равно 0). Функция curl_easy_strerror() принимает код ошибки и возвращает строку с текстом ошибки.

Самостоятельно. Проверяйте результат curl_easy_perform(), если произошли ошибка, печатайте её текст и завершайте программу вызовом exit(1). Сделайте коммит.

Работа с буфером, загруженным по сети

Содержимое файла из сети нужно не печатать, а разбирать, считывая из него те же данные, что и со стандартного ввода. Проще всего сначала считать весь файл из сети в память. Такое промежуточное хранилище называют буфером. Затем можно организовать поток, который будет читать не из стандартного ввода, а из буфера.

Для загрузки файла выделим функцию download(), которая принимает строку с адресом файла. Файл <sstream> подключает stringstream — поток, который может читать из буфера в памяти. Также в переменные типа stringstream можно записывать данные, то есть заполнять буфер.

#include <sstream>
#include <string>

// ...прежние функции...

Input
download(const string& address) {
    stringstream buffer;

    // TODO: заполнить буфер.

    return input_data(buffer, false);
}

Обратите внимание: CURLOPT_URL требует адрес в виде строки C (const char*). Чтобы получить её из address типа string можно как address.c_str().

В зависимости от того, переданы ли программе параметры, она получает исходные данные (Input input) либо из сети, либо из стандартного ввода:

int
main(int argc, char* argv[]) {
    Input input;
    if (argc > 1) {
        input = download(argv[1]);
    } else {
        input = input_data(cin, true);
    }

    const auto bins = make_histogram(input);
    show_histogram_svg(bins);
}

Самостоятельно. Перенесите код работы с cURL в место, отмеченное комментарием // TODO. Убедитесь, что программа компилируется (запускать ее с параметром пока нельзя).

Сохранение данных из сети в буфер

Как же получить доступ к данным, которые cURL загружает и (пока) печатает? Каждый раз, когда cURL получает из сети очередной блок данных, библиотека может вызывать функцию, которую укажет программа. Эта функция должна добавлять полученный блок данных в буфер.

Такой элемент интерфейса библиотеки называется callback-функцией, или функцией обратного вызова.

Функция должна иметь такой вид:

size_t
write_data(void* items, size_t item_size, size_t item_count, void* ctx) {
    // TODO: дописывать данные к буферу.
    return 0;
}

Имя функции и параметров может быть любым, но типы данных должны быть строго такими, как описано в документации. Ошибку может быть очень сложно отладить. К счастью, компилятор предупреждает о несоответствии.

Параметры:

  • void* items — указатель на принятые данные: item_count блоков по item_size каждый, итого item_size * item_count байтов.
  • size_t item_size — размер одного блока данных;
  • size_t item_count — количество блоков данных;
  • void* ctx — пользовательские данные (поясняется ниже).

Чтобы cURL продолжила загрузку, функция должна возвращать размер принятых данных в байтах, иначе curl_easy_perform() остановит работу с ошибкой. Пока функция возвращает 0, просто чтобы возвращать какое-нибудь значение.

Указать cURL, какую функцию вызывать, можно так:

curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);

Самостоятельно. Внутри write_data() вычислите количество принятых байтов (см. описание параметров), сохраните в переменную data_size.

Далее необходимо внутри write_data() добавлять данные в буфер, для этого есть специальный метод buffer.write():

    buffer.write(items, data_size);

Но это не компилируется: переменная buffer объявлена в download(), внутри write_data() она недоступна. Перенести buffer внутрь write_data() — не выход: buffer нужна и в download(). Объявлять в write_data() еще один буфер тоже неправильно: это должен быть тот же самый буфер, который затем использует download().

Заметим, что хотя buffer недоступна, она существует где-то в памяти, потому что write_data() вызывается из curl_easy_perform(), и пока этот вызов не закончен, не закончится и download(). Задача в том, чтобы получить этот адрес, так как имея адрес переменной (то есть указатель на нее), с ней можно работать.

Это типовая необходимость и обычно библиотеки, в том числе cURL, предоставляют в callback-функциях пользовательские данные (user data). Перед тем, как позволить библиотеке вызывать функцию, программа указывает также фиксированные данные (обычно один указатель), которые будут передаваться библиотекой в функцию. Именно для этого write_data() принимает параметр ctx (контекст). Можно передавать в этом контексте адрес buffer так (перед curl_easy_perform()):

curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);

Контекст имеет тип void*, но нам известно, что это указатель на buffer, то есть реальный тип — stringstream*. Это как раз тот случай, когда программист может и должен принудительно указать компилятору тип данных (приведение типов из ЛР № 1):

stringstream* buffer = reinterpret_cast<stringstream*>(ctx);

Поскольку buffer здесь указатель, его нужно разыменовать перед вызовом метода:

(*buffer).write(items, data_size);

Осталась последняя ошибка: если прочитать документацию на write(), она принимает первым параметром const char*, а items имеет тип void*.

Самостоятельно. Сделайте приведение типа для items, чтобы программа компилировалась.

Конструкция (*buffer).write(...) выглядит громоздко, потому в C++ введен специальный оператор стрелки, который позволяет вызвать метод по указателю красивее (параметры write() прежние, они опущены для краткости):

buffer->write(...);

Самостоятельно. Добейтесь компиляции и проверьте корректную работу программы с примером из задания.

Индивидуальные задания

Всю необходимую информацию по функциям cURL нужно изучить по официальной документации и на защите предъявить ссылку на конкретную страницу. В частности, если не указан конкретный параметр curl_easy_setopt(), нужно подобрать его по описанию в списке.

Во всех вариантах нужно обрабатывать ошибки cURL, как это делается в основной программе.

Если вариант требует ввода дополнительных данных, проверить работу с файлом «из сети» можно через локальный файл и адрес вида file://C:/Users/kozlyuk/Desktop/marks.txt для файла marks.txt на рабочем столе пользователя kozlyuk. Полный путь к файлу можно получить, щелкнув в адресной строке «Проводника» (так же, как перед запуском cmd).

Вариант 1

С помощью функции curl_easy_getinfo() печатайте на стандартный вывод ошибок суммарное время (total), затраченное на загрузку файла по сети.

Вариант 2

Добавьте программе опцию -format, которая определяет формат вывода: lab34.exe -format text — текст, lab34.exe -format svg — изображение. Опция может стоять до или после URL: lab34.exe -format svg http://... или lab04.exe http://... -format svg. Если после -format не стоит text или svg, нужно печатать сообщение с подсказкой, как запускать программу, и завершать работу.

Вариант 3

С помощью функции curl_easy_getinfo() печатайте на стандартный вывод ошибок скорость загрузки файла по сети.

Вариант 4

Добавьте программе опцию -bins, которая позволяет задать количество корзин при запуске. Считывать его в read_input() при этом не нужно. Например: lab34.exe -bins 3. Опция может стоять до или после URL: lab34.exe -bins 3 http://... или lab04.exe http://... -bins 3. Если после -bins не стоит числа, нужно печатать сообщение с подсказкой, как запускать программу, и завершать работу.

Вариант 5

Добавьте программе опцию -generate, при указании которой программа будет генерировать числа вместо их считывания. Количество столбцов считывать по-прежнему нужно. Например, lab34.exe -generate 100 генерирует 100 чисел. Опция может стоять до или после URL: lab34.exe -generate 10 http://... или lab04.exe http://... -generate 10. Если после -generate не стоит числа, нужно печатать сообщение с подсказкой, как запускать программу, и завершать работу.

Вариант 6

С помощью функции curl_easy_getinfo() печатайте на стандартный вывод ошибок размер файла, загружаемого по сети.

Вариант 7

При помощи CURLOPT_PROGRESSFUNCTION добавьте в программу отображение прогресса загрузки файла. Каждый раз, когда cURL рапортует прогресс, печатайте строку вида: Progress: 42%.

Указание. Чтобы cURL рапортовала прогресс, нужно установить CURLOPT_NOPROGRESS в 0.

Вариант 8

С помощью функции curl_easy_getinfo() печатайте на стандартный вывод ошибок IP-адрес сервера, с которого скачан файл (не local).

Вариант 9

С помощью функции curl_version_info() печатайте на стандартный вывод ошибок версию cURL и версию OpenSSL в строковом виде.

Вариант 10

Добавьте программе опцию -verbose, при указании которой нужно включать отладочный вывод cURL с помощью CURLOPT_VERBOSE. Опция может стоять до или после URL: lab34.exe -verbose http://... или lab04.exe http://... -verbose. Если встретился иной аргумент, начинающийся с дефиса, нужно печатать сообщение с подсказкой, как запускать программу, и завершать работу.

Вариант 11

С помощью функции curl_version_info() печатайте на стандартный вывод ошибок список протоколов, которые поддерживает cURL (protocols).

Вариант 12

Добавьте программе опцию -fill для задания цвета заливки столбцов. Например, lab34.exe -fill red или lab04.exe -fill "#ff0000" заливает их красным. Опция может стоять до или после URL: lab34.exe -fill red http://... или lab04.exe http://... -fill red. Если после -fill не стоит еще одного аргумента, нужно печатать сообщение с подсказкой, как запускать программу, и завершать работу.

Вариант 13

Добавьте программе опцию -stroke для задания цвета границ столбцов. Например, lab34.exe -stroke red или lab04.exe -stroke "#ff0000" делает их красными. Опция может стоять до или после URL: lab34.exe -stroke red http://... или lab04.exe http://... -fill red. Если после -stroke не стоит еще одного аргумента, нужно печатать сообщение с подсказкой, как запускать программу, и завершать работу.

Вариант 14

С помощью функции curl_easy_getinfo() печатайте на стандартный вывод ошибок размер запроса, который cURL отправила серверу (request size).

Вариант 15

Установите callback-функцию CURLOPT_HEADERDATA, откуда печатайте полученные данные на стандартный вывод ошибок.

Вариант 16

С помощью функции curl_easy_getinfo() печатайте на стандартный вывод ошибок время, затраченное на установку соединения с сервером (connect).

Вариант 17

Устанавливайте опцию CURLOPT_FAILONERROR и анализируйте результат curl_easy_perform(), чтобы проверить результат загрузки файла, а не выполнения запроса. Придумайте способ тестирования.

Вариант 18

С помощью функции curl_easy_getinfo() печатайте на стандартный вывод ошибок время, затраченное на получение IP-адреса сервера по его имени (name lookup).

Контрольные вопросы

  1. Сколько аргументов в команде git add -p file.txt? Чему равно argc?
  2. Из каких файлов и как собирается программа, использующая библиотеку?
  3. Чем отличаются статические и динамические библиотеки?
  4. Как расшифровывается термин «API» и что представляет собой API библиотеки?
  5. Что такое дескриптор (handle), для чего он предназначен?
  6. Что такое функция обратного вызова (callback)?
  7. Зачем и как применяются пользовательские данные в callback-функциях?