Antecedentes:¿Por qué este código dinámico de carga de la biblioteca funciona con gcc?
me he encontrado con la difícil tarea de portar una aplicación de GNU/Linux C++ a Windows. Una de las cosas que hace esta aplicación es buscar bibliotecas compartidas en rutas específicas y luego carga las clases de ellas dinámicamente usando las llamadas posix dlopen() y dlsym(). Tenemos una muy buena razón para cargar de esta manera que no voy a entrar aquí.
El problema:
Para descubrir dinámicamente símbolos generados por un compilador C++ con dlsym() o GetProcAddress() que debe ser unmangled utilizando un extern "C" bloque de ligamiento. Por ejemplo:
#include <list>
#include <string>
using std::list;
using std::string;
extern "C" {
list<string> get_list()
{
list<string> myList;
myList.push_back("list object");
return myList;
}
}
Este código es perfectamente válida C++ y compila y se ejecuta en numerosos compiladores en Linux y Windows. Sin embargo, no compila con MSVC porque "el tipo de devolución no es válido C". La solución que hemos llegado con es cambiar la función para devolver un puntero a la lista en lugar del objeto de lista:
#include <list>
#include <string>
using std::list;
using std::string;
extern "C" {
list<string>* get_list()
{
list<string>* myList = new list<string>();
myList->push_back("ptr to list");
return myList;
}
}
que he estado tratando de encontrar una solución óptima para el cargador de GNU/Linux que funcionará tanto con las nuevas funciones como con el prototipo de la antigua función heredada o, al menos, detectará cuándo se encuentra la función en desuso y emitirá una advertencia. Sería indecoroso para nuestros usuarios si el código solo fallase cuando intentaron usar una biblioteca anterior. Mi idea original fue establecer un manejador de señal SIGSEGV durante la llamada a get_list (sé que esto es asqueroso - estoy abierto a mejores ideas). Así que solo para confirmar que al cargar una biblioteca antigua se segfault donde pensé que correría una biblioteca usando el viejo prototipo de función (devolviendo un objeto de lista) a través del nuevo código de carga (que espera un puntero a una lista) y para mi sorpresa solo funcionó. La pregunta que tengo es ¿por qué?
El siguiente código de carga funciona con los dos prototipos de función enumerados anteriormente. Confirmé que funciona en Fedora 12, RedHat 5.5 y RedHawk 5.1 utilizando las versiones 4.1.2 y 4.4.4 de gcc. Compile las bibliotecas usando g ++ con -shared y -fPIC y el ejecutable debe estar vinculado contra dl (-ldl).
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <list>
#include <string>
using std::list;
using std::string;
int main(int argc, char **argv)
{
void *handle;
list<string>* (*getList)(void);
char *error;
handle = dlopen("library path", RTLD_LAZY);
if (!handle)
{
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
dlerror();
*(void **) (&getList) = dlsym(handle, "get_list");
if ((error = dlerror()) != NULL)
{
printf("%s\n", error);
exit(EXIT_FAILURE);
}
list<string>* libList = (*getList)();
for(list<string>::iterator iter = libList->begin();
iter != libList->end(); iter++)
{
printf("\t%s\n", iter->c_str());
}
dlclose(handle);
exit(EXIT_SUCCESS);
}
Porque tienes suerte. Sospecho que si probaras este tipo de cosas con un programa más complicado, comenzarías a ver los efectos de una pila rota o similar. – aschepler
El código que publiqué está simplificado. La aplicación real tiene alrededor de 100k líneas de código y he ejecutado algunos casos de prueba bastante extensos que parecen funcionar. Aunque estoy de acuerdo, esto no debería funcionar a menos que exista alguna peculiaridad con GCC en este caso. – bckohan
No estoy seguro si 'they must is unmangled' es cierto. Si le pides a dlsym() el nombre destrozado, no lo encontrará correctamente. –