Поделиться через


версия DLL MFC

В этом примечании описывается, как использовать MFCxx.DLL и MFCxxD.DLL (где xx — номер версии MFC) общие динамические библиотеки с приложениями MFC и DLL-библиотеками расширений MFC. Дополнительные сведения о обычных библиотеках DLL MFC см. в разделе "Использование MFC в составе библиотеки DLL".

Эта техническая заметка охватывает три аспекта библиотек DLL. Последние два — для более продвинутых пользователей:

Если вы заинтересованы в создании библиотеки DLL с помощью MFC, которая может использоваться с приложениями, не являющимися MFC (известной как стандартная библиотека DLL MFC), обратитесь к Техническому примечанию 11.

Обзор поддержки MFCxx.DLL: терминология и файлы

Обычная библиотека DLL MFC: вы используете обычную библиотеку DLL MFC для создания автономной библиотеки DLL с помощью некоторых классов MFC. Интерфейсы через границу App/DLL — это интерфейсы C, а клиентское приложение не должно быть приложением MFC.

Обычные библиотеки DLL MFC — это версия библиотек DLL, поддерживаемых в MFC 1.0. Они описаны в техническом примечании 11 и в образце DLLScreenCapрасширенных концепций MFC.

Замечание

По состоянию на Visual C++ версии 4.0 термин USRDLL устарел и заменен обычным библиотекой DLL MFC, которая статически связывается с MFC. Вы также можете создать обычную библиотеку DLL MFC, которая динамически связывается с MFC.

MFC 3.0 (и выше) поддерживает обычные библиотеки DLL MFC со всеми новыми функциями, включая классы OLE и Database.

AFXDLL: также называется общей версией библиотек MFC. Это новая поддержка DLL, добавленная в MFC 2.0. Библиотека MFC находится в ряде DLL-библиотек (описано ниже). Клиентское приложение или библиотека DLL динамически связывает необходимые библиотеки DLL. Интерфейсы через границу приложения или библиотеки DLL — это интерфейсы классов C++/MFC. Клиентское приложение ДОЛЖНО быть приложением MFC. Эта библиотека DLL поддерживает все функциональные возможности MFC 3.0 (исключение: ЮНИКОД не поддерживается для классов базы данных).

Замечание

По состоянию на Visual C++ версии 4.0 этот тип библиотеки DLL называется библиотекой DLL расширения.

Это примечание будет использоваться MFCxx.DLL для ссылки на весь набор DLL MFC, который включает в себя:

  • Отладка: MFCxxD.DLL (объединенная) и MFCSxxD.LIB (статичная).

  • Выпуск: MFCxx.DLL (объединенный) и MFCSxx.LIB (статический).

  • Отладка Юникода: MFCxxUD.DLL (объединенная) и MFCSxxD.LIB (статичная).

  • Выпуск Юникода: MFCxxU.DLL (объединенный) и MFCSxxU.LIB (статический).

Замечание

Библиотеки MFCSxx[U][D].LIB используются в сочетании с общими библиотеками DLL MFC. Эти библиотеки содержат код, который должен быть статически связан с приложением или библиотекой DLL.

Приложение ссылается на соответствующие библиотеки импорта:

  • Отладка – MFCxxD.LIB

  • Отпускать: MFCxx.LIB

  • Отладка Юникода: MFCxxUD.LIB

  • Выпуск Юникода: MFCxxU.LIB

Библиотека DLL расширения MFC — это библиотека DLL, которая расширяет MFCxx.DLL (или другие общие библиотеки DLL MFC). Здесь архитектура компонента MFC вступает в действие. Если вы наследуете полезный класс из класса MFC или создадите другой набор средств, похожий на MFC, его можно поместить в библиотеку DLL. Ваша библиотека динамических ссылок (DLL) использует MFCxx.DLL, как и конечное клиентское приложение. Библиотека DLL расширения MFC позволяет повторно использовать конечные классы, повторно использовать базовые классы, а также повторно использовать классы представлений и документов.

Плюсы и минусы

Почему следует использовать общую версию MFC

  • Использование общей библиотеки может привести к более мелким приложениям. (Минимальное приложение, использующее большую часть библиотеки MFC, меньше 10 КБ).

  • Общая версия MFC поддерживает библиотеки DLL расширения MFC и обычные библиотеки DLL MFC.

  • Быстрее создавать приложение, использующее общие библиотеки MFC, чем статическое связанное приложение MFC. Это связано с тем, что не требуется связать сам MFC. Это особенно актуально в DEBUG сборках, где линкер должен уплотнять данные отладки. Когда ваше приложение связывается с DLL, которая уже содержит информацию об отладке, объем отладочной информации, которая нуждается в сжатии, уменьшается.

