如何在库之间传递对象和调用成员函数?

时间:2016-06-03 10:38:06

标签: c++ qt gcc

当我将核心应用程序的类文件包含在库的编译(Qt-Plugin)中时,我试图理解会发生什么。假设我有一个插件 - 一个处理程序 - 和一个Query(hcpp)(私有实现) - 要处理的对象。

修改

query.h(来自链接)

class Query final
{
public:
    friend class ExtensionManager;

    Query(const QString &term);
    ~Query();
    void addMatch(shared_ptr<AlbertItem> item, short score = 0);
    void reset();
    void setValid(bool b = true);
    bool isValid();
private:
    QueryPrivate *impl;
};

我假设编译器(至少在链接阶段)获取目标文件并将其放入共享对象文件中。但实际上名称查询没有出现在cmake编译和链接过程的输出中(实际上是执行的g ++命令),只是其目录的包含。

当我编译插件/库时,编译器/链接器除了检查接口/头之外还做了什么吗?插件如何在运行时了解有关Query的任何信息?插入调用如何在运行时对对象起作用?

1 个答案:

答案 0 :(得分:3)

  

插件如何在运行时了解查询?

在不同的编译单元(dll,共享对象,可执行文件)之间共享信息是一个有问题的设计。

  • C ++ ABI没有标准。这允许不同的编译器提供者布置它们的对象(例如,vtable的位置,虚拟析构函数在vtable中的位置),以及如何在同一台机器上以不同方式调用方法。
  • .h文件是一个弱接口定义,并且#define可能在同一事物的不同编译器之间存在差异。 (例如,Microsoft调试STL不适用于发行版STL)。
  • 存储在.h中的内联函数可能导致在库和插件之间调用不同的实现。
  • 内存管理可能会受到影响,因为释放对象的代码可能无法理解分配的位置和方式。

修改数据

假设一个类有公共成员,(并且两个模块共享一个编译器),可以在创建该对象的库和实现它的库中修改它们。

class Example1 {
    public:
      int value1;
};

在可执行文件中。

example1.value1 = 12;
插件中的

if( this->value1 == 12 ){
}

这不适用于复杂的对象,例如std::string

调用函数

class Example2 {
      public:
      void AFunction();
};

AFunction的任何调用者都需要可用的实现。这将被静态调用,并且可以在二进制文件和共享对象之间共享

 +-------------------+          +-----------------------+
 | binary            |          | shared object         |
 | Query::AFunction()|          |                       |
 | {                 |          |  Process( Query &q )  |
 | }                 |          |  {                    |
 |                   |    o-->  |     q.AFunction();    | <<< may be in
 | main()            |    |     |                       | shared object
 | {                 |    |     |                       | could call binary
 |    Query q;       |    |     |                       |
 |    Process( q );  | ===o     |                       |
 +-------------------+          +-----------------------+

如果共享对象有一个实现(它是一个内联函数,或者query.cpp包含在共享对象makefile中),那么AFunction的实现可能是不同的。 / p>

**使用STL - 两个二进制文件都有自己的实现,如果它们在不同时间编译,可能会有所不同(并且不兼容)。 **

共享对象的行为是这样的,如果它有未解析的外部,它正在加载它的二进制文件,它将使用它们的实现。在Windows上不是这样,可以使用-z, defs生成窗口行为。

为了调用非虚函数,调用者需要在编译时知道该类。该方法是固定调用,第一个(通常)参数是this指针。因此,为了生成代码,编译器直接(或通过修复表)调用函数。

调用虚拟函数

虚拟函数总是通过this指针调用,这意味着选择了一个类的虚函数&#39;通过构造对象的代码。这在Windows中用于COM实现,并且是一种用于对象共享的有用技术 - 允许在框架编译之后提供具有不同功能的新类,但没有任何知识调用实现对象。

vtable需要稳定才能工作。编译调用者和被调用者时,基类或接口应该是相同的。

设计库时,可以生成一个接口对象。

class ICallback {
     virtual void Funcion1( class MyData * data ) = 0;
};

编译库时,它不知道实现 ICallback及其任何功能,但它确实知道如何调用它们。

所以函数定义

class Plugin {
     bool Process( ICallback * pCallback );
};

允许声明和实现函数,而不知道回调的实现(ICallback)。这不会创建一个未解析的符号,也不需要插件在编译插件之前知道该项目。它需要的是,它的调用者(m_pluginObject.Process( &myQueryImplementation );)具有一个可以传入的具体类型。

汇编

当编译器编译代码时,它会为windows创建一个目标文件(.obj,在unix上创建.o)。

在此文件中,是链接文件所需的所有代码和数据定义。

名义对象文件

<dictionary>
    int SomeIntValue = Address1
    bool Class1::SomeFunction( char * value ) = Address2
</dictionary>
<Requires>
    std::ostream::operator<<( const char *);
    std::cout
</Requires>
<Data>
      Address1 : SomeIntValue = 12
</Data>
<Code>
    Address2 .MangledSomeFunctionCharStarBool
                  // some assembly
          call ostream::operator<<(char*)
</Code>

此objecf文件中应包含足够的信息以满足编译过程的一部分。虽然MyClass.cc这样的文件通常可能具有实现MyClass所需的所有功能,但它不需要具备所有这些功能。

当编译器正在读取头文件或任何类声明时,它正在创建一个未解析的外部列表,稍后将需要它。

 class Class1 {
       int ClassData;
    public:
        bool SomeFunction( char * value);
        ....
 };

描述Class1的成员函数接受char *作为值,并且返回值为bool。在编译C ++程序时,可以在编译器看到

时实现这个未解析的函数
  bool Class1::SomeFunction( char * value )
  {
     bool success = false;
     cout << value;
       // some work
      return success;
  }  

这个实现的函数被添加到实现的字典中,并且它所需的函数和数据被添加到需求中。

图书馆文件

库文件在unix和windows上略有不同。最初,unix库文件是.o文件的容器。这些只是.o的连接项(ar)。然后,为了找到正确的项目,将库编入索引(ranlib)以生成工作库。最近我认为档案的标准已经改变,但概念必须保留。

链接库

在windows中,在构建DLL时会创建一个链接库,在unix中,链接库内置在共享对象中。

链接库是动态加载对象的可交付成果列表,以及提供它的.dll.so的名称。这导致信息被添加到二进制文件中,例如: -

<SharedObjects>
     printf : glibc:4.xx
</SharedObjects>

描述需要加载的共享对象及其提供的功能(该程序的子集)。

链接

当编译器生成二进制文件(.so.dll.exe或unix二进制文件)时,命令行中指定的目标文件将绑定到二进制文件中。这会创建一组已实现的函数(例如main)和一组未解决的需求。

然后搜索每个库(.a.lib)以查看它们是否提供完成整个过程所需的功能。如果他们提供任何功能,则将其视为已解决。实现已解析函数的单个目标文件完全添加到二进制文件中。

他们也可能有要求,这些是: -

  1. 由已加载的二进制文件解决
  2. 添加到未解决的值。
  3. 请注意,库的顺序很重要,因为只有部分库需要添加到二进制文件中。

    如果此过程成功,则在Windows上,然后添加了所需的所有功能。

    在unix上,您可能需要通过-z,defs SO : unresolved externals。这允许unix .so通过加载二进制文件来满足它的一些要求,但是可能导致二进制文件不完整。

    总结

    二进制文件有: -

    1. 链接命令行中的所有目标文件。
    2. 满足未解析的外部所需的静态库中的任何目标文件
    3. 提供工作程序所需的shared objects及其功能列表。
    4. 使用接口和基类,允许在原始设计完成后添加新类。