Взаимный импорт
Операции экспорта из другого исполняемого файла и импорта в него могут быть сопряжены с трудностями, если импорт осуществляется на взаимной (циклической) основе. Например, между двумя библиотеками DLL может быть организован двусторонний процесс импорта символов по аналогии со взаимно рекурсивными функциями.
Проблема взаимного импорта исполняемых файлов (как правило, это библиотеки DLL) заключается в том, что построение каждого из них возможно только после построения второго файла. Каждый процесс построения в этом случае будет использовать в качестве входных данных библиотеку импорта, создаваемую в рамках другого процесса построения.
Решение в этой ситуации заключается в использовании служебной программы LIB с параметром /DEF, который позволяет создавать библиотеку импорта без построения исполняемого файла. С помощью этой программы можно построить все необходимые библиотеки импорта независимо от того, сколько библиотек DLL участвует в этом процессе и насколько сложны зависимости между ними.
Общее решение для ситуаций взаимного импорта выглядит так:
Берите поочередно каждую библиотеку DLL. (Любой заказ возможен, хотя некоторые заказы являются более оптимальными.) Если существуют все необходимые библиотеки импорта и являются текущими, выполните LINK, чтобы создать исполняемый файл (DLL). Таким образом создается библиотека импорта. Также можно запустить программу LIB, чтобы создать библиотеку импорта.
При запуске программы LIB с параметром /DEF создается дополнительный файл с расширением EXP. Этот EXP-файл будет использоваться впоследствии для построения исполняемого файла.
После построения всех библиотек импорта с помощью программ LINK или LIB запустите программу LINK для построения исполняемых файлов, которые не создавались на предыдущем шаге. Обратите внимание, что в строке LINK необходимо указать соответствующий EXP-файл.
Если до этого вы создали библиотеку импорта для DLL1 с помощью программы LIB, одновременно с этим также должен был быть создан файл DLL1.exp. Для построения файла DLL1.dll в качестве входных данных для программы LINK необходимо указывать этот файл DLL1.exp.
На следующем рисунке показано решение для двух библиотек (DLL1 и DLL2), осуществляющих взаимный импорт. Шаг 1. Запустите программу LIB с параметром /DEF для DLL1. Шаг 1 создает dll1.lib, библиотеку импорта и DLL1.exp. На шаге 2 библиотека импорта используется для сборки DLL2, которая, в свою очередь, создает библиотеку импорта для символов DLL2. Шаг 3. Постройте библиотеку DLL1, используя в качестве входных данных файлы DLL1.exp и DLL2.lib. Обратите внимание, что EXP-файл для библиотеки DLL2 не требуется, поскольку построение библиотеки импорта DLL2 осуществляется без использования программы LIB.
Компоновка двух библиотек DLL с использованием взаимного импорта
Ограничения для символа _AFXEXT
Символ препроцессора _AFXEXT
можно использовать в библиотеках 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, а не используете ее. Рассмотрим пример с двумя библиотеками 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 будет осуществляться с использованием /D A_IMPL
, а для B.dll будет использоваться /D B_IMPL
. С использованием отдельных символов для каждой библиотеки DLL CExampleB
экспортируется, а CExampleA
импортируется при построении B.dll. CExampleA
экспортируется при построении A.dll и импортируется при использовании в B.dll (или другом клиенте).
Такую многоуровневую организацию невозможно реализовать с помощью встроенных символов препроцессора AFX_EXT_CLASS и _AFXEXT
. Описываемый выше подход позволяет решить проблему способом, отличным от механизма, применяемого в MFC при построении библиотек DLL для расширений активных технологий, баз данных и сети 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