Understanding the classical model for linking: Sometimes you don’t want a symbol to come along for a ride

Raymond Chen

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.libwhere you put functions that areused by various modules in different projects.One of the files in your library might look like this:

// filedatestuff.cpp

BOOL 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 theGet­File­Creation­TimeWfunction,I get a linker error:unresolved external: __imp__SHFormat­Date­TimeW.If I remove my call to Get­File­Creation­TimeW,then my program builds fine.

You scratch your head.“The program is callingGet­File­Creation­TimeW,but that function doesn’t callSHFormat­Date­TimeW,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 theGet­File­Creation­Time­As­StringWfunction got taken along for the ride.When the customer’s program calledGet­File­Creation­TimeW,that pulled in the filedatestuff.obj file,and that OBJ file contains bothGet­File­Creation­TimeWandGet­File­Creation­Time­As­StringW.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?

TheGet­File­Creation­TimeWfunction has a dependency onGet­File­Attributes­ExW,which is a function in KERNEL32.DLL.On the other hand, theGet­File­Creation­Time­As­StringWfunction has a dependency onSHFormat­Date­TimeW,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 toSHFormat­Date­TimeWand have no way of resolving it.

Nobody ran into this before because SHLWAPI.LIBhas 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 putGet­File­Creation­Time­As­StringWandHash­Stringin 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.