Экспорт класса C++ из DLL

Большая часть моей разработки на C / C++ включает в себя файлы монолитных модулей и абсолютно никаких классов, поэтому обычно, когда мне нужно создать DLL с доступными функциями, я просто экспортирую их, используя стандартную директиву __declspec(dllexport). Затем обращайтесь к ним либо динамически через LoadLibrary(), либо во время компиляции с помощью файла заголовка и lib.

Как вы это делаете, если хотите экспортировать весь класс (и все его общедоступные методы и свойства)?

Можно ли динамически загружать этот класс во время выполнения, и если да, то как?

Как бы вы сделали это с заголовком и библиотекой для компоновки во время компиляции?

Очень хороший ответ, написанный позже: stackoverflow.com/a/22797419/1995714

cp.engr 25.08.2016 22:21
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
27
1
37 024
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Я использую некоторые макросы, чтобы пометить код для импорта или экспорта

#ifdef ISDLL
#define DLL __declspec(dllexport)
#endif

#ifdef USEDLL
#define DLL __declspec(dllimport)
#endif

Затем объявите класс в файле заголовка:

class DLL MyClassToExport { ... }

Затем #define ISDLL в библиотеке и USEDLL перед включением файла заголовка в то место, где вы хотите использовать класс.

Не знаю, может понадобиться что-то другое для работы с LoadLibrary.

Когда вы создаете DLL и модуль, который будет использовать DLL, имейте какой-то #define, который вы можете использовать для различения одного и другого, тогда вы можете сделать что-то вроде этого в файле заголовка вашего класса:

#if defined( BUILD_DLL )
    #define IMPORT_EXPORT __declspec(dllexport)
#else
    #define IMPORT_EXPORT __declspec(dllimport)
#endif
class IMPORT_EXPORT MyClass {
    ...
};

Обновлено: crashmstr опередил меня!

как бороться с искаженными именами классов и конструкторов (и т. д.), сэр.?

null 20.03.2013 12:06

В том-то и дело - вам не нужно иметь дело с искаженными именами. __declspec(dllimport) сообщает компилятору / компоновщику, что вы его импортируете, и он поступает правильно.

Graeme Perrow 20.03.2013 16:53

Ни этот ответ, ни ответ crashmstr не имеют ничего общего с исходным вопросом. Также вопрос null не был должным образом решен.

AudioGL 22.02.2014 10:42

@AudioGL О чем ты говоришь? Если вы включаете файл заголовка, который определяет класс с помощью __declspec(dllimport), а затем связываете exe с библиотекой импорта (в результате сборки DLL), полученный исполняемый файл автоматически загружает DLL во время выполнения и импортирует класс, включая все методы на нем. . Искаженные имена и перегруженные конструкторы не являются проблемой, потому что все они обрабатываются компоновщиком и загрузчиком. Это может быть не такой полный ответ, как ответ Джеймса Девлина, но в этом нет ничего плохого, и он действительно отвечает на исходный вопрос.

Graeme Perrow 22.02.2014 19:16

Фактически, если вы читаете ответ г-на Девлина, он говорит: «Возможно, лучше просто использовать неявное связывание, и в этом случае вы определенно хотите использовать метод препроцессора, показанный выше» (выделено мной).

Graeme Perrow 22.02.2014 19:17

Извините - я предположил, что Адам мог иметь дело с проблемами несовместимости компилятора. Вы не можете использовать этот метод, если вы пытались экспортировать классы между MSVC и MinGW, например, из-за различных схем изменения имен.

AudioGL 22.02.2014 23:34
Ответ принят как подходящий

What about late-binding? As in loading it with LoadLibrary() and GetProcAddress() ? I'm used being able to load the library at run time and it would be great if you could do that here.

Итак, есть два способа загрузить DLL. Первый - указать один или несколько символов из DLL (например, имя вашего класса), предоставить соответствующий import .LIB и позволить компоновщику все выяснить.

Второй - явно загрузить DLL через LoadLibrary.

Любой из подходов отлично подходит для экспорта функций уровня C. Вы можете либо позволить компоновщику обработать это, либо вызвать GetProcAddress, как вы отметили.

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

Если вы хотите связать класс, определенный в DLL, и хотите, чтобы эта DLL загружалась динамически, через некоторое время после запуска программы у вас есть два варианта:

  1. Создавайте объекты класса, используя специальную фабричную функцию, которая внутри должна будет использовать (крошечный бит) ассемблер для «подключения» вновь созданных объектов к их соответствующим смещениям. Очевидно, это должно быть сделано во время выполнения ПОСЛЕ загрузки DLL. Хорошее объяснение этого подхода можно найти в здесь.

  2. Используйте DLL с отложенной загрузкой.

