Importaciones mutuas
Actualización: noviembre 2007
La exportación a otro archivo ejecutable (o la importación desde otro archivo ejecutable) presenta complicaciones cuando las importaciones son mutuas (o circulares). Por ejemplo, dos archivos DLL importan símbolos entre sí, de forma similar a las funciones mutuamente recursivas.
El problema de importar mutuamente archivos ejecutables (generalmente archivos DLL) es que ninguno se pueden generar sin generar al otro primero. Cada proceso de generación requiere, como entrada, una biblioteca de importación producida por el otro proceso de generación.
La solución es utilizar la herramienta LIB con la opción /DEF, que produce una biblioteca de importación sin generar el archivo ejecutable. Con esta herramienta, puede generar todas las bibliotecas de importación que necesite, independientemente de cuántos archivos DLL haya implicados y de la complejidad de las dependencias.
La solución general para procesar importaciones mutuas es la siguiente:
Utilice un archivo DLL cada vez. Cualquier orden es válido, aunque algunos órdenes son mejores. Si todas las bibliotecas de importación necesarias existen y están actualizadas, ejecute LINK para generar el archivo ejecutable (DLL). Esto produce una biblioteca de importación. En caso contrario, ejecute LIB para producir una biblioteca de importación.
Si ejecuta LIB con la opción /DEF se producirá un archivo adicional con la extensión .EXP. Este archivo .EXP deberá utilizarse posteriormente para generar el archivo ejecutable.
Después de utilizar LINK o LIB para generar todas las bibliotecas de importación, vuelva para ejecutar LINK a fin de generar los archivos ejecutables que no se generaron en el paso anterior. Tenga en cuenta que el archivo .exp correspondiente debe especificarse en la línea de comandos de LINK.
Si ejecutó antes la herramienta LIB para producir una biblioteca de importación para DLL1, LIB también habrá producido el archivo DLL1.exp. Debe utilizar DLL1.exp como entrada de LINK al generar DLL1.dll.
La ilustración siguiente muestra una solución para dos archivos DLL con importaciones mutuas, DLL1 y DLL2. El primer paso es ejecutar LIB en DLL1, con la opción /DEF establecida. Este primer paso produce DLL1.lib, una biblioteca de importación, y DLL1.exp. En el segundo paso, la biblioteca de importación se utiliza para generar DLL2, que a su vez produce una biblioteca de importación para los símbolos de DLL2. En el tercer paso se genera DLL1, con DLL1.exp y DLL2.lib como entrada. Tenga en cuenta que no es necesario un archivo .exp para DLL2, puesto que no se utilizó LIB para generar la biblioteca de importación de DLL2.
Vincular dos archivos DLL con importaciones mutuas
Limitaciones de _AFXEXT
Puede utilizar el símbolo de preprocesador _AFXEXT para los archivos DLL de extensión siempre que no tenga varios niveles de archivos DLL de extensión. Si tiene archivos DLL de extensión que puede llamar o derivar desde clases de sus propios archivos DLL de extensión, que se derivan de las clases MFC, debe utilizar su propio símbolo de preprocesador para evitar la ambigüedad.
El problema es que en Win32, debe declarar explícitamente todos los datos como __declspec(dllexport) si se van a exportar desde un archivo DLL, y como __declspec(dllimport) si se van a importar desde un archivo DLL. Cuando defina _AFXEXT, los encabezados de MFC garantizarán que AFX_EXT_CLASS esté correctamente definido.
Cuando tenga varios niveles, un símbolo como AFX_EXT_CLASS no será suficiente, puesto que un archivo DLL de extensión puede exportar clases nuevas, así como importar otras clases desde otro archivo DLL de extensión. Para solucionar este problema, utilice un símbolo especial de preprocesador que indique que está generando el archivo DLL, no utilizándolo. Por ejemplo, imagine dos archivos DLL de extensión, A.dll y B.dll. Cada uno de ellos exporta algunas clases en A.h y B.h, respectivamente. B.dll utiliza las clases de A.dll. Los archivos de encabezado presentarían el siguiente aspecto:
/* 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 se genera con /D A_IMPL y B.dll se genera con /D B_IMPL. Si se utilizan símbolos independientes para cada archivo DLL, CExampleB se exporta y CExampleA se importa al generar B.dll. CExampleA se exporta al generar A.dll y se importa al ser utilizada por B.dll (o por algún otro cliente).
Este tipo de organización en niveles no se puede hacer sin utilizar los símbolos de preprocesador integrados AFX_EXT_CLASS y _AFXEXT. La técnica antes descrita soluciona este problema de la misma manera que el mecanismo que utiliza MFC al generar sus archivos DLL de extensión de tecnologías activas, bases de datos y redes.
No se exporta la clase completa
Cuando no se exporte la clase completa, deberá asegurarse de que los elementos de datos necesarios creados por las macros MFC se exportan correctamente. Esto puede realizarse volviendo a definir AFX_DATA para la macro específica de la clase. Debe hacerse siempre que no se exporte toda la clase.
Por ejemplo:
/* 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