Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

812 строки
44 KiB
Markdown

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

# Использование библиотек
## Цель работы
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).
### Ввод из произвольного потока
Рефакторинг (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: <https://curl.haxx.se/libcurl/>.
На этой странице есть раздел *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 <curl/curl.h>
>
> CURLcode curl_global_init(long flags);
«Synopsis» значит «сводка» (отсюда слово «синоптик»), это типовой раздел
справки к библиотекам, в котором кратко показывается, как функция объявлена
и какой заголовочный нужно подключить, чтобы ее использовать. В данном случае
нужно добавить в начало `main.cpp`:
```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` аналогично предыдущему шагу.
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 <input.txt` вызывает `lab01.exe` без аргументов.
Получить значения аргументов позволяет следующая версия функции `main()`:
```cpp
int
main(int argc, char* argv[]) {
...
```
**Примечание.**
В некоторых источниках можно найти `int main(int argc, char** argv)` —
это то же самое.
Все формы `void main(...)` являются нестандартными.
Параметр `argc` (**arg**ument **c**ount) показывает количество элементов
в массиве `argv` (**arg**ument **v**alues).
Начальный элемент (с индексом 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_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()`,
которая принимает строку с адресом файла.
Файл `<sstream>` подключает `stringstream` —
поток, который может читать из буфера в памяти.
Также в переменные типа `stringstream` можно записывать данные,
то есть заполнять буфер.
```cpp
#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`)
либо из сети, либо из стандартного ввода:
```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<stringstream*>(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}
<!-- ЛР 3: пользователь задает ширину гистограммы -->
С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок
суммарное время (total), затраченное на загрузку файла по сети.
#### Вариант 2 {.unnumbered}
<!-- ЛР 3: автоматический расчет цвета столбцов -->
Добавьте программе опцию `-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}
<!-- ЛР 3: пользователь задает толщину столбца -->
С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок
скорость загрузки файла по сети.
#### Вариант 4 {.unnumbered}
<!-- ЛР 3: высоты столбцов в процентах -->
Добавьте программе опцию `-bins`, которая позволяет задать количество
корзин при запуске. Считывать его в `read_input()` при этом не нужно.
Например: `lab34.exe -bins 3`.
Опция может стоять до или после URL:
`lab34.exe -bins 3 http://...` или `lab04.exe http://... -bins 3`.
Если после `-bins` не стоит числа, нужно печатать сообщение с подсказкой,
как запускать программу, и завершать работу.
#### Вариант 5 {.unnumbered}
<!-- ЛР 1/3: зеркальное отображение -->
Добавьте программе опцию `-generate`, при указании которой программа будет
генерировать числа вместо их считывания. Количество столбцов считывать
по-прежнему нужно. Например, `lab34.exe -generate 100` генерирует 100 чисел.
Опция может стоять до или после URL:
`lab34.exe -generate 10 http://...` или `lab04.exe http://... -generate 10`.
Если после `-generate` не стоит числа, нужно печатать сообщение с подсказкой,
как запускать программу, и завершать работу.
#### Вариант 6 {.unnumbered}
<!-- ЛР 3: пользователь задает цвета столбцов -->
С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок
размер файла, загружаемого по сети.
#### Вариант 7 {.unnumbered}
<!-- ЛР 3: цвет столбца зависит от высоты соседних -->
При помощи `CURLOPT_PROGRESSFUNCTION` добавьте в программу отображение
прогресса загрузки файла. Каждый раз, когда cURL рапортует прогресс,
печатайте строку вида: `Progress: 42%`.
**Указание.**
Чтобы cURL рапортовала прогресс, нужно установить `CURLOPT_NOPROGRESS` в 0.
#### Вариант 8 {.unnumbered}
<!-- ЛР 3: ввод размера шрифта с проверками -->
С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок
IP-адрес сервера, с которого скачан файл (не local).
#### Вариант 9 {.unnumbered}
<!-- ЛР 3: ввод размера "блока" гистограммы -->
С помощью функции `curl_version_info()` печатайте на стандартный вывод ошибок
версию cURL и версию OpenSSL в строковом виде.
#### Вариант 10 {.unnumbered}
<!-- ЛР 1/3: вертикальная гистограмма, подписи сверху -->
Добавьте программе опцию `-verbose`, при указании которой нужно включать
отладочный вывод cURL с помощью `CURLOPT_VERBOSE`.
Опция может стоять до или после URL:
`lab34.exe -verbose http://...` или `lab04.exe http://... -verbose`.
Если встретился иной аргумент, начинающийся с дефиса, нужно печатать сообщение
с подсказкой, как запускать программу, и завершать работу.
#### Вариант 11 {.unnumbered}
<!-- ЛР 3: рамка вокруг гистограммы -->
С помощью функции `curl_version_info()` печатайте на стандартный вывод ошибок
список протоколов, которые поддерживает cURL (`protocols`).
#### Вариант 12 {.unnumbered}
<!-- ЛР 1/3: отметки на оси -->
Добавьте программе опцию `-fill` для задания цвета заливки столбцов. Например,
`lab34.exe -fill red` или `lab04.exe -fill "#ff0000"` заливает их красным.
Опция может стоять до или после URL:
`lab34.exe -fill red http://...` или `lab04.exe http://... -fill red`.
Если после `-fill` не стоит еще одного аргумента, нужно печатать сообщение
с подсказкой, как запускать программу, и завершать работу.
#### Вариант 13 {.unnumbered}
<!-- ЛР 1/3: вертикальная гистограмма, подписи снизу -->
Добавьте программе опцию `-stroke` для задания цвета границ столбцов.
Например, `lab34.exe -stroke red` или `lab04.exe -stroke "#ff0000"` делает их
красными.
Опция может стоять до или после URL:
`lab34.exe -stroke red http://...` или `lab04.exe http://... -fill red`.
Если после `-stroke` не стоит еще одного аргумента, нужно печатать сообщение
с подсказкой, как запускать программу, и завершать работу.
#### Вариант 14 {.unnumbered}
<!-- ЛР 3: пунктирные линии с вводом их параметров -->
С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок
размер запроса, который cURL отправила серверу (request size).
#### Вариант 15 {.unnumbered}
<!-- ЛР 3: горизонтальная шкала с расчетами -->
Установите callback-функцию `CURLOPT_HEADERDATA`, откуда печатайте
полученные данные на стандартный вывод ошибок.
#### Вариант 16 {.unnumbered}
<!-- ЛР 3: ввод цвета линий для каждого столбца -->
С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок
время, затраченное на установку соединения с сервером (connect).
#### Вариант 17 {.unnumbered}
<!-- ЛР 3: автоматический расчет прозрачности -->
Устанавливайте опцию `CURLOPT_FAILONERROR` и анализируйте результат
`curl_easy_perform()`, чтобы проверить результат загрузки файла,
а не выполнения запроса. Придумайте способ тестирования.
#### Вариант 18 {.unnumbered}
<!-- ЛР 3: ввод стиля надписей -->
С помощью функции `curl_easy_getinfo()` печатайте на стандартный вывод ошибок
время, затраченное на получение IP-адреса сервера по его имени (name lookup).
## Контрольные вопросы
1. Сколько аргументов в команде `git add -p file.txt`? Чему равно `argc`?
1. Из каких файлов и как собирается программа, использующая библиотеку?
1. Чем отличаются статические и динамические библиотеки?
1. Как расшифровывается термин «API» и что представляет собой API библиотеки?
1. Что такое дескриптор (handle), для чего он предназначен?
1. Что такое функция обратного вызова (callback)?
1. Зачем и как применяются пользовательские данные в callback-функциях?