Использование библиотек DLL C ++ с различными версиями компилятора

StackOverflow https://stackoverflow.com/questions/331045

Вопрос

Этот вопрос связан с "Как создать согласованные двоичные файлы dll для разных версий VS?"

  • У нас есть приложения и библиотеки DLL, созданные с помощью VC6, и новое приложение, созданное с помощью VC9.Приложение VC9 должно использовать Библиотеки DLL, скомпилированные с помощью VC6, большинство из которых написаны на C и одна на C ++.
  • Библиотека C ++ проблематична из-за проблем с оформлением / искажением имени.
  • Компиляция всего с помощью VC9 - это в настоящее время не вариант, поскольку есть по-видимому, некоторые побочные эффекты.Решение этих проблем заняло бы довольно много времени отнимает много времени.
  • Я могу изменить библиотеку C ++, однако она должна быть скомпилирована с помощью VC6.
  • Библиотека C ++ по сути является OO-оболочкой для другой библиотеки C.VC9-app использует некоторые статические функции, а также некоторые нестатические.

В то время как статические функции могут быть обработаны с помощью чего-то вроде

// Header file
class DLL_API Foo
{
    int init();
}

extern "C"
{
    int DLL_API Foo_init();
}

// Implementation file
int Foo_init()
{
    return Foo::init();
}

с нестатическими методами это не так просто.

Как я это понимаю, Криса Бека предложение использовать COM-подобный интерфейс мне не поможет, потому что имена элементов интерфейса по-прежнему будут оформлены и, следовательно, недоступны из двоичного файла, созданного с помощью другого компилятора. Я прямо там?

Будет ли единственным решением написать DLL-интерфейс в стиле C, используя обработчики для объектов, или я что-то упускаю?В этом случае, я полагаю, у меня, вероятно, было бы меньше усилий при непосредственном использовании обернутой C-библиотеки.

Это было полезно?

Решение

Имена участников интерфейса будут не быть оформленными - это просто смещения в виртуальной таблице.Вы можете определить интерфейс (используя структуру C, а не COM "интерфейс") в заголовочном файле, таким образом:

struct IFoo {
    int Init() = 0;
};

Затем вы можете экспортировать функцию из библиотеки DLL без каких-либо искажений:

class CFoo : public IFoo { /* ... */ };
extern "C" IFoo * __stdcall GetFoo() { return new CFoo(); }

Это будет работать нормально, при условии, что вы используете компилятор, который генерирует совместимые vtables.Microsoft C ++ сгенерировал vtable того же формата, что и (по крайней мере, я думаю) MSVC6.1 для DOS, где vtable представляет собой простой список указателей на функции (с заглушением в случае множественного наследования).GNU C ++ (если я правильно помню) генерирует vtables с указателями на функции и относительными смещениями.Они несовместимы друг с другом.

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

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

Я предполагаю, что вы можете обойти искажение имен (и соглашение о вызовах), что несложно, если вы используете компилятор с совместимым искажением (я думаю, VC6 в целом совместим с VS2008), или если вы используете внешний "C".

Где вы столкнетесь с проблемами, так это когда вы выделяете что-то, используя new (или malloc) из библиотеки DLL, а затем вы возвращаете это вызывающему.Звонящий delete (или free) попытается освободить объект из другой кучи.Это пойдет ужасно неправильно.

Вы можете либо выполнить COM-стиль IFoo::Release вещь, или MyDllFree() вещь.Оба из них, поскольку они вызывают обратный вызов в DLL, будут использовать правильную реализацию delete (или free()), поэтому они удалят правильный объект.

Или же вы можете убедиться, что вы используете LocalAlloc (например), так что EXE и DLL используют одну и ту же кучу.

