我正在使用MSVS 9(VS 2008)。我的应用程序以及共享库(dll)(我用来链接我的应用程序)也是c ++环境。现在观察以下情况:
在调试模式下构建共享库/ dll时,我的应用程序也在调试模式下构建 结果:应用程序已成功执行
在发布模式下构建共享库/ dll时,我的应用程序也在发布模式下构建 结果:应用程序已成功执行
在发布模式下构建共享库/ 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情况下正常工作
请帮我解决这个问题。非常感谢任何形式的帮助。
提前致谢。
答案 0 :(得分:3)
当在发布模式下构建共享库/ dll并且我的应用程序也在调试模式下构建时结果:应用程序崩溃而不从调用堆栈加载任何符号。
那是因为这不是受支持的配置。默认情况下,调试和发布目标链接到CRT的不同版本,其中(除其他外)使用不同的策略来分配内存并且彼此不兼容。
这只是更一般规则的扩展,您不应该混合链接到不同版本CRT的库。所有项目都需要匹配。正如你已经看到的,当他们这样做时,一切都正常。
这方面有一些解决方法,但要做到这一点,还有很多工作要做。实质上,您确保在单个DLL中隔离所有内存分配,以便不会跨越模块边界。您需要从DLL中导出特定的函数来分配和释放内存,以确保分配内存的堆管理器与销毁内存的堆管理器相同。使用new
和delete
运算符时,不能依赖于此情况。坦率地说,在这种情况下,我看不出所有这些努力如何为你带来任何有用的东西。
请注意,这与是否启用了优化无关(默认情况下,它们是发布版本,而不是在Debug版本中)。该设置与链接的CRT版本正交。恰好“Debug”和“Release”目标意味着多个选项。您可以打开一个项目的优化并将其关闭另一个项目,这应该有效,只要您确保它们都链接到相同版本的CRT。但同样,我真的没有看到这样做的意义......如果你想为一个启用优化,你为什么要让它们被另一个抑制?
答案 1 :(得分:1)
这不应该是崩溃,但是,通常你会在Windows内存管理器中得到一个调试中断,以警告你程序正在破坏堆。 Windows中调试堆的一个功能,可在Vista及更高版本上使用。在“输出”窗口中查找消息。
启用符号服务器也很重要,这样堆栈跟踪就变得可读和准确。使用工具+选项,调试,符号并勾选预定义的msdl.microsoft.com服务器名称前面的复选框。为符号存储选择一个好的临时目录。当您再次启动程序时,它将在开始运行之前暂停一段时间,下载符号文件。这只发生过一次。您现在应该获得一个高度可读的堆栈跟踪,现在也可以返回到main()方法。
您通常发现的是,跨模块边界公开C ++类是一件棘手的事情。不止一件事可能出错,这里的一种失败模式是Schema类对象在DLL边界两侧的大小不同。这是因为调试构建设置,_HAS_ITERATOR_DEBUGGING #define很重要。它在Debug版本中默认打开,在Release版本中关闭。很好的调试功能,但他们实现它的唯一方法是向标准C ++库类添加字段。这使得std :: string在Debug版本中更大。这使您的Scheme类更大。您现在正在避开此问题,失败模式是具有EXE版本的DLL的调试版本。然后,Scheme类构造函数在初始化字符串时会破坏堆,因为对象的分配不够大。
另一种失败模式是您的流程中有两个版本的CRT,应用程序中的调试版本以及DLL中的发行版本。他们不会使用相同的堆来分配。当你在一个中分配并在另一个中释放时会出错。 GetName()方法返回的字符串会遇到该问题,它是在DLL内部的GetName()方法中创建的,并且在EXE中调用方法后将被销毁。由错误的分配器。在您再次对堆执行某些操作(例如删除Scheme对象)之前,不会检测到导致这种堆的损坏。如果不使用/ MD构建代码,也可以调用此故障模式。在VS2012 btw中解决了这个问题,所有分配现在都是从默认进程堆中完成的。
使用一致的构建设置对于在模块边界中生存至关重要。设置VS解决方案以便始终始终如一地使用正确的DLL构建不是问题,只需确保DLL项目与EXE项目位于同一解决方案中。
但请注意,将来你可能会遇到麻烦,DLL有自己的生活诀窍,并且可能有一天会被另一个版本的编译器构建的应用程序使用。 Kaboom然后。设计你的DLL接口,这样就可以永远不会发生,但你必须放弃暴露C ++对象。 C风格的界面是后备,COM将其提升为对象模型的方式也是一种很好的方法。当然,这是相当严苛的,如果你不能保证EXE和DLL总是同时构建和部署,那么只考虑这个。