Click here to Skip to main content
15,886,362 members
Articles / Programming Languages / C++
Article

Exporting Member Templates from DLLs

Rate me:
Please Sign up or sign in to vote.
2.84/5 (12 votes)
16 Nov 20037 min read 71.4K   1.1K   20   12
This article compares various methods of using member templates from DLLs

Image 1

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.

//  The default approach, no inlining and no instantiation.
class MEMBERTEMPLATEDLL_API CMemberTemplateDLL
{
public:
  //  Member template used to print to cout.
  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.

//  Main entry point.  We won't be using the arguments so comment them out to
//  avoid warnings.
int main(int /* argc */, char* /* argv */[])
{
  CMemberTemplateDLL printer;

  std::cout << std::endl;
  //  Use the <int> version.
  std::cout << "Using <int> version of CMemberTemplateDLL::printOut()." 
    << std::endl;
  printer.printOut(3);

  std::cout << std::endl;
  //  Use the <char *> version.
  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.

//  The inlining approach.
class MEMBERTEMPLATEDLL_API CMemberTemplateDLL
{
public:
  //  Member template used to print to cout.  The inline keyword is a
  //  recommendation, the compiler may ignore it.
  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.

//  The standard explicit instantiation approach.
class MEMBERTEMPLATEDLL_API CMemberTemplateDLL
{
public:
  //  Member template used to print to cout.
  template<typename T>
  VOID printOut(T t)
  {
    std::cout << "Printing " << t << std::endl;
  }

  //  Explicit instantiations.
  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.

//  The "lazy explicit" instantiation approach.
class MEMBERTEMPLATEDLL_API CMemberTemplateDLL
{
public:
  //  Member template used to print to cout.
  template<typename T>
  VOID printOut(T t)
  {
    std::cout << "Printing " << t << std::endl;
  }

private:
  //  This method exists only to force the compiler to 
  //  instantiate versions of
  //  our member template.  It should never be called.
  VOID doNotCall()
  {
//  Disable "conditional expression is constant" warning.
#pragma warning(push)
#pragma warning(disable: 4127)
    //  We were serious about that "doNotCall" thing.
    _ASSERT(FALSE);
#pragma warning(pop)

    //  Create template VOID printOut<int>(int);
    int i = 0;
    printOut(i);

    //  Create template VOID printOut<char *>(char *);
    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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
Cagey is a Senior Software Engineer toiling in Chicago. He primarily uses C, C++ and Java on Windows, but has occasionally experimented with other platforms and languages.

Comments and Discussions

 
GeneralMy vote of 1 Pin
Ajay Vijayvargiya19-Sep-11 7:12
Ajay Vijayvargiya19-Sep-11 7:12 
GeneralThanks for the article. Pin
Sameerkumar Namdeo22-Apr-08 21:51
Sameerkumar Namdeo22-Apr-08 21:51 
QuestionHow about this one? Pin
Joonhwan15-Jan-04 15:49
Joonhwan15-Jan-04 15:49 
instead of writing private functions just wrote somewhere in the source code module put some codes like


void _JustForExportingPurpose(void)
{
CMemberTemplateDLL exporter;
exporter.printOut((int)1);
exporter.printOut((char*)"Test");
}

silly method?
AnswerRe: How about this one? Pin
cagey28-Jan-04 5:16
cagey28-Jan-04 5:16 
GeneralAnother problem exporting templates Pin
Nathan Holt at EMOM20-Nov-03 4:52
Nathan Holt at EMOM20-Nov-03 4:52 
GeneralRe: Another problem exporting templates Pin
cagey20-Nov-03 16:27
cagey20-Nov-03 16:27 
GeneralA cleaner solution Pin
jopett18-Nov-03 21:54
jopett18-Nov-03 21:54 
GeneralRe: A cleaner solution Pin
cagey19-Nov-03 9:16
cagey19-Nov-03 9:16 
GeneralRe: A cleaner solution Pin
jopett19-Nov-03 23:52
jopett19-Nov-03 23:52 
GeneralRe: A cleaner solution Pin
cagey20-Nov-03 16:23
cagey20-Nov-03 16:23 
Generalthe 'export' keyword Pin
roel_17-Nov-03 23:12
roel_17-Nov-03 23:12 
GeneralRe: the 'export' keyword Pin
cagey18-Nov-03 9:15
cagey18-Nov-03 9:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.