在另一个DLL中调用虚函数时的Segfault

时间:2010-11-30 21:49:47

标签: c++ windows dll

我有一个由两部分组成的程序,其中主核需要注册适配器,然后回调到寄存器中。这些部分位于单独的DLL中,并且核心不知道适配器的细节(除了提前的方法和参数)。我尝试使用以下代码进行设置,并在每次核心尝试调用适配器方法时收到段错误:

Core.hpp / cpp(合并和简化)

class Core
{
public:
    Core()
        : mAdapter(NULL)
    { }

    void DoStuff(int param)
    {
        if ( this->mAdapter )
        {
            this->mAdapter->Prepare(param);
        }
    }

    void Register(Adapter * adapter)
    {
        this->mAdapter = adapter;
    }

private:
    Adapter * mAdapter;
};

Adapter.hpp / cpp(在核心库中)

class Adapter
{
public:
    Adapter(Core * core) { }

    virtual void Prepare(int param)
    {
        // For testing, this is defined here
        throw("Non-overridden adapter function called.");
    }
};

AdapterSpecific.hpp / cpp(第二个库)

class Adapter_Specific
    : Adapter
{
public:
    Adapter_Specific(Core * core)
    {
        core->Register(this);
    }

    void Prepare(int param) { ... }
};

在构建第一个模块(核心)时,所有类和方法都标记为DLL导出,核心标记为export,适配器在构建适配器时作为导入。

代码运行正常,直到调用Core :: DoStuff。从遍历程序集开始,它似乎从vftable中解析了函数,但它最终得到的地址是0x0013nnnn,我的模块在0x0084nnnn及以上范围内。 Visual Studio的源调试器和intellisense显示vftable中的条目是相同的,并且适当的条目确实转到一个非常低的地址(一个也转到0xF-something,这似乎同样奇怪)。

为了清晰起见,

编辑:执行永远不会重新进入adapter_specific类或模块。调用的假定地址无效,执行在那里丢失,导致段错误。这不是适配器类中任何代码的问题,这就是我将其排除在外的原因。

我在调试模式下不止一次重建了两个库。这是纯粹的C ++,没什么特别的。我不确定为什么它不起作用,但我需要回调到另一个类,而宁愿避免使用函数ptrs的结构。

有没有办法在模块之间使用这样的简单回调,或者它在某种程度上是不可能的?

4 个答案:

答案 0 :(得分:0)

你说你的所有方法都被声明为DLL导出。方法(成员函数)不必以这种方式标记,导出类就足够了。如果你这样做,我不知道这是否有害。

答案 1 :(得分:0)

  1. 你正在使用很多指针。他们的生命在哪里管理?
  2. Adapter_Specific在您的示例
  3. 中继承了Adapter
  4. 适配器具有虚拟方法,因此可能还需要虚拟析构函数
  5. Adapter也没有默认构造函数,因此Adapter_Specific的构造函数不会编译。您可能希望它使用相同的参数构造基类。这不会自动发生。但是请参见第6点。
  6. 通常应将明确声明一个参数(复制构造函数除外)的构造函数声明为显式。
  7. 基类Adapter接受一个不使用的参数。

答案 2 :(得分:0)

这个问题最终成了我的一个愚蠢的错误。

在代码中的另一个函数中,我不小心为Core *而不是Adapter *提供了一个函数,并且在我的阅读中没有捕获它。不知怎的,编译器也没有捕获它(它应该失败,但没有给出隐式转换警告,可能是因为它是一个引用计数点)。

试图将Core *转换为适配器并从该突变对象获取vftable,该对象失败并导致段错误。我把它修好为合适的类型,现在一切正常。

答案 3 :(得分:0)

关于类和类成员的

__declspec(dllexport)是一个非常糟糕的主意。最好使用一个接口(只包含虚函数的基类,它与你不想要的“函数指针结构”基本相同,除了编译器处理所有细节),并且只使用__declspec(dllexport)用于工厂功能等全局功能。特别是不要直接跨DLL边界调用构造函数和析构函数,因为你会得到不匹配的分配器,暴露一个包含特殊函数的普通函数。