F
Felix I. Wyss
Good Afternoon,
I recently noticed that some very simple methods of a template declared and
used in a DLL library get inlined when used by the DLL itself, but not by
other DLLs and EXEs. After some investigating, I narrowed this down to a
very odd behavior (bug?) of the VC++.NET 2003 compiler: If a class that is
declared as __declspec(dllimport) derives from a template, that template's
methods are never inlined, even if declared with the "inline" specifier.
Methods that are defined within the class body are correctly inlined by the
compiler. The same is true to templates explicitly instantiated for a type
as "template class __declspec(dllimport) Template<Type>;".
As this is a bit tricky to explain, consider the following code:
exporttest_dll.h
================
// Header file of DLL
#ifdef BUILDING_EXPORTTEST_DLL
#define EXPORTTEST_DLL __declspec(dllexport)
#else
#define EXPORTTEST_DLL __declspec(dllimport)
#endif
namespace exporttest_dll
{
template<typename TYPE_T>
class Templ
{
public:
Templ(void) : m_value() { }
TYPE_T get(void) const
{
return m_value;
}
void set(TYPE_T value)
{
m_value = value;
}
private:
TYPE_T m_value;
};
template<typename TYPE_T>
class Templ2
{
public:
Templ2(void);
TYPE_T get(void) const;
void set(TYPE_T value);
private:
TYPE_T m_value;
};
template<typename TYPE_T>
inline Templ2<TYPE_T>::Templ2(void) : m_value()
{
}
template<typename TYPE_T>
inline TYPE_T Templ2<TYPE_T>::get(void) const
{
return m_value;
}
template<typename TYPE_T>
inline void Templ2<TYPE_T>::set(TYPE_T value)
{
m_value = value;
}
class EXPORTTEST_DLL Derived : public Templ<int>
{
};
class EXPORTTEST_DLL Derived2 : public Templ2<int>
{
};
template class EXPORTTEST_DLL Templ<long>;
template class EXPORTTEST_DLL Templ2<long>;
} // end of namespace exporttest_dll
main.cpp
==============
// Executable that uses the templates and classes declared by DLL:
#include <exporttest_dll.h>
#include <iostream>
int main(int, char*[])
{
exporttest_dll:erived test1;
test1.set(1);
std::cout << test1.get();
exporttest_dll:erived2 test2;
test2.set(2);
std::cout << test2.get();
exporttest_dll::Templ<int> test3;
test3.set(3);
std::cout << test3.get();
exporttest_dll::Templ2<int> test4;
test4.set(4);
std::cout << test4.get();
exporttest_dll::Templ<long> test5;
test5.set(5);
std::cout << test5.get();
exporttest_dll::Templ2<long> test6;
test6.set(6);
std::cout << test6.get();
exporttest_dll::Templ<unsigned long> test7;
test7.set(7);
std::cout << test7.get();
exporttest_dll::Templ2<unsigned long> test8;
test8.set(8);
std::cout << test8.get();
return 0;
}
Building main.cpp with full optimization turned on (/Ob2 /Ogity /O2) results
in the code below. Note that for test1, test3, test5, test7, and test8
the compiler optimizes everything down to a constant.
However, for test2, test4, and test6, the compiler generates explicit calls
to the method specializations exported by the DLL.
Depends shows the exported methods for the specializations of Templ<int>,
Templ2<int>, Templ<long>, and Templ<long>. Thus, the *only* difference
between Templ and Templ2 lies in Templ having the methods inlined in the
class body and Templ2 declared as inline outside the class body, with the
"inline" specifier. Just in case you were wondering: Yes I tried using
__forceinline -- it doesn't make a difference.
Also note that test7 and test8 use a type as template argument for which the
template was not instantiated with __declspec(dllimport) and the compiler
correctly inlines both Templ and Templ2's methods as expected.
int main(int, char*[])
{
exporttest_dll:erived test1;
test1.set(1);
std::cout << test1.get();
00401000 8B 0D 08 20 40 00 mov ecx,dword ptr [__imp_std::cout
(402008h)]
00401006 83 EC 0C sub esp,0Ch
00401009 53 push ebx
0040100A 56 push esi
0040100B 57 push edi
0040100C 6A 01 push 1
0040100E FF 15 0C 20 40 00 call dword ptr
[__imp_std::basic_ostream<char,std::char_traits<char> >:perator<<
(40200Ch)]
exporttest_dll:erived2 test2;
00401014 8B 35 68 20 40 00 mov esi,dword ptr
[__imp_exporttest_dll::Templ2<int>::Templ2<int> (402068h)]
0040101A 8D 4C 24 0C lea ecx,[esp+0Ch]
0040101E FF D6 call esi
test2.set(2);
00401020 8B 3D 6C 20 40 00 mov edi,dword ptr
[__imp_exporttest_dll::Templ2<int>::set (40206Ch)]
00401026 6A 02 push 2
00401028 8D 4C 24 10 lea ecx,[esp+10h]
0040102C FF D7 call edi
std::cout << test2.get();
0040102E 8B 1D 70 20 40 00 mov ebx,dword ptr
[__imp_exporttest_dll::Templ2<int>::get (402070h)]
00401034 8D 4C 24 0C lea ecx,[esp+0Ch]
00401038 FF D3 call ebx
0040103A 8B 0D 08 20 40 00 mov ecx,dword ptr [__imp_std::cout
(402008h)]
00401040 50 push eax
00401041 FF 15 0C 20 40 00 call dword ptr
[__imp_std::basic_ostream<char,std::char_traits<char> >:perator<<
(40200Ch)]
exporttest_dll::Templ<int> test3;
test3.set(3);
std::cout << test3.get();
00401047 8B 0D 08 20 40 00 mov ecx,dword ptr [__imp_std::cout
(402008h)]
0040104D 6A 03 push 3
0040104F FF 15 0C 20 40 00 call dword ptr
[__imp_std::basic_ostream<char,std::char_traits<char> >:perator<<
(40200Ch)]
exporttest_dll::Templ2<int> test4;
00401055 8D 4C 24 10 lea ecx,[esp+10h]
00401059 FF D6 call esi
test4.set(4);
0040105B 6A 04 push 4
0040105D 8D 4C 24 14 lea ecx,[esp+14h]
00401061 FF D7 call edi
std::cout << test4.get();
00401063 8D 4C 24 10 lea ecx,[esp+10h]
00401067 FF D3 call ebx
00401069 8B 0D 08 20 40 00 mov ecx,dword ptr [__imp_std::cout
(402008h)]
0040106F 50 push eax
00401070 FF 15 0C 20 40 00 call dword ptr
[__imp_std::basic_ostream<char,std::char_traits<char> >:perator<<
(40200Ch)]
exporttest_dll::Templ<long> test5;
test5.set(5);
std::cout << test5.get();
00401076 8B 0D 08 20 40 00 mov ecx,dword ptr [__imp_std::cout
(402008h)]
0040107C 6A 05 push 5
0040107E FF 15 10 20 40 00 call dword ptr
[__imp_std::basic_ostream<char,std::char_traits<char> >:perator<<
(402010h)]
exporttest_dll::Templ2<long> test6;
00401084 8D 4C 24 14 lea ecx,[esp+14h]
00401088 FF 15 74 20 40 00 call dword ptr
[__imp_exporttest_dll::Templ2<long>::Templ2<long> (402074h)]
test6.set(6);
0040108E 6A 06 push 6
00401090 8D 4C 24 18 lea ecx,[esp+18h]
00401094 FF 15 78 20 40 00 call dword ptr
[__imp_exporttest_dll::Templ2<long>::set (402078h)]
std::cout << test6.get();
0040109A 8D 4C 24 14 lea ecx,[esp+14h]
0040109E FF 15 7C 20 40 00 call dword ptr
[__imp_exporttest_dll::Templ2<long>::get (40207Ch)]
004010A4 8B 0D 08 20 40 00 mov ecx,dword ptr [__imp_std::cout
(402008h)]
004010AA 50 push eax
004010AB FF 15 10 20 40 00 call dword ptr
[__imp_std::basic_ostream<char,std::char_traits<char> >:perator<<
(402010h)]
exporttest_dll::Templ<unsigned long> test7;
test7.set(7);
std::cout << test7.get();
004010B1 8B 0D 08 20 40 00 mov ecx,dword ptr [__imp_std::cout
(402008h)]
004010B7 6A 07 push 7
004010B9 FF 15 14 20 40 00 call dword ptr
[__imp_std::basic_ostream<char,std::char_traits<char> >:perator<<
(402014h)]
exporttest_dll::Templ2<unsigned long> test8;
test8.set(8);
std::cout << test8.get();
004010BF 8B 0D 08 20 40 00 mov ecx,dword ptr [__imp_std::cout
(402008h)]
004010C5 6A 08 push 8
004010C7 FF 15 14 20 40 00 call dword ptr
[__imp_std::basic_ostream<char,std::char_traits<char> >:perator<<
(402014h)]
004010CD 5F pop edi
004010CE 5E pop esi
Thanks,
Felix I. Wyss
Interactive Intelligence, Inc.
I recently noticed that some very simple methods of a template declared and
used in a DLL library get inlined when used by the DLL itself, but not by
other DLLs and EXEs. After some investigating, I narrowed this down to a
very odd behavior (bug?) of the VC++.NET 2003 compiler: If a class that is
declared as __declspec(dllimport) derives from a template, that template's
methods are never inlined, even if declared with the "inline" specifier.
Methods that are defined within the class body are correctly inlined by the
compiler. The same is true to templates explicitly instantiated for a type
as "template class __declspec(dllimport) Template<Type>;".
As this is a bit tricky to explain, consider the following code:
exporttest_dll.h
================
// Header file of DLL
#ifdef BUILDING_EXPORTTEST_DLL
#define EXPORTTEST_DLL __declspec(dllexport)
#else
#define EXPORTTEST_DLL __declspec(dllimport)
#endif
namespace exporttest_dll
{
template<typename TYPE_T>
class Templ
{
public:
Templ(void) : m_value() { }
TYPE_T get(void) const
{
return m_value;
}
void set(TYPE_T value)
{
m_value = value;
}
private:
TYPE_T m_value;
};
template<typename TYPE_T>
class Templ2
{
public:
Templ2(void);
TYPE_T get(void) const;
void set(TYPE_T value);
private:
TYPE_T m_value;
};
template<typename TYPE_T>
inline Templ2<TYPE_T>::Templ2(void) : m_value()
{
}
template<typename TYPE_T>
inline TYPE_T Templ2<TYPE_T>::get(void) const
{
return m_value;
}
template<typename TYPE_T>
inline void Templ2<TYPE_T>::set(TYPE_T value)
{
m_value = value;
}
class EXPORTTEST_DLL Derived : public Templ<int>
{
};
class EXPORTTEST_DLL Derived2 : public Templ2<int>
{
};
template class EXPORTTEST_DLL Templ<long>;
template class EXPORTTEST_DLL Templ2<long>;
} // end of namespace exporttest_dll
main.cpp
==============
// Executable that uses the templates and classes declared by DLL:
#include <exporttest_dll.h>
#include <iostream>
int main(int, char*[])
{
exporttest_dll:erived test1;
test1.set(1);
std::cout << test1.get();
exporttest_dll:erived2 test2;
test2.set(2);
std::cout << test2.get();
exporttest_dll::Templ<int> test3;
test3.set(3);
std::cout << test3.get();
exporttest_dll::Templ2<int> test4;
test4.set(4);
std::cout << test4.get();
exporttest_dll::Templ<long> test5;
test5.set(5);
std::cout << test5.get();
exporttest_dll::Templ2<long> test6;
test6.set(6);
std::cout << test6.get();
exporttest_dll::Templ<unsigned long> test7;
test7.set(7);
std::cout << test7.get();
exporttest_dll::Templ2<unsigned long> test8;
test8.set(8);
std::cout << test8.get();
return 0;
}
Building main.cpp with full optimization turned on (/Ob2 /Ogity /O2) results
in the code below. Note that for test1, test3, test5, test7, and test8
the compiler optimizes everything down to a constant.
However, for test2, test4, and test6, the compiler generates explicit calls
to the method specializations exported by the DLL.
Depends shows the exported methods for the specializations of Templ<int>,
Templ2<int>, Templ<long>, and Templ<long>. Thus, the *only* difference
between Templ and Templ2 lies in Templ having the methods inlined in the
class body and Templ2 declared as inline outside the class body, with the
"inline" specifier. Just in case you were wondering: Yes I tried using
__forceinline -- it doesn't make a difference.
Also note that test7 and test8 use a type as template argument for which the
template was not instantiated with __declspec(dllimport) and the compiler
correctly inlines both Templ and Templ2's methods as expected.
int main(int, char*[])
{
exporttest_dll:erived test1;
test1.set(1);
std::cout << test1.get();
00401000 8B 0D 08 20 40 00 mov ecx,dword ptr [__imp_std::cout
(402008h)]
00401006 83 EC 0C sub esp,0Ch
00401009 53 push ebx
0040100A 56 push esi
0040100B 57 push edi
0040100C 6A 01 push 1
0040100E FF 15 0C 20 40 00 call dword ptr
[__imp_std::basic_ostream<char,std::char_traits<char> >:perator<<
(40200Ch)]
exporttest_dll:erived2 test2;
00401014 8B 35 68 20 40 00 mov esi,dword ptr
[__imp_exporttest_dll::Templ2<int>::Templ2<int> (402068h)]
0040101A 8D 4C 24 0C lea ecx,[esp+0Ch]
0040101E FF D6 call esi
test2.set(2);
00401020 8B 3D 6C 20 40 00 mov edi,dword ptr
[__imp_exporttest_dll::Templ2<int>::set (40206Ch)]
00401026 6A 02 push 2
00401028 8D 4C 24 10 lea ecx,[esp+10h]
0040102C FF D7 call edi
std::cout << test2.get();
0040102E 8B 1D 70 20 40 00 mov ebx,dword ptr
[__imp_exporttest_dll::Templ2<int>::get (402070h)]
00401034 8D 4C 24 0C lea ecx,[esp+0Ch]
00401038 FF D3 call ebx
0040103A 8B 0D 08 20 40 00 mov ecx,dword ptr [__imp_std::cout
(402008h)]
00401040 50 push eax
00401041 FF 15 0C 20 40 00 call dword ptr
[__imp_std::basic_ostream<char,std::char_traits<char> >:perator<<
(40200Ch)]
exporttest_dll::Templ<int> test3;
test3.set(3);
std::cout << test3.get();
00401047 8B 0D 08 20 40 00 mov ecx,dword ptr [__imp_std::cout
(402008h)]
0040104D 6A 03 push 3
0040104F FF 15 0C 20 40 00 call dword ptr
[__imp_std::basic_ostream<char,std::char_traits<char> >:perator<<
(40200Ch)]
exporttest_dll::Templ2<int> test4;
00401055 8D 4C 24 10 lea ecx,[esp+10h]
00401059 FF D6 call esi
test4.set(4);
0040105B 6A 04 push 4
0040105D 8D 4C 24 14 lea ecx,[esp+14h]
00401061 FF D7 call edi
std::cout << test4.get();
00401063 8D 4C 24 10 lea ecx,[esp+10h]
00401067 FF D3 call ebx
00401069 8B 0D 08 20 40 00 mov ecx,dword ptr [__imp_std::cout
(402008h)]
0040106F 50 push eax
00401070 FF 15 0C 20 40 00 call dword ptr
[__imp_std::basic_ostream<char,std::char_traits<char> >:perator<<
(40200Ch)]
exporttest_dll::Templ<long> test5;
test5.set(5);
std::cout << test5.get();
00401076 8B 0D 08 20 40 00 mov ecx,dword ptr [__imp_std::cout
(402008h)]
0040107C 6A 05 push 5
0040107E FF 15 10 20 40 00 call dword ptr
[__imp_std::basic_ostream<char,std::char_traits<char> >:perator<<
(402010h)]
exporttest_dll::Templ2<long> test6;
00401084 8D 4C 24 14 lea ecx,[esp+14h]
00401088 FF 15 74 20 40 00 call dword ptr
[__imp_exporttest_dll::Templ2<long>::Templ2<long> (402074h)]
test6.set(6);
0040108E 6A 06 push 6
00401090 8D 4C 24 18 lea ecx,[esp+18h]
00401094 FF 15 78 20 40 00 call dword ptr
[__imp_exporttest_dll::Templ2<long>::set (402078h)]
std::cout << test6.get();
0040109A 8D 4C 24 14 lea ecx,[esp+14h]
0040109E FF 15 7C 20 40 00 call dword ptr
[__imp_exporttest_dll::Templ2<long>::get (40207Ch)]
004010A4 8B 0D 08 20 40 00 mov ecx,dword ptr [__imp_std::cout
(402008h)]
004010AA 50 push eax
004010AB FF 15 10 20 40 00 call dword ptr
[__imp_std::basic_ostream<char,std::char_traits<char> >:perator<<
(402010h)]
exporttest_dll::Templ<unsigned long> test7;
test7.set(7);
std::cout << test7.get();
004010B1 8B 0D 08 20 40 00 mov ecx,dword ptr [__imp_std::cout
(402008h)]
004010B7 6A 07 push 7
004010B9 FF 15 14 20 40 00 call dword ptr
[__imp_std::basic_ostream<char,std::char_traits<char> >:perator<<
(402014h)]
exporttest_dll::Templ2<unsigned long> test8;
test8.set(8);
std::cout << test8.get();
004010BF 8B 0D 08 20 40 00 mov ecx,dword ptr [__imp_std::cout
(402008h)]
004010C5 6A 08 push 8
004010C7 FF 15 14 20 40 00 call dword ptr
[__imp_std::basic_ostream<char,std::char_traits<char> >:perator<<
(402014h)]
004010CD 5F pop edi
004010CE 5E pop esi
Thanks,
Felix I. Wyss
Interactive Intelligence, Inc.