捕获DLL异常后访问冲突

时间:2009-06-09 03:19:58

标签: c++ exception dll virtual access-violation

我必须在运行时动态加载模块作为dll,因为它们不是提前知道的,只是它们符合类接口。我注意到的是,在我捕获到dll抛出的异常(在主线程的主程序中)之后,调用了正确的析构函数并且模块被销毁并且dll被卸载,但是然后作为catch块末尾的}当逐步步进时,Visual Studio C ++调试器到达,我得到另一个异常,它使程序崩溃

xxxxx.exe中0x68ad2377(msvcr90d.dll)的第一次机会异常:0xC0000005:访问冲突读取位置0x02958f14。

如果我启用中断异常,则打破第二个异常会将位置显示为

msvcr90d.dll!__ DestructExceptionObject(EHExceptionRecord * pExcept = 0x0017ee4c,unsigned char fThrowNotAllowed = 0)1803行+ 0xf字节

但看起来框架堆栈可能已损坏。我无法弄清楚为什么抛出这个异常。

我的代码结构的简化版本如下:

该计划的一个非常简化的结构:

//shared header:
class Module
{
public:
    virtual void Foo(void) = 0;
};


//dll:
class SomeSpecificModule : public Module
{
public:
    virtual void Foo(void);
};

void SomeSpecificModule::Foo(void)
{
    throw 1;
}

extern "C" __declspec(dllexport) Module* GetModule()
{
    return new SomeSpecificModule;
}


//program:
typedef ptrGetModule* (*GetModule)();

int main(void)
{
    HANDLE hMod = LoadLibrary("SomeSpecificModule.dll");
    ptrGetModule GetModule = (ptrGetModule)GetProcAddress(hMod, "GetModule");
    try
    {
        Module *d = GetModule();
        d->Foo();
    }
    catch (...)
    {
        cout << '!' << endl;
    }
    return 0;
}

7 个答案:

答案 0 :(得分:4)

要记住的是C运行时库的每个副本都有自己的状态。如果SomeSpecificModule.dll静态链接到C运行时库,则可能会发生此类问题。如果是这种情况,请尝试链接到C版本的C运行时库的DLL版本。您还必须确保SomeSpecificModule.dll的编译和链接方式与主模块完全相同。

你提到DLL被卸载并且调用了正确的析构函数,听起来你的真实程序比你发布的样本还要多得多。如果您在try块中卸载了SomeSpecificModule.dll,那么您已经卸载了SomeSpecificModule :: Foo()的异常记录,我想这就是你在m svcr90d.dll!__DestructExceptionObject(EHExceptionRecord * ...发生崩溃的方式

但是,通常在DLL边界上抛出异常会导致麻烦。如果您正在抛出非POD对象,则可能会遇到由不同堆,不同编译器设置,STL版本中的不同C运行时库分配的内存问题...您明白了。

更改代码,这样就不会抛出DLL边界。有一天,你团队中的某个人改变了编译器设置或第三方标题#define已经改变,你的程序开始崩溃,你将很难找到根本原因。

无论如何,在没有看到真实代码的情况下,我只想猜测可能出现的问题。希望它有所帮助。

答案 1 :(得分:3)

DLL中引发异常时需要调用的大部分堆栈展开代码都在DLL中。如果卸载DLL,该代码是如何调用的?

不要在动态链接的模块边界上抛出异常。

答案 2 :(得分:1)

您是否在实际代码中按值捕获异常?在这种情况下,catch块末尾的复制异常对象的析构函数中可能存在异常。

答案 3 :(得分:1)

我没有在这段代码中看到DLL被卸载(就像你说的那样)。您能否发布相关代码?

DLL的卸载可能是至关重要的,因为你的DLL包含了破坏对象,展开堆栈等所需的代码,并且不清楚你在哪个点上卸载了DLL。

答案 4 :(得分:0)

检查poject设置,如果您的应用程序是多线程的,那么您应该链接到多线程DLL

答案 5 :(得分:0)

这可能是在黑暗中拍摄的,但值得一试。

您的应用程序似乎是在DEBUG中编译的,因为错误显示在msvcr90d.dll中。您使用的dll是否也在DEBUG中编译?在使用dll时,使用msvcr90.dll创建内存并使用msvcr90d.dll释放(反之亦然)是一个常见问题。

我认为你的函数指针typedef看起来有点可疑。我会这样写:

typedef Module* (*moduleFnType)();

int main(void)
{
    HANDLE hMod = LoadLibrary("SomeSpecificModule.dll");
    moduleFnType GetModule = (moduleFnType)GetProcAddress(hMod, "GetModule");
    try
    {
        Module *d = GetModule();
        d->Foo();
    }
    catch (...)
    {
        cout << '!' << endl;
    }
    return 0;
}

你的typedef没有说明函数GetModule的返回类型。

答案 6 :(得分:0)

Canopus:当我抛出一个int作为异常时,同样的事情发生了。

TK ___:我在所有项目中链接到多线程dll。

Assaf和Shing Yip:这些dll确实是由FreeLibrary()在它们的包装器的析构函数中卸载的,因为包装器对象我推入了tr1 :: shared_ptr的向量(因为包装器本身是不可复制的资源)持有者等不能放在STL向量中)仅存在于try {}范围内。这似乎是正确的事情,因此我可以确保在出现错误情况时清理包括DLL卸载,我倾向于喜欢RAII风格的设计。如果这是问题的根源,那么我想知道从软件工程的角度来看,哪种设计可以正常运行并且仍然看起来很好。 是什么让我怀疑这可能不是问题,但是,当我逐步执行抛出异常时发生的析构函数调用时,FreeLibrary()运行时没有错误,我可以继续步进直到我到达结束}捕捉{}。

Magnus Skog:在发布模式下,我也会遇到崩溃而不是捕获异常然后继续正常执行。 在少数情况下使用1)operator new处理动态内存,2)tr1 :: shared_ptr,以及3)_mm_malloc / _mm_free,我需要对齐。