2011-02-25 14 views
6

Tengo una DLL que se está cargando en tiempo de ejecución. La DLL se basa en una variable estática para el funcionamiento interno (es un std :: map), esta variable se define dentro de la DLL.Cargando DLL no inicializando clases C++ estáticas

Cuando llamo a la primera función de la DLL después de cargar, obtengo un SegFault de la DLL, el mapa nunca se inicializó. De todo lo que he leído de la carga DLL, la inicialización de datos estáticos y globales debe ocurrir incluso antes de la llamada a DLLMain.

Para probar la inicialización estática agregué una estructura estática que imprime un mensaje, e incluso arrojó un punto de interrupción para una buena medida.

static struct a 
{ 
    a(void) { puts("Constructing\n"); } 
}statica; 

No hubo ningún mensaje o ruptura antes de llamar a DLLMain o a la función.

Aquí está mi código de carga:

dll = LoadLibrary("NetSim"); 
    //Error Handling 

    ChangeReliability = reinterpret_cast<NetSim::ChangeReliability> 
         (GetProcAddress(dll, "ChangeReliability")); 


ChangeReliability(100); 

He comprobado que la versión DLL es la correcta, reconstruido todo el proyecto varias veces, pero no hubo diferencias. Estoy recién de ideas.

+0

¿Hay alguna referencia a su objeto 'statica'? De lo contrario, podría optimizarse. – atzz

Respuesta

8

Cuando vinculó su DLL, ¿se especificó el modificador/ENTRY? Si es así, anulará el valor predeterminado del vinculador, que es DllMainCRTStartup. Como resultado, no se llamará a _CRT_INIT y, a su vez, no se invocarán los inicializadores globales, lo que dará como resultado datos globales (estáticos) no inicializados.

Si está especificando/ENTRADA para su propio punto de entrada, debe llamar a _CRT_INIT durante el proceso de adjuntar y procesar el desenglose.

Si no está especificando/ENTRY, el enlazador debe utilizar el punto de entrada del CRT que llama _CRT_INIT al proceso de adjuntar/separar antes de llamar a su DllMain.

+0

/ENTRY no está especificado. Estaba asumiendo eso de la documentación de Microsoft de DllMain. Trataré de llamarlo explícitamente. –

+1

Bueno, esto parece funcionar. La documentación de Microsoft es muy confusa. Finalizó los siguientes pasos en http://support.microsoft.com/kb/94248 Sección 2, y funcionó. Gracias por la ayuda. –

0

Aunque no estoy seguro de por qué la inicialización falla, una solución alternativa sería crear e inicializar explícitamente las variables en su DllMain. Según Microsoft's best practices de papel, se debe evitar el uso de la función de asignación de CRT en DllMain, así que utiliza las ventanas del montón de funciones de asignación lugar, con un asignador personalizado (nota: el código no probado, pero debe ser más o menos derecha):

template<typename T> struct HeapAllocator 
{ 
    typedef T value_type, *pointer, &reference; 
    typedef const T *const_pointer, &const_reference; 
    typedef size_t size_type; 
    typedef ptrdiff_t difference_type; 

    HeapAllocator() throw() { } 
    HeapAllocator(const HeapAllocator&) throw() { } 
    typedef<typename U> 
    HeapAllocator(const HeapAllocator<U>&) throw() { } 

    pointer address(reference x) const { return &x; } 
    const_pointer address(const_reference x) const { return &x; } 

    pointer allocate(size_type n, HeapAllocator<void>::const_pointer hint = 0) 
    { 
     LPVOID rv = HeapAlloc(GetProcessHeap(), 0, n * sizeof(value_type)); 
     if (!rv) throw std::bad_alloc(); 
     return (pointer)rv; 
    } 

    void deallocate(pointer p, size_type n) 
    { 
     HeapFree(GetProcessHeap(), 0, (LPVOID)p); 
    } 

    size_type max_size() const throw() 
    { 
     // Make a wild guess... 
     return (2 * 1024 * 1024 * 1024)/sizeof(value_type); 
    } 

    void construct(pointer p, const_reference val) 
    { 
     new ((void*)p) T(val); 
    } 

    void destroy(pointer p) 
    { 
     p->~T(); 
    } 
}; 

std::map<foo, HeapAllocator> *myMap; 

BOOL WINAPI DllMain(HANDLE hInst, ULONG ul_reason, LPVOID lpReserved) 
{ 
    switch(ul_reason) { 
     case DLL_PROCESS_ATTACH: 
      myMap = (std::map<foo, HeapAllocator> *)HeapAlloc(GetProcessHeap(), 0, sizeof(*myMap)); 
      if (!myMap) return FALSE; // failed DLL init 

      new ((void*)myMap) std::map<foo, HeapAllocator>; 
      break; 
     case DLL_PROCESS_DETACH: 
      myMap->~map(); 
      HeapFree(GetProcessHeap(), 0, (LPVOID)myMap); 
      break; 
    } 
    return TRUE; 
} 
+3

La asignación de memoria dinámica en DllMain no es segura y debe evitarse. P.ej. vea este artículo de MS: http://go.microsoft.com/FWLink/?LinkId=84138 – atzz