Почему не следует использовать общую версию MFC:

  • При распространении приложения, которое использует общую библиотеку, необходимо включить MFCxx.DLL и другие библиотеки в поставку программы. MFCxx.DLL является свободно распространяемым, как и многие библиотеки DLL, но вы по-прежнему должны установить библиотеку DLL в программе УСТАНОВКИ. Кроме того, вам придется поставлять другие распространяемые библиотеки, используемые как вашей программой, так и DLL-библиотеками MFC.

Как написать расширяющую библиотеку DLL MFC

Библиотека DLL расширения MFC — это библиотека DLL, содержащая классы и функции для увеличения функциональности классов MFC. Библиотека DLL расширения MFC использует общие библиотеки DLL MFC таким же образом, как и приложение, с несколькими дополнительными рекомендациями.

  • Процесс сборки аналогичен созданию приложения, использующего общие библиотеки MFC с несколькими дополнительными параметрами компилятора и компоновщика.

  • Библиотека расширения MFC DLL не имеет класс, производный от CWinApp.

  • Библиотека DLL расширения MFC должна предоставлять особую DllMain. AppWizard предоставляет функцию DllMain , которую можно изменить.

  • Библиотека DLL расширения MFC обычно предоставляет подпрограмму инициализации для создания CDynLinkLibrary, если библиотека DLL расширения MFC экспортирует типы или ресурсы CRuntimeClass в приложение. Производный CDynLinkLibrary класс может использоваться, если данные для каждого приложения должны поддерживаться библиотекой DLL расширения MFC.

Эти рекомендации подробно описаны ниже. Также см. пример DLLHUSKрасширенных концепций MFC. В нем показано, как:

  • Создайте приложение с помощью общих библиотек. (DLLHUSK.EXE — это приложение MFC, которое динамически связывается с библиотеками MFC и другими библиотеками DLL.)

  • Создание библиотеки DLL расширения MFC. (В нём показано, как используются специальные флаги, такие как _AFXEXT при создании DLL расширения MFC.)

  • Создайте два примера библиотек DLL расширения MFC. В одной из них показана базовая структура библиотеки DLL расширения MFC с ограниченными экспортами (TESTDLL1), а другая — экспорт всего интерфейса класса (TESTDLL2).

Как клиентское приложение, так и все библиотеки MFCxx.DLLDLL расширения MFC должны использовать одну и ту же версию. Следуйте соглашениям библиотек DLL MFC и предоставьте как отладочную, так и релизную версию вашей библиотеки DLL расширения MFC /release. Эта практика позволяет клиентским программам создавать версии отладки и выпуска приложений и связывать их с соответствующей версией отладки или выпуска всех библиотек DLL.

Замечание

Из-за проблем с изменением имен (name mangling) и экспортом в C++, список экспорта из библиотеки DLL расширения MFC может отличаться между версиями отладки и выпуска одной и той же библиотеки DLL, а также DLL для разных платформ. Выпуск MFCxx.DLL имеет около 2000 экспортированных точек входа; отладка MFCxxD.DLL имеет около 3000 экспортированных точек входа.

Краткое примечание по управлению памятью

В разделе "Управление памятью" в конце этой технической заметки описывается реализация MFCxx.DLL с общей версией MFC. Сведения, которые вам нужно знать для реализации именно библиотеки DLL расширения MFC, описаны здесь.

MFCxx.DLL и все библиотеки DLL расширения MFC, загруженные в адресное пространство клиентского приложения, будут использовать тот же распределитель памяти, загрузку ресурсов и другие глобальные состояния MFC, как если бы они были частью одного приложения. Это важно, так как библиотеки DLL, не являющиеся библиотеками MFC, и обычные библиотеки DLL MFC, которые статически связываются с MFC, делают прямо противоположное: каждая библиотека DLL выделяет из собственного пула памяти.

Если библиотека DLL расширения MFC выделяет память, то эта память может свободно взаимодействовать с любым другим выделенным приложением объектом. Кроме того, если приложение, использующее общие библиотеки MFC, завершает работу, операционная система сохраняет целостность любого другого приложения MFC, которое использует библиотеку DLL.

Аналогичным образом, другие "глобальные" параметры MFC, такие как текущий исполняемый файл для загрузки ресурсов, также передаются для общего использования между клиентским приложением, всеми библиотеками DLL расширения MFC и MFCxx.DLL самим собой.

Создание библиотеки DLL расширения MFC

С помощью AppWizard можно создать проект библиотеки DLL расширения MFC, и он автоматически настраивает соответствующие параметры компилятора и компоновщика. Он также создает функцию DllMain , которую можно изменить.

