Как сделать совместимые двоичные файлы DLL для версий VS?

Например, winsock libs прекрасно работает во всех версиях визуальной студии. Но у меня есть реальная проблема, чтобы обеспечить согласованный двоичный файл для всех версий. DLL, скомпилированная с VS 2005, не будет работать, если она связана с приложением, написанным в 2008 году. Я обновил 2k5 и 2k8 до SP1, но результаты не сильно изменились. Это работает кое-что хорошо. Но когда они включают это в приложение C#, приложение C# получает ошибки нарушения доступа, но с классическим приложением C++ оно работает нормально.

Есть ли стратегия, которую я должен знать, когда я предоставляю DLL?

3 ответа

Решение

Во-первых, не передавайте ничего, кроме простых старых данных, через границы DLL. т.е. структуры в порядке. классы нет. Во-вторых, убедитесь, что право собственности не передано - т.е. любые структуры, пересекающие границы dll, никогда не освобождаются за пределами dll. Итак, если вы экспортируете dll-функцию X* GetX(), то есть соответствующая функция типа FreeX(X*), гарантирующая, что за перераспределение отвечает та же самая среда выполнения, которая была выделена.

Далее: получите ваши библиотеки DLL для ссылки на статическую среду выполнения. Составление проекта, включающего dls от нескольких сторонних производителей, каждый из которых связан с различными средами исполнения и ожидает их разного времени выполнения, потенциально отличающегося от времени исполнения, ожидаемого приложением, - это трудная задача, потенциально заставляющая программу установки устанавливать среды выполнения для 7.0, 7.1, 8.0 и 9.0 - некоторые из которых существуют в разных пакетах обновления, которые могут вызывать или не вызывать проблемы. Будьте добры - статически связывайте свои проекты dll.

- Изменить: вы не можете экспортировать класс C++ напрямую с этим подходом. Совместное использование определений классов между модулями означает, что вы ДОЛЖНЫ иметь однородную среду выполнения, поскольку разные компиляторы или версии компиляторов будут генерировать декорированные имена по-разному.

Вы можете обойти это ограничение, экспортировав свой класс вместо этого как интерфейс в стиле COM... то есть, хотя вы не можете экспортировать класс независимо от среды выполнения, вы МОЖЕТЕ экспортировать "интерфейс", который вы можете легко сделать, объявив класс, содержащий только чисто виртуальные функции...

  struct IExportedMethods {
    virtual long __stdcall AMethod(void)=0;
  };
  // with the win32 macros:
  interface IExportedMethods {
    STDMETHOD_(long,AMethod)(THIS)PURE;
  };

В вашем определении класса вы наследуете от этого интерфейса:

  class CMyObject: public IExportedMethods { ...

Вы можете экспортировать такие интерфейсы, создав методы фабрики C:

  extern "C" __declspec(dllexport) IExportedClass* WINAPI CreateMyExportedObject(){
    return new CMyObject; 
  }

Это очень легкий способ экспорта версии компилятора и независимых от класса версий. Обратите внимание, что вы все еще не можете удалить один из них. Вы должны включить функцию release как член dll или интерфейса. Как член интерфейса это может выглядеть так:

  interface IExportedMethods {
    STDMETHOD_(void) Release(THIS) PURE; };
  class CMyObject : public IExportedMethods {
    STDMETHODIMP_(void) Release(){
      delete this;
    }
  };

Вы можете взять эту идею и продолжить работать с ней - унаследовать свой интерфейс от IUnknown, реализовать методы AddRef и Release с подсчетом ссылок, а также возможность QueryInterface для интерфейсов v2 или других функций. И, наконец, используйте DllCreateClassObject в качестве средства для создания вашего объекта и получения необходимой регистрации COM. Все это необязательно, однако вы можете легко получить простое определение интерфейса, доступ к которому осуществляется через функцию C.

Я не согласен с точкой зрения Криса Бекке, хотя вижу преимущества его подхода.

Недостатком является то, что вы не можете создавать библиотеки служебных объектов, потому что вам запрещено делиться ими между библиотеками.

Расширение решения Криса

Как сделать совместимые двоичные файлы DLL для версий VS?

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

В Windows Win32 API они решали проблему через "обработчики". Вы делаете то же самое путем:

1 - Никогда не подвергайте структуру. Выставлять только указатели (т.е. указатель void *)
2 - Доступ к данным этой структуры осуществляется через функции, принимающие указатель в качестве первого параметра.
3 - данные о распределении / освобождении указателя этой структуры осуществляются через функции

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

C++ способ сделать это - PImpl. Смотрите http://en.wikipedia.org/wiki/Opaque_pointer

Он имеет то же поведение, что и концепция void *, описанная выше, но с помощью PImpl вы можете использовать как RAII, инкапсуляцию, так и прибыль от строгой безопасности типов. Для этого потребуется совместимое оформление (тот же компилятор), но не одно и то же время выполнения или версия (если в разных версиях одинаковое оформление).

Другое решение?

Надеясь объединить библиотеки DLL из разных версий компиляторов / компиляторов - это либо повод для катастрофы (как вы объяснили в своем вопросе), либо утомительно, поскольку вы отказались от большинства (если не всех) решений C++ для своего кода, чтобы вернуться к базовому кодированию на C, или оба.

Мое решение будет:

1. Убедитесь, что все ваши модули скомпилированы с одним и тем же компилятором / версией. Период.
2. Убедитесь, что все ваши модули скомпилированы для динамического соединения с одной и той же средой выполнения.
3 - Убедитесь, что у вас есть "инкапсуляция" всех сторонних модулей, над которыми у вас нет контроля (невозможно компилировать с вашим компилятором), как правильно объяснил Крис Бекк из " Как сделать согласованные двоичные файлы dll для версий VS?,

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

Не позволяйте никому говорить вам, что смешивание компиляторов - это хорошо. Это не. Свобода микширования компилятора для большинства людей та же самая свобода, которой можно наслаждаться, выпрыгивая с вершины здания: вы можете делать это свободно, но обычно вы просто этого не хотите.

Мое решение позволяет вам:

1 - экспортируйте классы и, таким образом, создайте настоящие некастрированные библиотеки C++ (как вы должны делать, например, с __declspec(dllexport) Visual C++)
2 - передать владение распределением (что происходит без вашего согласия при использовании выделенного и / или освобождающего встроенного кода или STL)
3 - не раздражаться проблемами, связанными с тем фактом, что каждый модуль имеет свою собственную версию среды выполнения (т. Е. Распределение памяти и некоторые глобальные данные, используемые API C или C++)

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

Что касается проблемы передачи структуры, эта вещь безопасна, пока вы выравниваете свою структуру, такую ​​как:


#pragma pack(push,4)
typedef myStruct {
  int a;
  char b;
  float c;
}myStruct;
#pragma pack(pop)

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

Кроме того, убедитесь, что вы статически связаны с библиотеками времени выполнения и не пытайтесь делать такие вещи, как выделение памяти (ptr=malloc(1024)) в модуле, а затем освобождение этой памяти в другом модуле (бесплатно (ptr)).

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