1

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

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

private:
  wstring mName;
};

如果没有某种 hack,mName 将不能被可执行文件使用,因此它不能在 mName 上执行方法,也不能构造/破坏这个对象。

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

4

6 回答 6

5

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

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

下一步是根本不公开 Customer,而是公开一个纯抽象基类(又名接口)。现在,您可以阻止客户端代码创建 Customer 的实例并击退他们的腿。而且您还将轻松隐藏 std::string 。基于接口的编程技术在模块互操作场景中很常见。也是COM采取的方法。

于 2013-05-29T20:35:48.130 回答
1

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

如果您准备约束客户端使用与您相同的编译器,或提供源以允许客户端使用他们的编译器编译您的代码,那么您几乎可以在您的界面上做任何事情。否则你应该坚持使用 C 风格的接口。

于 2013-05-29T19:05:43.423 回答
1

只要类实例的分配器和释放器的设置相同,就可以了,但是避免这种情况是正确的。
就调试/发布、代码生成(多线程 DLL 与单线程)而言,.exe 和 .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;
}

应该可以。

于 2013-05-29T18:39:17.443 回答
0

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

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

第二个问题(更微妙)与内存的分配/释放有关:由于 windows 的工作方式,每个模块都可以有一个不同的堆。但是标准的 C++ 并没有指定如何new以及delete关心对象来自哪个堆。如果字符串缓冲区是在一个模块上分配的,而不是移动到另一个模块上的字符串实例,则可能(在销毁时)将内存归还给错误的堆(这取决于如何new/delete以及如何malloc/free实现HeapAlloc/HeapFree:这仅涉及STL实现对底层操作系统的“意识”级别。操作本身不是破坏性的——操作只是失败了——但它会泄漏源的堆)。

综上所述,通过容器并非不可能。由于编译器和链接器无法交叉检查,因此您可以在双方之间授予一致的实现。

于 2013-05-29T19:09:39.773 回答
0

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

任何其他在由不同编译器编译的代码之间共享类的尝试都有可能失败。您可能会得到一些似乎在大多数情况下都有效的东西,但不能保证它有效。

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

于 2013-05-29T19:11:05.150 回答
0

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

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

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

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

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

  • 通过显式导出获取/释放功能(Photon 的回答)。在这种情况下,您被迫使用工厂模式和抽象类型。基本上是COM或 COM 克隆
  • 强制一组 DLL 链接到同一个动态 CRT。在这种情况下,您可以安全地导出任何类型的函数/类。
于 2013-05-29T19:12:55.707 回答