Загрузка DLL не инициализирует статические классы C ++ - PullRequest
7 голосов
/ 25 февраля 2011

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

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

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

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

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

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

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

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


ChangeReliability(100);

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

Ответы [ 5 ]

9 голосов
/ 25 февраля 2011

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

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

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

8 голосов
/ 25 февраля 2011

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

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

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

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

1 голос
/ 25 февраля 2011

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

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

См. Пункт 4 Эффективного C ++:

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

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

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

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

0 голосов
/ 25 февраля 2011

Будет работать «классическая» простая реализация Singelton:

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

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

0 голосов
/ 25 февраля 2011

Хотя я не уверен, почему инициализация не удалась, одним из обходных путей было бы явное создание и инициализация переменных в вашем 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;
}
...