【问题标题】:How does pure virtual functions mechanism in c++ exposes functions from DLLc++中的纯虚函数机制如何从DLL中暴露函数
【发布时间】:2017-07-12 20:04:25
【问题描述】:

考虑下一个 c++ 代码片段

1.在 EXE 中:

Base.hpp

#ifndef _BASE_H_
#define _BASE_H_ 
class Base
{
    public:
    Base(){};
    virtual double Add(double &x, double &y) = 0; 

};
#endif

ma​​in.cpp

#include <iostream>
#include "Base.hpp"
#include "DerivedFactory.hpp"
void main()
{
    Base *theBase = DerivedFactory::Create();
    double x = 4.9, y = 3.3,z;
    z = theBase->Add(x, y);//Works when Add is pure virtual function, but how??? 
                           //Linker error when Add is not pure virtual
}

2。在隐式链接的 DLL 中

Derived.hpp

#ifndef _DERIVED_H_
#define _DERIVED_H_
#include "Base.hpp"
class Derived : public Base
{
    public:
    double Add(double &x, double &y);
};
#endif

DerivedFactory.hpp

#ifndef _DERIVEDFACTORY_H_
#define _DERIVEDFACTORY_H_
#include "Derived.hpp"

class DerivedFactory
{
    public:
    __declspec(dllexport) static  Derived* Create();
};
#endif

Derived.cpp

#include "Derived.hpp"
 double Derived::Add(double &x, double &y)
{
    return x + y;
}

DerivedFactory.cpp

#include "DerivedFactory.hpp"
Derived* DerivedFactory::Create()
{
    Derived* theDerived = new Derived;
    return theDerived;
}
  1. 主要问题是,当唯一导出的函数是 Create() 时,exe 如何“知道”从 dll 中正确执行 Add?

  2. 当 Add() 是“只是”虚拟而不是纯虚拟时,为什么会出现链接器错误?

【问题讨论】:

  • 另外说明,以下划线开头后跟大写字母的名称(_BASE_H__DERIVED_H__DERIVED_FACTORY_H_)和包含两个连续下划线的名称保留供执行。不要在你的代码中使用它们。
  • 另外,将 Base 析构函数设为虚拟或受保护。使用非虚析构函数,对Base指针调用delete是UB;受保护时,会阻止调用 delete 并且您可能有一个单独的销毁方法(这更好,因为 DLL 和 EXE 可能有不同的堆)。

标签: c++ dll


【解决方案1】:
  1. DLL 将在其全局数据部分的某处包含Derived 的vtable。在 DLL 中创建的 Derived 的实例会将其 vtable 指针分配给该地址。

  2. 如果BaseAdd() 声明不是纯虚的,则必须在声明之上为其提供定义。

编辑:关于什么是 vtable 的解释,请参考这个答案: why do I need virtual table?

从纯粹的风格角度来看,我还要提一下,在这种情况下,工厂通常会返回 Base 类的实例,如下所示:

DerivedFactory.hpp

#include "Base.hpp"

class DerivedFactory
{
public:
  __declspec(dllexport) static  Base* Create();
};

这样,Derived 的封装完全且整齐地保留在 DLL 中。

【讨论】:

    【解决方案2】:

    Derived 中的虚拟表是 DLL 在创建实例时设置的,所以当通过 Base 指针调用方法时,虚拟表中已经有正确的地址设置在虚拟表中(EXE 将使用 Derived 虚拟来自 DLL 的表)。

    注意EXE在链接的时候不会从DLL中导入虚拟表,因为它不需要(它通过Base接口访问内存中DLL的Derived的虚拟表)。因此,它不仅适用于隐式链接,而且适用于在运行时加载 DLL 时(LoadLibrary/GetProcAddress - 但工厂方法最好不是工厂类的一部分,而是独立的 extern "C" 导出函数)。

    有关纯虚拟接口和 DLL 的更多详细信息,另请参阅此处:Providing SDK for my C++ Application

    【讨论】:

      猜你喜欢
      • 2023-03-12
      • 1970-01-01
      • 2014-01-18
      • 1970-01-01
      • 2018-08-08
      • 1970-01-01
      • 2010-10-12
      • 1970-01-01
      相关资源
      最近更新 更多