|
|
# Использование библиотек
|
|
|
|
|
|
## Цель работы
|
|
|
|
|
|
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`).
|
|
|
|
|
|

|
|
|
|
|
|
Что такое «работать с потоком ввода»?
|
|
|
Рассмотрим любой пример работы со стандартным потоком ввода:
|
|
|
|
|
|
```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* со ссылкой на страницу загрузок:
|
|
|
|
|
|

|
|
|
|
|
|
Вариантов загрузки очень много, потому что 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()`][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`):
|
|
|
|
|
|

|
|
|
|
|
|
Дело в том, что программа собрана с использованием *динамической* библиотеки.
|
|
|
В `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 можно найти
|
|
|
[сайт с готовыми сборками](https://slproweb.com/products/Win32OpenSSL.html).
|
|
|
|
|
|
Скачайте установочный пакет (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()`:
|
|
|
|
|
|
```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-функциях?
|