Ну, я думаю Предложение Криса Бека это просто прекрасно.Я бы не стал использовать Первое решение Роджера, который использует интерфейс только по имени и, как он упоминает, может столкнуться с проблемами несовместимой обработки компилятором абстрактных классов и виртуальных методов.Роджер указывает на привлекательный последовательный пример в его последующие действия.

  1. Болевая точка:Вам нужно научиться делать запросы COM-интерфейса и правильно работать с IUnknown, полагаясь, по крайней мере, на IUnknown: AddRef и IUnknown:Release.Если реализации интерфейсов могут поддерживать более одного интерфейса или если методы также могут возвращать интерфейсы, вам также может потребоваться освоиться с IUnknown:QueryInterface.

  2. Вот ключевая идея.Все программы, которые используют реализацию интерфейса (но не реализуют его), используют общий файл #include "*.h", который определяет интерфейс как структуру (C) или класс C / C ++ (VC ++) или struct (не VC ++, но C ++).Файл *.h автоматически адаптируется соответствующим образом в зависимости от того, компилируете ли вы программу на языке C или программу на языке C ++.Вам не обязательно знать об этой части, чтобы просто использовать файл *.h.Что делает файл *.h, так это определяет структуру или тип интерфейса, скажем, IFoo, с его виртуальными функциями-членами (и только функциями, прямой видимости к элементам данных в этом подходе нет).

  3. Файл заголовка сконструирован с учетом двоичного стандарта COM способом, который работает для C и который работает для C ++ независимо от используемого компилятора C ++.(Люди из Java JNI разобрались с этим.) Это означает, что он работает между отдельно скомпилированными модулями любого происхождения до тех пор, пока структура, состоящая полностью из указателей на вход функций (vtable), отображается в память всеми ними одинаково (поэтому все они должны быть 32-разрядными x86 или, например, все x64).

  4. В DLL, которая реализует COM-интерфейс через какой-либо класс-оболочку, вам нужна только заводская точка входа.Что - то вроде

    внешний "C" HRESULT MkIFooImplementation(недействительный **ppv);

который возвращает HRESULT (вам тоже нужно будет узнать о них), а также вернет * pv в местоположении, которое вы предоставляете для получения указателя интерфейса IFoo.(Я бегло просматриваю, и здесь вам понадобятся более подробные сведения.Не доверяйте моему синтаксису) Фактический стереотип функции, который вы используете для этого, также объявлен в файле *.h.

  1. Дело в том, что заводская запись, которая всегда является недекорированным внешним "C", выполняет все необходимое создание класса-оболочки, а затем доставляет указатель интерфейса Ifoo в указанное вами местоположение.Это означает, что все управление памятью для создания класса и все управление памятью для его завершения и т.д. будет происходить в DLL, где вы создаете оболочку.Это единственное место, где вам приходится иметь дело с этими деталями.

  2. Когда вы получаете результат OK от заводской функции, вам был выдан указатель интерфейса, и он уже зарезервирован для вас (существует неявная операция IFoo: Addref, уже выполненная от имени переданного вам указателя интерфейса).

  3. Когда вы закончите с интерфейсом, вы освобождаете его с помощью вызова метода IFoo:Release интерфейса.Это реализация окончательного выпуска (на случай, если вы сделали больше копий с добавленными ссылками), которая приведет к удалению класса и поддержки его интерфейса в заводской DLL.Это то, что позволяет вам правильно полагаться на согласованное динамическое распределение ресурсов и выпуск за интерфейсом, независимо от того, использует ли DLL, содержащая заводскую функцию, те же библиотеки, что и вызывающий код.

  4. Вероятно, вам также следует реализовать IUnknown:QueryInterface (как метод IFoo:QueryInterface), даже если он всегда завершается неудачей.Если вы хотите быть более изощренным в использовании модели двоичного интерфейса COM, поскольку у вас больше опыта, вы можете научиться предоставлять полные реализации QueryInterface.

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

Я не описал ресурсы, к которым вам, возможно, потребуется обратиться за примерами, и то, что вам нужно изучить, чтобы создавать файлы * .h и фактически реализовывать оболочки заводских функций библиотек, которыми вы хотите поделиться.Если вы хотите копнуть глубже, кричите.

Есть и другие вещи, которые вам также необходимо учитывать, например, какое время выполнения используется различными библиотеками.Если никакие объекты не используются совместно, это нормально, но на первый взгляд это кажется довольно маловероятным.
Предложения Криса Беккера довольно точны - используя фактический COM-интерфейс может помочь вам получить необходимую двоичную совместимость.Ваш пробег может отличаться :)

не весело, чувак.вас ждет большое разочарование, вам, вероятно, следует отказаться от этого:

Было бы единственным решением написать Интерфейс DLL в стиле C с использованием обработчиков для объектов или я чего-то не хватает ?В этом случае, я полагаю, у меня вероятно, было бы меньше усилий с прямым использованием обернутой C-библиотеки.

действительно пристальный взгляд.удачи.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top