我的理解是,暴露跨越DLL边界获取或返回stl容器(例如std::string
)的函数可能会导致问题,因为2个二进制文件中这些容器的STL实现存在差异。但是导出像以下类这样的类是否安全?
class Customer
{
public:
wchar_t * getName() const;
private:
wstring mName;
};
如果没有某种hack,mName将无法被可执行文件使用,因此它无法在mName上执行方法,也无法构造/销毁此对象。
我的直觉是“不要这样做,这是不安全的”,但我无法找出一个很好的理由。
答案 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 ++没有指定new
和delete
如何关注对象来自哪个堆。如果字符串缓冲区在一个模块上分配,而不是移动到另一个模块上的字符串实例,则存在(在销毁时)将内存返回到错误堆的风险(这取决于new/delete
和{{1相对于malloc/free
实现:这仅仅涉及STL实现相对于底层操作系统的“awarness”级别。操作本身并不具有破坏性 - 操作只是失败 - 但它泄漏了原始堆)。
所有这一切,传递容器不是不可能。由于编译器和链接器无法交叉检查,因此您可以在各方之间授予一致的实现。
答案 4 :(得分:0)
如果您想在DLL中提供真正安全的面向对象接口,我建议在COM object model之上构建它。这就是它的设计目标。
在不同编译器编译的代码之间共享类的任何其他尝试都有可能失败。你可能能够获得一些似乎在大多数时间都能正常工作的东西,但它无法保证工作。
在某些时候,您可能会依赖于调用约定或类结构或内存分配方面的未定义行为。
答案 5 :(得分:0)
C ++标准没有说明实现提供的ABI。即使在单一平台上,更改编译器选项也可能会更改二进制布局或功能接口。
因此,为了确保可以跨DLL边界使用标准类型,您有责任确保:
这不是特定于C ++的。在C中,例如malloc
/ free
,fopen
/ fclose
调用对必须分别转到单个C运行时。
这可以通过以下任一方式完成: