c++ - 在库之间传递对象和调用成员函数是如何工作的?

标签 c++ qt gcc

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

编辑

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++ 命令)的输出中,只是其目录的包含。

当我编译插件/库时,编译器/链接器除了检查接口(interface)/头文件之外还做其他事情吗?插件如何在运行时知道有关 Query 的任何信息?在运行时,插入调用如何在对象上运行?

最佳答案

How can the plugin know about the query at runtime?



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

  • 修改数据

    假设一个类具有公共(public)成员(并且两个模块共享一个编译器),可以在创建对象的库和实现它的库中修改这些成员。
    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 的实现可能是不同的。

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

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

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

    调用虚函数

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

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

    在设计库时,可以生成接口(interface)对象。
    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 二进制文件)时,命令行上指定的目标文件将绑定(bind)到二进制文件中。这会创建一组已实现的功能(例如 main )和一组 Unresolved 需求。

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

    他们可能也有要求,这些是:-
  • 由已加载的二进制文件解决
  • 添加到未解析的值中。

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

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

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

    总之

    二进制文件有:-
  • 来自链接命令行的所有目标文件。
  • 静态库中的任何目标文件需要满足未解析的外部变量
  • shared objects名单及其交付工作计划所需的职能。
  • 使用接口(interface)和基类,允许在原始设计完成后添加新类。
  • 关于c++ - 在库之间传递对象和调用成员函数是如何工作的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37612156/

    相关文章:

    c++ - Qt:在函数中等待一段时间而不挂起用户界面

    c - memcpy 与指针交换?

    gcc - 推送 ebp : operand type mismatch for `push'

    c++ - g++ : Is there a way to access compile flags inside the code that is being compiled?

    c++ - C++ STL map 如何管理内存,以及如何绕过它

    c++ - 如何在 Linux 上使用 C++ 中的 read() 和 O_DIRECT 读取文件?

    c++ - 使用 Qt 通过 TagLib 获取 mp3 封面艺术

    c++ - 是否允许类类型的 std::function 成员变量(不完整类型)?

    c++ - 尝试使用对 protobuffers 2.6.1 和 3.4.1 的引用来编译代码

    c++ - C 库的 Qt 包装器