Если вы преобразуете существующий проект в библиотеку DLL расширения MFC, начните со стандартных параметров, которые создаются с помощью общей версии MFC. Затем внесите следующие изменения:

  • Добавьте /D_AFXEXT в флаги компилятора. В диалоговом окне "Свойства проекта" выберите категорию препроцессора>. Добавьте _AFXEXT в поле "Определение макросов" , разделяя каждый из элементов точкой с запятой.

  • Удалите переключатель компилятора /Gy . В диалоговом окне "Свойства проекта" выберите категорию создания кода>. Убедитесь, что свойство Enable Function-Level Linking не включено. Этот параметр упрощает экспорт классов, так как компоновщик не удаляет функции без ссылок. Если исходный проект создал обычную библиотеку DLL MFC, которая статически связана с MFC, измените параметр компилятора /MT (или /MTd) на /MD (или /MDd).

  • Создайте библиотеку экспорта с помощью параметра /DLL LINK. Этот параметр задается при создании новой цели и указании Win32 Dynamic-Link Library как типа цели.

Изменение файлов заголовков

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

Чтобы обеспечить корректную маркировку каждой функции-члена для импорта или экспорта, используйте специальные объявления __declspec(dllexport) и __declspec(dllimport). Если клиентские приложения используют классы, они должны быть объявлены как __declspec(dllimport). При построении библиотеки DLL расширения MFC функции должны быть объявлены как __declspec(dllexport). Встроенная библиотека DLL также должна экспортировать функции, чтобы клиентские программы могли привязать их во время загрузки.

Чтобы экспортировать весь класс, используйте AFX_EXT_CLASS в определении класса. Платформа определяет этот макрос как __declspec(dllexport) когда _AFXDLL и _AFXEXT определяется, но определяет его как __declspec(dllimport) если _AFXEXT не определено. _AFXEXT определяется только при создании библиотеки DLL расширения MFC. Рассмотрим пример.

class AFX_EXT_CLASS CExampleExport : public CObject
{ /* ... class definition ... */ };

Неполный экспорт класса

Иногда вам может понадобиться экспортировать только отдельные необходимые члены вашего класса. Например, если вы экспортируете производный класс CDialog, вам может понадобиться экспортировать только конструктор и вызов функции DoModal. Вы можете экспортировать эти компоненты с помощью DEF-файла библиотеки DLL, но также можете использовать AFX_EXT_CLASS аналогично на отдельных компонентах, которые необходимо экспортировать.

Рассмотрим пример.

class CExampleDialog : public CDialog
{
public:
    AFX_EXT_CLASS CExampleDialog();
    AFX_EXT_CLASS int DoModal();
    // rest of class definition
    // ...
};

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

Например, макрос DECLARE_DYNAMIC определяется следующим образом при создании библиотеки DLL расширения MFC:

#define DECLARE_DYNAMIC(class_name) \
protected: \
    static CRuntimeClass* PASCAL _GetBaseClass(); \
    public: \
    static AFX_DATA CRuntimeClass class##class_name; \
    virtual CRuntimeClass* GetRuntimeClass() const; \

Строка, начинающаяся с static AFX_DATA, объявляет статический объект внутри вашего класса. Чтобы правильно экспортировать этот класс и получить доступ к данным среды выполнения из клиентского EXE, необходимо экспортировать этот статический объект. Поскольку статический объект объявлен с помощью модификатора AFX_DATA, вам нужно определить AFX_DATA как __declspec(dllexport) только при сборке DLL. Определите его как __declspec(dllimport) при сборке исполняемого файла клиента.

Как описано выше, AFX_EXT_CLASS уже определен таким образом. Вам просто нужно переопределить AFX_DATA, чтобы он совпадал с AFX_EXT_CLASS в вашем определении класса.

Рассмотрим пример.

#undef  AFX_DATA
#define AFX_DATA AFX_EXT_CLASS
class CExampleView : public CView
{
    DECLARE_DYNAMIC()
    // ... class definition ...
};
#undef  AFX_DATA
#define AFX_DATA

MFC всегда использует AFX_DATA символ для элементов данных, которые он определяет в своих макросах, поэтому этот метод будет работать для всех таких сценариев. Например, он будет работать для DECLARE_MESSAGE_MAP.

Замечание

При экспорте всего класса вместо отдельных элементов класса статические элементы данных экспортируются автоматически.

Вы можете использовать тот же метод для автоматического экспорта CArchive оператора извлечения для классов, использующих макросы DECLARE_SERIAL и IMPLEMENT_SERIAL. Экспортируйте оператор архива, обрамляя объявления класса (расположенные в файле заголовка) следующим кодом:

