跨动态库使用stl出现崩溃问题

在一个mfc扩展动态库导出函数返回了map变量 ,在exe使用该变量会导致崩溃。如果直接调用该变量不通过函数也一样。不要在动态库接口中传递STL容器!!VC的STL版本,编译选项也是一样的。针对Vector引用的传递没有问题,但是Map就出现问题了。vc6下、通过“一个exe/dll中的指针”指向或者引用“另一个exe/dll中的、包含stl的类”时、这个包含stl的类在析构时就可能出现这样的问题。
1、微软的解释:
大部分C++标准库里提供的类直接或间接地使用了静态变量。由于这些类是通过模板扩展而来的,因此每个可执行映像(通常是. dll或.exe文件)就会存在一份只属于自己的、给定类的静态数据成员。当一个需要访问这些静态成员的类方法执行时,它使用的是“这个方法的代码当前所 在的那份可执行映像”里的静态成员变量。由于两份可执行映像各自的静态数据成员并未同步,这个行为就可能导致访问违例,或者数据看起来似乎丢失或被破坏 了。

可能不太好懂,我举个例子:假如类A<T>有个静态变量m_s,那么当1.exe使用了2.dll中提供的某个A<int>对象时,由于模板扩展机制,1.exe和2.dll中会分别存在自己的一份类静态变量A<int>.m_s。
这 样,假如1.exe中从2.dll中取得了一个的类A<int>的实例对象a,那么当在1.exe中直接访问a.m_s时,其实访问的是 1.exe中的对应拷贝(正确情况应该是访问了2.dll中的a.m_s)。这样就可能导致非法访问、应当改变的数据没有改变、不应改变的数据被错误地更 改等异常情形。

邮件列表里的Conrad Weyns这样认为(意译,就不一句句对应了):
每个.dll或.exe文档可以看作一个执行单元。而由于stl的特 性,每个执行单元中可能会有一个自己的内存分配器(通俗点说,就是堆内存管理器,或者内存池)。当跨越执行单元调用构造/析构函数时,如果这两个调用所在 的执行单元不同,就可能出现通过A的堆管理器去释放B的堆管理器所分配的对象的问题。这就导致了RtlValidateHeap抛出异常。
他所认为的正确解决办法是:使用各种措施,保证程序中只用了一个堆管理器;或者使用智能型的堆管理器(作者建议使用SmartHeap)。


1、保证资源的分配/删除操作对等并处于同一个执行单元;
   比如,可以把这些操作(包括构造/析构函数、某些容器自动扩容{这个需要特别注意}时的内存再分配等)隐藏到接口函数里面。换句话说:尽量不要直接从dll中输出stl对象;如果一定要输出,给它加上一层包装,然后输出这个包装接口而不是原始接口。

2、保证所有的执行单元使用同样版本的STL运行库。
   比如,全部使用release库或debug库,否则两个执行单元扩展出来的STL类的内存布局就可能会不一样。

参考:
http://support.microsoft.com/default.aspx?scid=kb%3ben-us%3b168958

How to export an instantiation of a Standard Template Library (STL) class and a class that contains a data member that is an STL object

<script type="text/javascript">function loadTOCNode(){}</script>
Article ID:168958
Last Review:September 6, 2005
Revision:3.0
This article was previously published under Q168958
<script type="text/javascript"> var sectionFilter = "type != 'notice' && type != 'securedata' && type != 'querywords'"; var tocArrow = "/library/images/support/kbgraphics/public/en-us/downarrow.gif"; var depthLimit = 10; var depth3Limit = 10; var depth4Limit = 5; var depth5Limit = 3; var tocEntryMinimum = 1; </script> <script src="http://support.microsoft.com/common/script/gsfx/kbtoc.js?10" type="text/javascript"></script>

SUMMARY

