7

所以,我有一个带有 MSVCRT 静态链接副本的 C++ 库。我希望任何人都能够将我的库与任何版本的 MSVC 运行时一起使用。实现这一目标的最佳方法是什么?

我已经对事情的完成方式非常小心了。

  1. 内存永远不会通过要释放的 DLL 屏障
  2. 运行时 C++ 对象不会跨越障碍(即向量、地图等。除非它们是在障碍的那一侧创建的)
  3. 没有文件句柄或资源句柄在屏障之间传递

然而,我仍然有一些导致堆损坏的简单代码。

我的图书馆里有一个像这样的对象:

class Foos
{
public: //There is an Add method, but it's not used, so not relevant here
    DLL_API Foos();
    DLL_API ~Foos();

private:
    std::map<std::wstring, Foo*> map;
};

Foos::~Foos()
{
    // start at the begining and go to the end deleting the data object
    for(std::map<std::wstring, Foo*>::iterator it = map.begin(); it != map.end(); it++)
    {
        delete it->second;
    }
    map.clear();
}

然后我从我的应用程序中使用它,如下所示:

void bar() {
    Foos list;
}

从任何地方调用此函数后,我都会收到有关堆栈损坏的调试警告。如果我真的让它用完,它实际上会破坏堆栈和段错误。

我的调用应用程序是使用 Visual Studio 2012 平台工具编译的。该库是使用 Visual Studio 2010 平台工具编译的。

这只是我绝对不应该做的事情,还是我实际上违反了使用多个运行时的规则?

4

4 回答 4

9

内存永远不会通过 DLL 屏障

但是,确实如此。事实上很多次。您的应用程序为类对象创建了存储,在本例中是在堆栈上。然后传递一个指向库中方法的指针。从构造函数调用开始。该指针就是库代码中的this 。

在这种情况下出现的问题是它没有创建正确的存储量。你得到了 VS2012 编译器来查看你的类声明。它使用 std::map 的 VS2012 实现。然而,您的库是用 VS2010 编译的,它使用了完全不同的 std::map 实现。具有完全不同的尺寸。得益于 C++11 的巨大变化。

这只是工作中的完全内存损坏,应用程序中写入堆栈变量的代码将损坏 std::map。反之亦然。

跨模块边界公开 C++ 类充满了类似的陷阱。仅当您可以保证使用完全相同的编译器版本和完全相同的设置编译所有内容时才考虑它。没有捷径,你也不能混合调试和发布构建代码。制作库以便暴露实现细节当然是可能的,您必须遵守以下规则:

  • 只用虚方法暴露纯接口,参数类型必须是简单类型或接口指针。
  • 使用类工厂创建接口实例
  • 使用引用计数进行内存管理,因此它始终是释放的库。
  • 用硬性规则确定核心细节,如打包和调用约定。
  • 绝不允许异常跨越模块边界,只使用错误代码。

到那时你会很好地编写 COM 代码,以及你在 DirectX 中看到的样式。

于 2013-11-14T16:44:54.350 回答
3

map成员变量仍然由应用程序创建,其中一些内部数据由应用程序而不是 DLL 分配(并且它们可能使用不同的实现map)。根据经验,不要使用 DLL 中的堆栈对象,在 DLL 中添加类似的内容Foos * CreateFoos()

于 2013-11-14T15:59:01.350 回答
3

运行时 C++ 对象不会跨越障碍(即向量、地图等。除非它们是在障碍的那一侧创建的)

你正在这样做。您的 Foos 对象由堆栈上的主程序创建,然后在库中使用。该对象包含一个地图作为它的一部分......

当您编译主程序时,它会查看头文件等以确定为 Foos 对象分配多少堆栈空间。并调用库中定义的构造函数......这可能期望对象的布局/大小完全不同

于 2013-11-14T16:13:43.970 回答
0

它可能不符合您的需求,但不要忘记在头文件中实现整个事情可以简化问题(有点):-)

于 2013-11-14T19:23:03.487 回答