1

我正在使用 MSVS 9 (VS 2008)。我的应用程序以及共享库(dll)(我用来链接我的应用程序)也是 c++ 环境。现在观察以下情况:

  1. 当共享库/dll以调试模式构建并且我的应用程序也以调试模式构建时结果:应用程序执行成功

  2. 当共享库/dll以发布模式构建并且我的应用程序也以发布模式构建时结果:应用程序成功执行

  3. 当共享库/dll 在发布模式下构建并且我的应用程序也在调试模式下构建时结果:应用程序在没有从调用堆栈加载任何符号的情况下崩溃。

    调用栈:

    ntdll.dll!76e94684()
    [下面的帧可能不正确和/或丢失,没有为 ntdll.dll 加载符号]

    ntdll.dll!76e7d55f()
    ntdll.dll!76e5fa18()
    ntdll.dll!76e2b3c8()

当我尝试在我的应用程序中使用以下 SetName() 和 GetName() 定义时,会出现此问题。

    using namespace std;
    void main()
    { 
        Schema * schemaExp = new Schema();
        schemaExp -> SetName("ExpSchema");
        string srctable;
        srctable=schemaExp->GetName();
        cout <<"\nConnection EXPORT using the target table:" << srctable.c_str()  << endl;
        delete schemaExp;
    }

模式类定义:

    using namespace std;
    class Schema
    {
       public:
       TELAPI_EXPORT void   SetName(char *name); 
       TELAPI_EXPORT string     GetName(); 
      protected: 
       string tableName; 
    };
    void Schema::SetName(char *name)
    { 
       string str(name);
       tableName = str; 
    }
    string Schema::GetName()
    {
      return tableName;
    }

注意:以上只是我的应用程序的一部分,我的应用程序仅在 #3 中崩溃,并且在上面的 #1 和 #2 情况下工作正常

请帮我解决这个问题。非常感谢任何形式的帮助。

提前致谢。

4

2 回答 2

3

当共享库/dll 在发布模式下构建并且我的应用程序也在调试模式下构建时结果:应用程序在没有从调用堆栈加载任何符号的情况下崩溃。

那是因为这不是受支持的配置。默认情况下,Debug 和 Release 目标链接到不同版本的 CRT,它们(除其他外)使用不同的策略来分配内存并且彼此不兼容。

这只是更一般规则的扩展,即您不应该混合链接到不同版本 CRT 的库。所有项目都需要匹配。正如您已经看到的那样,当他们这样做时,一切正常。

对此有一些解决方法,但要做到这一点还需要做很多工作。从本质上讲,您要确保所有内存分配都在单个 DLL 中隔离,以便没有任何东西跨越模块边界。您需要从 DLL 导出特定函数以分配和释放内存,以确保分配内存的堆管理器与破坏它的堆管理器相同。new当您使用and运算符时,您不能依赖这种情况delete。坦率地说,在这种情况下,我看不出所有这些努力如何为您带来任何有用的东西。

请注意,这与是否启用优化无关(默认情况下,它们在发布版本中启用,而不在调试版本中)。该设置与链接的 CRT 版本正交。恰好“调试”和“发布”目标暗示了多个选项。您可以为一个项目打开优化并为另一个项目关闭它们,这应该可以工作,只要您确保它们都链接到相同版本的 CRT。但同样,我真的看不出这样做的意义……如果你想为一个启用优化,你为什么要为另一个抑制它们?

相关:混合调试和发布库/二进制文件 - 不好的做法?

于 2013-07-10T07:54:22.757 回答
1

这不应该是一个崩溃,只是,通常你会在 Windows 内存管理器中得到一个调试中断,以警告你你的程序正在破坏堆。Windows 中调试堆的一项功能,可在 Vista 及更高版本中使用。在“输出”窗口中查找消息。

启用符号服务器也很重要,这样堆栈跟踪变得可读和准确。使用工具 + 选项、调试、符号执行此操作,然后勾选预定义的 msdl.microsoft.com 服务器名称前面的复选框。为符号存储选择一个好的临时目录。当您再次启动程序时,它会在开始运行之前滚动一段时间,下载符号文件。这只会发生一次。您现在应该得到一个高度可读的堆栈跟踪,现在也可以返回到您的 main() 方法。

您通常会发现,跨模块边界公开 C++ 类是一件棘手的事情。不止一件事情可能出错,这里的一种故障模式是 Schema 类对象在 DLL 边界两侧的大小不同。这是因为 Debug 构建设置,_HAS_ITERATOR_DEBUGGING#define 很重要。它在 Debug 版本中默认打开,在 Release 版本中关闭。不错的调试功能,但他们实现它的唯一方法是向标准 C++ 库类添加字段。这使得调试版本中的 std::string 更大。这使您的 Scheme 课程更大。您现在正在绕过这个问题,故障模式是 DLL 的 Debug 版本和 EXE 的 Release 版本。Scheme 类构造函数会在初始化字符串时破坏堆,因为对象的分配不够大。

另一种失败模式是您的进程中有两个版本的 CRT,即应用程序中的调试版本和 DLL 中的发布版本。他们不会使用相同的堆来分配。当您在一个中分配并在另一个中释放时,哪个会出错。您的 GetName() 方法返回的字符串存在该问题,它是在 DLL 中的 GetName() 方法中创建的,并且在您的 EXE 中的方法调用后将被销毁。通过错误的分配器。在您再次对堆执行某些操作(例如删除 Scheme 对象)之前,不会检测到由此导致的堆损坏。如果您不使用 /MD 构建代码,您还将调用此故障模式。顺便说一句,这个问题在 VS2012 中得到解决,现在所有分配都是从默认进程堆中进行的。

使用一致的构建设置对于模块边界的生存至关重要。设置您的 VS 解决方案,以便始终始终为 DLL 使用正确的构建不是问题,只需确保 DLL 项目与 EXE 项目位于相同的解决方案中。

但是请注意,您将来可能会遇到麻烦,DLL 有自己的生活诀窍,并且有一天可能会被使用另一个版本的编译器构建的应用程序使用。卡布姆然后。设计您的 DLL 接口以使其永远不会发生是很有可能的,但是您将不得不放弃公开 C++ 对象。C 风格的接口是后备,COM 将其提升为对象模型的方式也是一个好方法。当然,这非常严格,只有在您不能保证 EXE 和 DLL 始终同时构建和部署时才考虑这一点。

于 2013-07-10T09:42:59.797 回答