Связывание исполняемого файла с библиотекой DLL
Исполняемый файл можно связать с библиотекой DLL (загрузить ее) одним из двух способов:
- Неявное связывание — операционная система загружает библиотеку DLL в тот момент, когда она используется исполняемым файлом. Исполняемый файл клиента вызывает экспортированные функции библиотеки DLL так же, как статически скомпонованные и включенные в состав самого исполняемого файла функции. Процесс неявного связывания также иногда называют статической загрузкой или динамической компоновкой времени загрузки.
- Явное связывание — операционная система загружает библиотеку DLL по запросу во время выполнения. Исполняемый файл, который использует библиотеку DLL, должен явно загружать и выгружать ее. Кроме того, в нем должен быть настроен указатель функции для доступа к каждой используемой функции из библиотеки DLL. В отличие от вызовов функций в статически скомпонованной или неявно связанной библиотеке DLL, при работе с явно связанной DLL исполняемый файл клиента должен вызывать экспортированные функции с помощью указателей функций. Процесс явного связывания также иногда называют динамической загрузкой или динамической компоновкой времени выполнения.
Для связывания с одной и той же библиотекой DLL исполняемый файл может использовать любой из этих способов. Кроме того, они не являются взаимоисключающими, то есть два разных исполняемых файла могут связываться с одной библиотекой DLL разными способами.
Определение подходящего метода связывания
Решение о применении неявного или явного связывания принимается на уровне архитектуры приложения. Каждый из этих способов имеет свои преимущества и недостатки.
Неявное связывание
Неявное связывание происходит в момент, когда из кода приложения вызывается экспортированная функция библиотеки DLL. При компиляции или сборке исходного кода вызывающего исполняемого файла для вызова функции DLL в коде объекта создается ссылка на внешнюю функцию. Для разрешения этой внешней ссылки приложение должно связаться с библиотекой импорта (LIB-файл), которая предоставляется разработчиком библиотеки DLL.
Библиотека импорта содержит только код для загрузки библиотеки DLL и реализации вызовов ее функций. При поиске внешней функции в библиотеке импорта компоновщик определяет, что код этой функции находится в библиотеке DLL. Для разрешения внешних ссылок на библиотеки DLL компоновщик просто добавляет в исполняемый файл сведения, на основании которых система определяет, где следует искать код DLL при запуске процесса.
При запуске программы, которая содержит динамически связываемые ссылки, система использует сведения из исполняемого файла для поиска необходимых библиотек DLL. Если найти библиотеку DLL не удается, система завершает процесс и отображает диалоговое окно с сообщением об ошибке. В противном случае система сопоставляет модули DLL в адресном пространстве процесса.
Если любая из библиотек DLL содержит функцию точки входа для инициализации и завершения кода, например DllMain , операционная система вызывает эту функцию. С помощью одного из параметров, передаваемого в функцию точки входа, задается код, который указывает на связывание библиотеки DLL с процессом. Если функция точки входа не возвращает значение TRUE, система завершает процесс и возвращает ошибку.
Наконец, система изменяет код исполняемого файла процесса и предоставляет функциям DLL начальные адреса.
Как и в случае с остальном кодом программы, загрузчик сопоставляет код DLL в адресном пространстве процесса при запуске процесса. Операционная система загружает его в память только при необходимости. В связи с этим атрибуты кода PRELOAD и LOADONCALL , которые использовались DEF-файлами для управления загрузкой в предыдущих версиях Windows, более не имеют смысла.
Явное связывание
В большинстве приложений используется более простой способ неявного связывания. Тем не менее иногда явное связывание необходимо. Такой подход может требоваться в следующих случаях:
- Имя библиотеки DLL, которую необходимо загружать, становится известно приложению только во время выполнения. Например, приложение может получать имя библиотеки DLL и экспортированные функции из файла конфигурации во время запуска.
- Если при запуске не удается найти нужную библиотеку DLL, процесс, в котором используется неявное связывание, завершается операционной системой. Процесс с явным связыванием в таких ситуациях не завершается и может попытаться восстановиться после ошибки. Например, процесс может уведомить пользователя об ошибке и запросить указать другой путь к библиотеке DLL.
- Процесс с неявным связыванием также завершается, если в любой из связанных библиотек DLL функция DllMain завершается сбоем. Процесс с явным связыванием в таких ситуациях не завершается.
- Приложение, в котором применяется неявное связывание с множеством библиотек DLL, может долго запускаться, поскольку операционная система Windows при загрузке приложения загружает все библиотеки DLL. Чтобы ускорить процесс запуска приложения, можно выполнять неявное связывание только с теми библиотеками DLL, которые необходимы непосредственно после загрузки. Другие библиотеки DLL могут загружаться позднее по мере необходимости посредством явного связывания.
- При явном связывании приложению не требуется библиотека импорта. Если из-за изменений в библиотеке DLL изменяются порядковые номера экспорта, приложениям не нужно будет повторно выполнять связывание, если для вызова GetProcAddress в них используется имя функции, а не ее порядковый номер. Если приложение использует неявное связывание, в случае изменений в библиотеке импорта им потребуется выполнить связывание повторно.
При использовании явного связывания следует учитывать два потенциально опасных фактора:
- Если библиотека DLL имеет функцию точки входа DllMain , операционная система вызывает функцию в контексте потока, который вызывал LoadLibrary . Функция точки входа не вызывается, если библиотека DLL уже связана с процессом в рамках предыдущего вызова LoadLibrary , во время которого не выполнялся соответствующий вызов функции FreeLibrary . При явном связывании могут возникать проблемы, если библиотека DLL использует функцию DllMain для инициализации каждого потока в процессе, поскольку все потоки, существовавшие на момент вызова LoadLibrary (или AfxLoadLibrary ), не инициализируются.
- Если в библиотеке DLL статические элементы данных объявляются как __declspec(thread) , при явном связывании может произойти сбой системы защиты. После загрузки библиотеки DLL посредством вызова LoadLibrary сбой системы защиты будет происходить каждый раз, когда в коде используется ссылка на такие данные. (Данные статической экстенты включают как глобальные, так и локальные статические элементы.) Поэтому при создании библиотеки DLL следует избегать использования локального хранилища потоков. Если это все же необходимо, следует проинформировать пользователей библиотеки о возможных проблемах при ее динамической загрузке. Дополнительные сведения см. в разделе Использование локальной памяти потока в библиотеке динамической компоновки (пакет SDK для Windows).
Использование неявного связывания
Чтобы использовать библиотеку DLL посредством неявного связывания, исполняемые файлы клиента должны получить от ее поставщика следующие файлы:
- Один или несколько файлов заголовка (с расширением h), в которых содержатся объявления экспортированных данных, функций и классов C++ в библиотеке DLL. Все классы, функции и данные, экспортируемые из библиотеки DLL, должны быть отмечены в файле заголовка как __declspec(dllimport) . Дополнительные сведения см. в статье dllexport, dllimport.
- Библиотека импорта, которая связывается с исполняемым файлом. Библиотека импорта создается компоновщиком при построении DLL. Дополнительные сведения см. в разделе Использование LIB-файлов в качестве входных данных для компоновщика.
- DLL-файл библиотеки.
Чтобы использовать данные, функции и классы в библиотеке DLL посредством неявного связывания, исходный файл клиента должен содержать файлы заголовков, в которых они объявляются. С точки зрения кода вызовы экспортированных функций выполняются так же, как и в случае с любыми другими функциями.
Для построения исполняемого файла клиента необходимо выполнить связывание с библиотекой импорта DLL. Если вы используете внешние файл makefile или систему сборки, необходимо указывать библиотеку импорта вместе с другими связываемыми файлами объектов или библиотеками.
При вызове исполняемого файла операционная система должна иметь возможность найти DLL-файл. Это означает, что при установке приложения необходимо либо развернуть нужную библиотеку DLL, либо проверить ее наличие.
Явное связывание с библиотекой DLL
Чтобы использовать библиотеку DLL посредством явного связывания, необходимо вызвать функцию для явной загрузки библиотеки DLL во время выполнения. Для явного связывания с библиотекой DLL приложение должно выполнить следующие действия:
- Вызвать LoadLibraryEx или аналогичную функцию для загрузки библиотеки DLL и получения дескриптора модуля.
- Вызвать GetProcAddress, чтобы получить указатель для каждой экспортированной функции, которую вызывает приложение. Поскольку приложения вызывают функции DLL с помощью указателя, компилятор не создает внешние ссылки и нет необходимости связываться с библиотекой импорта. Тем не менее, необходимо использовать оператор typedef или using , определяющий сигнатуру вызова для вызываемых экспортированных функций.
- По завершении работы с библиотекой DLL вызовите FreeLibrary.
В приведенном ниже примере функции вызывается LoadLibrary для загрузки библиотеки MyDLL, затем вызывается GetProcAddress для получения указателя на функцию DLLFunc1, далее вызывается эта функция и сохраняется результат, после чего вызывается FreeLibrary для выгрузки библиотеки DLL.
#include "windows.h" typedef HRESULT (CALLBACK* LPFNDLLFUNC1)(DWORD,UINT*); HRESULT LoadAndCallSomeFunction(DWORD dwParam1, UINT * puParam2) < HINSTANCE hDLL; // Handle to DLL LPFNDLLFUNC1 lpfnDllFunc1; // Function pointer HRESULT hrReturnVal; hDLL = LoadLibrary("MyDLL"); if (NULL != hDLL) < lpfnDllFunc1 = (LPFNDLLFUNC1)GetProcAddress(hDLL, "DLLFunc1"); if (NULL != lpfnDllFunc1) < // call the function hrReturnVal = lpfnDllFunc1(dwParam1, puParam2); >else < // report the error hrReturnVal = ERROR_DELAY_LOAD_FAILED; >FreeLibrary(hDLL); > else < hrReturnVal = ERROR_DELAY_LOAD_FAILED; >return hrReturnVal; >
В отличие от этого примера, в большинстве случаев LoadLibrary и FreeLibrary следует вызывать в приложении только один раз для нужной библиотеки DLL. Это особенно важно, если вы планируете многократно вызывать функции в библиотеке DLL.
Дополнительные сведения
- Работа с библиотеками импорта и файлами экспорта
- Порядок поиска библиотеки динамической компоновки (DLL)
Где взять stdafx.h в Visual Studio 2019?
Установил Visual Studio, скачать всё нужное начал работу и увидел что «stdafx» отсутствует. Где его можно найти в общем доступе для скачивания и куда его потом скидывать?
Отслеживать
20.2k 6 6 золотых знаков 37 37 серебряных знаков 81 81 бронзовый знак
задан 6 окт 2019 в 12:56
DrumpiVumpi DrumpiVumpi
21 1 1 золотой знак 1 1 серебряный знак 2 2 бронзовых знака
Вот это прочтите habr.com/ru/company/pvs-studio/blog/227521
6 окт 2019 в 12:58
gbg,спасибо за помощь
6 окт 2019 в 14:50
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
stdafx.h — это стандартное название предварительно скомпилированного заголовочного файла в Visual Studio 2017 и более ранних версиях. Начиная с VS2019, вместо этого используется pch.h. Его не нужно ниоткуда скачивать, в него вписываются часто включаемые в коде заголовочные файлы для ускорения компиляции. Сам файл должен создаваться автоматически при создании проекта; но если его нет, можно добавить его вручную. Содержимое должно выглядеть как-то так:
#pragma once #include //другие директивы include.
Совместно с ним в проекте должен находится pch.cpp, состоящий из единственной строки:
#include "pch.h"
Для использования предварительно скомпилированных заголовков их необходимо включить параметром /Y или через страницу свойств проекта Configuration Properties > C/C++ > Precompiled Headers. После этого в каждом cpp-файле в самом начале (до любых директив препроцессора или строк кода) нужно добавить #include «pch.h» .
Как подключить curl к проекту
Здравствуйте я не совсем понимаю каким образом нужно подключать curl откуда скачивать и т.д Насколько я понял в проекте нужно включить curl.h и libcurl.lib но когда я скачиваю curl https://curl.se/windows/ там нет libcurl.lib поэтому я не понимаю я что-то не то скачиваю или где-то не там ищу libcurl.lib . Поэтому возник у меня вопрос откуда нужно скачать curl и что подключить что-бы он заработал в проекте
Отслеживать
задан 31 июл 2021 в 7:28
VladimirIfraimov VladimirIfraimov
3 2 2 бронзовых знака
curl.h и libcurl.lib можно получить скачав исходники и самостоятельно собрав библиотеку с настройками, совместимыми с настройками вашего проекта.
Использование библиотеки OpenSSL в проектах на C++
В своем первом топике я постараюсь подробно объяснить как начать использовать библиотеку OpenSSL. Сразу хочу отметить, что статья ориентирована на новичков, а так как я сам один из них, исходный код выполнен без проверок и не претендует на звание лучшего. Все действия выполнялись под Windows со средой разработки Visual Studio 2013.
Шаг 1. Установка необходимых компонентов и компиляция библиотеки
Для того, чтобы подружить библиотеку OpenSSL с Visual Studio нам потребуется:
- Архив с исходниками для компиляции библиотеки версии 1.0.1 (Скачать с оф. сайта)
- Perl для конфигурирования библиотеки (Скачать с оф. сайта)
- Среда разработки Visual Studio, а именно ее утилита «Командная строка разработчика»
cd c:\openssl perl Configure VC-WIN32 --prefix=c:\Temp\openssl ms\do_ms
Обратите внимание, что после выполнения второй команды последней строчкой в консоле должно быть Configured for VC-WIN32. Конфигурирование завершено, теперь приступим к компиляции. Находим в пуске в каталоге Visual Studio «Командная строка разработчика», запускаем от имени администратора и вводим команды:
cd c:\openssl nmake -f ms\ntdll.mak nmake -f ms\ntdll.mak install
На этом компиляция закончена. В папке C:\Temp\openssl\bin появились две библиотеки ssleay32.dll и libeay32.dll, а в папке C:\Temp\openssl\lib появились ssleay32.lib и libeay32.lib.
Шаг 2. Подключение библиотек и исправление ошибок в среде разработки
Для использования функций библиотеки нам потребуется подключить к проекту заголовочные файлы, но сначала их надо поместить в папку, где установлена Visual Studio. Для этого переходим в каталог ..\Visual Studio\VC\include и копируем туда папку C:\Temp\openssl\include\openssl.
Теперь можно смело подключать заголовочные файлы (на примере rsa):
#include // Алгоритм RSA #include // Для работы с файлами ключей
Также необходимо добавить в проект файлы с расширением .lib, которые мы получили ранее. Для этого откроем Обозреватель решений в Visual Studio, в дереве нашего проекта найдем папку Файлы ресурсов, щелкнем по ней правой клавишей -> Добавить -> Существующий элемент и выберем наши .lib файлы, находящиеся в C:\Temp\openssl\lib.

