Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Force loading of dll

This thread is locked; no one can reply to it. rss feed Print
Force loading of dll
Edgar Reynaldo
Major Reynaldo
May 2007
avatar

I'm having some problems with dll load order.

Apparently it seems gcc produces code that delays loading of dlls until they are actually used or referenced.

Consider the following code :

#include "Eagle/backends/Allegro5Backend.hpp"

int main() {
EAGLE_ASSERT(ALLEGRO5_REGISTERED);
EagleSystem* sys = Eagle::EagleLibrary::System("Allegro5"); return 0; }

It works until you comment the highlighted line. Then it throws an error saying the creation function for an "Allegro5" system hasn't been registered.

In Allegro5System.hpp I have an extern reference to ALLEGRO5_REGISTERED.
In Allegro5System.cpp I define that reference :

Allegro5System.cpp#SelectExpand
257int Allegro5System::RegisterSystem() { 258 return Eagle::EagleLibrary::RegisterSystemCreator("Allegro5" , Allegro5System::CreateAllegro5System); 259} 260 261 262 263const int ALLEGRO5_REGISTERED = Allegro5System::RegisterSystem();

So basically, the line that defines ALLEGRO5_REGISTERED doesn't get called until the allegro 5 backend dll is loaded. And the dll doesn't load unless you reference something in it.

Is there a way to force gcc to load the dll for the backend on startup?

GullRaDriel
Member #3,861
September 2003
avatar

Make it a statically linked library. Problem solved.
8-)

Or you can just use a dummy func that you call at the beginning of your main to force the dll to be loaded ?

Or better, load yourself the dll with dlopen equivalent like on unix/linux. See here for various and cross platform dll loading.

AFAIK there is no other way.

"Code is like shit - it only smells if it is not yours"
Allegro Wiki, full of examples and articles !!

Peter Hull
Member #1,136
March 2001

