правильное использование std :: vector в интерфейсах dll (класс с виртуальным деструктором!) - PullRequest
0 голосов
/ 04 ноября 2019

Я попытаюсь объяснить мою проблему на примере.

У меня есть следующий класс:

__declspec(dllexport) class myclass
{
public:
    int a;
    int b;
    myclass() {};
    virtual ~myclass() {};
    // ~myclass() {};
};

myclass используется внутри DLL, который содержит только определение класса Test (который на самом деле является просто интерфейсом для методов foo1 и foo2 ):

class Test
{
public:
    void                 foo1   (std::vector<myclass>& val);
    std::vector<myclass> foo2   ();
};

extern "C"
{
    __declspec(dllexport) bool foo1(std::vector<myclass>& val)
    {
        val.clear();
        myclass tmp;
        val.push_back(tmp);
        val.push_back(tmp);
        val.push_back(tmp); //just an example!
        return true;
    }

    __declspec(dllexport) std::vector<myclass> foo2()
    {
        std::vector<myclass> val;
        myclass tmp;
        val.push_back(tmp);
        val.push_back(tmp);
        val.push_back(tmp); //just an example!
        return val;
    }
}

Главное приложение открывает DLL и вызывает методы интерфейса для получения вектора, заполненного методами foo1 или foo2 .

typedef void                 (*FNPTR1)(std::vector<myclass>& val);
typedef std::vector<myclass> (*FNPTR2)();


int main()
{
    if (0)
    {
        HINSTANCE hInst = LoadLibrary(L"C:\\software\\mydll.dll");
        if (!hInst) { std::cout << "\nCould Not Load the Library";  return EXIT_FAILURE; }
        FNPTR1 fn = (FNPTR1)GetProcAddress(hInst, "foo1");
        if (!fn) { std::cout << "\nCould not locate the function";  return EXIT_FAILURE; }

        std::vector<myclass> tmp;
        fn(tmp);
        FreeLibrary(hInst);
        tmp.clear(); //crash here!
    }

    if (1)
    {
        HINSTANCE hInst = LoadLibrary(L"C:\\software\\mydll.dll");
        if (!hInst) { std::cout << "\nCould Not Load the Library";  return EXIT_FAILURE; }
        FNPTR2 fn = (FNPTR2)GetProcAddress(hInst, "foo2");
        if (!fn) { std::cout << "\nCould not locate the function";  return EXIT_FAILURE; }

        std::vector<myclass> tmp;
        tmp = fn();
        FreeLibrary(hInst);
        tmp.clear(); //crash here!
    }

    return 1;
}
}

К сожалению, случается, что в обоих случаях (используя foo1 или foo2 ) я получаю "Чтение нарушения доступа XXXX" ошибка, когда я пытаюсь очистить (или выполнить любую другую операцию) вектор tmp после выгрузки DLL.

Эта проблема возникает, только если деструктор myclass является виртуальным .

В моем приложении myclass генерируется автоматически, и мне не разрешено изменять определение деструктора. Тем не менее, я могу свободно изменять методы интерфейса dll (например, foo1 / foo2), если на этом уровне существует какое-либо решение.

Мне также интересно, можно ли как-то сказать системе использовать для tmp векторная куча основной программы вместо той, которая используется .dll.

В настоящее время я использую Visual Studio c ++ 2017 и мне разрешено использовать стандарт C ++ 11 (или более поздний)

Спасибо за вашу помощь.


Отвечая на комментарии ниже:

  • Да, exe и dll скомпилированы с использованием одной и той же цепочки инструментов. Я использую параметр / MD. Пожалуйста, скажите мне, если я должен проверить дополнительные вещи.
  • Что касается комментария о происхождении myclass , я попробовал также все четыре возможные комбинации с базовым классом myclassbase и myclass , полученные из него:

    1- myclassbase со стандартным деструктором и myclass со стандартным деструктором -> ok

    2- myclassbase со стандартным деструктором и myclass свиртуальный деструктор -> crash

    3- myclassbase с виртуальным деструктором и myclass со стандартным деструктором -> crash

    4- myclassbase с виртуальным деструктором и myclass с виртуальным деструктором -> crash

    Следовательно, я могу сделать вывод, что наличия одного виртуального деструктора достаточно для генерации вопроса.

1 Ответ

0 голосов
/ 05 ноября 2019

Я, наконец, перед компьютером и успел внимательно проверить ваш код.

  1. __declspec(dllexport) class myclass - это неправильно . Если вы определите это так, и DLL, и EXE будут ожидать экспортированную переменную, но DLL должна видеть класс dllexport ed, а EXE должен видеть класс dllimport ed.
    • Чтобы правильно экспортировать класс, вы должны определить макрос (см. Ниже), который интерпретируется как экспорт в проекте DLL и как импорт в EXE .
    • Макрос должен быть вставлен после class ключевое слово : class TEST_API myclass.
  2. То же самое действительно для экспортируемых функций ;перед типом возвращаемой функции должен быть добавлен только макрос экспорта: TEST_API inline bool foo1(std::vector<myclass>& val).
  3. Вам также необходимо добавить в проект DLL определение препроцессора , которое разрешает экспорт : Project Properties | Configuration Properties |Preprocessor | Preprocessor Definitions: TEST_EXPORTS
  4. Почему вы добавили extern «C» и проигнорировали предупреждения? Удалите его .
  5. Если ваши глобальные функции находятся в заголовочном файле, не забудьте добавить inline.

Теперь это должно работать.

// mydll_exports.h
#pragma once

#ifdef TEST_EXPORTS
  #define TEST_API __declspec(dllexport)
#else
  #define TEST_API __declspec(dllimport)
  #pragma comment(lib,"mydll.lib")
#endif
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...