Загрузка DLL без инициализации статических классов C++

у меня есть DLL, которая загружается во время выполнения. DLL полагается на статическую переменную для внутренних работ (это std::map), эта переменная определена в DLL.

когда я вызываю первую функцию из DLL после загрузки, я получаю SegFault из DLL, карта никогда не была инициализирована. Из всего, что я прочитал из загрузки DLL, статическая и глобальная инициализация данных должна произойти даже до вызова DLLMain.

для проверки статической инициализации I добавлена статическая структура, которая печатает сообщение, и даже бросила точку останова для хорошей меры.

static struct a
{
  a(void) { puts("Constructingn"); }
}statica;

не было никакого сообщения или перерыва перед вызовом DLLMain или функции.

вот мой код загрузки:

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

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


ChangeReliability(100);

Я проверил, что версия dll является правильной, перестроил весь проект несколько раз, но никакой разницы. У меня закончились идеи.

5 ответов


когда вы связали свою DLL, был ли указан параметр / ENTRY? Если это так, он переопределит значение по умолчанию компоновщика DllMainCRTStartup. В результате _CRT_INIT не будет вызываться и, в свою очередь, глобальные инициализаторы не будут вызываться, что приведет к неинициализированным глобальным (статическим) данным.

Если вы указываете /ENTRY для вашей собственной точки входа, вам нужно вызвать _CRT_INIT во время присоединения процесса и отсоединения процесса.

Если вы не указываете /запись, компоновщик следует использовать точку входа CRT, которая вызывает _CRT_INIT в процессе attach / detach перед вызовом в ваш DllMain.


Я хотел бы отметить, что сложных статических объектов в DLL следует избегать.

помните, что статические инициализаторы в DLL вызываются из DllMain, и поэтому все ограничения на код DllMain применяются к конструкторам и деструкторам статических объектов. В частности, std:: map может выделять динамическую память во время построения, что может привести к непредсказуемым результатам, если среда выполнения c++ еще не инициализирована (если вы используете динамически связанную среду выполнения).

там хорошая статья о написании DllMain:рекомендации по созданию DLL.

Я бы предложил изменить статический объект карты на статический указатель (который может быть безопасно инициализирован нулем) и либо добавить отдельную функцию DLL-экспорта для инициализации, либо использовать ленивую инициализацию (т. е. проверить указатель перед доступом к нему и создать объект, если он равен нулю).


на самом деле, скорее всего, вы делаете неверное предположение:

загрузка, статическая и глобальная инициализация данных должна произойти даже до вызова DLLMain.

см. пункт 4 эффективного C++:

порядок инициализации нелокальные статические объекты, определенные в различные единицы перевода неопределено

причина в том, что обеспечение "правильного" порядка инициализации невозможно, и поэтому стандарт C++ просто отказывается от него и просто оставляет это неопределенным.

Итак, если ваш DllMain находится в другом файле, чем код, где объявлена статическая переменная, поведение не определено, и у вас есть очень хорошие шансы получить DllMain вызывается до инициализации на самом деле сделано.

решение довольно простое и изложено в том же пункте эффективного C++ (кстати: я настоятельно рекомендую вам прочитать эту книгу!), и требует объявления статическая переменная внутри функции, которая просто возвращает ее.


" классический " простой singelton implemention будет работать:

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

конечно, вы не должны называть это от DllMain.


хотя я не уверен, почему инициализация не удается, одним из обходных путей было бы явно создать и инициализировать переменные в вашем DllMain. Согласно рекомендации Microsoft бумага, вы должны избегать использования функции распределения CRT в DllMain, поэтому я использую функции распределения кучи windows вместо этого с пользовательским распределителем (Примечание: непроверенный код, но должен быть более или менее правильным):

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;
}