关于DLL接口处传入string类型在其析构时报堆损坏的问题

来源:互联网 发布:淘宝买普通话二甲证书 编辑:程序博客网 时间:2024/05/09 08:45

最近已经遇到这个问题两次了,看了网上一些资料了解了下,引用两篇过来,以免忘记


文章一:出处http://hi.baidu.com/anowsober/blog/item/9c1abcd95d20b4ee38012f6b.html


 

一次 STL string 析构异常原因分析
2009-01-23 16:31

  本剧关键字:exe、dll、STL string、local heap。

  情景:一个结构体 ITEM 中包含了多个 string 类型字段,在 exe 中的 ExeFunc 中定义 ITEM 变量 item,传 item 的引用到 DllFunc 函数中,DllFunc 是一个 dll 的导出函数,负责从文件中读取一些数据到 ITEM 结构中。在 ExeFunc 结束的时候 string 异常了,提示堆内存损坏。排除法发现多个 string 字段中只有一个引发了异常,其它的都没有问题,觉得很奇怪。string 异常的字段赋值的字符串大于 16 个字符(string 默认是15 还是 16 个 byte 来着)其它的都小于 16 个字符,显然是 string 对象重新分配内存了。

  查看内存也没有发现异常情况,怀疑 string 有问题,于是另外建了个工程测试 string,单个 exe 或者模拟工程中建立一个 dll 赋值给引用 string 变量都正常,而且字符数大于 16 个。为什么为什么为什么呢?同样是重新分配了内存,差别怎么就这么大捏。

  跟踪定位到 _CrtIsValidHeapPointer ,注意到 dbgheap.c 文件中 _CrtIsValidHeapPointer 处注释:

         /*
         * If this ASSERT fails, a bad pointer has been passed in. It may be
         * totally bogus, or it may have been allocated from another heap.
         * The pointer MUST come from the 'local' heap.
         */

         _ASSERTE(_CrtIsValidHeapPointer(pUserData));

  搜索到一些关于本地堆(local heap)的文章,才明白是因为 dll 如果静态链接了运行时库,dll 就会拥有独立于应用程序堆(也称作local heap)的运行时堆实例。此时在 dll 外部就不能访问此 local heap,所以也就有上面所出现的异常啦。MSDN 中也有介绍:

  The _CrtIsValidHeapPointer function is used to ensure that a specific memory address is within the local heap. The local heap refers to the heap created and managed by a particular instance of the C run-time library. If a dynamic-link library (DLL) contains a static link to the run-time library, it has its own instance of the run-time heap, and therefore its own heap, independent of the application's local heap. When _DEBUG is not defined, calls to_CrtIsValidHeapPointer are removed during preprocessing.

  查找这个问题浪费了不少的时间,解决办法就是在编译 dll 时动态链接运行时库( VS2005 下动态链接 MFC 库默认就是动态链接运行时库,如果更改静态链接 MFC 库,运行时库的链接方式也跟着改变为静态的),并且 exe 中也要使用相同的链接方式才行。这或许是有时候改变 MFC 库的链接方式会引起一些莫名错误的原因吧;对了在 _CrtIsValidHeapPointer 内部也有这样的注释:

         /*
         * Go through the heap regions and see if the pointer lies within one
         * of the regions of the local heap.
         *
         * Pointers from non-local heaps cannot be handled. For example, a
         * non-local pointer may come from a DLL that has the CRT linked-in.
         *
         */

  结论:应用程序使用的 dll 库在链接时尽量使用相同的链接方式,大多数情况下不同的链接方式不会出现问题,但有少数情况总是存在滴。俺要是在第一时间看清楚明了的 MSDN 就不会走N多的弯路了555(在弯路上也认识了不少东西算是弥补吧呵呵)。


文章二:出处http://topic.csdn.net/u/20091202/09/999d3d15-cec4-4e40-8ad3-e1f35442d9ef.html


 

动态库接口层使用STL的运行异常及一个解决思路


楼主发表于:2009-12-02 09:17:54
我平时编码中,喜欢使用STL的东西,主要是自己太懒了。
在内部使用一下无所谓,但是在动态库接口层使用,会有问题。比如我的动态库有接口如下:

C/C++ code
class __declspec(dllexport) FDAClient_Helper{ bool get_child_path(vector<string> &child_path, const string &parent);}

我们假定这个接口的作用是根据输入的目录路径,查找目录包含的所有子目录。

如果是静态编译,也就是没有__declspec(dllexport)。编译为一个lib文件。这样供exe直接链接,那么运行是没有问题的。

但是如果我们动态编译为一个dll和lib导入库。那么运行的时候几乎是100%抛异常的。异常是发生了child_path使用后,自动析构的时候。

原因分析:STL是模板编码的,实际编码后,dll的运行空间和exe的运行空间并不在一起。而child_path在exe这边声明,然后在dll里面分配内存,最后在exe这边释放内存。

首先说一个不优雅的解决方式:

修改接口
C/C++ code
class __declspec(dllexport) FDAClient_Helper{ bool get_child_path(vector<string> &child_path, const string &parent); void destroy(vector<string> &vec_str);}//////////////////////////////////////////////////////////////////////////void FDAClient_Helper::destroy(vector<string> &vec_str){ vec_str.clear();}

每次使用child_path,在系统会自动析构它之前,首先调用destroy(child_path).

这样就实现了,在dll这边申请空间,同时也在dll这边释放空间。

唯一不爽的就是每次需要显示调用destroy,很不优雅。


再说一个稍微优雅的方法:
以string为例
C/C++ code
class __declspec(dllexport) smart_string { public: smart_string(); ~smart_string(); const char *get_data() const; void set_data(const char *data); void clear(); smart_string & operator += (const char *data); private: std::string *data; }; smart_string::smart_string() { data = new std::string(); } smart_string::~smart_string() { if (data) { data->clear(); delete data; } } const char * smart_string::get_data() const { return data->c_str(); } void smart_string::set_data(const char *_data) { data->clear(); data->replace(0, data->size(), _data); } void smart_string::clear() { data->clear(); } smart_string & smart_string::operator += (const char *_data) { *data += _data; return *this; }

这样在凡是需要string的地方,都用smart_string代替,exe中每次使用smart_string以后,出了作用域,系统会自动析构smart_string。而dll空间会使用~smart_string会正确释放内存的。

vector等都可以使用这种方式。这样使用不会有使用STL的麻烦,却有使用STL的便利。


希望高手能够提供更加优雅的写法。当然最好的方法还是在接口层不使用类,只是有结构体和简单变量。