<script type="text/javascript">loadTOCNode(1, 'summary');</script>
This article discusses how to perform the following tasks:
Export an instantiation of a Standard Template Library (STL) class.
Export a class that contains a data member that is an STL object.
Note that you may not export a generalized template. The template must be instantiated; that is, all of the template parameters must be supplied and must be completely defined types at the point of instantiation. For instance "stack<int>;" instantiates the STL stack class. The instantiation forces all members of class stack<int> to be generated.

Also note that some STL containers (map, set, queue, list, deque) cannot be exported. Please refer to the More Information section to follow for a detailed explanation.

Back to the top

MORE INFORMATION

<script type="text/javascript">loadTOCNode(1, 'moreinformation');</script> Beginning with Visual C++ 5.0, it is possible to force an instantiation of a template class and export the instantiation. To export a template class instantiation, use the following syntax:

Back to the top

To Export an STL Class

<script type="text/javascript">loadTOCNode(2, 'moreinformation');</script>
1.In both the DLL and the .exe file, link with the same DLL version of the C run time. Either link both with Msvcrt.lib (release build) or link both with Msvcrtd.lib (debug build).
2.In the DLL, provide the __declspec specifier in the template instantiation declaration to export the STL class instantiation from the DLL.
3.In the .exe file, provide the extern and __declspec specifiers in the template instantiation declaration to import the class from the DLL. This results in a warning C4231 "nonstandard extension used : 'extern' before template explicit instantiation." You can ignore this warning.

Back to the top

To Export a Class Containing a Data Member that Is an STL Object

<script type="text/javascript">loadTOCNode(2, 'moreinformation');</script>
1.In both the DLL and the .exe file, link with the same DLL version of the C run time. Either link both with Msvcrt.lib (release build) or link both with Msvcrtd.lib (debug build).
2.In the DLL, provide the __declspec specifier in the template instantiation declaration to export the STL class instantiation from the DLL.

NOTE: You cannot skip step 2. You must export the instantiation of the STL class that you use to create the data member.
3.In the DLL, provide the __declspec specifier in the declaration of the class to export the class from the DLL.
4.In the .exe file, provide the __declspec specifier in the declaration of the class to import the class from the DLL.

If the class you are exporting has one or more base classes, then you must export the base classes as well. If the class you are exporting contains data members that are of class type, then you must export the classes of the data members as well.
NOTE: Some STL classes use other STL classes. These other classes must also be exported. The classes that must be exported are listed in compiler warnings if you compile with a warning level lower than 1; that is, /W2, /W3, or /W4. Warning level 4 generates a lot of warning messages for STL headers and is not currently recommended for that reason.

Some STL classes contain nested classes. These classes can not be exported. For instance, deque contains a nested class deque::iterator. If you export deque, you will get a warning that you must export deque::iterator. If you export deque::iterator, you get a warning that you must export deque. This is caused by a designed limitation that once a template class is instantiated, it can not be re-instantiated and exported. The only STL container that can currently be exported is vector. The other containers (that is, map, set, queue, list, deque) all contain nested classes and cannot be exported.

When you export an STL container parameterized with a user-defined type (UDT), you must define the operators < and == for your UDT. For example, if you export vector<MyClass>, you must define MyClass::operator < and MyClass operator ==. This is because all STL container classes have member comparison operators that require the existence of the operators < and == for the contained type. Normally, these are not instantiated because they are not used. When you instantiate an instance of a template class, all member functions are generated. Because the STL container classes have member functions that use the operators < and == for the contained type, you must implement them. If comparing objects of your UDT does not make sense, you can define the comparison operators to simply return "true."

