Understanding the classical model for linking: Sometimes you don’t want a symbol to come along for a ride
Continuing our study ofthe classical model for linking,let’s take another look at the trick oftaking symbols along for the ride.
The technique of taking symbols along for the ride is quite handyif that’s what you want,but sometimes you don’t actually want it.For example, a symbol taken along for the ride may createconflicts or create unwanted dependencies.
Here’s an example:Suppose you have a library called stuff.lib
where you put functions that areused by various modules in different projects.One of the files in your library might look like this:
// filedatestuff.cppBOOL GetFileCreationTimeW( LPCWSTR pszFile, FILETIME *pft) { WIN32_FILE_ATTRIBUTE_DATA wfad; BOOL fSuccess = GetFileAttributesExW(pszFile, GetFileExInfoStandard, &wfad); if (fSuccess) { *pft = wfad.ftCreationTime; } else { pft->dwLowDateTime = 0; pft->dwHighDateTime = 0; } return fSuccess; }
BOOL GetFileCreationTimeAsStringW( LPCWSTR pszFile, LPWSTR pszBuf, UINT cchBuf) { FILETIME ft; BOOL fSuccess = GetFileCreationTimeW(pszFile, &ft); if (fSuccess) { fSuccess = SHFormatDateTimeW(&ft, NULL, pszBuf, cchBuf) > 0; } return fSuccess; }
Things are working out great,people like the helper functions in your library,and then you get a bug report:
When my program calls theGetFileCreationTimeW
function,I get a linker error:unresolved external: __imp__SHFormatDateTimeW.If I remove my call toGetFileCreationTimeW
,then my program builds fine.
You scratch your head.“The program is callingGetFileCreationTimeW
,but that function doesn’t callSHFormatDateTimeW
,so why are we getting an unresolved external error?Any why hasn’t anybody else run into this problem before?”
First question first.Why are we getting an unresolved external errorfor a nonexistent external dependency?
Because theGetFileCreationTimeAsStringW
function got taken along for the ride.When the customer’s program calledGetFileCreationTimeW
,that pulled in the filedatestuff.obj
file,and that OBJ file contains bothGetFileCreationTimeW
andGetFileCreationTimeAsStringW
.Since they are in the same OBJ file,pulling in one function pulls in all of them.
The fix is to split the filedatastuff.cpp
fileinto two files,one for each function.That way, when you pull in one function,nobody else comes along for the ride.
Now to the second half of the question:Why did nobody run into this problem before?
TheGetFileCreationTimeW
function has a dependency onGetFileAttributesExW
,which is a function in KERNEL32.DLL
.On the other hand, theGetFileCreationTimeAsStringW
function has a dependency onSHFormatDateTimeW
,which is a function inSHLWAPI.DLL
.If somebody listsKERNEL32.LIB
as a dependent libraryin their project,but they don’t includeSHLWAPI.LIB
on that list,then they will encounter this problembecause the linker will pull in the reference toSHFormatDateTimeW
and have no way of resolving it.
Nobody ran into this before because SHLWAPI.LIB
has lots of cute little functions in it,so most people include it in their project.Only if somebody is being frugal and leavingSHLWAPI.LIB
out of their projectwill they run into this problem.
Bonus chatter:The suggestion to split the file into two will work,but if you are really clever, you can still do some consolidation.Instead of splitting up files by functional group(for example, “all FILETIME
functions”),you need to split them up based on their dependencies(“functions that are dependent solely on SHLWAPI.LIB
“).Of course, this type of organization may make the code harderto follow (“Why did you putGetFileCreationTimeAsStringW
andHashString
in the same file?”),so you have to balance this against maintainability andreadability.For example, somebody who is not aware of the classicalmodel for linking may add a function to the file that hasa dependency on SHELL32.DLL
,and now your careful separation has fallen apart.
0 comments