在DLL边界使用的类中使用字符串作为私有数据成员是否安全?

时间:2013-05-29 18:27:03

标签: c++ dll

我的理解是,暴露跨越DLL边界获取或返回stl容器(例如std::string)的函数可能会导致问题,因为2个二进制文件中这些容器的STL实现存在差异。但是导出像以下类这样的类是否安全?

class Customer
{
public:
  wchar_t * getName() const;

private:
  wstring mName;
};

如果没有某种hack,mName将无法被可执行文件使用,因此它无法在mName上执行方法,也无法构造/销毁此对象。

我的直觉是“不要这样做,这是不安全的”,但我无法找出一个很好的理由。

6 个答案:

答案 0 :(得分:5)

这不是问题。因为它被更大的问题所取代,所以不能在代码中创建该类的对象,该代码位于包含该类代码的模块之外的模块中。另一个模块中的代码无法准确知道所需的对象大小,它们对std :: string类的实现可能会有所不同。正如声明的那样,它也会影响Customer对象的大小。即使是相同的编译器也无法保证这一点,例如混合优化和调试这些模块的构建。虽然这通常很容易避免。

所以必须为Customer对象创建一个类工厂,这个工厂位于同一个模块中。然后,它自动暗示接触“mName”成员的任何代码也存在于同一模块中。因此是安全的。

接下来的步骤是公开客户,但公开一个纯抽象基类(又名接口)。现在,您可以阻止客户端代码创建Customer实例并开始关闭。而且你也可以轻而易举地隐藏std :: string。基于接口的编程技术在模块互操作场景中很常见。也是COM采取的方法。

答案 1 :(得分:1)

只要类和解除分配器的实例的分配器具有相同的设置,你应该没问题,但你应该避免这种情况。
.exe和.dll之间在调试/发布方面的差异,代码生成(多线程DLL与单线程)可能会在某些情况下导致问题。
我建议在DLL接口中使用抽象类,只在DLL内部进行创建和删除 接口如:

class A {
protected:
  virtual ~A() {}
public:
  virtual void func() = 0;
};

//exported create/delete functions
A* create_A();
void destroy_A(A*);

DLL实现如:

class A_Impl : public A{
public:
  ~A_Impl() {}
  void func() { do_something(); }
}

A* create_A() { return new A_Impl; }
void destroy_A(A* a) { 
  A_Impl* ai=static_cast<A_Impl*>(a);
  delete ai;
}

应该没问题。

答案 2 :(得分:1)

即使您的类没有数据成员,也不能指望它可以从使用不同编译器编译的代码中使用。 C ++类没有通用的ABI。对于初学者来说,你可以预期名称差异会有所不同。

如果您准备约束客户端使用与您相同的编译器,或者提供源以允许客户端使用其编译器编译代码,那么您可以在您的界面上执行几乎任何操作。否则你应该坚持使用C风格的界面。

答案 3 :(得分:0)

还有两个“潜在的错误”(除其他外)你必须小心,因为它们与语言“在”之下有关。

首先,std :: strng是一个模板,因此它在每个翻译单元中实例化。如果它们都链接到同一个模块(exe或dll),则链接器将解析与相同代码相同的功能,并最终将不一致的代码(具有不同主体的相同功能)视为错误。
但如果它们链接到不同的模块(和exe和dll),则没有任何共同点(编译器和链接器)。所以 - 取决于编译模块的方式 - 你可能有不同的成员和内存布局的同一类的实现(例如,一个可能有一些调试或分析添加功能,另一个没有)。使用另一方编译的方法访问在一侧创建的对象,如果您没有其他方式来授予实现一致性,可能会以泪流满面。

第二个问题(更微妙)涉及内存的分配/解除分配:由于Windows的工作方式,每个模块都可以有一个不同的堆。但是标准C ++没有指定newdelete如何关注对象来自哪个堆。如果字符串缓冲区在一个模块上分配,而不是移动到另一个模块上的字符串实例,则存在(在销毁时)将内存返回到错误堆的风险(这取决于new/delete和{{1相对于malloc/free实现:这仅仅涉及STL实现相对于底层操作系统的“awarness”级别。操作本身并不具有破坏性 - 操作只是失败 - 但它泄漏了原始堆)。

所有这一切,传递容器不是不可能。由于编译器和链接器无法交叉检查,因此您可以在各方之间授予一致的实现。

答案 4 :(得分:0)

如果您想在DLL中提供真正安全的面向对象接口,我建议在COM object model之上构建它。这就是它的设计目标。

在不同编译器编译的代码之间共享类的任何其他尝试都有可能失败。你可能能够获得一些似乎在大多数时间都能正常工作的东西,但它无法保证工作。

在某些时候,您可能会依赖于调用约定或类结构或内存分配方面的未定义行为。

答案 5 :(得分:0)

C ++标准没有说明实现提供的ABI。即使在单一平台上,更改编译器选项也可能会更改二进制布局或功能接口。

因此,为了确保可以跨DLL边界使用标准类型,您有责任确保:

  • 标准类型的资源获取/发布由同一DLL完成。 (注意:您可以在进程中拥有多个crt,但crt1.DLL获取的资源必须由crt1.DLL发布。

这不是特定于C ++的。在C中,例如malloc / freefopen / fclose调用对必须分别转到单个C运行时。

这可以通过以下任一方式完成:

  • 明确导出采集/释放功能(Photon's answer)。在这种情况下,您被迫使用工厂模式和抽象类型。基本上COM或COM克隆
  • 强制一组DLL链接到同一个动态CRT。在这种情况下,您可以安全地导出任何类型的函数/类。