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

Man*_*d3r 5 c++ qt gcc

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

编辑

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;
};
Run Code Online (Sandbox Code Playgroud)

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

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

mks*_*eve 3

插件如何在运行时知道查询?

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

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

修改数据

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

class Example1 {
    public:
      int value1;
};
Run Code Online (Sandbox Code Playgroud)

在可执行文件中。

example1.value1 = 12;
Run Code Online (Sandbox Code Playgroud)

在插件中

if( this->value1 == 12 ){
}
Run Code Online (Sandbox Code Playgroud)

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

调用函数

class Example2 {
      public:
      void AFunction();
};
Run Code Online (Sandbox Code Playgroud)

任何调用者都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     |                       |
 +-------------------+          +-----------------------+
Run Code Online (Sandbox Code Playgroud)

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

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

共享对象的行为是这样的:如果它具有未解析的外部对象(加载它的二进制文件满足这些外部对象),它将使用它们的实现。在 Windows 上情况并非如此,可以使用 生成 Windows 行为-z, defs

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

调用虚函数

虚函数总是通过 this 指针调用,这意味着类的虚函数是由构造对象的代码“选择”的。这在 Windows 中用于 COM 实现,并且是一种有用的对象共享技术 - 允许在框架编译后交付具有不同功能的新类,而无需任何知识来调用实现对象。

vtable 需要稳定才能正常工作。当调用者和被调用者编译时,基类或接口应该相同,以便这一切都能正常工作。

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

class ICallback {
     virtual void Funcion1( class MyData * data ) = 0;
};
Run Code Online (Sandbox Code Playgroud)

当编译库时,它不知道什么实现了ICallback 及其任何函数,但它确实知道如何调用它们。

所以一个函数定义

class Plugin {
     bool Process( ICallback * pCallback );
};
Run Code Online (Sandbox Code Playgroud)

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

汇编

当编译器编译代码时,它会创建一个目标文件(.obj对于 Windows 和.oUNIX)。

该文件中包含链接该文件所需的所有代码和数据定义。

名义目标文件

<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>
Run Code Online (Sandbox Code Playgroud)

该 objectcf 文件中应包含足够的信息来满足部分编译过程的需要。虽然通常情况下,诸如 之类的文件MyClass.cc可能具有实现所需的所有功能MyClass,但它不需要具有所有这些功能。

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

 class Class1 {
       int ClassData;
    public:
        bool SomeFunction( char * value);
        ....
 };
Run Code Online (Sandbox Code Playgroud)

描述 Class1 有一个成员函数,它接受char *一个值,并且返回值为bool. 当继续编译 C++ 程序时,当编译器看到诸如以下内容时,可能会实现此未解析的函数

  bool Class1::SomeFunction( char * value )
  {
     bool success = false;
     cout << value;
       // some work
      return success;
  }  
Run Code Online (Sandbox Code Playgroud)

这个实现的功能被添加到已实现的字典中,并且它需要的功能和数据被添加到需求中。

库文件

UNIX 和 Windows 上的库文件略有不同。最初,unix 库文件是 .o 文件的容器。这些只是ar.o 的串联项 ( )。然后,为了找到正确的项目,对库进行索引 ( ranlib) 以生成工作库。最近,我相信档案的标准已经改变,但概念必须保留。

链接库

在windows中,链接库是在构建DLL时创建的,在unix中,链接库被构建到共享对象中。

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

<SharedObjects>
     printf : glibc:4.xx
</SharedObjects>
Run Code Online (Sandbox Code Playgroud)

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

链接

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

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

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

  1. 由已加载的二进制文件解决
  2. 添加到未解析的值。

请注意,库的顺序很重要,因为仅将所需库的部分添加到二进制文件中。

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

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

总之

二进制文件具有:-

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