问题
在 windows 平台下,如果在动态库的接口中使用 std::string 或其它 std 容器,会导致崩溃或其它内存问题,所以一般要求动态库的接口必须是 C 语言实现。
原则
一个原则:某个模块中分配的空间就应该由它来释放!比如说在 dll 中分配的空间就应由这个 dll 来释放,而不应该由 main 来释放,因为 dll 中用来分配空间的环境可能和 main 中用来分配的环境不一样。关于这一点 window核心编程 中的 dll 里面进行了讲解,但是说真的我还是不明白为什么会这样。但是要记住这个原则!
示例一
main 中的如下语句:
string str1("l10"); string str2("l10value"); pi->addElement(str1, str2 ); //pi是指向 dll 提供的一个接口的指针。
dll 中是如下实现 addElement 函数的:
bool Test::addElement(string elementName, string elementValue) { // ... 省略 return true; // 返回时对 elementName,elementValue 进行析构,这导致释放它们具体字符串的空间, // 但是这些字符串的空间是在 main 中分配的,所以出现运行时错误!!!(其实是无效内存访问) }
解决方案
对于上面这种情况我们只要把 dll 的改成引用就可以了:
bool Test::addElement(string &elementName, string &elementValue) { // ... 省略 return true; }
备注:感觉把 string 用于 dll 并不是一个好主意。
示例二
前言:为什么要用浅拷贝。因为假如字符串空间很大的话,若不用浅拷贝则将非常费时且浪费空间。
- string 作为 dll 导出接口的方法的输入参数,这时可以作为引用来传递。这种情况下容易解决,如上。 这里是指 dll 中不会对 string 作任何改变。
2.dll 导出接口的方法返回一个 string,也就是字符串空间在 dll 中分配,然后在 main 中获得这个 string, 则以为在 main 中释放空间,但由于是浅拷贝,所以将出现错误。
dll 中的代码如下:
string Test::getString() { string s("abc"); return s; }
main 中的代码如下:
main() { // 省略 ... string strretdll = pi->getString(); return 0; // 返回时调用 string 的析构函数,进而释放字符串空间,但由于这个空间不是在 main 模块 // 中分配的,这将导致错误。 }
好问题出来了:在 getString 返回时为什么不会把字符串空间析构掉呢? 事实上 string 的析构函数要调用一个称为 _Tidy(bool) 的函数来处理。注意不同的 stl 实现如何析构 string 的具体方式是不一样的。
总之经过我观察之后,在 getString 返回时并不释放字符串空间,尽管执行了析构函数。我想这一点有点像智能指针。
解决方法
对于这种情况的解决方法:
- 把空间分配和释放均在 main 中,但是 main 并不知道要具体分配多少空间
- 把空间分配和释放均在 dll 中, 但是如何才能在 main 中调用 dll 的方法来要求 dll 释放空间。
// 现在该想到 com 中 IUnknown 的重要作用了吧!!!! - string 作为 dll 导出接口的方法的输出参数。这种情况同样出现情况 2 的问题。
- string 作为 dll 导出接口的方法的输入输出参数。具有输出特性时和情况 2 相似。
思考:能不能用指向 string 的指针呢?
不方便!!
最后我下一个结论:在 dll 中 string 不能作为输出属性的参数!!
所以,我们只能显式地在 dll 中定义一个输出函数,用这个输出函数来释放 dll 分配的空间!!
不过也可以在 VC 工程中使用 PROGECT—>SETTINGS 中,选选择 C/C++ CATEGORY 选择 code generation 中 user run-time lib 选择 debug multithreanded ,这样也可以避免 string 内存没有释放问题。建议一般不在动态链接库中返回 string 。
参考
☆