Как использовать глобальные переменные в С++ при работе с dll?

Суть делемы: я пишу небольшую dll библиотеку которая устанавливает соединение с WS сервером, т.к. язык для которого это делается просто не имеет такой возможности. Я успешно собрал exeник со следующим содержимым.

namespace beast = boost::beast;         // from <boost/beast.hpp>
namespace http = beast::http;           // from <boost/beast/http.hpp>
namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
namespace net = boost::asio;            // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp;       // from <boost/asio/ip/tcp.hpp>

net::io_context ioc;
websocket::stream<tcp::socket> ws{ ioc };

// Sends a WebSocket message and prints the response
int main(int argc, char** argv)
{
    try
    {
        // Check command line arguments.
        std::string host = "192.168.1.104";
        auto const port = "3001";
        auto const text = "Hello, world!";

        // The io_context is required for all I/O
        //net::io_context ioc;

        // These objects perform our I/O
        tcp::resolver resolver{ ioc };
        //websocket::stream<tcp::socket> ws{ ioc };

        // Look up the domain name
        auto const results = resolver.resolve(host, port);

        // Make the connection on the IP address we get from a lookup
        auto ep = net::connect(ws.next_layer(), results);
        // ну и т.д. 

Для удобства использования я распилил код на 3 части сделав функции connect,read,disconnect Само соединение с сервером я положил в переменную ws - которая имеет глобальную область видимости. - сосбтвенно при сборки exe файла это работает хорошо.

Когда я попробывал собрать dll файл, у меня не чего не полчилось, не смотря на то что ошибок компиляции небыло, программа просто зависала и не чего не работало. Тогда мой код для dll выглядил так

#include "stdafx.h"
#include "Header1.h"

int iParam;

BOOL APIENTRY DllMain(HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        iParam = 7;
        break;
    case DLL_THREAD_ATTACH:
        iParam += 1;
        break;
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

const int GetSomeParam() {
    return iParam;
}

Ну и самое интересное - файл с экспортируемыми функциями

namespace beast = boost::beast;         // from <boost/beast.hpp>
namespace http = beast::http;           // from <boost/beast/http.hpp>
namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
namespace net = boost::asio;            // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp;       // from <boost/asio/ip/tcp.hpp>
// Reads the storage
net::io_context ioc;
websocket::stream<tcp::socket> ws{ ioc };

PROJECT2_API void connect(void) {
        std::string host = "192.168.1.104";
        auto const port = "3001";
        auto const text = "Hello, world!";
        //net::io_context ioc;
        //websocket::stream<tcp::socket> ws{ ioc };
        // The io_context is required for all I/O
        //net::io_context ioc;
        // These objects perform our I/O
        tcp::resolver resolver{ ioc };

Немного поковырявшись я выяснил что в dll есть некая особеность работы с глобальными переменными и

net::io_context ioc;
websocket::stream<tcp::socket> ws{ ioc }; 

Просто напросто не видны в функции PROJECT2_API void connect(void) они там не определены, и из за этого не чего не работало. перенеся эти строки в connect я успешно собрал приложение и оно работало. Что подтвердило мое предположение о зоне видимости переменных.

Собственно если я буду постоянно переопределять эти переменные в каждом вызове функции connect,read,disconnect то это будет лишено всякого смысла. т.к. соединение с ws будет каждый раз устанавливатся по новой.

Как я хочу это сделать: я хочу положить инстанс соединения в глобальную переменную при вызвое функции connect (которая откроет соединение с сервером) после чего вернуть управление в программу которая вызвала эту функцию. Самое соединение на данный момент должно быть открыто. а потом по открытому соединению из программы которая вызывает dll файл вызвать метод read который должен будет использовать рание созданное соединение для чтения данных.

Я хочу узнать как сделать это в dll веть если я объявляю переменую ws внутри PROJECT2_API void connect(void) она уничтожается при окончании работы функции connect. Простым решением казалось сделать ее глобальной, но в dll так не работает. Дак как мне сделать то что я хочу?


Ответы (2 шт):

Автор решения: gbg

Вычеркнуть слова "глобальные переменные" из своего словаря насовсем, это раз.

В виду того, что ABI C++ нестабилен не то что между, компиляторами, а даже между версиями компиляторов, передача объектов C++ между DLL возможна только при условии, что и DLL и место ее присоединения собраны идентичными компиляторами с идентичными настройками.

Чтобы собрать универсальную DLL, придется сделать прокладку на C - в отличии от C++, тут все стандартно, он нормально переносится.

Хорошим решением будет создать нужный объект на куче, потом отдать указатель на него вызывающей стороне. Когда вызывающая сторона закончит работу, нужно обязательно вернуть его в dll, чтобы она корректно вызвала деструктор и освободила память.

→ Ссылка
Автор решения: KoVadim

Использование глобальных переменных - это всегда проблема. Как бы я сделал. Сделайте структуру (или класс), в которую перенесите все свои "глобальные переменные". К всем своим функциям, которые нуждаются в них передавайте первый параметр, который может называться context и тип у него void*. Первым делом всегда приводите к своему типу структуры, а дальше работайте как обычно.

Почему void*? а для того, чтобы наружу библиотекам не показывать, что там у Вам. Если у Вас в хедере, который отдается пользователю, будет к примеру буст, то некоторым это может попортить жизнь. (include в хередрах имеют тенденцию расползаться по всему проекту) Во вторых, если Вы захотите переделать все внутри (на условный curl), то пользователи снова ничего не узнают. В третьих, такой код легко будет интегрировать с разными языками программирования, в тот же пхп

кстати, писать вот так void connect(void) (c void в скобках) уже давно не принято.

Некоторые могут предложить использовать не void*, а полноценный указатель на объект. Но это очень и очень плохой совет. Он усложняет интеграцию с другими языками, плюс наружу торчит интерфейс, который никому кроме Вас не интересен. Плюс, передача объекта через границу длл-приложение почти всегда приводит к разнообразным приключениям. Используйте "сишный интерфейс".

→ Ссылка