用本文原作者的话:“in my opinion, the Qt framework will be used more and more by software developers”.个人也觉得Qt是有其优势,有兴趣的可以利用一下。
—————————————————————以下正式开始———————————————————
(一) 内部机制
我见过的最严谨的C++框架就是Qt框架,Qt将C++带入了一个新的高度。Qt引入的信号(signal)和槽(slot)技术很有创意,其中一点就是,一个对象可以不要声明就可以调用其它对象的方法。为了运作信号和槽,Qt采用了动态化机制(dynamism)。这种动态化机制可以由Qt框架自动实现,也可以由开发人员通过QMetaObject类手动实现。有关信号和槽的内容可以参考http://doc.trolltech.com/4.7/signalsandslots.html。
我们看一个简单的信号和槽的例子:
// sas.h
int methodOffset() const;
int enumeratorOffset() const;
int propertyOffset() const;
int classInfoOffset() const;
int methodCount() const;
int enumeratorCount() const;
int propertyCount() const;
int classInfoCount() const;
int indexOfMethod(const char *method) const;
int indexOfSignal(const char *signal) const;
int indexOfSlot(const char *slot) const;
int indexOfEnumerator(const char *name) const;
int indexOfProperty(const char *name) const;
int indexOfClassInfo(const char *name) const;
struct { // private data
const QMetaObject *superdata;
const char *stringdata;
const uint *data;
const QMetaObject **extradata;
} d;
};
需要注意的是它的d结构。d结构的第一个成员是一个QMetaObject类指针,指向父Qt元数据类。用户设计的类可以从多个类派生,但只能拥有一个QObject(或从它派生)基类,这同时也是超类。看一个Qt对话框的例子,它经常从多个类派生:
class ConvDialog : public QDialog, private Ui::ConvDialog
{
Q_OBJECT
Moc将产生以下代码:
const QMetaObject ConvDialog::staticMetaObject = {
{ &QDialog::staticMetaObject, qt_meta_stringdata_ConvDialog,
qt_meta_data_ConvDialog, 0 }
};
如果在QDialog前先继承Ui::ConvDialog,moc将会生成:
const QMetaObject ConvDialog::staticMetaObject = {
{ &Ui::ConvDialog::staticMetaObject, qt_meta_stringdata_ConvDialog,
qt_meta_data_ConvDialog, 0 }
};
这是错误的,因为Ui::ConvDialog不是QObject的一个派生类,由此不拥有staticMetaObject成员,这样做只会导致一个编译错误。
d结构的第二个成员是一个字符数值,表示类的字面元数据。第三个成员是一个无符号整型数组,这个数组是一个表,包含所有元数据的偏移、特征等等。所以,如果你想枚举一个类的信号和槽,那就应该遍历这个表,通过偏移量从stringdata数组中获得方法名。它也涉及到属性(properties)和枚举类型(enums)。第四个成员是以null结尾的QMetaObject类数组,用来保存附加类的元数据信息,它一般由QMetaObject_findMetaObject函数引用。
static const QMetaObject *QMetaObject_findMetaObject(const QMetaObject *self, const char *name)
{
while (self) {
if (strcmp(self->d.stringdata, name) == 0)
return self;
if (self->d.extradata) {
const QMetaObject **e = self->d.extradata;
while (*e) {
if (const QMetaObject *m =QMetaObject_findMetaObject((*e), name))
return m;
++e;
}
}
self = self->d.superdata;
}
return self;
}
这个函数只被property方法调用,property又被propertyCount, propertyOffset 和 indexOfProperty调用。
下面是我们的Counter 类被moc生成的代码:
/****************************************************************************
** Meta object code from reading C++ file 'sas.h'
**
** Created: Mon 3. Nov 15:20:11 2008
** by: The Qt Meta Object Compiler version 59 (Qt 4.4.3)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/
#include "../sas.h"
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'sas.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 59
#error "This file was generated using the moc from 4.4.3. It"
#error "cannot be used with the include files from this version of Qt."
#error "(The moc has changed too much.)"
#endif
void *Counter::qt_metacast(const char *_clname)
{
if (!_clname) return 0;
if (!strcmp(_clname, qt_meta_stringdata_Counter))
return static_cast<void*>(const_cast< Counter*>(this));
return QObject::qt_metacast(_clname);
}
int Counter::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
_id = QObject::qt_metacall(_c, _id, _a);
if (_id < 0)
return _id;
if (_c == QMetaObject::InvokeMetaMethod) {
switch (_id) {
case 0: valueChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
case 1: setValue((*reinterpret_cast< int(*)>(_a[1]))); break;
}
_id -= 2;
}
return _id;
}
// SIGNAL 0
void Counter::valueChanged(int _t1)
{
void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
QT_END_MOC_NAMESPACE
qt_metacall方法通过索引调用其它内部方法。Qt动态机制不采用指针,而由索引实现。实际调用方法的工作由编译器实现。这使得信号和槽的机制执行效率比较高。
参数由一个指向指针数组的指针进行传递,并在调用方法时进行适当的转换。当然,使用指针是将不同类型的参数放在一个数组的唯一办法。参数索引从1开始,因为0号代表函数返回值。我们这个例子中信号和槽被声明为void,所以没有值返回。如果需要返回数据,那么包含在switch中的代码将与下面代码类似:
if (_c == QMetaObject::InvokeMetaMethod) {
switch (_id) {
case 0: valueChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
case 1: setValue((*reinterpret_cast< int(*)>(_a[1]))); break;
case 2: { int _r = exampleMethod((*reinterpret_cast< int(*)>(_a[1])));
if (_a[0]) *reinterpret_cast< int*>(_a[0]) = _r; } break;
}
另外一个需要关注的是valueChanged,它将调用QMetaObject的activate方法。
void QMetaObject::activate(QObject *sender, int from_signal_index, int to_signal_index, void **argv)
{
// [... other code ...]
// emit signals in the following order: from_signal_index <= signals <= to_signal_index, signal < 0
for (int signal = from_signal_index;
(signal >= from_signal_index && signal <= to_signal_index) || (signal == -2);
(signal == to_signal_index ? signal = -2 : ++signal))
{
if (signal >= connectionLists->count()) {
signal = to_signal_index;
continue;
}
const QObjectPrivate::ConnectionList &connectionList = connectionLists->at(signal);
int count = connectionList.count();
for (int i = 0; i < count; ++i) {
const QObjectPrivate::Connection *c = &connectionList[i];
if (!c->receiver)
continue;
QObject * const receiver = c->receiver;
// determine if this connection should be sent immediately or
// put into the event queue
if ((c->connectionType == Qt::AutoConnection
&& (currentThreadData != sender->d_func()->threadData
|| receiver->d_func()->threadData != sender->d_func()->threadData))
|| (c->connectionType == Qt::QueuedConnection)) {
queued_activate(sender, signal, *c, argv);
continue;
} else if (c->connectionType == Qt::BlockingQueuedConnection) {
blocking_activate(sender, signal, *c, argv);
continue;
}
int paramCount;
for (paramCount = 1; paramCount < MaximumParamCount; ++paramCount) {
len = qstrlen(typeNames[paramCount]);
if (len <= 0)
break;
sig.append(typeNames[paramCount], len);
sig.append(',');
}
if (paramCount == 1)
sig.append(')'); // no parameters
else
sig[sig.size() - 1] = ')';
sig.append('\0');
int idx = obj->metaObject()->indexOfMethod(sig.constData());
if (idx < 0) {
QByteArray norm = QMetaObject::normalizedSignature(sig.constData());
idx = obj->metaObject()->indexOfMethod(norm.constData());
}
if (idx < 0)
return false;
// check return type
if (ret.data()) {
const char *retType = obj->metaObject()->method(idx).typeName();
if (qstrcmp(ret.name(), retType) != 0) {
// normalize the return value as well
// the trick here is to make a function signature out of the return type
// so that we can call normalizedSignature() and avoid duplicating code
QByteArray unnormalized;
int len = qstrlen(ret.name());
unnormalized.reserve(len + 3);
unnormalized = "_("; // the function is called "_"
unnormalized.append(ret.name());
unnormalized.append(')');
QByteArray normalized = QMetaObject::normalizedSignature(unnormalized.constData());
normalized.truncate(normalized.length() - 1); // drop the ending ')'
0 // eod
};
这个表不仅仅告诉了我们方法的数量,同时也给出了方法的偏移。下面是此结构的C++头:
struct QMetaObjectPrivate
{
int revision;
int className;
int classInfoCount, classInfoData;
int methodCount, methodData;
int propertyCount, propertyData;
int enumeratorCount, enumeratorData;
};
我们已经知道了方法的数量和偏移,接下来就是分析方法本身的数据。这个数据可以分为五个整数,其意义分别是:签名、参数、类型、标签、特征。签名(signature)段是一个字符串数据中的偏移,对应valueChanged(int)方法的部分声明(不包含返回类型)。参数(parameters)段是对应参数名的偏移。我们例子中的名字就是'newValue'。名字用逗号分开,所以,如果我们的槽有两个参数,那么会显示为'newValue1,newValue2'。类型(type)段对应方法的返回类型。如果像我们例子中一样为空字符串,那么可以确定方法是void类型。标签(tag)段暂时不用(Tags are special macros recognized by moc that make it possible to add extra information about a method. For the moment, moc doesn't support any special tags.)。最后一个段——特征(flags)不是一个偏移量。特征值有:
enum MethodFlags {
AccessPrivate = 0x00,
AccessProtected = 0x01,
AccessPublic = 0x02,
AccessMask = 0x03, //mask
# change for 64 bit exes
b64bit = False
# i'm assuming that the exe is Little Endian
# the external methods used by this class are Byte(addr) and Dword(addr)
def AddressSize():
if b64bit == True:
return 8
return 4
def ReadString(self, addr):
c = addr
b = Byte(c)
str = ""
while b != 0:
# set a limit, just in case
if (c - addr) > 1000:
return "error"
str += chr(b)
c += 1
b = Byte(c)
return str
# read metadata
"""
struct QMetaObjectPrivate
{
int revision;
int className;
int classInfoCount, classInfoData;
int methodCount, methodData;
int propertyCount, propertyData;
int enumeratorCount, enumeratorData;
};
"""
# ---------------------------------------------
# enums (quick way to convert them to python)
# ---------------------------------------------
class Enum:
def __init__(self, **entries): self.__dict__.update(entries)
# if argument types and names are not the same number,
# then show signature without argument names
if len(arg_types) != len(arg_names):
return access_type + rettype + " " + psign
# build signatrue with argument names
ntypes = len(arg_types)
x = 0
args = ""
while x < ntypes:
if x != 0:
args += ", "
if arg_types[x] == "":
args += arg_names[x]
elif arg_names[x] == "":
args += arg_types[x]
else:
args += (arg_types[x] + " " + arg_names[x])
# increment loop
x += 1
x = 0
while x < num_methods:
# print if it's a signal
if parser.GetMethodType(x) == parser.MethodFlags.MethodSignal:
DisplayMethod(parser, x)
# increment loop
x += 1
# slots
print("\n--- Slots:\n")
x = 0
while x < num_methods:
# print if it's a slot
if parser.GetMethodType(x) == parser.MethodFlags.MethodSlot:
DisplayMethod(parser, x)
# increment loop
x += 1
# other methods
print("\n--- Other Methods:\n")
x = 0
while x < num_methods:
# print if it's a slot
if parser.GetMethodType(x) == parser.MethodFlags.MethodMethod:
DisplayMethod(parser, x)
# increment loop
x += 1