Учитывая все обстоятельства ... вероятно, лучше просто использовать неявное связывание, и в этом случае вы определенно захотите использовать метод препроцессора, показанный выше. Фактически, если вы создадите новую DLL в Visual Studio и выберете опцию «экспорт символов», эти макросы будут созданы для вас.

Удачи...

Я не верю, что вы можете использовать delayload с импортированными классами, если классы имеют виртуальные функции, потому что vtables этих классов будут импортированы как символы данных. Импорт символов данных предварительно вычисляется с использованием delayload.

RichieHindle 08.03.2011 14:38

Если вы хотите поместить vtable в экспортируемый класс, вы можете экспортировать функцию, которая возвращает интерфейс и реализовать класс в .dll, а затем поместить ее в файл .def. Возможно, вам придется проделать некоторые уловки с объявлением, но это не должно быть слишком сложно.

Так же, как COM. :)

Недавно я задал себе точно такой же вопрос и резюмировал свои выводы в сообщении в блоге. Вы можете найти это полезным.

Он охватывает экспорт классов C++ из DLL, а также их динамическую загрузку с помощью LoadLibrary и обсуждает некоторые связанные с этим проблемы, такие как управление памятью, изменение имен и соглашения о вызовах.

Добавляем простой рабочий пример для экспорта класса C++ из DLL:

Приведенный ниже пример дает вам только краткий обзор того, как dll и exe могут взаимодействовать друг с другом (не требует пояснений), но для изменения в производственный код нужно добавить больше вещей.

Полный примерный пример разделен на две части.

A. Создание библиотеки .dll (MyDLL.dll)

Б. Создание приложения, использующего библиотеку .dll (приложение).

A. Файл проекта .dll (MyDLL.dll):

1. dllHeader.h

#ifdef  MYDLL_EXPORTS 
#define DLLCALL __declspec(dllexport)   /* Should be enabled before compiling 
                                           .dll project for creating .dll*/
#else
#define DLLCALL __declspec(dllimport)  /* Should be enabled in Application side
                                          for using already created .dll*/
#endif

// Interface Class
class ImyMath {
public:
    virtual ~ImyMath() {;}
    virtual int Add(int a, int b) = 0;
    virtual int Subtract(int a, int b) = 0;
};

// Concrete Class
class MyMath: public ImyMath {
public:
    MyMath() {}
    int Add(int a, int b);
    int Subtract(int a, int b);
    int a,b;
};

//  Factory function that will return the new object instance. (Only function
//  should be declared with DLLCALL)
extern "C" /*Important for avoiding Name decoration*/
{
    DLLCALL ImyMath* _cdecl CreateMathObject();
};

// Function Pointer Declaration of CreateMathObject() [Entry Point Function]
typedef ImyMath* (*CREATE_MATH) ();

2. dllSrc.cpp

#include "dllHeader.h"

// Create Object
DLLCALL ImyMath* _cdecl CreateMathObject() {
    return new MyMath();
}

int MyMath::Add(int a, int b) {
    return a+b;
}

int MyMath::Subtract(int a, int b) {
    return a-b;
}

B. Проект приложения, который загружает и связывает уже созданный файл .dll:

 #include <iostream>
#include <windows.h>
#include "dllHeader.h"

int main()
{
    HINSTANCE hDLL = LoadLibrary(L"MyDLL.dll"); // L".\Debug\MyDLL.dll"

    if (hDLL == NULL) {
        std::cout << "Failed to load library.\n";
    }
    else {
        CREATE_MATH pEntryFunction = (CREATE_MATH)GetProcAddress(hDLL,"CreateMathObject");
        ImyMath* pMath = pEntryFunction();
        if (pMath) {
            std::cout << "10+10 = " << pMath->Add(10, 10) << std::endl;
            std::cout << "50-10 = " << pMath->Subtract(50, 10) << std::endl;
        }
        FreeLibrary(hDLL);
    }
    std::cin.get();
    return 0;
}

Спасибо. Без подхода класса интерфейса в этом ответе мне пришлось поместить все в файл .h, чтобы предотвратить ошибки компоновщика.

James_UK_DEV 06.10.2014 13:38

зачем нужна заводская функция? Если без заводской функции, как мне поступить?

Nibnat 10.12.2016 08:38

Другие вопросы по теме