一个 std::string 可以通过值传递方式跨 DLL 边界吗?

4

在使用不同版本的Visual Studio编译的DLL之间,是否可以通过值传递std::string跨DLL边界传递?

2个回答

5
不行,因为模板代码是每个模块单独生成的。
所以,当你的EXE实例化一个std::string并将其传递给DLL时,DLL将开始在其上使用完全不同的实现。结果是一团糟,但它通常会有点工作,因为实现非常相似,或者这种混乱很难检测,因为它是某种微妙的堆损坏。
即使它们都使用相同版本的VS构建,也非常不稳定/脆弱,我不建议这样做。要么在模块之间使用C风格接口(例如COM),要么就不要使用DLL。
更详细的解释在这里:Creating c++ DLL without static methods 和这里:How can I call a function of a C++ DLL that accepts a parameter of type stringstream from C#?

而且,即使它们是使用相同的编译器/版本/选项构建的,两个模块(DLL或EXE)也使用不同的堆来管理动态内存分配,因此,任何重新分配或删除(例如,在传递的字符串对象末尾添加新字符)都会调用错误的堆,并导致该堆损坏和原始堆泄漏。我以前遇到过这种混乱,当时我还不知道更好的方法,尝试调试这样的问题甚至不是我最坏的敌人所希望的事情。顺便说一句,现在我喜欢使用Python绑定作为C++模块之间的粘合剂。 - Mikael Persson
我同意你回答的第一个词,但随后的解释非常误导和/或错误。重点是,只要所有执行文件和DLL都知道这个对象的外观,就可以在它们之间通过值传递字符串或其他对象。虽然正确实现有一些棘手的问题和许多陷阱,但这与模板代码无关。此外,可执行文件和DLL使用不同的堆的评论是误导性的,只需使它们两个使用相同的堆即可解决。 - Ulrich Eckhardt
从技术上讲,“模板化”或“内联”与此并没有太大关系。但在实践中,我已经见证了太多次内联代码加重了这种情况。开发人员将std库视为操作系统调用的一种黑盒DLL,没有意识到它正在为每个调用者自定义生成和嵌入。开发人员对在项目之间共享头文件毫不犹豫。但是当开发人员共享源文件时,他们知道他们正在进入危险区域,并且后果非常明显(每个模块都有单独的实现)。但是也许我可以让这更清楚些。 - tenfour
有一件事是确定的:这是一个大忌,即使有方法可以使其工作,但它太脆弱了,不能被视为良好的实践。 - tenfour
老实说,我不明白你想表达什么,"it" 指的是什么我也不清楚。据我的经验,你可以在 DLL 之间传递 C++ 对象。基本上有两件事情(每个都包含了几个方面)需要注意,一是不能违反 ODR,二是不能混合使用不兼容编译器的代码,但只要注意这些,就能做到。你也可以从许多 C++ 项目中看到它的工作原理,比如 Boost 等。 - Ulrich Eckhardt

0
通常情况下,您不能混合使用由不同编译器构建的二进制代码,这包括同一编译器的不同版本(甚至可以包括使用不同命令行选项调用的同一编译器),因此您尝试做的事情的答案是明确的“不行”。
原因是不同的编译器可能提供std::string的不同实现。例如,一个实现可能具有固定的静态缓冲区,而另一个版本则没有,这已经导致了不同的对象大小。还有一堆其他的东西可能会使接口不兼容,比如底层分配器、内部表示等。一些东西已经无法链接,由于名称重整或不同的私有API,这两者都保护您免受做错事情的伤害。
一些注意事项:
即使您没有按值而是按引用传递对象,被调用的代码也可能对此对象的外观有不同的想法。编译器提供的类型并不重要,即使您自己定义了类并使用不同版本的类定义编译了两个DLL,您也会遇到问题。如果您更改标准库实现,也会使二进制文件不兼容。其他代码位于DLL中也无关紧要,它也适用于同一可执行文件或DLL中的代码,尽管头文件和更改后的自动重新编译使这种情况变得很少见。具体针对MS Windows,您还有一个调试堆和一个发布堆,不能将在其中一个中分配的内存返回到另一个中。出于这个原因,您通常有两个DLL,一个带有“d”后缀(调试版本)和一个没有。这是编译器设置已经影响兼容性的情况,但是您也可以通过提供两个版本的DLL来解决这个问题。在某种程度上,C代码也会出现类似的问题,编译器必须同意结构布局和调用约定等等。由于年龄较大且复杂度较低,不同的C编译器实际上是兼容的。这也被认为是C相对于C ++所需的一个必要特征。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接