It ought to work. As far as I know, delay-loading DLLs is an opt-in via linker options (which I assume you haven't done!)

Is it possible that the code in Allegro5System runs, but before whatever implements Eagle::EagleLibrary::RegisterSystemCreator is initialized, so that the registration is subsequently overwritten (does that make sense?)

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Gully, I can link statically if I want, but I don't want to force that on my users. ;) The user shouldn't need to do anything to make it work.

Peter, the library is created on demand. It's a singleton. Allegro5System.cpp is part of the A5 backend DLL.

Lib.cpp#SelectExpand
61EagleLibrary* EagleLibrary::Eagle() { 62 if (create) { 63 eagle_lib = new EagleLibrary(); 64 create = 0; 65 destroy = 1; 66 } 67 return eagle_lib; 68} 69 70 71 72int EagleLibrary::RegisterSystemCreator(std::string driver , SYS_CREATION_FUNC sys_create_func) { 73 Eagle()->sys_creation_map[driver] = sys_create_func; 74 return 1; 75}

If it was destroyed and created again that would explain it, but I would know if that was the case. Simply referencing a variable in a DLL changes the behavior of the program.

I set a breakpoint in RegisterSystemCreator, and it isn't even called if I don't include the line about EAGLE_ASSERT(ALLEGRO5_REGISTERED).

GullRaDriel
Member #3,861
September 2003
avatar

"The user shouldn't need to do anything to make it work"
That's commonly the case when static linking, as opposite as dynamic linking which can be messed up by the users path configurations, old dll versions and all.

I wasn't kidding that much.

Edit: and forcibly loading the dlls as referenced by the wiki link can be a solution.

"Code is like shit - it only smells if it is not yours"
Allegro Wiki, full of examples and articles !!

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Gully, assuming the dlls are on the path and that there are no versioning problems, everything should still work exactly the same.

Static linking isn't an option either, as symbols are usually stripped from the executable, and ALLEGRO5_REGISTERED would be removed from the exe. I tried it, it crashes in the same place. RegisterSystemCreator hasn't run.

Peter,
I most certainly did NOT opt-in to delayed dll loading. I know for a fact that it is doing it though, because when I run my program through GDB and try to set a breakpoint in RegisterSystemCreator it never runs, static linking or not. When I reference ALLEGRO5_REGISTERED though, it must be forced to load the dll.

GullRaDriel
Member #3,861
September 2003
avatar

I would tend to say it's a C++ specific bug. Have you tried to use a different c++ compiler ?

Or maybe (so I would finally use the gui) swap your entire Eagle library to C ? ;-p

"Code is like shit - it only smells if it is not yours"
Allegro Wiki, full of examples and articles !!

Oscar Giner
Member #2,207
April 2002
avatar

If I get how everything works right, I see something wrong in your code, although I'm not sure if it's the cause of your specific problem:

You call Allegro5System::RegisterSystem() for initializing a global variable. At this point, EagleLibrary's static member variables (create, eagle_lib, destroy) may not have been initialized yet (the order at which global stuff in different transaction units is initialized is undefined, and ALLEGRO5_REGISTERED and EagleLibrary are in different cpp files), so when this function calls EagleLibrary::Eagle(), the 'create' member variable may still be unitialized and most probably not 0 so the condition fails and eagle_lib points to a random chunk of memory. When you try to access it, it crashes.

The call to EAGLE_ASSERT may make the compiler change the order in which everything is initialized so it works (you're lucky). I'd say in general it's better to just never call static member functions when initializing globals because it can cause a lot of headaches.

You should never ever have this kind of dependency on initializing order of global variables (and remember: static member functions are also global variables, they're not specifically initialized before or after non member function globals). You can only be sure that variables in the same transaction unit will be initialized in the order they're defined.

Another thing, while your way is not wrong, you don't really need your create and destroy variables. Just initialize eagle_lib to null. Check if it's null in your Eagle() function instead. In the function that should destroy it also set it back to null (you don't even need to check if it's already null, since delete null is a valid operation that does nothing).

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Oscar,

Thank you for helping me analyze this. If I understand correctly, the load order should be okay. There's a main eagle dll, which has the EagleLibrary class, and the create and destroy variables. Then there's a second dll for the allegro 5 backend, which has the Allegro5System class.

What I'm trying to do is automatically register the allegro 5 system creation function, so that if a user tries to call System("Allegro5") everything will just work.

I admit, the global initialization order can be hard to follow, but I thought I did it right. The A5 backend dll should depend on the Eagle dll, and so things in Lib.cpp should be initialized before the backend is loaded if I'm correct about how the dlls get loaded. The problem is they're not loaded til they're needed, which causes initialization order problems.

How should I solve this? The EagleSystem class doesn't know anything about the backends like Allegro5System, so at some point I need to tell it how to create an Allegro5System. This is why I was registering the creation function with the line :
ALLEGRO5REGISTERED = Allegro5System::RegisterSystem();

The only real restriction is that there should only ever be one instance of an Allegro5System. There could also be an instance of SDL2System or whatever other driver is available, but only one. What's the best way to design a singleton like this? And still have it available through my Library class?

You call Allegro5System::RegisterSystem() for initializing a global variable. At this point, EagleLibrary's static member variables (create, eagle_lib, destroy) may not have been initialized yet (the order at which global stuff in different transaction units is initialized is undefined, and ALLEGRO5_REGISTERED and EagleLibrary are in different cpp files), so when this function calls EagleLibrary::Eagle(), the 'create' member variable may still be unitialized and most probably not 0 so the condition fails and eagle_lib points to a random chunk of memory. When you try to access it, it crashes.

I removed the 'create' and 'destroy' variables. Now the only way to access the EagleLibrary is through the 'Eagle()' function, which gets and or creates the EagleLibrary object.

When RegisterSystemCreator is called, it calls Eagle(), so the library has to be created before anything can be registered.

The real problem is that the backend dll isn't being loaded until referenced, which means the Allegro5System driver isn't getting registered before attempting to retrieve it.

Kitty Cat
Member #2,815
October 2002
avatar

const int ALLEGRO5_REGISTERED = Allegro5System::RegisterSystem();

If ALLEGRO5_REGISTERED isn't actually referenced anywhere, the compiler/linker can remove it since it's unused. As a result, Allegro5System::RegisterSystem() would never be called to initialize the variable since it doesn't exist anymore. With the EAGLE_ASSERT line, the variable is referenced and kept, so it's correctly initialized, but when uncommented, it's unused and removed, taking all initialization side-effects with it.

--
"Do not meddle in the affairs of cats, for they are subtle and will pee on your computer." -- Bruce Graham

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Kitty Cat
Member #2,815
October 2002
avatar

On Windows, declare a DllMain function, and it will automatically be called when the DLL it's in is loaded into a process, is about to be unloaded from a process, or when a thread is started/ended in a process (do a search for it to see what parameters it takes).

Otherwise with GCC or Clang, you can mark a plain old static function with __attribute__((constructor)) and it will be called automatically when loaded (similarly __attribute__((destructor)) for when unloading).

--
"Do not meddle in the affairs of cats, for they are subtle and will pee on your computer." -- Bruce Graham

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Kitty Cat
Member #2,815
October 2002
avatar

For static-linking, GCC and Clang's attribute should still work. For MSVC, you need to do some silly section function pointer placement. This is essentially what I do in OpenAL Soft:

static void my_init_func(void)
{
    ...
}

#pragma section(".CRT$XCU",read)
__declspec(allocate(".CRT$XCU")) void (__cdecl* my_init_func_ptr)(void) = my_init_func;

I forget exactly where I learned that from or the exact details, so you may need to search around if you need more info.

--
"Do not meddle in the affairs of cats, for they are subtle and will pee on your computer." -- Bruce Graham

Go to: