# Использование библиотек ## Цель работы 1. Уметь устанавливать и подключать к программе внешние библиотеки. 1. Уметь использовать типовые элементы API библиотек. 1. Уметь работать с параметрами командной строки программы. ## Задание Добавить возможность построения гистограммы по данным из файла в сети. Адрес файла задается аргументом командной строки программы. Если адрес не задан, читать данные со стандартного ввода, как раньше. Пример запуска программы с загрузкой данных из файла на сайте кафедры: ``` lab34.exe http://uit.mpei.ru/study/courses/cs/lab03/marks.txt >marks.svg ``` Работу нужно вести на основе кода общего задания к ЛР № 3 в прежнем репозитарии. По этой причине во всех примерах используется `lab34.exe`. Защита представляет собой демонстрацию выполненного индивидуального задания, ответы на вопросы по коду и по релевантной теории (см. контрольные вопросы). ## Указания Скачивать файл по сети будем с помощью библиотеки [cURL](https://curl.haxx.se). Получать сведения о системе будем с помощью API операционной системы. Это тоже внешняя библиотека, но её не требуется устанавливать. ### Ввод из произвольного потока Рефакторинг (refactoring) — это изменение кода программы без изменения её функциональных характеристик (интересующего пользователя поведения). Зачем менять код, если это не добавляет возможностей и не исправляет ошибок? Например, для того, чтобы облегчить дальнейшее внесение изменений в него. В нашем случае программа жестко завязана на то, что данные поступают со стандартного ввода. Ожидается, что они смогут поступать не только оттуда. Код нужно переписать так, чтобы он мог работать с любым входящим потоком. Тогда текущее поведение будет частным случаем нового, когда поток — стандартный ввод (`cin`). ![Желаемая схема работы программы.](assets/flow.svg) Что такое «работать с потоком ввода»? Рассмотрим любой пример работы со стандартным потоком ввода: ```cpp cin >> number_count; ``` Здесь есть переменная `cin`, которая представляет поток, и операция `>>` с ней. Работать с другим потоком значит использовать иную переменную вместо `cin`. Что такое «работать с любым потоком», если поток — это переменная в программе? Это значит, в программе есть блок кода для ввода данных, который принимает переменную-поток и работает с ней. Но параметризованный блок кода — это же функция! Вводом данных занимается функция `input_data()`. Следовательно, у нее должен появится параметр — поток, из которого читать. ```cpp Input input_data(istream& in) { // Прежний код, но работающий с in вместо cin. } ``` Это должна быть именно ссылка, потому что нельзя скопировать поток ввода — он объективно один во всей программе. Это не может быть константная ссылка, потому что функция меняет поток: данные после вычитывания оттуда «исчезают». Вызов функции будет выглядеть так: ```cpp auto in = input_data(cin); ``` **Самостоятельно.** Перепишите функцию `input_data()` и ее вызов, как предложено. Убедитесь, что поведение программы не изменилось. Сделайте коммит. **Самостоятельно.** Добавьте `input_data()` новый параметр: `bool prompt`. Если он равен `true`, то нужно выводить в `cerr` подсказки, если `false`, то не нужно их выводить. Сделайте коммит. ### Загрузка данных по сети #### Установка cURL Страница библиотеки cURL: . На этой странице есть раздел *Download* со ссылкой на страницу загрузок: ![](assets/curl-download.png) Вариантов загрузки очень много, потому что cURL собирается под много операционных систем (Windows, Linux, различные Unix-системы), архитектур процессора (Intel/AMD, ARM и других) и их вариантов (32 или 64 бита). Windows находится в конце списка: ![](assets/curl-windows.png) Для Windows следует выбрать ссылку с описанием `binary the curl project`: Странице загрузок для Windows — ссылки, чтобы скачать архив для системы нужной разрядности. Как выбрать разрядность? Это зависит не от версии Windows, а от того, собирает компилятор 32-битные или 64-битные программы. По умолчанию с CodeBlocks идет компилятор 32-битных программ mingw32, он же устанавливается по инструкции для РПОСУ. Используемый компилятор можно увидеть в *Build log* при сборке программы. Если там mingw64, значит, компилятор 64-битный. ![](assets/curl-link.png) В архиве находится папка `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()`][curl_global_init]. Например, это можно сделать первой строкой в `main()`: ```cpp 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 > > CURLcode curl_global_init(long flags); «Synopsis» значит «сводка» (отсюда слово «синоптик»), это типовой раздел справки к библиотекам, в котором кратко показывается, как функция объявлена и какой заголовочный нужно подключить, чтобы ее использовать. В данном случае нужно добавить в начало `main.cpp`: ```cpp #include ``` При компиляции все равно будет ошибка, но другая — что файл не найден: ``` 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` аналогично предыдущему шагу. 1. Указать, какую именно библиотеку подключить. Если просмотреть каталог `curl/lib`, там есть `libcurl.a` и `libcurl.dll.a`. Сейчас выберем `libcurl.dll.a`, разница будет пояснена в последующих пунктах. На вкладке *Linker settings* (соседняя с *Search directories)* нужно нажать *Add* и ввести `libcurl.dll.a`, после чего сохранить настройки и покинуть диалог нажатиями *OK.* Если все сделано правильно, сборка должна проходить успешно. [curl_global_init]: https://curl.haxx.se/libcurl/c/curl_global_init.html #### Установка динамических библиотек Перейдите в каталог `bin/Debug`, откройте там консоль и запустите программу: ```cmd lab34.exe ``` Будет ошибка (главное в ней — имя библиотеки `libcurl….dll`): ![](assets/dll-not-found.png) Дело в том, что программа собрана с использованием *динамической* библиотеки. В `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 для установки защищенных соединений. Откуда их взять? Разумеется, в интернете найдется всё: ![](assets/search.png) **Никогда не устанавливайте библиотеки из непроверенных источников!** Чтобы установить недостающие библиотеки, нужно посетить официальный сайт, как правило, там есть ссылки для загрузки, и им можно доверять. Кроме того, даже если скачанная библиотека будет подлинной (безопасной), раскладывать отдельные библиотеки вручную ненадежно. Через OpenSSL wiki можно найти [сайт с готовыми сборками](https://slproweb.com/products/Win32OpenSSL.html). Скачайте установочный пакет (MSI) по ссылке в первой или третьей строке в зависимости от разрядности системы: **Внимание.** Разрядность cURL и OpenSSL должны совпадать. В процессе установки: ![](assets/installer.png) * При выборе *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 NUL` (скачайте `marks.txt`) **Примечание.** Необязательные аргументы, которые имеют фиксированные имена (например, `/N` у `fc`), иногда называют опциями. Имена опций принято начинать с `-`, `--` или `/` (только в Windows). Остальные аргументы называют *позиционными.* #### Загрузка файла по сети Весь код в этом пункте должен выполняться только в случае `argc > 1`. Для простых сценариев cURL имеет набор easy-функций, которые позволяют скачать файл всего в несколько строк. Первой должна быть вызвана [`curl_easy_init()`][curl_easy_init]: [curl_easy_init]: https://curl.haxx.se/libcurl/c/curl_easy_init.html ```cpp 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` должна делаться между написанной строкой и следующим вызовом: ```cpp curl_easy_cleanup(curl); ``` **Самостоятельно.** Изучите пример (раздел *EXAMPLE*) документации на `curl_easy_init()`. Перенесите пример в программу, но вместо фиксированного адреса используйте первый аргумент командной строки (с индексом 1). Убедитесь, что программа работает — содержимое файла из сети печатается на стандартный вывод. Код печати `argc` и `argv` удалите. Сделайте коммит. Структура программы должна стать такой: ```cpp int main(int argc, char* argv[]) { if (argc > 1) { // работа с cURL return 0; } // прежний код построения гистограммы } ``` #### Обработка ошибок В переменную `res` пример сохраняет результат `curl_easy_perform()`. Значение `res` — числовой [код ошибки][libcurl-errors], а если ошибок не было, `CURLE_OK` (равно 0). Функция [`curl_easy_strerror()`][curl_easy_strerror] принимает код ошибки и возвращает строку с текстом ошибки. **Самостоятельно.** Проверяйте результат `curl_easy_perform()`, если произошли ошибка, печатайте её текст и завершайте программу вызовом `exit(1)`. Сделайте коммит. [libcurl-errors]: https://curl.haxx.se/libcurl/c/libcurl-errors.html [curl_easy_strerror]: https://curl.haxx.se/libcurl/c/curl_easy_strerror.html #### Работа с буфером, загруженным по сети Содержимое файла из сети нужно не печатать, а разбирать, считывая из него те же данные, что и со стандартного ввода. Проще всего сначала считать весь файл из сети в память. Такое промежуточное хранилище называют *буфером*. Затем можно организовать поток, который будет читать не из стандартного ввода, а из буфера. Для загрузки файла выделим функцию `download()`, которая принимает строку с адресом файла. Файл `` подключает `stringstream` — поток, который может читать из буфера в памяти. Также в переменные типа `stringstream` можно записывать данные, то есть заполнять буфер. ```cpp #include #include // ...прежние функции... 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`) либо из сети, либо из стандартного ввода: ```cpp 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-функцией,* или *функцией обратного вызова.* Функция должна иметь такой вид: ```cpp 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, какую функцию вызывать, можно так: ```cpp curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); ``` **Самостоятельно.** Внутри `write_data()` вычислите количество принятых байтов (см. описание параметров), сохраните в переменную `data_size`. Далее необходимо внутри `write_data()` добавлять данные в буфер, для этого есть специальный метод [`buffer.write()`][basic_ostream/write]: ```cpp 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()`): ```cpp curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); ``` Контекст имеет тип `void*`, но нам известно, что это указатель на `buffer`, то есть реальный тип — `stringstream*`. Это как раз тот случай, когда программист может и должен принудительно указать компилятору тип данных (приведение типов из ЛР № 1): ```cpp stringstream* buffer = reinterpret_cast(ctx); ``` Поскольку `buffer` здесь указатель, его нужно разыменовать перед вызовом метода: ```cpp (*buffer).write(items, data_size); ``` Осталась последняя ошибка: если прочитать документацию на `write()`, она принимает первым параметром `const char*`, а `items` имеет тип `void*`. **Самостоятельно.** Сделайте приведение типа для `items`, чтобы программа компилировалась. Конструкция `(*buffer).write(...)` выглядит громоздко, потому в C++ введен специальный оператор стрелки, который позволяет вызвать метод по указателю красивее (параметры `write()` прежние, они опущены для краткости): ```cpp buffer->write(...); ``` **Самостоятельно.** Добейтесь компиляции и проверьте корректную работу программы с примером из задания. [basic_ostream/write]: https://en.cppreference.com/w/cpp/io/basic_ostream/write ### Индивидуальные задания Всю необходимую информацию по функциям cURL нужно изучить по официальной документации и на защите предъявить ссылку на конкретную страницу. В частности, если не указан конкретный параметр `curl_easy_setopt()`, нужно подобрать его по описанию в списке. Во всех вариантах нужно обрабатывать ошибки cURL, как это делается в основной программе. Если вариант требует ввода дополнительных данных, проверить работу с файлом «из сети» можно через локальный файл и адрес вида `file://C:/Users/kozlyuk/Desktop/marks.txt` для файла `marks.txt` на рабочем столе пользователя `kozlyuk`. Полный путь к файлу можно получить, щелкнув в адресной строке «Проводника» (так же, как перед запуском `cmd`). #### Вариант 1 {.unnumbered} С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок суммарное время (total), затраченное на загрузку файла по сети. #### Вариант 2 {.unnumbered} Добавьте программе опцию `-format`, которая определяет формат вывода: `lab34.exe -format text` — текст, `lab34.exe -format svg` — изображение. Опция может стоять до или после URL: `lab34.exe -format svg http://...` или `lab04.exe http://... -format svg`. Если после `-format` не стоит `text` или `svg`, нужно печатать сообщение с подсказкой, как запускать программу, и завершать работу. #### Вариант 3 {.unnumbered} С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок скорость загрузки файла по сети. #### Вариант 4 {.unnumbered} Добавьте программе опцию `-bins`, которая позволяет задать количество корзин при запуске. Считывать его в `read_input()` при этом не нужно. Например: `lab34.exe -bins 3`. Опция может стоять до или после URL: `lab34.exe -bins 3 http://...` или `lab04.exe http://... -bins 3`. Если после `-bins` не стоит числа, нужно печатать сообщение с подсказкой, как запускать программу, и завершать работу. #### Вариант 5 {.unnumbered} Добавьте программе опцию `-generate`, при указании которой программа будет генерировать числа вместо их считывания. Количество столбцов считывать по-прежнему нужно. Например, `lab34.exe -generate 100` генерирует 100 чисел. Опция может стоять до или после URL: `lab34.exe -generate 10 http://...` или `lab04.exe http://... -generate 10`. Если после `-generate` не стоит числа, нужно печатать сообщение с подсказкой, как запускать программу, и завершать работу. #### Вариант 6 {.unnumbered} С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок размер файла, загружаемого по сети. #### Вариант 7 {.unnumbered} При помощи `CURLOPT_PROGRESSFUNCTION` добавьте в программу отображение прогресса загрузки файла. Каждый раз, когда cURL рапортует прогресс, печатайте строку вида: `Progress: 42%`. **Указание.** Чтобы cURL рапортовала прогресс, нужно установить `CURLOPT_NOPROGRESS` в 0. #### Вариант 8 {.unnumbered} С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок IP-адрес сервера, с которого скачан файл (не local). #### Вариант 9 {.unnumbered} С помощью функции `curl_version_info()` печатайте на стандартный вывод ошибок версию cURL и версию OpenSSL в строковом виде. #### Вариант 10 {.unnumbered} Добавьте программе опцию `-verbose`, при указании которой нужно включать отладочный вывод cURL с помощью `CURLOPT_VERBOSE`. Опция может стоять до или после URL: `lab34.exe -verbose http://...` или `lab04.exe http://... -verbose`. Если встретился иной аргумент, начинающийся с дефиса, нужно печатать сообщение с подсказкой, как запускать программу, и завершать работу. #### Вариант 11 {.unnumbered} С помощью функции `curl_version_info()` печатайте на стандартный вывод ошибок список протоколов, которые поддерживает cURL (`protocols`). #### Вариант 12 {.unnumbered} Добавьте программе опцию `-fill` для задания цвета заливки столбцов. Например, `lab34.exe -fill red` или `lab04.exe -fill "#ff0000"` заливает их красным. Опция может стоять до или после URL: `lab34.exe -fill red http://...` или `lab04.exe http://... -fill red`. Если после `-fill` не стоит еще одного аргумента, нужно печатать сообщение с подсказкой, как запускать программу, и завершать работу. #### Вариант 13 {.unnumbered} Добавьте программе опцию `-stroke` для задания цвета границ столбцов. Например, `lab34.exe -stroke red` или `lab04.exe -stroke "#ff0000"` делает их красными. Опция может стоять до или после URL: `lab34.exe -stroke red http://...` или `lab04.exe http://... -fill red`. Если после `-stroke` не стоит еще одного аргумента, нужно печатать сообщение с подсказкой, как запускать программу, и завершать работу. #### Вариант 14 {.unnumbered} С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок размер запроса, который cURL отправила серверу (request size). #### Вариант 15 {.unnumbered} Установите callback-функцию `CURLOPT_HEADERDATA`, откуда печатайте полученные данные на стандартный вывод ошибок. #### Вариант 16 {.unnumbered} С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок время, затраченное на установку соединения с сервером (connect). #### Вариант 17 {.unnumbered} Устанавливайте опцию `CURLOPT_FAILONERROR` и анализируйте результат `curl_easy_perform()`, чтобы проверить результат загрузки файла, а не выполнения запроса. Придумайте способ тестирования. #### Вариант 18 {.unnumbered} С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок время, затраченное на получение IP-адреса сервера по его имени (name lookup). ## Контрольные вопросы 1. Сколько аргументов в команде `git add -p file.txt`? Чему равно `argc`? 1. Из каких файлов и как собирается программа, использующая библиотеку? 1. Чем отличаются статические и динамические библиотеки? 1. Как расшифровывается термин «API» и что представляет собой API библиотеки? 1. Что такое дескриптор (handle), для чего он предназначен? 1. Что такое функция обратного вызова (callback)? 1. Зачем и как применяются пользовательские данные в callback-функциях?