Загрузка 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;
}