前面讲了那么多,Qt的元对象系统是多么多么厉害,多么多么好,那么Moc是具体是怎么工作的,所谓源码面前,了无秘密,下面让我们一探究竟。
前提时安装qt的时候必须选择 源码。比如我的路径是
/opt/Qt5.9.5/5.9.5/Src/qtbase/src/tools/moc/moc.pro
打开main.cpp,找到main函数:
int main(int _argc, char **_argv)
{
return QT_PREPEND_NAMESPACE(runMoc)(_argc, _argv);
}
调用了 runMoc 函数,先来看一部分:
int runMoc(int argc, char **argv)
{
QCoreApplication app(argc, argv);
QCoreApplication::setApplicationVersion(QString::fromLatin1(QT_VERSION_STR));
bool autoInclude = true;
bool defaultInclude = true;
Preprocessor pp;
Moc moc;
pp.macros["Q_MOC_RUN"];
pp.macros["__cplusplus"];
...
注意Preprocessor和Moc类,这两个是比较关键的类。Preprocessor做一些预处理的工作,比如找到头文件中的Q_OBJECT、signals和slots等宏。然后Moc类负责分析文件,将结果输出到moc_xxx.cpp文件中去。这也就能解释为什么Q_OBJECT只能生命在头文件,而不能写到cpp文件中了,因为moc根本找不到,它只会扫描头文件。
再往下是一堆 QCommandLineOption,也就是Qt元对象系统(Meta-Object)(二)、moc的使用一文中写到的moc所支持的一些命令行选项。
// Note that moc isn't translated.
// If you use this code as an example for a translated app, make sure to translate the strings.
QCommandLineParser parser;
parser.setApplicationDescription(QStringLiteral("Qt Meta Object Compiler version %1 (Qt %2)")
.arg(mocOutputRevision).arg(QString::fromLatin1(QT_VERSION_STR)));
parser.addHelpOption();
parser.addVersionOption();
parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
QCommandLineOption outputOption(QStringLiteral("o"));
outputOption.setDescription(QStringLiteral("Write output to file rather than stdout."));
outputOption.setValueName(QStringLiteral("file"));
outputOption.setFlags(QCommandLineOption::ShortOptionStyle);
parser.addOption(outputOption);
QCommandLineOption includePathOption(QStringLiteral("I"));
includePathOption.setDescription(QStringLiteral("Add dir to the include path for header files."));
includePathOption.setValueName(QStringLiteral("dir"));
includePathOption.setFlags(QCommandLineOption::ShortOptionStyle);
parser.addOption(includePathOption);
QCommandLineOption macFrameworkOption(QStringLiteral("F"));
macFrameworkOption.setDescription(QStringLiteral("Add Mac framework to the include path for header files."));
macFrameworkOption.setValueName(QStringLiteral("framework"));
macFrameworkOption.setFlags(QCommandLineOption::ShortOptionStyle);
parser.addOption(macFrameworkOption);
QCommandLineOption preprocessOption(QStringLiteral("E"));
preprocessOption.setDescription(QStringLiteral("Preprocess only; do not generate meta object code."));
parser.addOption(preprocessOption);
...
再接下来的话会是一些命令选项的解析,比如-D所指定的宏"-DQT_GUI_LIB"
,或者 -I 指定的头文件目录。
然后我们看关键代码,有一些qDebug语句是我自己加的:
// 1. preprocess
// 包含的所有头文件
const auto includeFiles = parser.values(includeOption);
// 依次扫面解析头文件
for (const QString &includeName : includeFiles) {
QByteArray rawName = pp.resolveInclude(QFile::encodeName(includeName), moc.filename);
if (rawName.isEmpty()) {
fprintf(stderr, "Warning: Failed to resolve include \"%s\" for moc file %s\n",
includeName.toLocal8Bit().constData(),
moc.filename.isEmpty() ? "" : moc.filename.constData());
} else {
QFile f(QFile::decodeName(rawName));
if (f.open(QIODevice::ReadOnly)) {
moc.symbols += Symbol(0, MOC_INCLUDE_BEGIN, rawName);
/// 关键代码!!!预处理头文件
moc.symbols += pp.preprocessed(rawName, &f);
moc.symbols += Symbol(0, MOC_INCLUDE_END, rawName);
} else {
fprintf(stderr, "Warning: Cannot open %s included by moc file %s: %s\n",
rawName.constData(),
moc.filename.isEmpty() ? "" : moc.filename.constData(),
f.errorString().toLocal8Bit().constData());
}
}
}
if (!pp.preprocessOnly) {
// 2. parse
moc.parse();
}
// 3. and output meta object code
qDebug() << "outputfile: " << output;
if (output.size()) { // output file specified
#if defined(_MSC_VER)
if (_wfopen_s(&out, reinterpret_cast<const wchar_t *>(output.utf16()), L"w") != 0)
#else
out = fopen(QFile::encodeName(output).constData(), "w"); // create output file
if (!out)
#endif
{
fprintf(stderr, "moc: Cannot create %s\n", QFile::encodeName(output).constData());
return 1;
}
} else { // use stdout
out = stdout;
}
qDebug() << "preprocessOnly: " << pp.preprocessOnly;
if (pp.preprocessOnly) {
fprintf(out, "%s\n", composePreprocessorOutput(moc.symbols).constData());
qDebug() << "outputfile: " << composePreprocessorOutput(moc.symbols).constData();
} else {
if (moc.classList.isEmpty())
moc.note("No relevant classes found. No output generated.");
else
moc.generate(out);
}
if (output.size())
fclose(out);
moc.symbols += pp.preprocessed(moc.filename, &in);
Symbols这个类很简单,大家可以自己去看下,主要记录一些符号的信息,比如标记,行号等。pp.preprocessed(rawName, &f) 是很关键的一个函数,我们跳转进去一探究竟:
Symbols Preprocessor::preprocessed(const QByteArray &filename, QFile *file)
{
// 读出文件的内容或者将内容映射到内存
QByteArray input = readOrMapFile(file);
if (input.isEmpty())
return symbols;
// phase 1: get rid of backslash-newlines
// 将\r\n转换为\n
// \r转换成\n (os9样式)
// 反斜杠-换行换成成换行符
input = cleaned(input);
// phase 2: tokenize for the preprocessor
index = 0;
// 先标记需要预处理的地方,
// 比如: #include, signals, slots...
symbols = tokenize(input);
#if 0
for (int j = 0; j < symbols.size(); ++j)
fprintf(stderr, "line %d: %s(%s)\n",
symbols[j].lineNum,
symbols[j].lexem().constData(),
tokenTypeName(symbols[j].token));
#endif
// phase 3: preprocess conditions and substitute macros
Symbols result;
// Preallocate some space to speed up the code below.
// The magic value was found by logging the final size
// and calculating an average when running moc over FOSS projects.
result.reserve(file->size() / 300000);
preprocess(filename, result);
mergeStringLiterals(&result);
#if 0
for (int j = 0; j < result.size(); ++j)
fprintf(stderr, "line %d: %s(%s)\n",
result[j].lineNum,
result[j].lexem().constData(),
tokenTypeName(result[j].token));
#endif
return result;
}
其中
symbols = tokenize(input);
的作用是先标记需要预处理的地方比如: #include, signals, slots…,比如有头文件:
#ifndef WIDGET_H
#define WIDGET_H
#include
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
signals:
void Sig();
private slots:
void onSig();
private:
};
#endif // WIDGET_H
打印的结果是:
line 12: Widget(IDENTIFIER)
line 12: ((LPAREN)
line 12: )(RPAREN)
line 12: ;(SEMIC)
line 14: signals(SIGNALS)
line 14: :(COLON)
line 15: void(VOID)
line 15: Sig(IDENTIFIER)
line 15: ((LPAREN)
line 15: )(RPAREN)
line 15: ;(SEMIC)
line 17: private(PRIVATE)
line 17: slots(SLOTS)
line 17: :(COLON)
line 18: void(VOID)
line 18: onSig(IDENTIFIER)
line 18: ((LPAREN)
line 18: )(RPAREN)
再往下的话也是一个预处理函数 preprocess(filename, result) ,进一步处理。函数原型:
// 参数preprocessed传递的就是preprocessed函数中找到的一些需要预处理的标记信息等
void preprocess(const QByteArray &filename, Symbols &preprocessed);
依次判断preprocessed的token,
void Preprocessor::preprocess(const QByteArray &filename, Symbols &preprocessed)
{
currentFilenames.push(filename);
preprocessed.reserve(preprocessed.size() + symbols.size());
while (hasNext()) {
Token token = next();
switch (token) {
case PP_INCLUDE:
{
int lineNum = symbol().lineNum;
QByteArray include;
bool local = false;
if (test(PP_STRING_LITERAL)) {
local = lexem().startsWith('\"');
include = unquotedLexem();
} else
continue;
until(PP_NEWLINE);
...
}
// 熟悉的信号槽
case SIGNALS:
case SLOTS: {
Symbol sym = symbol();
if (macros.contains("QT_NO_KEYWORDS"))
sym.token = IDENTIFIER;
else
sym.token = (token == SIGNALS ? Q_SIGNALS_TOKEN : Q_SLOTS_TOKEN);
preprocessed += sym;
} continue;
...
}
小结:关于preprocessed函数大概有了个认识,它读取所有需要的头文件,处理保存所有读到的的宏定义信息,比如:#include, public, signals, slots, Q_OBJECT等具体的信息保存到Symbols的一个实例中。
再接下来就是main.cpp中的parse()函数。
if (!pp.preprocessOnly) {
// 2. parse
moc.parse();
}
根据读到的宏定义进行解析:
void Moc::parse()
{
QVector<NamespaceDef> namespaceList;
bool templateClass = false;
while (hasNext()) {
Token t = next();
switch (t) {
case NAMESPACE: {
int rewind = index;
if (test(IDENTIFIER)) {
QByteArray nsName = lexem();
QByteArrayList nested;
while (test(SCOPE)) {
next(IDENTIFIER);
nested.append(nsName);
nsName = lexem();
}
...
}
if ((t != CLASS && t != STRUCT)|| currentFilenames.size() > 1)
continue;
ClassDef def;
// 判断是否是一个前置声明
if (parseClassHead(&def)) {
FunctionDef::Access access = FunctionDef::Private;
for (int i = namespaceList.size() - 1; i >= 0; --i)
if (inNamespace(&namespaceList.at(i)))
def.qualified.prepend(namespaceList.at(i).classname + "::");
while (inClass(&def) && hasNext()) {
switch ((t = next())) {
case PRIVATE:
access = FunctionDef::Private;
if (test(Q_SIGNALS_TOKEN))
error("Signals cannot have access specifier");
break;
case PROTECTED:
access = FunctionDef::Protected;
if (test(Q_SIGNALS_TOKEN))
error("Signals cannot have access specifier");
break;
case PUBLIC:
access = FunctionDef::Public;
if (test(Q_SIGNALS_TOKEN))
error("Signals cannot have access specifier");
break;
case CLASS: {
ClassDef nestedDef;
if (parseClassHead(&nestedDef)) {
while (inClass(&nestedDef) && inClass(&def)) {
t = next();
if (t >= Q_META_TOKEN_BEGIN && t < Q_META_TOKEN_END)
error("Meta object features not supported for nested classes");
}
}
} break;
case Q_SIGNALS_TOKEN:
parseSignals(&def);
break;
case Q_SLOTS_TOKEN:
switch (lookup(-1)) {
case PUBLIC:
case PROTECTED:
case PRIVATE:
parseSlots(&def, access);
break;
default:
error("Missing access specifier for slots");
}
break;
case Q_OBJECT_TOKEN:
def.hasQObject = true;
if (templateClass)
error("Template classes not supported by Q_OBJECT");
if (def.classname != "Qt" && def.classname != "QObject" && def.superclassList.isEmpty())
error("Class contains Q_OBJECT macro but does not inherit from QObject");
break;
...
}
ClassDef类中保存了每一个类的具体数据信息,类名,借口id,构造函数列表,信号列表,槽函数列表等:
struct ClassDef : BaseDef {
QVector<QPair<QByteArray, FunctionDef::Access> > superclassList;
struct Interface
{
Interface() {} // for QVector, don't use
inline explicit Interface(const QByteArray &_className)
: className(_className) {}
QByteArray className;
QByteArray interfaceId;
};
QVector<QVector<Interface> >interfaceList;
bool hasQObject = false;
bool hasQGadget = false;
struct PluginData {
QByteArray iid;
QMap<QString, QJsonArray> metaArgs;
QJsonDocument metaData;
} pluginData;
QVector<FunctionDef> constructorList;
QVector<FunctionDef> signalList, slotList, methodList, publicList;
int notifyableProperties = 0;
QVector<PropertyDef> propertyList;
int revisionedMethods = 0;
int revisionedProperties = 0;
};
再看parse函数,因为太多了,我们以信号和槽为例,当读到Q_SIGNALS_TOKEN和Q_SLOTS_TOKEN时分别调用了parseSignals和parseSolts函数。这两个函数大体一致,我们只看看parseSignals函数吧。parseSignals函数进行一些检查和信号函数解析保存的工作。
void Moc::parseSignals(ClassDef *def)
{
int defaultRevision = -1;
if (test(Q_REVISION_TOKEN)) {
next(LPAREN);
QByteArray revision = lexemUntil(RPAREN);
revision.remove(0, 1);
revision.chop(1);
bool ok = false;
defaultRevision = revision.toInt(&ok);
if (!ok || defaultRevision < 0)
error("Invalid revision");
}
next(COLON);
while (inClass(def) && hasNext()) {
switch (next()) {
case PUBLIC:
case PROTECTED:
case PRIVATE:
case Q_SIGNALS_TOKEN:
case Q_SLOTS_TOKEN:
prev();
return;
case SEMIC:
continue;
case FRIEND:
until(SEMIC);
continue;
case USING:
error("'using' directive not supported in 'signals' section");
default:
prev();
}
FunctionDef funcDef;
funcDef.access = FunctionDef::Public;
parseFunction(&funcDef);
if (funcDef.isVirtual)
warning("Signals cannot be declared virtual");
if (funcDef.inlineCode)
error("Not a signal declaration");
if (funcDef.revision > 0) {
++def->revisionedMethods;
} else if (defaultRevision != -1) {
funcDef.revision = defaultRevision;
++def->revisionedMethods;
}
def->signalList += funcDef;
while (funcDef.arguments.size() > 0 && funcDef.arguments.constLast().isDefault) {
funcDef.wasCloned = true;
funcDef.arguments.removeLast();
def->signalList += funcDef;
}
}
}
其中的FunctionDef类与ClassDef的功能类似,FunctionDef保存的时函数的具体信息,比如参数列表,返回值等:
struct FunctionDef
{
FunctionDef(): returnTypeIsVolatile(false), access(Private), isConst(false), isVirtual(false), isStatic(false),
inlineCode(false), wasCloned(false), isCompat(false), isInvokable(false),
isScriptable(false), isSlot(false), isSignal(false), isPrivateSignal(false),
isConstructor(false), isDestructor(false), isAbstract(false), revision(0) {}
Type type;
QByteArray normalizedType;
QByteArray tag;
QByteArray name;
bool returnTypeIsVolatile;
QVector<ArgumentDef> arguments;
enum Access { Private, Protected, Public };
Access access;
bool isConst;
bool isVirtual;
bool isStatic;
bool inlineCode;
bool wasCloned;
QByteArray inPrivateClass;
bool isCompat;
bool isInvokable;
bool isScriptable;
bool isSlot;
bool isSignal;
bool isPrivateSignal;
bool isConstructor;
bool isDestructor;
bool isAbstract;
int revision;
};
再接下来就是生成代码了,还是main.cpp中的函数,代码生成主要还是generate函数:
// 3. and output meta object code
if (output.size()) { // output file specified
#if defined(_MSC_VER)
if (_wfopen_s(&out, reinterpret_cast<const wchar_t *>(output.utf16()), L"w") != 0)
#else
out = fopen(QFile::encodeName(output).constData(), "w"); // create output file
if (!out)
#endif
{
fprintf(stderr, "moc: Cannot create %s\n", QFile::encodeName(output).constData());
return 1;
}
} else { // use stdout
out = stdout;
}
if (pp.preprocessOnly) {
fprintf(out, "%s\n", composePreprocessorOutput(moc.symbols).constData());
qDebug() << "outputfile: " << composePreprocessorOutput(moc.symbols).constData();
} else {
if (moc.classList.isEmpty())
moc.note("No relevant classes found. No output generated.");
else
moc.generate(out);
}
generate函数:
void Moc::generate(FILE *out)
{
QByteArray fn = filename;
int i = filename.length()-1;
while (i > 0 && filename.at(i - 1) != '/' && filename.at(i - 1) != '\\')
--i; // skip path
if (i >= 0)
fn = filename.mid(i);
fprintf(out, "/****************************************************************************\n"
"** Meta object code from reading C++ file '%s'\n**\n" , fn.constData());
fprintf(out, "** Created by: The Qt Meta Object Compiler version %d (Qt %s)\n**\n" , mocOutputRevision, QT_VERSION_STR);
fprintf(out, "** WARNING! All changes made in this file will be lost!\n"
"*****************************************************************************/\n\n");
if (!noInclude) {
if (includePath.size() && !includePath.endsWith('/'))
includePath += '/';
for (int i = 0; i < includeFiles.size(); ++i) {
QByteArray inc = includeFiles.at(i);
if (inc.at(0) != '<' && inc.at(0) != '"') {
if (includePath.size() && includePath != "./")
inc.prepend(includePath);
inc = '\"' + inc + '\"';
}
fprintf(out, "#include %s\n", inc.constData());
}
}
if (classList.size() && classList.constFirst().classname == "Qt")
fprintf(out, "#include \n" );
fprintf(out, "#include \n" ); // For QByteArrayData
fprintf(out, "#include \n" ); // For QMetaType::Type
if (mustIncludeQPluginH)
fprintf(out, "#include \n" );
const auto qtContainers = requiredQtContainers(classList);
for (const QByteArray &qtContainer : qtContainers)
fprintf(out, "#include \n" , qtContainer.constData());
fprintf(out, "#if !defined(Q_MOC_OUTPUT_REVISION)\n"
"#error \"The header file '%s' doesn't include .\"\n" , fn.constData());
fprintf(out, "#elif Q_MOC_OUTPUT_REVISION != %d\n", mocOutputRevision);
fprintf(out, "#error \"This file was generated using the moc from %s."
" It\"\n#error \"cannot be used with the include files from"
" this version of Qt.\"\n#error \"(The moc has changed too"
" much.)\"\n", QT_VERSION_STR);
fprintf(out, "#endif\n\n");
fprintf(out, "QT_BEGIN_MOC_NAMESPACE\n");
fprintf(out, "QT_WARNING_PUSH\n");
fprintf(out, "QT_WARNING_DISABLE_DEPRECATED\n");
fputs("", out);
for (i = 0; i < classList.size(); ++i) {
Generator generator(&classList[i], metaTypes, knownQObjectClasses, knownGadgets, out);
generator.generateCode();
}
fputs("", out);
fprintf(out, "QT_WARNING_POP\n");
fprintf(out, "QT_END_MOC_NAMESPACE\n");
}
然后核心代码是由 generator.generateCode() 来生成的。void Generator::generateCode()的代码非常多,我们这里只分析一下moc是如何生成信号的实现的。
void Generator::generateCode()
{
...
//
// Generate internal signal functions
//
for (int signalindex = 0; signalindex < cdef->signalList.size(); ++signalindex)
generateSignal(&cdef->signalList[signalindex], signalindex);
...
}
void Generator::generateSignal(FunctionDef *def,int index)
{
if (def->wasCloned || def->isAbstract)
return;
fprintf(out, "\n// SIGNAL %d\n%s %s::%s(",
index, def->type.name.constData(), cdef->qualified.constData(), def->name.constData());
QByteArray thisPtr = "this";
const char *constQualifier = "";
if (def->isConst) {
thisPtr = "const_cast< " + cdef->qualified + " *>(this)";
constQualifier = "const";
}
Q_ASSERT(!def->normalizedType.isEmpty());
if (def->arguments.isEmpty() && def->normalizedType == "void" && !def->isPrivateSignal) {
fprintf(out, ")%s\n{\n"
" QMetaObject::activate(%s, &staticMetaObject, %d, nullptr);\n"
"}\n", constQualifier, thisPtr.constData(), index);
return;
}
int offset = 1;
for (int j = 0; j < def->arguments.count(); ++j) {
const ArgumentDef &a = def->arguments.at(j);
if (j)
fprintf(out, ", ");
fprintf(out, "%s _t%d%s", a.type.name.constData(), offset++, a.rightType.constData());
}
if (def->isPrivateSignal) {
if (!def->arguments.isEmpty())
fprintf(out, ", ");
fprintf(out, "QPrivateSignal _t%d", offset++);
}
fprintf(out, ")%s\n{\n", constQualifier);
if (def->type.name.size() && def->normalizedType != "void") {
QByteArray returnType = noRef(def->normalizedType);
fprintf(out, " %s _t0{};\n", returnType.constData());
}
fprintf(out, " void *_a[] = { ");
if (def->normalizedType == "void") {
fprintf(out, "nullptr");
} else {
if (def->returnTypeIsVolatile)
fprintf(out, "const_cast(reinterpret_cast(&_t0))" );
else
fprintf(out, "const_cast(reinterpret_cast(&_t0))" );
}
int i;
for (i = 1; i < offset; ++i)
if (i <= def->arguments.count() && def->arguments.at(i - 1).type.isVolatile)
fprintf(out, ", const_cast(reinterpret_cast(&_t%d))" , i);
else
fprintf(out, ", const_cast(reinterpret_cast(&_t%d))" , i);
fprintf(out, " };\n");
fprintf(out, " QMetaObject::activate(%s, &staticMetaObject, %d, _a);\n", thisPtr.constData(), index);
if (def->normalizedType != "void")
fprintf(out, " return _t0;\n");
fprintf(out, "}\n");
}
到这里基本上所有的主要函数已经分析完了,大致流程是:preprocessed函数读取所有的头文件,解析出来每一个宏和关键字,parse()函数再根据读出来的信息解析保存每一个类,函数等具体信息,然后generate负责将内容写入到文件。分析的比较粗糙,如果想一探究竟还是得自己研究每个细节,Qt的源码可读性也比较好,大家可根据我提到的几个函数自行去研究学习。