#undef AFX_API
#define AFX_API AFX_EXT_CLASS

/* your class declarations here */

#undef AFX_API
#define AFX_API

Ограничения для символа _AFXEXT

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

Проблема заключается в том, что в Win32 необходимо явно объявить любые данные как __declspec(dllexport) для экспорта из DLL и как __declspec(dllimport) для импорта из DLL. При определении _AFXEXT заголовки MFC удостоверяются, что AFX_EXT_CLASS определён правильно.

При наличии нескольких слоев один символ, например AFX_EXT_CLASS недостаточно: библиотека DLL расширения MFC может экспортировать собственные классы, а также импортировать другие классы из другой библиотеки DLL расширения MFC. Чтобы справиться с этой задачей, используйте символ препроцессора, указывающий, что вы создаете саму библиотеку DLL, а не используете её. Например, представьте, что есть две библиотеки расширения MFC: A.DLL и B.DLL. Каждый из них соответственно экспортирует некоторые классы в A.H и B.H. B.DLL использует классы из A.DLL. Файлы заголовка будут выглядеть примерно так:

/* A.H */
#ifdef A_IMPL
    #define CLASS_DECL_A   __declspec(dllexport)
#else
    #define CLASS_DECL_A   __declspec(dllimport)
#endif

class CLASS_DECL_A CExampleA : public CObject
{ /* ... class definition ... */ };

/* B.H */
#ifdef B_IMPL
    #define CLASS_DECL_B   __declspec(dllexport)
#else
    #define CLASS_DECL_B   __declspec(dllimport)
#endif

class CLASS_DECL_B CExampleB : public CExampleA
{ /* ... class definition ... */ };

Когда A.DLL построен, он построен с /DA_IMPL, и когда B.DLL построен, он построен с /DB_IMPL. С помощью отдельных символов для каждой библиотеки DLL CExampleB экспортируется и CExampleA импортируется при сборке B.DLL. CExampleA экспортируется при сборке A.DLL и импортируется при использовании B.DLL или другим клиентом.

Этот тип слоев нельзя сделать при использовании встроенных AFX_EXT_CLASS и _AFXEXT препроцессорных символов. Описанный выше метод решает эту проблему так же, как mFC. MFC использует этот метод при создании библиотек расширения MFC для OLE, баз данных и сетей.

По-прежнему не экспортирует весь класс

Снова вам придется проявлять особую осторожность, если вы экспортируете не весь класс. Убедитесь, что необходимые элементы данных, созданные макросами MFC, экспортируются правильно. Это можно сделать, переопределяя AFX_DATA макрос конкретного класса. Переопределите его всякий раз, когда вы не экспортируете весь класс.

Рассмотрим пример.

// A.H
#ifdef A_IMPL
    #define CLASS_DECL_A  _declspec(dllexport)
#else
    #define CLASS_DECL_A  _declspec(dllimport)
#endif

#undef  AFX_DATA
#define AFX_DATA CLASS_DECL_A

class CExampleA : public CObject
{
    DECLARE_DYNAMIC()
    CLASS_DECL_A int SomeFunction();
    // class definition
    // ...
};

#undef AFX_DATA
#define AFX_DATA

DllMain

Ниже приведен код, который следует поместить в основной исходный файл библиотеки DLL расширения MFC. Должно следовать после стандартных подключений. При использовании AppWizard для создания начальных файлов для библиотеки DLL расширения MFC он предоставляет вам доступ DllMain .

#include "afxdllx.h"

static AFX_EXTENSION_MODULE extensionDLL;

extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID)
{
   if (dwReason == DLL_PROCESS_ATTACH)
   {
      // MFC extension DLL one-time initialization
      if (!AfxInitExtensionModule(
             extensionDLL, hInstance))
         return 0;

      // TODO: perform other initialization tasks here
   }
   else if (dwReason == DLL_PROCESS_DETACH)
   {
      // MFC extension DLL per-process termination
      AfxTermExtensionModule(extensionDLL);

      // TODO: perform other cleanup tasks here
   }
   return 1;   // ok
}

Вызов AfxInitExtensionModule фиксирует классы среды исполнения модуля (CRuntimeClassструктуры) и его фабрики объектов (COleObjectFactoryобъекты) для последующего использования, когда создается объект CDynLinkLibrary. Вызов AfxTermExtensionModule (необязательно) позволяет MFC очистить библиотеку DLL расширения MFC, когда каждый процесс отсоединяется от библиотеки DLL расширения MFC (что происходит при выходе процесса или при выгрузке библиотеки DLL вызовом FreeLibrary). Так как большинство библиотек DLL расширения MFC не загружаются динамически (обычно они связаны с помощью библиотек импорта), вызов AfxTermExtensionModule обычно не требуется.

