Monday, August 5, 2013

Attack of the DLLs, and The Eclipse wars

Last week was an interesting week, filled with despair and joy in differing intervals

For whatever reason, i decided to change my current DLL renderer setup. I knew i wanted code and dependency isolation between my main project and the rendering code, but i also didn't see the reason to do it using inheritance, since i wasn't using polymorphism at all (and didn't need to).
My original rendering code was setup by having an ABC (Abstract Base Class) pretending to be an interface class to the rest of my code, by hiding all of the grisly details of how the rendering works behind it. But by doing so, i was using inheritance for something i had no need for. In my opinion, you should use inheritance only if you need the runtime polymorphism, and for my renderer, i didn't need it, as i was planning to statically link the different renderers into my code by using the same class names. And while this is possible, it's also impossible to do so all the while keeping the code and its dependencies separated in a DLL. I mean, it is, but not without sacrificing time and a whole lot of hair.

So after a small discussion on, i decided that using an interface wasn't so bad if i get to keep all of my rendering dependencies tucked away nicely in a DLL file. This does, however, mean that my renderer is going to have to manage all classes that in any way use or reference a concrete rendering API data type. However, this isn't that big of a deal, because this requires that:
1. Each of those classes needs to have an ABC interface
2. Each of those classes can only be referenced by a pointer or reference
3. The memory and lifetime of those classes needs to be managed by the DLL
4. The creation of these class instances is done through the renderer interface
5. (optional) Getting the reference/pointer to those classes requires an ID or name
The first four points just mean that i will need to have two managers for these objects, one to be inside the DLL and handle their memory and lifetime, and one outside of the DLL that controls the lifetime. And here i make a difference between handling and controlling the lifetime, because while i can only delete the object from inside the the DLL (thus ending its life), i get to control WHEN i do that from the outside.
The last point is a feature that i'd really like to have, because it allows me to reload and replace any resource (texture, model, etc) without needing a restart of the executable. Fun!

Another thing i did last weekend and which took me 2 days (well, one and a half, spent half a day drinking) was migrate my whole code base from VS2012 to Eclipse with MinGW GCC. Something that i thought would be easy, considering i organized my files in a way to make such a transition easier. What i didn't count on was how hard it would be to begin with.

I encountered several problems on my way to victory:
1. Getting everything imported into Eclipse without copying files from their original location
Since my source/lib/include directory is set up to allow any IDE to work with them, their disk structure is very rigorous, and i need the IDE to just reference their locations instead of copying them to the projects working directory. After a few hours of experimentation, i managed to do it by importing a file system and setting a flag to only link to the files. Seems obvious now, but the option is hidden in the import dialog under the advanced section. Doh.

2. Getting Eclipse to invoke the right compiler
This one took me the longest to figure out. When Eclipse starts up, it searches a couple of predefined locations and environment variables for the location of MinGWs compilers. But even after placing the compiler in the right directory, and setting the right variable, it still wouldn't find it. And only after several hours did i manage to find an obscure forum thread where a guy mentioned the procedure of how Eclipse searches for the compiler. Turns out the compiler's NAME had to be very specific, and mine was prefixed with a platform designation which made Eclipse think it didn't exist. Dooh.

3. Getting anything to build at all
After getting the project to reference the files and use the right compiler, the build wouldn't pass because it couldn't find the location of the files. After another couple hours of experimentation, i found where Eclipse keeps the settings for include paths, preprocessor options and such, and after setting just the right ones, i managed to get my files getting built. YAY!

4. Getting everything to build without errors
However, the joy wasn't long lived. As soon as my files started being compiled, i got error upon error in some of the files because i was using the VC++11 compiler (that's Microsofts compiler that comes with VS2012) for so long that i ignored what was part of the standard and what was not. It seems Microsoft likes to expand the language library in ways which aren't portable, like giving the std::exception a non-default constructor, or making enum classes hashable (which they shouldn't be, but Microsoft probably decided they would just implicitly cast them to integers or something, which is AGAINST the C++ standard). Doooh.

5. Getting the DLL to build using a .def file
In VS2012 i used to build my DLL file by using a definition file (.def) which contained the name of the library and the exported symbols. Eclipse on the other hand, either can't use the same .def file, or uses it in a completely different way (or i just don't know how to actually use .def files) which made by DLL build not export the symbols i needed. So to solve this i removed the need to have the .def file, and simply used the __declspec(dllimport/dllexport) macro which i appended to the functions i actually export, which made the compiler to export them, which in turn made loading the DLL work. YAAY!

6. Getting the post build event to work properly
In VS2012, the pre/post build events fire up an internal command prompt which then executes the commands you give it. Eclipse, on the other hand, doesn't fire up the console, and you have to do it yourself. The syntax for that is really horrible, and involves writing 'cmd;COMMAND', where you replace the COMMAND with whatever you want to do. Really stupid, which took away at least an hour before i figured it out.

7. Getting the debug path to be in a non-default directory
After getting almost everything important to work, one last thing to do was get the debugger to fire up the executable located in the folder where i keep all of my build artifacts, and which is not anywhere near the project directory. Getting this to work took some more time, and required me to check which of my log files was actually updated. I was trying to avoid using absolute paths in the project, but eventually figured that it doesn't work with environment variables neither, so i gave up and put in an absolute path. However, at least it works now! YAAAY!

8. Getting the precompiled headers to work
This is something i still need to get working. Eclipse doesn't support precompiled headers, and i really love the 7 second build time in VS2012 with them (as opposed to a minute and a half without them). MinGW GCC does support them though, but it requires invocation of the compiler manually to get the PCH to be built, and then some flags to get the rest of the files to use the PCH. I'll probably do that when i recuperate from the misery i went through to get the basic migration done.

The positive outcome of the migration is that now i have code that can be built with two compilers, which made it a bit more robust and more standard compliant, plus it gave me a confidence boost seeing my file structure is good enough to have the project folder completely empty, and all of the source files in one neatly organized place independent of compiler/IDE combinations. :)

No comments:

Post a Comment