TN033. Версия библиотеки DLL MFC

В этом примечании описывается, как использовать MFCxx.DLLMFCxxD.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 с несколькими дополнительными параметрами компилятора и компоновщика.

  • Библиотека DLL расширения MFC не имеет производный 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 и предоставьте отладочную и выпускную/release () версию библиотеки DLL расширения MFC. Эта практика позволяет клиентским программам создавать версии отладки и выпуска приложений и связывать их с соответствующей версией отладки или выпуска всех библиотек DLL.

Примечание.

Так как проблемы с изменением и экспортом имен C++ список экспорта из библиотеки DLL расширения MFC может отличаться от версий отладки и выпуска одной библиотеки DLL и DLL для разных платформ. MFCxx.DLL Выпуск имеет около 2000 экспортированных точек входа. Отладка MFCxxD.DLL имеет около 3000 экспортированных точек входа.

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

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

MFCxx.DLL и все библиотеки DLL расширения MFC, загруженные в адресное пространство клиентского приложения, будут использовать тот же распределитель памяти, загрузку ресурсов и другие состояния MFC "global", как если бы они находились в одном приложении. Это важно, так как библиотеки 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 в флаги компилятора. В диалоговом окне "Свойства проекта" выберите категорию препроцессора C/C++>. Добавьте _AFXEXT в поле "Определение макросов" , разделяя каждый из элементов точкой с запятой.

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

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

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

Обычной целью библиотеки 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 символ предварительного процессора для библиотек DLL расширения MFC, если у вас нет нескольких уровней БИБЛИОТЕК DLL расширения MFC. Если у вас есть библиотеки 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 вместо использования библиотеки DLL. Например, представьте, что два библиотеки DLL расширения MFC и A.DLLB.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 использует этот метод при создании библиотек DLL расширения OLE, Database и Network MFC.

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

Опять же, вам придется заботиться о том, что вы не экспортируете весь класс. Убедитесь, что необходимые элементы данных, созданные макросами 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 вызовом FreeLibrary ) из библиотеки DLL расширения MFC. Так как большинство библиотек 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 DECLARE_SERIALрасширения MFC, необходимо экспортировать классы, чтобы сделать их видимыми для клиентского приложения. Это также сделано путем прогулки по списку CDynLinkLibrary .

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

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, должно вызывать эту подпрограмму инициализации, если вы используете этот подход. Этот объект DllMain также можно выделить CDynLinkLibrary сразу после вызова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. Список экспорта для MFCxx.DLL библиотек DLL расширений OLE и Database MFC, несколько тысяч, был создан с таким процессом (хотя это не полностью автоматически и требуется некоторая настройка рук каждый раз в некоторое время).

CWinApp и CDynLinkLibrary

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

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

Использование ресурсов в реализации БИБЛИОТЕКИ 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 для использования 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.

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

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

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

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

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

_AFXDLL Влияние версии на обычные источники и заголовки библиотеки классов MFC является относительно незначительным. Существует специальный файл версии () и дополнительный файл заголовка (AFXV_DLL.HAFXDLL_.H), включенный в основной AFXWIN.H заголовок. Заголовок AFXDLL_.H содержит CDynLinkLibrary класс и другие сведения о реализации библиотек DLL для приложений и расширений _AFXDLL MFC. Заголовок 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 должны использовать один и тот же распределитель, вы не должны переопределить глобальный или operator deleteглобальный operator new C++. Те же правила применяются к остальным подпрограммам выделения памяти во время выполнения 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 в имени).

См. также

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