Если приложение загружает и освобождает библиотеки DLL расширений MFC динамически, обязательно вызовите AfxTermExtensionModule , как показано выше. Также обязательно используйте AfxLoadLibrary и AfxFreeLibrary (вместо функций LoadLibrary Win32 и FreeLibrary) если приложение использует несколько потоков или динамически загружает библиотеку DLL расширения MFC. Использование AfxLoadLibrary и AfxFreeLibrary гарантирует, что код запуска и завершения работы, выполняемый при загрузке и выгрузке библиотеки DLL расширения MFC, не повреждает глобальное состояние MFC.

Файл AFXDLLX.H заголовка содержит особые определения для структур, используемых в расширенных DLL MFC, например, определения AFX_EXTENSION_MODULE и CDynLinkLibrary.

Глобальный extensionDLL должен быть объявлен, как показано ниже. В отличие от 16-разрядной версии MFC, вы можете выделять память и вызывать функции MFC, поскольку MFCxx.DLL полностью инициализирован к моменту вызова DllMain.

Совместное использование ресурсов и классов

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

Экспорт ресурсов осуществляется посредством списка ресурсов. В каждом приложении имеется однонаправленный связный список объектов CDynLinkLibrary. При поиске ресурса большинство стандартных реализаций MFC, которые загружают ресурсы, сначала обращаются к текущему модулю ресурсов (AfxGetResourceHandle), а если он не найден, просматривают список объектов CDynLinkLibrary, пытающихся загрузить запрашиваемый ресурс.

Динамическое создание объектов C++ с именем класса C++ аналогично. Механизм десериализации объектов MFC должен иметь все CRuntimeClass объекты, зарегистрированные таким образом, чтобы он смог восстановить путем динамического создания объекта C++ требуемого типа в зависимости от того, что было сохранено ранее.

Если вы хотите, чтобы клиентское приложение использовало классы в DLL расширения MFC DECLARE_SERIAL, вам нужно экспортировать свои классы, чтобы они были видимы клиентскому приложению. Кроме того, это достигается путем обхода списка CDynLinkLibrary.

В примере расширенных концепций MFC DLLHUSK список выглядит примерно так:

head ->   DLLHUSK.EXE   - or - DLLHUSK.EXE
               |                    |
          TESTDLL2.DLL         TESTDLL2.DLL
               |                    |
          TESTDLL1.DLL         TESTDLL1.DLL
               |                    |
               |                    |
           MFC90D.DLL           MFC90.DLL

Запись MFCxx.DLL обычно располагается последней в списке ресурсов и классов. MFCxx.DLL включает все стандартные ресурсы MFC, включая строки запроса для всех стандартных идентификаторов команд. Размещение его в конце списка позволяет библиотекам DLL и самому клиентскому приложению полагаться на общие ресурсы в MFCxx.DLL, вместо того чтобы иметь собственные копии.

Слияние ресурсов и имен классов всех библиотек DLL в пространство имён клиентского приложения имеет недостаток: необходимо внимательно следить за выбором идентификаторов или имен. Эту функцию можно отключить, не экспортируя ресурсы или CDynLinkLibrary объект в клиентское приложение. Пример DLLHUSK управляет пространством имен общего ресурса с помощью нескольких файлов заголовков. Дополнительные советы по использованию общих файлов ресурсов см. в техническом примечание 35 .

Инициализация библиотеки DLL

Как упоминалось выше, обычно требуется создать CDynLinkLibrary объект для экспорта ресурсов и классов в клиентское приложение. Для инициализации библиотеки DLL необходимо предоставить экспортированную точку входа. Минимально, это рутина void, которая не принимает аргументы и ничего не возвращает, но может быть чем угодно, что вам нравится.

Каждое клиентское приложение, которое хочет использовать библиотеку DLL, должно вызывать эту подпрограмму инициализации, если вы используете этот подход. Вы также можете выделить этот объект CDynLinkLibrary в вашем DllMain сразу после вызова AfxInitExtensionModule.

Подпрограмма инициализации должна создать объект CDynLinkLibrary в куче текущего приложения, подключенный к информации о библиотеке расширения DLL MFC. Это можно сделать, определив такую функцию:

extern "C" extern void WINAPI InitXxxDLL()
{
    new CDynLinkLibrary(extensionDLL);
}