When the symbol _DLL is defined during compiling (this symbol is implicitly defined when compiling with /MD or /MDd to link with the DLL version of the C Runtime), the following STL classes, and various global operators and functions that operate on these classes, are already exported by the C Runtime DLL. Therefore, you cannot export them from your DLL. This should not cause a problem for the executable program that imports your class as long as it also uses the DLL version of the C run time:
Header      STL template class
------------------------------
<IOSFWD> basic_ios
<IOSFWD> <IOSFWD>
<IOSFWD> basic_istream
<IOSFWD> basic_string (also typedef'd as string and wstring)
<IOSFWD> complex
<LOCALE> messages
<XLOCALE> codecvt
<XLOCALE> ctype
<XLOCMON> moneypunct
<XLOCMON> money_get
<XLOCMON> money_put
<XLOCNUM> numpunct
<XLOCTIME> time_get
<XLOCTIME> time_put
<XSTRING> basic_string (also typedef'd as string and wstring)
For specific details on which template parameters are used and which global functions and operators are declared, please see the relevant header file.

Back to the top

Sample Code

<script type="text/javascript">loadTOCNode(2, 'moreinformation');</script>
   // -------------------------------------------
// MYHEADER.H
//disable warnings on 255 char debug symbols
#pragma warning (disable : 4786)
//disable warnings on extern before template instantiation
#pragma warning (disable : 4231)

#include <vector>

// Provide the storage class specifier (extern for an .exe file, null
// for DLL) and the __declspec specifier (dllimport for .an .exe file,
// dllexport for DLL).
// You must define EXP_STL when compiling the DLL.
// You can now use this header file in both the .exe file and DLL - a
// much safer means of using common declarations than two different
// header files.
#ifdef EXP_STL
# define DECLSPECIFIER __declspec(dllexport)
# define EXPIMP_TEMPLATE
#else
# define DECLSPECIFIER __declspec(dllimport)
# define EXPIMP_TEMPLATE extern
#endif

// Instantiate classes vector<int> and vector<char>
// This does not create an object. It only forces the generation of all
// of the members of classes vector<int> and vector<char>. It exports
// them from the DLL and imports them into the .exe file.
EXPIMP_TEMPLATE template class DECLSPECIFIER std::vector<int>;
EXPIMP_TEMPLATE template class DECLSPECIFIER std::vector<char>;

// Declare/Define a class that contains both a static and non-static
// data member of an STL object.
// Note that the two template instantiations above are required for
// the data members to be accessible. If the instantiations above are
// omitted, you may experience an access violation.
// Note that since you are exporting a vector of MyClass, you must
// provide implementations for the operator < and the operator ==.
class DECLSPECIFIER MyClass
{
public:
std::vector<int> VectorOfInts;
static std::vector<char> StaticVectorOfChars;

public:
bool operator < (const MyClass > c) const
{
return VectorOfInts < c. VectorOfInts;
}
bool operator == (const MyClass > c) const
{
return VectorOfInts == c. VectorOfInts;
}
};

// Instantiate the class vector<MyClass>
// This does not create an object. It only forces the generation of
// all of the members of the class vector<MyClass>. It exports them
// from the DLL and imports them into the .exe file.
EXPIMP_TEMPLATE template class DECLSPECIFIER std::vector<MyClass>;

// -------------------------------------------
// Compile options needed: /GX /LDd /MDd /D"EXP_STL"
// or: /GX /LD /MD /D"EXP_STL"
// DLL.CPP

#include "MyHeader.h"
std::vector<char> MyClass::StaticVectorOfChars;

// -------------------------------------------
// Compile options needed: /GX /MDd
// or: /GX /MD
// EXE.CPP

#include <iostream>
#include "MyHeader.h"

int main ()
{
MyClass x;

for (int i=0; i<5; i++) x.VectorOfInts.push_back(i);
for (char j=0; j<5; j++) x.StaticVectorOfChars.push_back('a' + j);

std::vector<int>::iterator vii = x.VectorOfInts.begin();
while (vii != x.VectorOfInts.end())
{
std::cout << *vii;
std::cout << " displayed from x.VectorOfInts" << std::endl;
vii++;
}
std::vector<char>::iterator vci = x.StaticVectorOfChars.begin();
while (vci != x.StaticVectorOfChars.end())
{
std::cout << *vci;
std::cout << " displayed from MyClass::StaticVectorOfChars";
std::cout << std::endl;
vci++;
}

std::vector<MyClass> vy;
for (i=0; i=5; i++) vy.push_back(MyClass());

return 1;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值