我试图了解当我将核心应用程序的类文件包含到库(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、共享对象、可执行文件)之间共享信息是一个有问题的设计。
.h
文件是一个弱接口(interface)定义,并遭受#define
's 在同一事物的不同编译器之间可能不同。 (例如,Microsoft 调试 STL 不适用于发行版 STL)。 修改数据
假设一个类具有公共(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
名单及其交付工作计划所需的职能。 关于c++ - 在库之间传递对象和调用成员函数是如何工作的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37612156/