Имя подпрограммы InitXxxDLL в этом примере может быть любым нужным. Это не обязательно, но extern "C"это упрощает обслуживание списка экспорта.

Замечание

Если вы используете библиотеку DLL расширения MFC из стандартной библиотеки DLL MFC, необходимо экспортировать эту функцию инициализации. Эта функция должна вызываться из обычной библиотеки DLL MFC, прежде чем использовать все классы или ресурсы библиотеки DLL расширения MFC.

Экспорт записей

Простой способ экспортировать классы и глобальные функции — использовать __declspec(dllimport) и __declspec(dllexport) для каждого класса и каждой функции, которую вы хотите экспортировать. Это гораздо проще, но это менее эффективно, чем именование каждой точки входа в DEF-файле, как описано ниже. Это связано с тем, что у вас меньше контроля над экспортируемыми функциями. И вы не можете экспортировать функции по порядковому номеру. TESTDLL1 и TESTDLL2 используют этот метод для экспорта своих входов.

Более эффективный метод — экспортировать каждую запись, именуя ее в DEF-файле. Этот метод используется MFCxx.DLL. Так как мы экспортируем выборочно из библиотеки DLL, мы должны решить, какие определенные интерфейсы мы хотим экспортировать. Это сложно, так как необходимо указать искаженные имена линкера в форме записей в файле DEF. Не экспортируйте класс C++, если для него не требуется символьная ссылка.

Если вы попытались экспортировать классы C++ с файлом DEF до этого, возможно, потребуется разработать средство для автоматического создания этого списка. Это можно сделать с помощью двухэтапного процесса связывания. Свяжите библиотеку DLL один раз без экспорта и разрешите компоновщику создать файл MAP. Файл MAP содержит список функций, которые следует экспортировать. С помощью некоторых переупорядочений его можно использовать для создания записей EXPORT для файла DEF. Список экспорта для нескольких тысяч библиотек DLL расширений OLE и Database MFC был создан с помощью такого процесса (хотя он не полностью автоматизирован и время от времени требует ручной настройки).

CWinApp и CDynLinkLibrary

Библиотека расширения MFC DLL не имеет собственного производного объекта CWinApp. Вместо этого он должен работать с производным CWinAppобъектом клиентского приложения. Это означает, что клиентское приложение владеет основным насосом сообщений, циклом простоя и т. д.

Если библиотека DLL расширения MFC должна поддерживать дополнительные данные для каждого приложения, можно вывести новый класс из CDynLinkLibrary и создать его в описанной выше подпрограмме InitXxxDLL. При запуске DLL может проверить список объектов текущего CDynLinkLibrary приложения, чтобы найти объект, соответствующий конкретной MFC расширяющей DLL.

Использование ресурсов в реализации библиотеки DLL

Как упоминалось выше, загрузка ресурсов по умолчанию будет просматривать список CDynLinkLibrary объектов, среди которых ищет первый EXE или DLL, содержащий запрошенный ресурс. Все API MFC и весь внутренний код используются AfxFindResourceHandle для обхода списка ресурсов, чтобы найти любой ресурс независимо от того, где он расположен.

Если вы хотите загрузить ресурсы только из определенного места, используйте API AfxGetResourceHandle и AfxSetResourceHandle сохраните старый дескриптор и задайте новый дескриптор. Не забудьте восстановить старый обработчик ресурсов перед возвратом в клиентское приложение. Пример TESTDLL2 использует этот подход для явной загрузки меню.

У обхода списка есть некоторые недостатки: это немного медленнее, при этом требует управления диапазонами идентификаторов ресурсов. Преимущество заключается в том, что клиентское приложение, которое ссылается на несколько библиотек DLL расширения MFC, может использовать любой ресурс, предоставляемый библиотекой DLL, без указания обработчика экземпляра DLL. AfxFindResourceHandle — это API, используемый для анализа списка ресурсов для поиска заданного соответствия. Он принимает имя и тип ресурса, и возвращает дескриптор ресурса, где он сначала находит ресурс или NULL.

Написание приложения, использующего версию DLL

Требования к приложению

Приложение, использующее общую версию MFC, должно соответствовать нескольким основным правилам:

  • Он должен иметь CWinApp объект и следовать стандартным правилам для насоса сообщений.

  • Он должен быть скомпилирован с набором обязательных флагов компилятора (см. ниже).

  • Он должен связаться с библиотеками импорта MFCxx. Задав необходимые флаги компилятора, заголовки MFC определяют во время ссылки, с какой библиотекой приложение должно связаться.

  • Чтобы запустить исполняемый файл, MFCxx.DLL должен находиться в пути или в системном каталоге Windows.

Создание среды разработки

