
Introduction
So you've got yourself a clever class using member templates, and you'd like to export this class from a DLL. You've created and exported the class from your DLL; you've imported it into your application; you've used the class including the member templates; and the compiler is perfectly happy. But then the linker gets hold of it and spews "unresolved external symbol" errors everywhere. At least that's how the debug build works. Switch to a release build and everything might work as expected. What's happening here? And how do we fix it?
Before we get to that, I need to point out a couple of caveats. First, templates are usually considered a somewhat advanced C++ subject, and using templates in DLLs is even more advanced. While this isn't quantum mechanics, I will assume you have a good working knowledge of how templates work along with inlining, and DLL creation and usage.
Also, I am working from MSVC 6.0, and, unfortunately, I do not have access to a more modern version. Compiler support of templates is notoriously inconsistent. While certain KB articles seem to indicate that this is still a problem with later versions of MSVC, I cannot confirm that myself. If you are not using MSVC 6.0, some of what I say here might not apply to you. Okay, where were we? Ah yes...
What's Happening Here?
I am going to present the problem and some of the unsuccessful attempts along the way to the solution. I believe we can learn as much from what doesn't work as we can from what does. However, if you want to get directly to how to fix the problem, feel free to jump ahead. Because multiple versions of the class involved are being shipped, the MemberTemplateDLL.h file begins with several #defines to control which version of the code the compiler includes. Usage of these #defines is described in the header file.
First, some example code that demonstrates the problem we're addressing. This class has a member template and is exported from a DLL.
class MEMBERTEMPLATEDLL_API CMemberTemplateDLL
{
public:
template<typename T>
VOID printOut(T t)
{
std::cout << "Printing " << t << std::endl;
}
};
And here is the main entry point for an application using our class and its member template.
int main(int , char* [])
{
CMemberTemplateDLL printer;
std::cout << std::endl;
std::cout << "Using <int> version of CMemberTemplateDLL::printOut()."
<< std::endl;
printer.printOut(3);
std::cout << std::endl;
std::cout << "Using <char *> version of CMemberTemplateDLL::printOut()."
<< std::endl;
printer.printOut("String");
return 0;
}
As noted here "Visual C++ has a limitation in which member templates must be fully defined within the enclosing class." In our case, the class is compiled into the DLL, but the compiler did not instantiate the member templates until they were used. In this case, that happened in the application. The instantiation of the member templates is in the application, but the linker is looking in the DLL so we get "unresolved external symbol" errors for each unique usage of printOut()
.
That is how the debug build works, but the release build compiles and links properly. Sometimes. Why does the release version sometimes work? Here we enter the realm of speculation on my part. I suspect that the compiler is doing some automatic inlining of the member template at the locations where the member template is instantiated. In fact, if you succeed in making a release build of this code, and then remove the DLL, the application will still run successfully. The code associated with the member template is compiled and linked into the application itself.
And How Do We Try To Fix It?
If inlining the code allows the linker to complete successfully, albeit by stuffing the code into the application, that might be an acceptable solution to the problem. Here is our class modified in such a way that we request that the compiler inlines our member template.
class MEMBERTEMPLATEDLL_API CMemberTemplateDLL
{
public:
template<typename T>
inline VOID printOut(T t)
{
std::cout << "Printing " << t << std::endl;
}
};
This works... or maybe it doesn't. Even if you use the __forceinline
directive (not recommended) and set your project settings to use "Any Suitable" inline expansion, this MSDN article indicates that, "The compiler treats the inline expansion options and keywords as suggestions. There is no guarantee that functions will be inlined. You cannot force the compiler to inline a particular function." There is simply no way to make the compiler inline a function 100% of the time. Also, inlining is disabled by default for debug builds.
Still, our problem is one of instantiation. If we could get the compiler to instantiate the member template while compiling the DLL, we would be in good shape. Here enters explicit instantiation. Shown below is our class with sample versions of our member template instantiated explicitly.
class MEMBERTEMPLATEDLL_API CMemberTemplateDLL
{
public:
template<typename T>
VOID printOut(T t)
{
std::cout << "Printing " << t << std::endl;
}
template VOID printOut<int>(int);
template VOID printOut<char *>(char *);
};
Compiling this produces one of the nastiest errors around, the dreaded "INTERNAL COMPILER ERROR." Unless you are entering some kind of contest to create the worst error you can, this is most definitely not what you want to have happen. Another KB article indicates that, "Member template implementation is not supported in this product." However, the same source also claims that, "This problem was corrected in Microsoft Visual C++ .NET." As noted above, I do not have access to any versions above 6.0 so I cannot confirm this, but this solution might work for users of more modern compilers.
And How Do We Actually Fix It?
Still, our problem is one of instantiation. We can use lazy instantiation to accomplish what we want. Here I've created another method in our class whose job is to force the compiler to instantiate the versions of our member template that we want. This method should never be called so it is named doNotCall()
, and it fails an assertion in its first line of code.
class MEMBERTEMPLATEDLL_API CMemberTemplateDLL
{
public:
template<typename T>
VOID printOut(T t)
{
std::cout << "Printing " << t << std::endl;
}
private:
VOID doNotCall()
{
#pragma warning(push)
#pragma warning(disable: 4127)
_ASSERT(FALSE);
#pragma warning(pop)
int i = 0;
printOut(i);
char * s = NULL;
printOut(s);
}
};
Whew! This actually compiles, links, and runs as expected. While compiling the doNotCall()
method the compiler creates versions of our member template that we need. These get packaged in our DLL with the rest of our class, and when we go to use those same instantiations in our application the linker can find them in the DLL where they belong.
While this method uses lazy instantiation, conceptually it is more similar to explicit instantiation in that we've created code whose only purpose is to force the compiler to create particular versions of our templated methods.
OK, But What About...?
This isn't exactly a perfect solution.
Any kind of explicit instantiation, including those discussed here, might generate extra code that is not actually needed. The merits of lazy and explicit instantiation are discussed in this excellent article.
A potentially larger problem is the clairvoyance needed by the author of the DLL. The person creating the member template has to have advance knowledge of what types will be used with the template, and these types must be accessible to the DLL containing the member template class. This dramatically reduces the flexibility of the template. After all, a person writing an application defining a brand new class cannot use your member template with their class. This may actually prove fatal, but I have been surprised at how often the person creating the member template has a good idea of what its usage will be. In those cases this solution may suffice, and the generality of the code may still be a worthwhile advantage.
Conclusion
When using member templates from DLLs, having the compiler inline the code associated with the member templates might work for you. It is important to keep in mind that the code associated with the member template ends up in the application, not the DLL. Whether that matters depends on how you plan to distribute your product, and what the usage of the DLL will be. In any case, it is impossible to guarantee that any particular function will be inlined.
Explicit instantiation, either using the C++ standard method or the "lazy explicit" instantiation method described here, might also work for you. However, this does require that the creator of the DLL have a deep understanding of how the DLL will be used by applications. In fact, the member templates can only be used with the types called out in the DLL.