Sign in to follow this  
Servant of the Lord

DLL and template specialization

Recommended Posts

What's the proper way of doing template specialization in a DLL? Do I put it in the header, or in the source file? How do I properly export it? Here's my function, in the header file.
template <typename Type> 
std::string ConvertToString(Type value)
{
	std::ostringstream string;
	
	string << value;
	
	if(!string.good())
		return "";
		
	return string.str();
}

And my specialization function:
template <>  
std::string ConvertToString<bool>(bool value)
{
	if(value == true)
		return "True";
	
	return "False";
}

If I put my specialization in the header file, I get "multiple defintion" errors. multiple definition of `std::string Library::String::ConvertToString<bool>(bool)' But if I put it in the source file, my test executable can't see the specialization, only the template function, but it compiles fine. I've tried putting it in the source, and exporting it, but it still doesn't see it. Here's how I exported it:
template <> DLL_EXPORT
std::string ConvertToString<bool>(bool value)
{
	if(value == true)
		return "True";
	
	return "False";
}

What's the proper way to do this?

Share this post


Link to post
Share on other sites
Quote:
Original post by Servant of the Lord
What's the proper way of doing template specialization in a DLL?

You don't.

DLL exports are for definitions. That is, they require an actual thing they can point to.

Templates are declarations, not definitions. That is, it declares an infinite number of functions that are defined at build time.


There are possible ways to do it if you don't mind broken code. (See Microsoft's KB168958 for an example which does it but generates a lot of serious warnings.) Basically you instantiate all the template versions you can think of, export them, and pray that the types match on both sides and that everything else is compatible. If for any reason they don't match, or if you are missing an instance, or they are using different memory allocators, or if anything is even slightly incompatible, the app will either not compile or crash.

Whenever you cross a library boundary you should stick to basic types.

Share this post


Link to post
Share on other sites
Hmm, good point. I wasn't thinking about different versions of the same variable types or things like that, it never occured to me.

However, I like the convience of keeping functions, like template functions, in a DLL for organizational purposes. I've figured out a way to do this, that works, and that I think wont suffer from the problems you pointed out.

Since most people and IDEs use a #define when building a DLL, usually called 'BUILDING_DLL' or 'BUILD_DLL', like this:
#ifdef BUILD_DLL
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT __declspec(dllimport)
#endif
I figure, why not take advantage of that for templates?

I don't gain anything, but I get to keep my specializations with the rest of my library.
Here's a (functional) example of what I mean:
//======================================================================

template <typename Type>
Type ConvertFromString(std::string string)
{
std::istringstream istream(string);

Type value;
istream >> value;

return value;
}


//We only have the template specialization functions when we are compiling
//the executable - not when we are compiling the DLL.
#ifndef BUILD_DLL

//======================================================================

//Special case for Type 'std::string': Do nothing.
template <>
std::string ConvertFromString<std::string>(std::string string)
{
return string;
}

//======================================================================

//Special case for Type 'sf::Color'.
//Format: '(n,n,n,n)' or '(n,n,n)' where 'n' is between 0 and 255, or 0.0 to 1.0.
//If the 4th parameter is missing, a value of 255 is assumed.
//The parenthesis are optional. ('n,n,n' is acceptable)
template <>
Math::Color ConvertFromString<Math::Color>(std::string string)
{
//Strip out all spaces.
string = RemoveAll(string, " ");

//Strip out paranthesis:
string = RemoveAll(string, "(");
string = RemoveAll(string, ")");

Math::Color color;
color.r = (int) math_color_GetNextParam(&string);
color.g = (int) math_color_GetNextParam(&string);
color.b = (int) math_color_GetNextParam(&string);
color.a = (int) math_color_GetNextParam(&string);

return color;
}


//======================================================================

//Special case for Type 'sf::Vector2f'.
//Format: '(n,n)' where 'n' is float, negative or positive, unbounded.
template <>
Math::Point ConvertFromString<Math::Point>(std::string string)
{
//Strip out all spaces.
string = RemoveAll(string, " ");

//Strip out paranthesis:
string = RemoveAll(string, "(");
string = RemoveAll(string, ")");

Math::Point point;
point.x = (float) math_point_GetNextParam(&string);
point.y = (float) math_point_GetNextParam(&string);

return point;
}

//======================================================================

//Special case for Type 'bool'.
//Converts the following (caps-insensitive), to 'true': "true", "yes", "1". Anything else is false.
template <>
bool ConvertFromString<bool>(std::string string)
{
string = RemoveAll(string, " ");
string = ConvertToLowercase(string);

if(string == "true" || string == "yes" || string == "1")
return true;

return false;
}

//======================================================================

#endif //End of '#ifndef BUILD_DLL'




I can't think of any problem with this. Is this is a bad way to do it? Is there some problem I'm overlooking?

Share this post


Link to post
Share on other sites
Just don't do it. It's not worth the rather serious hassle you will encounter in the short run (and especially the long run).

Not only are you extremely dependent upon the compiler version you use (as any difference will change the name mangling which is required for templates to even operate (as they are essentially overloads with the same name).

The rules with DLLs is as follows:
- Expose interfaces instead of objects (think COM).
- Keep your interface simple.
- Export only C functions.
- Do nothing in DllMain.

Stick to those and you should be good, try and go outside of those and you're in for a world of pain the likes of which you cannot begin to imagine.

Share this post


Link to post
Share on other sites
Quote:
Original post by Washu
Just don't do it. It's not worth the rather serious hassle you will encounter in the short run (and especially the long run).

But the method I displayed above required no exporting, and in-fact, doesn't get compiled into the DLL at all, only the executable. (Although, now that I think about it, it would increase compile times a bit, as each file that includes the header would compile it's own version of each specialization... oops)
Quote:
Stick to those and you should be good, try and go outside of those and you're in for a world of pain the likes of which you cannot begin to imagine.

Grr, alright, I'll bow to wisdom here. [smile]
Quote:
The rules with DLLs is as follows:
- Expose interfaces instead of objects (think COM).
- Keep your interface simple.
- Export only C functions.
- Do nothing in DllMain.

By 'interfaces' do you mean, pure virtual classes?
So I shouldn't have classes, or even structs, in a DLL?

[Edit:]
Wait a sec... structs are (usually) defined in header files. The header files are just distributed with the DLLs. If you just use a simple #ifndef, like I showed above with the template functions, the structs will be available to the DLL with no hassel and absolutely no side-effects; sure, the DLL can't use the structs, but the executable will see it just as if it was the executable's own header.

There'd be zero conflicts if different compilers compiled the executable and the DLL, because the struct would only be seen by the compiler compiling the executable. Why is this a bad idea? It's not at all a hacky way of doing things, it's a perfectly clear and legitimate way of doing things. You wouldn't be able to do the same thing with classes, unless all the functions where implemented in the header, which would increase executable size and compiler time, but for small classes it should be fine.

Why is this not a good idea?

What do engines/APIs like Irrlicht and SFML or do to get their classes working in DLLs?

[Edited by - Servant of the Lord on February 16, 2010 7:01:21 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by Servant of the Lord
Quote:
Original post by Washu
Just don't do it. It's not worth the rather serious hassle you will encounter in the short run (and especially the long run).

But the method I displayed above required no exporting, and in-fact, doesn't get compiled into the DLL at all, only the executable. (Although, now that I think about it, it would increase compile times a bit, as each file that includes the header would compile it's own version of each specialization... oops)

Sticking them in the headers is fine, provided the entire implementation is in the headers and thus you're not trying to pass templates back and forth across the DLL boundry.
Quote:

Quote:
Stick to those and you should be good, try and go outside of those and you're in for a world of pain the likes of which you cannot begin to imagine.

Grr, alright, I'll bow to wisdom here. [smile]
Quote:
The rules with DLLs is as follows:
- Expose interfaces instead of objects (think COM).
- Keep your interface simple.
- Export only C functions.
- Do nothing in DllMain.

By 'interfaces' do you mean, pure virtual classes?
So I shouldn't have classes, or even structs, in a DLL?

No, you can have classes and structures in the DLL. However your public interface should be as simple as possible... so let me give you a simple public interface:

//Note that the other common method is to wrap the entire header in an extern "C" like so:
#ifdef __cplusplus
extern "C" {
#endif
#ifdef DLL_EXPORTING
#define DLLFUNC __declspec(dllexport)
#else
#define DLLFUNC __declspec(dllimport)
#endif

enum Sex { Male, Female };

struct MonkeyInfo {
int age;
Sex sex;
bool throwsPoop;
};

struct IMonkey {
virtual void ThrowPoop() = 0;
virtual void Release() = 0;
};

IMonkey* DLLFUNC CreateMonkey(MonkeyInfo const& /* or use a pointer here */);
#ifdef __cplusplus
}
#endif





Quote:

[Edit:]
Wait a sec... structs are (usually) defined in header files. The header files are just distributed with the DLLs. If you just use a simple #ifndef, like I showed above with the template functions, the structs will be available to the DLL with no hassel and absolutely no side-effects; sure, the DLL can't use the structs, but the executable will see it just as if it was the executable's own header.

There'd be zero conflicts if different compilers compiled the executable and the DLL, because the struct would only be seen by the compiler compiling the executable. Why is this a bad idea? It's not at all a hacky way of doing things, it's a perfectly clear and legitimate way of doing things. You wouldn't be able to do the same thing with classes, unless all the functions where implemented in the header, which would increase executable size and compiler time, but for small classes it should be fine.

Why is this not a good idea?

What do engines/APIs like Irrlicht and SFML or do to get their classes working in DLLs?

Your external headers should eliminate all traces of internal implementation details as possible. This is to prevent the user from accidentally relying on said details, and also to ensure maximal portability of your DLL. Structures can change size based on the compiler, internal buffering of the various components varies from compiler to compiler (which is why you usually see windows headers specifying an explicit packing for the structures...)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this