3

我必须在运行时动态地将模块加载为 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;
}
4

7 回答 7

4

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

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

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

更改您的代码,这样您就不会跨越 DLL 边界。有一天,您团队中的某个人更改了编译器设置或更改了第三方标头#define,并且您的程序开始崩溃,您将很难追查根本原因。

无论如何,没有看到真正的代码,我只是想猜测可能会出现什么问题。希望能帮助到你。

于 2009-06-09T05:25:48.783 回答
3

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

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

于 2009-06-09T16:39:27.557 回答
1

我没有在这段代码中看到 DLL 被卸载(正如你所说的那样)。你能发布相关代码吗?

DLL 的卸载可能至关重要,因为您的 DLL 包含破坏对象、展开堆栈等所需的代码,并且从您发布的内容中不清楚 DLL 的卸载时间。

于 2009-06-09T04:08:25.313 回答
1

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

于 2009-06-09T03:38:16.427 回答
0

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

于 2009-06-09T04:54:43.033 回答
0

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

由于错误出现在 msvcr90d.dll 中,因此您的应用程序似乎是在 DEBUG 中编译的。您使用的 dll 是否也在 DEBUG 中编译?使用 msvcr90.dll 创建内存并使用 msvcr90d.dll 释放内存(反之亦然)是使用 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 的返回类型。

于 2009-06-09T07:21:34.717 回答
0

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

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

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

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

于 2009-06-09T16:32:28.327 回答