+0

@atzz, actualizado para no usar la asignación de CRT :) – bdonlan

+2

Sí, pero ¿qué ocurre con los objetos almacenados en el mapa? Tienen destructores propios, que serán llamados desde DllMain durante la descarga. Se puede hacer que este enfoque funcione, pero personalmente lo reservaría para casos extremos cuando todo lo demás no es una opción. – atzz

8

Me gustaría señalar que deben evitarse los objetos estáticos complejos en las DLL.

Recuerde que los intializadores estáticos en una DLL se llaman desde DllMain, y por lo tanto todas las restricciones en el código DllMain se aplican a constructores y destructores de objetos estáticos. En particular, std :: map puede asignar memoria dinámica durante la construcción, lo que puede generar resultados impredecibles si el tiempo de ejecución de C++ aún no se ha inicializado (en caso de que esté utilizando el tiempo de ejecución vinculado dinámicamente).

Hay un buen artículo sobre cómo escribir DllMain: Best Practices for Creating DLLs.

Sugeriría cambiar su objeto de mapa estático a un puntero estático (que puede inicializarse con cero de forma segura) y agregar una función exportada por DLL para inicialización o usar inicialización lenta (es decir, verificar el puntero antes de acceder a él y crea el objeto si es nulo).

+0

Votando esto ya que atzz trae un excelente punto. Realmente desea evitar hacer cualquier cosa bajo el bloqueo del cargador e incluso limitarse a acciones que usted sabe que son intrínsecamente seguras (la lista no es larga, eso es seguro). –

+0

Lazy init no sirve de nada: terminarías llamando a las funciones de CRT para liberar la memoria en DllMain() cuando la DLL está descargada – bdonlan

+1

@bdonlan, eso depende de la implementación. La forma en que lo redacté anteriormente, no hay desasignación en absoluto y el objeto se filtrará en la descarga DLL :). Pero estoy de acuerdo con su punto, que usar la inicialización lenta aquí complica la limpieza. – atzz

1

En realidad, es probable que usted está haciendo una suposición errónea:

Cargando, la inicialización de datos estáticos y global debería ocurrir incluso antes de la llamada a DLLMain.

Véase el punto 4 de efectivo de C++:

El orden de inicialización de objetos estáticos no locales se define en unidades de traducción diferentes es indefinido

La razón es que para asegurar un orden de inicialización "correcto" es imposible y, por lo tanto, el estándar de C++ simplemente lo abandona y lo deja como indefinido.

Por lo tanto, si su DllMain está en un archivo diferente al código donde se declara la variable estática, el comportamiento es indefinido y tiene muy buenas posibilidades de llamar a DllMain antes de que la inicialización realmente se realice.

La solución es bastante simple, y se resume en el mismo elemento de C++ efectivo (por cierto, ¡recomiendo leer ese libro!), Y requiere declarar la variable estática dentro de una función, que simplemente la devuelve.

+2

Está confundiendo C++ estándar (que ni siquiera conoce las DLL) y Windows. En Windows, la inicialización estática y global aún ocurre en un orden no especificado, pero ocurre desde dentro de DllMain. – MSalters

+1

El orden de inicialización es específico del compilador. Windows sabe algo sobre la inicialización (¿y cómo podría hacerlo?): Simplemente llama a DllMain con DLL_PROCESS_ATTACH. Es posible que un compilador inicialice todas las variables estáticas antes de DllMain, pero no está garantizado. Al menos, recuerdo que con VC 6.0 (sí, hace mucho tiempo) tuve el mismo problema que el informado, y el culpable finalmente fue una suposición errónea sobre la inicialización estática. –

+1

@Roberto: declarar variables estáticas dentro de las funciones es bastante peligroso porque no es seguro para subprocesos (y por lo tanto puede conducir a la inicialización doble). Las funciones internas de los inicializadores estáticos se deben usar solo para las variables POD simples. – user396672

0

El IMPLEMENTACIÓN "clásico" simple singelton funcionará:

std::map<Key,Value>& GetMap() { 
    static std::map<Key,Value> the Map; 
    return theMap; 
} 

Por supuesto, no debe llamar a esto desde DllMain.

+1

Todavía tiene el problema con la desasignación, que sucederá en DllMain en la descarga de DLL (y puede tener consecuencias divertidas: http://blogs.msdn.com/b/oldnewthing/archive/2010/01/22/9951750.aspx) . – atzz

+0

¿Estás seguro? Eso es sobre secciones críticas. Por lo que yo entiendo, los CRT modernos no se preocupan por la desasignación de memoria durante el cierre. El sistema operativo limpiará de todos modos. – MSalters

+0

En la descarga DLL, se llamará al destructor de TheMap desde DllMain. Este destructor desasignará la memoria dinámica mantenida por el mapa. El montón CRT está protegido con una sección crítica, por lo que los efectos descritos en el enlace pueden ocurrir. Además, los destructores de los objetos 'Key' y' Value' dentro del mapa serán llamados también, y no sabemos si son DllMain-safe o no. – atzz