Вполне возможно, что при попытке собрать проект в окне вывода будут появляться ошибки такого рода:
Загружено "C:\Windows\SysWOW64\srvcli.dll". Невозможно найти или открыть PDB-файл.
Это лечится путем разрешения загрузки символов с серверов Microsoft. Переходим во вкладку Сервис -> Параметры, слева в дереве выбираем Отладка -> Символы, ставим галку напротив «Серверы символов Microsoft» и нажимаем ОК. Теперь при следующей компиляции проекта недостающие файлы загрузятся автоматически.

Также рекомендую переставить конфигурацию сборки программы в Release, только так мне удалось избавиться от некоторых ошибок.
Шаг 3. Описание некоторых функций библиотеки и пример кода
int RSA_size(const RSA *rsa) Вернет длину RSA модуля в байтах RSA * RSA_generate_key(int bits, unsigned long e, void(*callback)(int, int, void*), void *cb_arg) генерирует пару ключей и возвращает в RSA структуру int PEM_write_RSAPrivateKey(FILE *fp, RSA *x, const EVP_CIPHER *enc, unsigned char *kstr, int klen, pem_password_cb *cb, void *u) записывает закрытый ключ из структуры RSA в открытый файл int PEM_write_RSAPublicKey(FILE *fp, const RSA *x) записывает открытый ключ из структуры RSA в открытый файл RSA * PEM_read_RSAPublicKey(FILE *fp, RSA **x, pem_password_cb *cb, void *u) читает из файла открытый ключ и возвращает в RSA структуру RSA * PEM_read_RSAPrivateKey(FILE *fp, RSA **x, pem_password_cb *cb, void *u) читает из файла закрытый ключ и возвращает в RSA структуру int RSA_public_encrypt(int flen, const unsigned char *from, const unsigned char *to, RSA *rsa, int padding) шифрует строку "from" с помощью открытого ключа и возвращает в строку "to" int RSA_private_decrypt(int flen, const unsigned char *from, const unsigned char *to, RSA *rsa, int padding) расшифровывает строку "from" с помощью закрытого ключа и возвращает в строку "to". Вернет "-1" если не удалось дешифровать.
Все необходимые компоненты, включая скомпилированный пример можно скачать с Яндекс.Диска.
- Компиляция библиотеки
- Примеры использования функций
- Описание функций
Пример кода
#define _CRT_SECURE_NO_WARNINGS #include #include #include #include #include #include #include using namespace std; void GenKeys(char secret[]); void Enc(); void Dec(char secret[]); void GenKeysMenu(); void EncryptMenu(); void DecryptMenu(); void main()< setlocale(LC_ALL, "Russian"); char key; StartMenu: system("cls"); cout > key; switch (key) < case '1': GenKeysMenu(); goto StartMenu; case '2': EncryptMenu(); goto StartMenu; case '3': DecryptMenu(); goto StartMenu; default: goto StartMenu; >> void GenKeys(char secret[]) < /* указатель на структуру для хранения ключей */ RSA * rsa = NULL; unsigned long bits = 1024; /* длина ключа в битах */ FILE * privKey_file = NULL, *pubKey_file = NULL; /* контекст алгоритма шифрования */ const EVP_CIPHER *cipher = NULL; /*Создаем файлы ключей*/ privKey_file = fopen("\private.key", "wb"); pubKey_file = fopen("\public.key", "wb"); /* Генерируем ключи */ rsa = RSA_generate_key(bits, RSA_F4, NULL, NULL); /* Формируем контекст алгоритма шифрования */ cipher = EVP_get_cipherbyname("bf-ofb"); /* Получаем из структуры rsa открытый и секретный ключи и сохраняем в файлах. * Секретный ключ шифруем с помощью парольной фразы */ PEM_write_RSAPrivateKey(privKey_file, rsa, cipher, NULL, 0, NULL, secret); PEM_write_RSAPublicKey(pubKey_file, rsa); /* Освобождаем память, выделенную под структуру rsa */ RSA_free(rsa); fclose(privKey_file); fclose(pubKey_file); cout void Encrypt() < /* структура для хранения открытого ключа */ RSA * pubKey = NULL; FILE * pubKey_file = NULL; unsigned char *ctext, *ptext; int inlen, outlen; /* Считываем открытый ключ */ pubKey_file = fopen("\public.key", "rb"); pubKey = PEM_read_RSAPublicKey(pubKey_file, NULL, NULL, NULL); fclose(pubKey_file); /* Определяем длину ключа */ int key_size = RSA_size(pubKey); ctext = (unsigned char *)malloc(key_size); ptext = (unsigned char *)malloc(key_size); OpenSSL_add_all_algorithms(); int out = _open("rsa.file", O_CREAT | O_TRUNC | O_RDWR, 0600); int in = _open("in.txt", O_RDWR); /* Шифруем содержимое входного файла */ while (1) < inlen = _read(in, ptext, key_size - 11); if (inlen cout void Decrypt(char secret[]) < RSA * privKey = NULL; FILE * privKey_file; unsigned char *ptext, *ctext; int inlen, outlen; /* Открываем ключевой файл и считываем секретный ключ */ OpenSSL_add_all_algorithms(); privKey_file = fopen("private.key", "rb"); privKey = PEM_read_RSAPrivateKey(privKey_file, NULL, NULL, secret); /* Определяем размер ключа */ int key_size = RSA_size(privKey); ptext = (unsigned char *)malloc(key_size); ctext = (unsigned char *)malloc(key_size); int out = _open("out.txt", O_CREAT | O_TRUNC | O_RDWR, 0600); int in = _open("rsa.file", O_RDWR); /* Дешифруем файл */ while (1) < inlen = _read(in, ctext, key_size); if (inlen cout void GenKeysMenu()< char secret[] = ""; system("cls"); cout > secret; GenKeys(secret); cout void EncryptMenu() < system("cls"); cout << "-------------- Шифрование RSA --------------" << endl << endl; Encrypt(); cout void DecryptMenu()< char secret[] = ""; system("cls"); cout > secret; Decrypt(secret); cout
