Взаимный импорт
Обновлен: Ноябрь 2007
Экспорт и импорт из другого исполняемого файла представляет затруднения, если импорт является взаимным (или циклическим). Например, две библиотеки DLL могут импортировать символы друг у друга, подобно взаимно рекурсивным функциям.
Проблема с исполняемыми файлами (обычно библиотеками DLL), использующими взаимный импорт, заключается в том, что ни один из этих файлов нельзя построить, пока не построен другой. Каждый процесс построения требует в качестве входного параметра библиотеку импорта, которая создается другим процессом построения.
Решение заключается в использовании программы LIB с параметром /DEF, который создает библиотеку импорта без построения исполняемого файла. С помощью этой утилиты можно построить все необходимые библиотеки импорта вне зависимости от того, сколько библиотек DLL задействовано и насколько сложны их взаимосвязи.
Общее решение для разрешения взаимного импорта:
Обрабатывайте библиотеки DLL последовательно (допустим любой порядок, хотя некоторые последовательности более выгодны). Если все необходимые библиотеки импорта существуют и не являются устаревшими, запустите программу LINK для построения исполняемого файла (DLL). В результате будет создана библиотека импорта. В противном случае запустите программу LIB для создания библиотеки импорта.
Запуск программы LIB с параметром /DEF приведет к созданию дополнительного файла с расширением EXP. В дальнейшем EXP-файл будет использоваться для построения исполняемого файла.
После использования одной из программ (LINK или LIB) для построения всех библиотек импорта вернитесь и выполните программу LINK для построения всех исполняемых файлов, которые не были построены на предыдущем шаге. Обратите внимание, что соответствующий EXP-файл должен быть указан в командной строке программы LINK.
Если ранее утилита LIB уже запускалась для создания библиотеки импорта для DLL1, она также должна была создать файл DLL1.exp. Когда выполняется построение DLL1.dll, необходимо использовать DLL1.exp в качестве входного файла программы LINK.
На приведенном ниже рисунке показано решение задачи взаимного импорта для двух библиотек — DLL1 и DLL2. На первом шаге для библиотеки DLL1 запускается программа LIB с параметром /DEF. На этом шаге создается библиотека импорта DLL1.lib и файл DLL1.exp. На втором шаге эта библиотека импорта используется для построения DLL2; при построении также создается библиотека импорта для символов DLL2. На третьем шаге выполняется построение DLL1 с использованием файлов DLL1.exp и DLL2.lib в качестве входных параметров. Обратите внимание, что файл EXP для DLL2 не нужен, потому что утилита LIB не использовала библиотеку импорта для построения DLL2.
Компоновка двух библиотек DLL, использующих взаимный импорт
Ограничения директивы _AFXEXT
Можно использовать для библиотек расширения директиву препроцессора _AFXEXT, если не возникает необходимость в нескольких уровнях библиотек расширения. Если существующие библиотеки расширения вызывают методы или являются производными от классов, находящихся в других библиотеках расширения, которые, в свою очередь, являются производными от классов MFC, то в таком случае необходимо использовать особую директиву препроцессора, чтобы избежать неоднозначности.
Проблема заключается в том, что в Win32 требуется явно объявлять все данные как __declspec(dllexport), если они должны экспортироваться из данной DLL, и как __declspec(dllimport), если они должны импортироваться из другой DLL. Когда объявляется директива _AFXEXT, заголовочные классы MFC обеспечивают корректность объявления директивы AFX_EXT_CLASS.
Если существует несколько уровней библиотек расширения, то одиночного символа AFX_EXT_CLASS уже недостаточно, потому что библиотека расширения может экспортировать новые классы, а также импортировать другие классы из другой библиотеки расширения. Для решения этой проблемы необходимо использовать специальную директиву препроцессора, которая укажет, что выполняется окончательное построение DLL, а не построение с целью создания библиотеки импорта этой DLL. Пусть, например, существует две библиотеки расширения — 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. Когда строится B.dll, с помощью использования отдельных символов для каждой библиотеки класс CExampleB экспортируется, а класс CExampleA импортируется. Класс CExampleA экспортируется, когда строится A.dll, и импортируется, когда он используется библиотекой B.dll (или другим клиентом).
Такой тип архитектуры не может быть реализован при использовании встроенных директив препроцессора AFX_EXT_CLASS и _AFXEXT. Вышеописанная техника решает эту проблему способом, отличным от того, который используется классами MFC при построении библиотек расширения для Active-технологий, технологий сетевого взаимодействия и работы с базами данных.
Частичный экспорт класса
Если класс экспортируется полностью, необходимо гарантировать, что необходимые элементы данных, создаваемые макросами 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