Если вы используете внутренний файл makefile с большинством стандартных значений по умолчанию, вы можете легко изменить проект, чтобы создать версию DLL.

На следующем шаге предполагается, что у вас есть правильно функционирующее приложение MFC, связанное с NAFXCWD.LIB (для отладки) и NAFXCW.LIB (для выпуска), и вы хотите преобразовать его для использования общей версии библиотеки MFC. Вы запускаете среду Visual Studio и имеете внутренний файл проекта.

  1. В меню "Проекты" выберите "Свойства". На странице «Общие» в разделе «Параметры по умолчанию проекта» укажите для Microsoft Foundation Classes следующее: Использовать MFC в общей DLL (MFCxx(d).dll).

Создание с помощью NMAKE

Если вы используете возможность внешнего makefile компилятора или напрямую используете NMAKE, вам потребуется отредактировать makefile для настройки необходимых параметров компилятора и линкера.

Обязательные флаги компилятора:

  • /D_AFXDLL /MD /D_AFXDLL

Стандартные заголовки MFC требуют определения символа _AFXDLL.

  • /MD Приложение должно использовать версию DLL библиотеки времени выполнения языка C.

Все остальные флаги компилятора следуют значениям по умолчанию MFC (например, _DEBUG для отладки).

Измените список библиотек для компоновщика. Измените NAFXCWD.LIB на MFCxxD.LIB, а NAFXCW.LIB на MFCxx.LIB. Замените LIBC.LIB на MSVCRT.LIB. Как и в любой другой библиотеке MFC, важно, чтобы MFCxxD.LIB был размещен перед любыми библиотеками среды выполнения C.

При необходимости добавьте /D_AFXDLL к параметрам компилятора ресурсов для сборки в режиме выпуска и отладки (тот, с которым фактически компилируется ресурсами /R). Этот параметр делает окончательный исполняемый файл меньше, предоставляя общий доступ к ресурсам, которые присутствуют в библиотеках DLL MFC.

После внесения этих изменений требуется полная перестройка.

Создание примеров

Большинство примеров программ MFC можно создавать из Visual C++ или из общего ФАЙЛА MAKEFILE, совместимого с NMAKE, из командной строки.

Чтобы преобразовать любой из этих примеров для использования MFCxx.DLL, можно загрузить MAK-файл в Visual C++ и задать параметры проекта, как описано выше. Если вы используете сборку NMAKE, можно указать AFXDLL=1 в командной строке NMAKE и создать пример с помощью общих библиотек MFC.

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

Заметки о упаковке

Версии выпусков библиотек DLL (MFCxx.DLL и MFCxxU.DLL) являются свободно распространяемыми. Отладочные версии библиотек DLL не являются свободно распространяемыми и должны использоваться только во время разработки приложения.

Отладочные DLL-библиотеки предоставляются с информацией для отладки. С помощью отладчика Visual C++ можно отслеживать выполнение как приложения, так и библиотеки DLL. Библиотеки DLL релизные (MFCxx.DLL и MFCxxU.DLL) не содержат сведений об отладке.

Если вы настраиваете и переустраиваете библиотеки DLL, то им следует давать имена, отличные от "MFCxx". Файл SRC MFCDLL.MAK MFC описывает параметры сборки и содержит логику переименования библиотеки DLL. Переименование файлов необходимо, так как эти библиотеки DLL потенциально совместно используются многими приложениями MFC. Если пользовательская версия библиотек DLL MFC заменяет те, которые установлены в системе, это может нарушить работу другого приложения MFC, использующего общие библиотеки DLL MFC.

Восстановление DLL библиотек MFC не рекомендуется.

Как реализована MFCxx.DLL

В следующем разделе описывается реализация библиотеки DLLMFCxx.DLLMFCxxD.DLL) MFC. Понимание деталей здесь также не важно, если все, что вам нужно, это использовать библиотеку DLL MFC с вашим приложением. Сведения здесь не являются важными для понимания того, как писать библиотеку DLL расширения MFC, но понимание этой реализации может помочь вам написать собственную библиотеку DLL.

Обзор реализации

Библиотека MFC DLL является особым случаем библиотеки расширения MFC DLL, как описано выше. У него много экспортов для множества классов. Есть несколько дополнительных функций в MFC DLL, которые делают их более уникальными, чем обычные MFC DLL расширения.

Win32 выполняет большую часть работы

