Abstract
I am writing this article today because I have encountered far too many developers that do not package their static libraries/dll releases in a "friendly" manner that can integrate with minimal fuss. Users of the library often have to make more unproductive changes than necessary, but this ends here today!
The following talks about VC++ library releases but they are general concepts which can be applied for any C++ library.
General Rules
We begin by examining the general steps required to integrate a 3rd party library. Assume the 3rd part library we are trying to integrate is called "Useful".
- Copy Useful headers and .libs into our machine.
- Include Useful headers in our working project.
- Link with Useful released .libs.
- If Useful is a DLL, copy useful.dll into our project output or a well known location (i.e. windows\system32)
If all goes well, our app will be using Useful happily. What could go wrong? Plently, really.
Bad release
Let's examine this type of package release which is very popular, and see the type of problems we can encounter.
This is how Useful is packaged:
-
Headers are placed in Useful\include. This is following a Microsoft convention which is good, since headers rarely have debug/release versions.
-
The .lib files for debug/release versions are placed in Useful\lib\debug and Useful\lib\release, and both are called Useful.lib. Placing library files in lib is another Microsoft convention.
-
The DLLs for debug/release is called Useful.dll and are placed in Useful\bin\debug and Useful\bin\release. Folder bin is another "standard" folder for binary output.
Now here I come along and try to integrate Useful to my projects. As Useful is very useful, I use it in 5 other projects.
Note: An project configuration in VC++ is like a makefile. In fact, it is.
-
I add Useful\include in my compiler include search path so I can use it directly for my 5 other projects. In case I move Useful folder to a different location (i.e from C:\Useful to D:\Useful, all I need is to change this path in my compiler and my 5 other projects can find the headers without any fuss. Good.
-
For linking the libraries, now's here the start of the problem. I only want to link my debug code with the debug version of Useful, likewise for release version. Because both debug and release versions are called Useful.lib, I can't add both folders to the compiler library search path. The compile wouldn't know which one to link. So the only solution is to hardcode C:\Useful\debug\lib into my debug project configuration, and C:\Useful\release\lib into my release project configuration.
-
In the unfortunate event that I have to move Useful to a different folder, look at the amount of work I need to do. I need to change the hardcoded library location in all my 5 project configurations. If you are working alone, that's fine; See what happens if you work in a group of developers. Everyone installs "Useful" in a different folder (by no fault of theirs), and the project configuration has to be changed each time a developer checks out from the source code repository. When he checks in the code, the source code sync will detect in project configuration have changed and will check the modified project configuration in, and the next developer will have to change when he checks out. Sounds fun? Hardly.
-
Now the same problems comes along with the DLL files. Again, the system has no way of detecting which DLL file is debug/release, so now we can't put Useful.dll in a shared location. (Actually you can if you're willing to swap the DLLs everytime you switch between debug/release, something I don't find productive.) So I copy Useful.dll into my debug/release output folders separately, so the system will find the closest DLL to my executable, and thus use the appropriate one. Repeat that for 5 projects.
Now, Useful developers have found a major bug, and released a new version of Useful. Now we have to copy the new DLLs to every project output folder. If Useful is released in a different folder (Say, C:\UsefulFixed instead of C:\Useful), we have to alter that path in every project configuration as well.
From this, you can see the problem here is assuming a library location path stays fixed for the lifetime of the project. Libraries are updated, installation paths change. All these changes happen regulary, and we poor developers have to be prepared to do a lot of work when that happens.
Microsoft Solution
This is how Microsoft packages their libraries. You can verify them in your VC++ folder.:
-
Headers are placed inside the Useful\include folder. The include name is more of a convention than anything, so you don't have to stick with that name.
-
.lib files are placed inside Useful\lib. Debug versions are appended with a _d so we have Useful_d.lib and Useful.lib inside C:\Useful\lib.
-
Note how this solves all the linking problem. In our code, we can do this to link Useful:
And we can add C:\Useful\lib to our compiler library search path.#ifdef _DEBUG #pragma comment( lib, "Useful_d" ) #elseif #pragma comment( lib, "Useful" ) #endif
-
Debug DLLs are appended with a _d, so we have Useful_d.dll and Useful.dll.
Both of these can be placed in a common folder, though Microsoft usually puts it in the windows\system32 folder. This is not a good practice unless it is a system DLL. For development, you can get away fine because the developer usually wants the latests DLLs installed. For a everyday user, this causes a version conflict situation known as "DLL HELL".
So remember if you are releasing an application, store the DLLs you use in your local folder. You can use a tool called "Dependency Walker" to see which DLLs you link with.
Conclusion
The above changes are simple changes to the library build process and has no effect on the development of the library. However, with a little bit of change in your release packaging, you will be saving the time of thousands who use your library.