Versión corta: si desarrollas una biblioteca dinámica para KDE, debes marcar tus clases con la macro KNOMBREBIBLIOTECA_EXPORT (de forma que su definición quede como “class KNOMBREBIBLIOTECA_EXPORT nombreClase: public claseBase {…“), y definir esa macro en un archivo de cabecera knombrebiblioteca_export.h (los nombres no tienen por qué ser necesariamente asÃ, pero por seguir la práctica común) con un contenido adaptado a la biblioteca en cuestión. Véase como ejemplo cualquiera de los archivos include/kloquesea_export.h de KDE 4, asà como los archivos de cabecera de las clases que los usan.
Para quien quiera aburrirse con la versión larga y detallada de mis aventuras y desventuras hasta llegar a tan sencilla conclusión, a continuación viene la versión larga.
“El miedo es el camino hacia el Lado Oscuro. El miedo lleva a la ira, la ira lleva al odio, el odio lleva al sufrimiento. Veo mucho miedo en ti.” Con estas palabras el maestro Yoda expresaba su preocupación ante el entrenamiento en los caminos de la Fuerza del joven Anakin Skywalker. Pues bien, si hoy el maestro Yoda me hubiese examinado a mÃ, miedo no habrÃa encontrado. Ahora que de ira, odio y sufrimiento iba servido.
Este cúmulo de sensaciones tan poco agradables eran debidas al cansancio y desesperación acumuladas durante las ¿5? horas que habÃa pasado intentando averiguar por qué cuando convertà la biblioteca KTutorial de estática a dinámica el enlazado desde una aplicación de prueba fallaba (una larga retahÃla de “undefined reference to `vtable for NombreClase::metodo()‘” en los lugares de la aplicación de prueba que se usaba la biblioteca).
Pero, ¿por qué? Inicialmente pensé que tal vez habrÃa que hacer algo más que añadir la opción SHARED en el objetivo para crear la biblioteca (“kde4_add_library(ktutorial SHARED ${ktutorial_LIB_SRCS})“) y por ese motivo fallaba. Sin embargo, el amigo Google no me devolvÃa nada concluyente a este respecto. Según parecÃa, con añadir esa opción ya era suficiente para crear la biblioteca.
Asà que busqué en las kdelibs algún archivo de CMake que generase una biblioteca dinámica, para ver si habÃa algo que se me escapase. La mayorÃa eran archivos “grandes”, aunque buscando, llegué al archivo de CMake para kdelibs/kde3support/kunittest. Generaba una biblioteca y un ejecutable que enlazaba con ésta, era pequeño y fácil de entender. ¡Excelente!
O no tanto. Porque resulta que el archivo de CMake tenÃa lo mismo que los que utilizaba yo. Tras varias pruebas, entre ellas aislar esa biblioteca y generarla de 0 (por si acaso CMake utilizaba objetivos heredados de los directorios padre) y ver que funcionaba, supuse que el problema no estarÃa en los objetivos de CMake. Pero, ¿dónde entonces?
Pensé que el problema podÃa residir en que la biblioteca dinámica enlazaba con una biblioteca estática temporal. Esta biblioteca estática simplemente contiene las clases de la vista, que están separadas del modelo en un subdirectorio. Me parecÃa extraño que fuese el problema, ya que anteriormente habÃa trabajado con un esquema similar, aunque en una biblioteca que no era de KDE, y no presentaba problemas. Pero como no sabÃa dónde buscar, intenté por ese lado. Y como era de esperar… seguÃa sin funcionar.
Puestos a dar palos de ciego, decidà enlazar la biblioteca no con una aplicación de verdad, sino con la mÃnima expresión de un programa de C++, a ver qué pasaba. Asà que hice simplemente un método main que crease un objeto de una clase de la biblioteca. Enlacé y… ¡funcionó! Por probar, cree un objeto de otra clase, y enlacé de nuevo. Y esta vez, fallaba al enlazar con el nuevo objeto. Ni que decir tiene que miraba incrédulo a la pantalla, esperando que mi querido ordenador sacase un diálogo llamándome inocente y diciéndome que ya paraba con la broma.
Pero en vista de que eso no ocurrÃa, decidà mirar dónde estaba la diferencia entre ambas clases, para que una enlazase y la otra no. Y la diferencia era que la que enlazaba era una clase normal, mientras que la que no, era derivada de QObject. Me encanta Qt, adoro todas sus caracterÃsticas, me emociono sólo de pensar en señales y ranuras. Pero en ese instante quise que David el gnomo acabase con todos los trolls (por si alguien no sabe a qué me refiero, Qt es obra de Trolltech, a cuyos empleados se les conoce como “los trolls”. SÃ, es el peor intento de chiste que leÃste en mucho tiempo, lo sé. Pero qué quieres, estoy cansado
).
Con todo esto, decidà volver sobre el código de kunittest. El sistema de construcción era igual al que yo usaba, por lo que la diferencia tenÃa que estar en el código de las clases. Lo primero que advertà era que dichas clases incluÃan en los cpp los archivos moc generados por la herramienta de Qt para hacer su magia.
No me constaba que en KDE 4 fuese necesario (aunque, hasta donde sé, con KDE 3 sÃ), pero dado que a esas alturas si alguien me decÃa que mi problema se solucionarÃa dando tres vueltas sobre mi mismo y diciendo “Oh todopoderoso enlazador yo te invoco” en arameo antes de construir la biblioteca yo lo intentarÃa, pues probé incluyendo los archivos moc.
Jamás adivinarÃais qué pasó. Exacto, siguió sin funcionar. A punto de subirme a la silla gritando “Je suis Napoléon!”, vi algo que me llamó la antención en un archivo .h de kunittest: “class KUNITTEST_EXPORT Runner : public QObject“. TenÃa que ser eso. Miré qué valor tenÃa la macro, y cuando se construÃa la biblioteca éste era “KDE_EXPORT“. “Eso suena a exportar sÃmbolos de la biblioteca”, pensé.
Asà que hice una prueba rápida y añadà dicha macro en la clase que utilizaba en mi main simplificado y que no enlazaba. Y un coro celestial bajó a arroparme entre cánticos de júbilo y felicidad. ¡¡¡AL FIN!!!
Tras esto, hice los cambios necesarios en la biblioteca para que todas las clases derivadas directa o indirectamente de QObject utilizasen dicha macro, y todo funcionó como era de esperar
Realmente no estoy seguro de la explicación técnica de por qué ocurre esto. Mis conocimientos sobre enlazado de C++ en Linux no llegan a tanto, y tampoco tengo tiempo de ponerme a investigarlo. Pero sà se la regla de oro: si haces una biblioteca dinámica en KDE, tienes que marcar tus clases públicas para que se pueda enlazar con ellas. Para ver cómo deben marcarse, a la versión corta del artÃculo me remito
Una vez arreglado esto, a seguir con los objetivos de instalación de CMake para poder usar la internacionalización.
Y tras este artÃculo es cuando el anterior pasa a ser claramente anacrónico, ya que no pude dedicarme la tarde entera a investigar lo narrado en él, cuando en éste se ve cómo estuve haciendo otra cosa distinta