16-разрядная версия MFC требовала ряда специальных методов, включая данные для каждого приложения в сегменте стека, специальные сегменты, созданные ассемблерным кодом 80x86, контексты исключений для каждого процесса и другие методы. Win32 напрямую поддерживает данные для каждого процесса в библиотеке DLL, что в большинстве случаев именно то, что вам нужно. В основном MFCxx.DLL просто NAFXCW.LIB, упаковывается в библиотеку DLL. Если вы посмотрите на исходный код MFC, вы найдете немного #ifdef _AFXDLL случаев, поскольку нет большого количества особых случаев, которые нужно учитывать. Специальные случаи предназначены для работы с Win32 в Windows 3.1 (иначе говоря, Win32s). Win32s не поддерживает данные DLL для каждого процесса напрямую. Библиотека MFC DLL должна использовать Win32 API хранения локальных данных потока (TLS) для получения данных, локальных для процесса.

Влияние на источники библиотеки, дополнительные файлы

Влияние версии _AFXDLL на обычные источники и заголовки библиотеки классов MFC является сравнительно незначительным. Существует специальный файл версии (AFXV_DLL.H) и дополнительный файл заголовка (AFXDLL_.H), которые включаются главным хедером AFXWIN.H. Заголовок AFXDLL_.H содержит класс CDynLinkLibrary и другие сведения о реализации как приложений _AFXDLL, так и MFC расширений DLL. Заголовок AFXDLLX.H предоставляется для создания библиотек DLL расширения MFC (см. выше).

Обычные исходные файлы библиотеки MFC в каталоге SRC MFC имеют дополнительный условный код под директивой _AFXDLL #ifdef. Дополнительный исходный файл (DLLINIT.CPP) содержит расширенный код инициализации DLL, а также дополнительные компоненты для общей версии MFC.

Чтобы создать общую версию MFC, предоставляются дополнительные файлы. (Дополнительные сведения о сборке библиотеки DLL см. ниже.

  • Два файла DEF используются для экспорта точек входа DLL MFC для отладочной (MFCxxD.DEF) и релизной (MFCxx.DEF) версии DLL.

  • Rc-файл (MFCDLL.RC) содержит все стандартные ресурсы MFC и VERSIONINFO ресурс библиотеки DLL.

  • Файл CLWMFCDLL.CLW () предоставляется для просмотра классов MFC с помощью ClassWizard. Эта функция не является специфичной для DLL версии MFC.

Управление памятью

Приложение, использующее MFCxx.DLL, использует общий выделитель памяти, предоставляемый MSVCRTxx.DLL, общей библиотекой DLL среды выполнения C. Приложение, все библиотеки DLL расширений MFC и библиотеки DLL MFC используют этот общий модуль выделения памяти. Используя общую библиотеку DLL для выделения памяти, библиотеки DLL MFC могут выделять память, которая позже освобождается приложением или наоборот. Так как приложение и библиотека DLL должны использовать один и тот же распределитель, вы не должны переопределять глобальный C++ operator new или operator delete. Те же правила применяются к остальным подпрограммам выделения памяти во время выполнения C (напримерmalloc, , reallocfreeи другим).

Порядковые номера, класс __declspec(dllexport) и именование DLL

Мы не используем функциональные class__declspec(dllexport) возможности компилятора C++. Вместо этого список экспортов включается в источники библиотеки классов (MFCxx.DEF и MFCxxD.DEF). Экспортируются только набор точек входа (функций и данных). Другие символы, такие как функции или классы частной реализации MFC, не экспортируются. Все операции экспорта выполняются порядковым порядком без имени строки в таблице имен резидентов или не резидентов.

Использование class__declspec(dllexport) может быть жизнеспособной альтернативой для создания небольших библиотек DLL, но в большой библиотеке DLL, такой как MFC, механизм экспорта по умолчанию имеет ограничения эффективности и емкости.

Все это означает, что мы можем собирать большое количество функциональности в выпуске MFCxx.DLL, который составляет всего около 800 КБ, без существенного снижения скорости выполнения или загрузки. Если бы этот метод не использовался, MFCxx.DLL было бы на 100 КБ больше. Этот метод позволяет добавить дополнительные точки входа в конце ФАЙЛА DEF. Он позволяет легко создавать версии, без ущерба для эффективности скорости и размера экспорта по порядковым номерам. Изменения основных версий в библиотеке классов MFC изменят имя библиотеки. То есть распространяемая библиотека DLL, MFC30.DLL содержащая версию 3.0 библиотеки классов MFC. Обновление этой библиотеки DLL, например, в гипотетической версии MFC 3.1, вместо этого будет называться MFC31.DLL библиотека DLL. Опять же, если изменить исходный код MFC, чтобы создать пользовательскую версию библиотеки DLL MFC, используйте другое имя (и желательно без MFC в имени).

См. также

Технические примечания по номеру
Технические заметки по категориям