Qt 信號槽機制與事件機制四 提升篇

標 題: 【翻譯】Qt內部機制及逆向
作 者: zouzhin
時 間: 2011-04-30,15:51:44
鏈 接: http://bbs.pediy.com/showthread.php?t=133181

【翻譯】Qt內部機制及逆向
原作者:Daniel Pistelli ;
翻   譯:zouzhin
參加看雪有很長一段時間了,一直無所貢獻,真是有愧各位同壇好友。前不久發了個Qt求助帖http://bbs.pediy.com/showthread.php?t=132491,沒人回覆,剛好看到了國外牛人Daniel Pistelli 寫的《Qt Internals & Reversing》,就翻譯一下給需要的人做個參考。由於E文水平不高,有不對的地方多包涵,高手請直接看原文http://ntcore.com/files/qtrev.htm。
開始之前,簡單補充介紹一下Qt:
用官網的話說:Qt——一個開源跨平臺應用程序和UI開發框架。優點有:
針對多個平臺只需編寫一次代碼
使用 Qt 您只需編寫一次應用程序和 UI,無須重新編寫源代碼,便可跨不同的桌面和嵌入式操作系統進行部署,既節省了時間又降低開發成本。
創建令人意想不到的用戶體驗
QQt 提供了應用程序生成塊,包括龐大的可定製 widget 集合、圖形畫布、風格引擎和其他內容,您可用來生成新穎的用戶界面。由於集成了 3D 圖形、多媒體音頻或視頻、視覺效果、動畫和自定義風格,使其在競爭中脫穎而出。
事半功倍(且倍道而進)
無論是使用全新的 Qt Creator 跨平臺 IDE 還是僅是 Qt 本身,Qt 都易學易用。而且由於有了 Qt 模塊化的類庫,您可以更多地關注創新,無須在平臺本身編碼上花費過多時間,這樣就可將軟件快速推向市場。

在單一應用程序中混合網絡和本地代碼
由於 Qt 集成了 WebKit 網絡渲染引擎, 您可以快速地(查看混合方式) 將網絡內容和服務集成到本地應用程序中,還可以利用網絡環境提供您的服務和功能,讓您的用戶在使用過程中留下深刻印象。

用本文原作者的話:“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

#include <QObject>

class Counter : public QObject
{
  Q_OBJECT

public:
  Counter() { m_value = 0; };

  int value() const { return m_value; };

  
public slots:

  void setValue(int value)
  {
    if (value != m_value) 
    {
      m_value = value;
      emit valueChanged(value);
    }
  };

signals:
  void valueChanged(int newValue);

private:
  int m_value;
};


// main.cpp

#include "sas.h"

int main(int argc, char *argv[])
{
  Counter a, b;
  QObject::connect(&a, SIGNAL(valueChanged(int)),
    &b, SLOT(setValue(int)));

  a.setValue(12);     // a.value() == 12, b.value() == 12
  b.setValue(48);     // a.value() == 12, b.value() == 48 
  return 0;
}
SIGNAL和SLOT宏將括號中的內容封裝成一個字符串,同時還附加一個ID號,如下所示:
#define SLOT(a)          "1"#a
#define SIGNAL(a)        "2"#a
所以,也可以直接這麼寫connect函數:
QObject::connect(&a, "2valueChanged(int)", &b, "1setValue(int)");
signals和slots是Qt關鍵詞,可以在頭文件中找到,只用於Qt的元編譯器(moc)。
# if defined(QT_NO_KEYWORDS)
#  define QT_NO_EMIT
# else
#   define slots
#   define signals protected
# endif
# define Q_SLOTS
# define Q_SIGNALS protected
# define Q_PRIVATE_SLOT(d, signature)
# define Q_EMIT
#ifndef QT_NO_EMIT
# define emit
#endif
emit宏沒什麼需要解釋的,signals宏有點不同,它限定Qt信號爲protected方法,而slots宏可以是任意類型。
我們接下來看看Q_OBJECT宏:
/* tmake ignore Q_OBJECT */
#define Q_OBJECT_CHECK \
    template <typename T> inline void qt_check_for_QOBJECT_macro(const T &_q_argument) 

const \
    { int i = qYouForgotTheQ_OBJECT_Macro(this, &_q_argument); i = i; }

template <typename T>
inline int qYouForgotTheQ_OBJECT_Macro(T, T) { return 0; }

template <typename T1, typename T2>
inline void qYouForgotTheQ_OBJECT_Macro(T1, T2) {}
#endif // QT_NO_MEMBER_TEMPLATES

/* tmake ignore Q_OBJECT */
#define Q_OBJECT \
public: \
    Q_OBJECT_CHECK \
    static const QMetaObject staticMetaObject; \
    virtual const QMetaObject *metaObject() const; \
    virtual void *qt_metacast(const char *); \
    QT_TR_FUNCTIONS \
    virtual int qt_metacall(QMetaObject::Call, int, void **); \
private:
staticMetaObject是 QMetaObject對象,因爲需要給屬於同一類的全部實例共享,所以它是靜態的。metaObject方法僅僅返回staticMetaObject。QT_TR_FUNCTIONS是一個用於所有tr函數的宏,用來實現多語言支持。qt_metacast用於按照類名或它的某個基類名進行動態轉換(dynamic cast)【Qt顯然不依賴運行時類型檢查(RTTI)】。qt_metacall通過索引調用內部信號和槽。以下是QMetaObject的聲明:
struct Q_CORE_EXPORT QMetaObject
{
    const char *className() const;
    const QMetaObject *superClass() const;

    QObject *cast(QObject *obj) const;

#ifndef QT_NO_TRANSLATION
    // ### Qt 4: Merge overloads
    QString tr(const char *s, const char *c) const;
    QString trUtf8(const char *s, const char *c) const;
    QString tr(const char *s, const char *c, int n) const;
    QString trUtf8(const char *s, const char *c, int n) const;
#endif // QT_NO_TRANSLATION

    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;

    QMetaMethod method(int index) const;
    QMetaEnum enumerator(int index) const;
    QMetaProperty property(int index) const;
    QMetaClassInfo classInfo(int index) const;
    QMetaProperty userProperty() const;

    static bool checkConnectArgs(const char *signal, const char *method);
    static QByteArray normalizedSignature(const char *method);
    static QByteArray normalizedType(const char *type);

    // internal index-based connect
    static bool connect(const QObject *sender, int signal_index,
                        const QObject *receiver, int method_index,
                        int type = 0, int *types = 0);
    // internal index-based disconnect
    static bool disconnect(const QObject *sender, int signal_index,
                           const QObject *receiver, int method_index);
    // internal slot-name based connect
    static void connectSlotsByName(QObject *o);

    // internal index-based signal activation
    static void activate(QObject *sender, int signal_index, void **argv);
    static void activate(QObject *sender, int from_signal_index, int to_signal_index, void **argv);
    static void activate(QObject *sender, const QMetaObject *, int local_signal_index, void **argv);
    static void activate(QObject *sender, const QMetaObject *, int from_local_signal_index, 
      int to_local_signal_index, void 

**argv);
    // internal guarded pointers
    static void addGuard(QObject **ptr);
    static void removeGuard(QObject **ptr);
    static void changeGuard(QObject **ptr, QObject *o);

    static bool invokeMethod(QObject *obj, const char *member,
                             Qt::ConnectionType,
                             QGenericReturnArgument ret,
                             QGenericArgument val0 = QGenericArgument(0),
                             QGenericArgument val1 = QGenericArgument(),
                             QGenericArgument val2 = QGenericArgument(),
                             QGenericArgument val3 = QGenericArgument(),
                             QGenericArgument val4 = QGenericArgument(),
                             QGenericArgument val5 = QGenericArgument(),
                             QGenericArgument val6 = QGenericArgument(),
                             QGenericArgument val7 = QGenericArgument(),
                             QGenericArgument val8 = QGenericArgument(),
                             QGenericArgument val9 = QGenericArgument());

    // [ ... several invokeMethod overloads ...]

    enum Call {
        InvokeMetaMethod,
        ReadProperty,
        WriteProperty,
        ResetProperty,
        QueryPropertyDesignable,
        QueryPropertyScriptable,
        QueryPropertyStored,
        QueryPropertyEditable,
        QueryPropertyUser
    };

#ifdef QT3_SUPPORT
    QT3_SUPPORT const char *superClassName() const;
#endif

    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

QT_BEGIN_MOC_NAMESPACE
static const uint qt_meta_data_Counter[] = {

 // content:
       1,       // revision
       0,       // classname
       0,    0, // classinfo
       2,   10, // methods
       0,    0, // properties
       0,    0, // enums/sets

 // signals: signature, parameters, type, tag, flags
      18,    9,    8,    8, 0x05,

 // slots: signature, parameters, type, tag, flags
      42,   36,    8,    8, 0x0a,

       0        // eod
};

static const char qt_meta_stringdata_Counter[] = {
    "Counter\0\0newValue\0valueChanged(int)\0"
    "value\0setValue(int)\0"
};

const QMetaObject Counter::staticMetaObject = {
    { &QObject::staticMetaObject, qt_meta_stringdata_Counter,
      qt_meta_data_Counter, 0 }
};

const QMetaObject *Counter::metaObject() const
{
    return &staticMetaObject;
}

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;
            }

            const int method = c->method;
            QObjectPrivate::Sender currentSender;
            currentSender.sender = sender;
            currentSender.signal = signal < 0 ? from_signal_index : signal;
            QObjectPrivate::Sender * const previousSender =
                QObjectPrivate::setCurrentSender(receiver, &currentSender);
            locker.unlock();

            if (qt_signal_spy_callback_set.slot_begin_callback != 0) {
                qt_signal_spy_callback_set.slot_begin_callback(receiver,
                                                               method,
                                                               argv ? argv : empty_argv);
            }

#if defined(QT_NO_EXCEPTIONS)
            receiver->qt_metacall(QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);
#else
            try {
                receiver->qt_metacall(QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);
            } catch (...) {
                locker.relock();

                QObjectPrivate::resetCurrentSender(receiver, &currentSender, previousSender);

                --connectionLists->inUse;
                Q_ASSERT(connectionLists->inUse >= 0);
                if (connectionLists->orphaned && !connectionLists->inUse)
                    delete connectionLists;
                throw;
            }
#endif

            locker.relock();

            if (qt_signal_spy_callback_set.slot_end_callback != 0)
                qt_signal_spy_callback_set.slot_end_callback(receiver, method);

            QObjectPrivate::resetCurrentSender(receiver, &currentSender, previousSender);

            if (connectionLists->orphaned)
                break;
        }

        if (connectionLists->orphaned)
            break;
    }

    --connectionLists->inUse;
    Q_ASSERT(connectionLists->inUse >= 0);
    if (connectionLists->orphaned && !connectionLists->inUse)
        delete connectionLists;

    locker.unlock();

    if (qt_signal_spy_callback_set.signal_end_callback != 0)
        qt_signal_spy_callback_set.signal_end_callback(sender, from_signal_index);
}
這個方法需要做很多工作,包括檢查當前的事件是需要立即處理還是先放進事件序列中,並分別調用不同的activate方法,而後繼續處理ConnectionList中的下一個事件。如果事件需要立即處理,首先從事件中獲得將要調用的方法的ID,從而調用接收方的qt_metacall。過程如下:
const QObjectPrivate::ConnectionList &connectionList = connectionLists->at(signal);
int count = connectionList.count();
for (int i = 0; i < count; ++i) {
  const QObjectPrivate::Connection *c = &connectionList[i];

  QObject * const receiver = c->receiver;
  const int method = c->method;
  receiver->qt_metacall(QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);
從中我們可以明白信號和槽的內部機制:當調用connect函數時,信號和槽的簽名轉換成ID,然後儲存在Connection類中。每次一個信號發出後,與此信號ID相關的事件即被確定,同時對應的槽被調用。
至於動態調用(dynamic invokes),主要通過QMetaObject類提供的invokeMethod方法實現。這個方法不同於信號和槽,它需要從它的返回類型、名字和參數類型構造一個簽名,然後查找元數據來得到其ID,最後調用對象的qt_metacall方法。
bool QMetaObject::invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type,
                 QGenericReturnArgument ret,
                 QGenericArgument val0,
                 QGenericArgument val1,
                 QGenericArgument val2,
                 QGenericArgument val3,
                 QGenericArgument val4,
                 QGenericArgument val5,
                 QGenericArgument val6,
                 QGenericArgument val7,
                 QGenericArgument val8,
                 QGenericArgument val9)
{
    if (!obj)
        return false;

    QVarLengthArray<char, 512> sig;
    int len = qstrlen(member);
    if (len <= 0)
        return false;
    sig.append(member, len);
    sig.append('(');

    enum { MaximumParamCount = 11 };
    const char *typeNames[] = {ret.name(), val0.name(), val1.name(), val2.name(), val3.name(),
                               val4.name(), val5.name(), val6.name(), val7.name(), val8.name(),
                               val9.name()};

    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 ')'

            if (qstrcmp(normalized.constData() + 2, retType) != 0)
                return false;
        }
    }
    void *param[] = {ret.data(), val0.data(), val1.data(), val2.data(), val3.data(), val4.data(),
                     val5.data(), val6.data(), val7.data(), val8.data(), val9.data()};
    if (type == Qt::AutoConnection) {
        type = QThread::currentThread() == obj->thread()
               ? Qt::DirectConnection
               : Qt::QueuedConnection;
    }

    if (type == Qt::DirectConnection) {
        return obj->qt_metacall(QMetaObject::InvokeMetaMethod, idx, param) < 0;
    } else {
        if (ret.data()) {
            qWarning("QMetaObject::invokeMethod: Unable to invoke methods with return values in queued "
                     "connections");
            return false;
        }
        int nargs = 1; // include return type
        void **args = (void **) qMalloc(paramCount * sizeof(void *));
        int *types = (int *) qMalloc(paramCount * sizeof(int));
        types[0] = 0; // return type
        args[0] = 0;
        for (int i = 1; i < paramCount; ++i) {
            types[i] = QMetaType::type(typeNames[i]);
            if (types[i]) {
                args[i] = QMetaType::construct(types[i], param[i]);
                ++nargs;
            } else if (param[i]) {
                qWarning("QMetaObject::invokeMethod: Unable to handle unregistered datatype '%s'",
                         typeNames[i]);
                for (int x = 1; x < i; ++x) {
                    if (types[x] && args[x])
                        QMetaType::destroy(types[x], args[x]);
                }
                qFree(types);
                qFree(args);
                return false;
            }
        }

        if (type == Qt::QueuedConnection) {
            QCoreApplication::postEvent(obj, new QMetaCallEvent(idx, 0, -1, nargs, types, args));
        } else {
            if (QThread::currentThread() == obj->thread()) {
                qWarning("QMetaObject::invokeMethod: Dead lock detected in BlockingQueuedConnection: "
                         "Receiver is %s(%p)",
                         obj->metaObject()->className(), obj);
            }

            // blocking queued connection
#ifdef QT_NO_THREAD
            QCoreApplication::postEvent(obj, new QMetaCallEvent(idx, 0, -1, nargs, types, args));
#else
            QSemaphore semaphore;
            QCoreApplication::postEvent(obj, new QMetaCallEvent(idx, 0, -1, nargs, types, args, &semaphore));
            semaphore.acquire();
#endif // QT_NO_THREAD
        }
    }
    return true;
}
方法的ID由indexOfMethod返回。如果方法的簽名不能創建,invokeMethod返回false。

(二)  逆向
逆向時我們需要使用Qt提供的元數據。先看看元數據表:
QT_BEGIN_MOC_NAMESPACE
static const uint qt_meta_data_Counter[] = {

 // content:
       1,       // revision
       0,       // classname
       0,    0, // classinfo
       2,   10, // methods
       0,    0, // properties
       0,    0, // enums/sets

 // signals: signature, parameters, type, tag, flags
      18,    9,    8,    8, 0x05,

 // slots: signature, parameters, type, tag, flags
      42,   36,    8,    8, 0x0a,

       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

    MethodMethod = 0x00,
    MethodSignal = 0x04,
    MethodSlot = 0x08,
    MethodTypeMask = 0x0c,

    MethodCompatibility = 0x10,
    MethodCloned = 0x20,
    MethodScriptable = 0x40
};
關於方法的知識就這些,下面我們研究枚舉和屬性。我們先在代碼中加入這兩類代碼:
class Counter : public QObject
{
  Q_OBJECT
  Q_PROPERTY(Priority priority READ priority WRITE setPriority)
    Q_ENUMS(Priority)


public:
  Counter() { m_value = 0; };
  
  enum Priority { High, Low, VeryHigh, VeryLow };

     void setPriority(Priority priority) { m_priority = priority; };
     Priority priority() const { return m_priority; };

  int value() const { return m_value; };
  
public slots:

    void setValue(int value)
    {
      if (value != m_value) 
      {
        m_value = value;
        emit valueChanged(value);
      }
    };
    
signals:
    void valueChanged(int newValue);

private:
  int m_value;
  Priority m_priority;
};
這時moc會生成:
static const uint qt_meta_data_Counter[] = {

 // content:
       1,       // revision
       0,       // classname
       0,    0, // classinfo
       2,   10, // methods
       1,   20, // properties
       1,   23, // enums/sets

 // signals: signature, parameters, type, tag, flags
      18,    9,    8,    8, 0x05,

 // slots: signature, parameters, type, tag, flags
      42,   36,    8,    8, 0x0a,

 // properties: name, type, flags
      65,   56, 0x0009510b,

 // enums: name, flags, count, data
      56, 0x0,    4,   27,

 // enum data: key, value
      74, uint(Counter::High),
      79, uint(Counter::Low),
      83, uint(Counter::VeryHigh),
      92, uint(Counter::VeryLow),

       0        // eod
};

static const char qt_meta_stringdata_Counter[] = {
    "Counter\0\0newValue\0valueChanged(int)\0"
    "value\0setValue(int)\0Priority\0priority\0"
    "High\0Low\0VeryHigh\0VeryLow\0"
};
同樣,我們也可以得到屬性和枚舉的數量和偏移。對屬性而言,包含3個整數:名字(name)、類型(type)和特徵(flags)。類型對應的是屬性的類型,我們例子中就是Priority。特徵值可分爲:
enum PropertyFlags  {
    Invalid = 0x00000000,
    Readable = 0x00000001,
    Writable = 0x00000002,
    Resettable = 0x00000004,
    EnumOrFlag = 0x00000008,
    StdCppSet = 0x00000100,
//    Override = 0x00000200,
    Designable = 0x00001000,
    ResolveDesignable = 0x00002000,
    Scriptable = 0x00004000,
    ResolveScriptable = 0x00008000,
    Stored = 0x00010000,
    ResolveStored = 0x00020000,
    Editable = 0x00040000,
    ResolveEditable = 0x00080000,
    User = 0x00100000,
    ResolveUser = 0x00200000
};
對於枚舉,有名字(name)、特徵(flags)、數量(count)和數據(data)。特徵段沒有使用。數量段表示枚舉中條目的數量。數據段是一個元數據表的偏移,指向枚舉中的條目。每個條目由兩個整數表示:key和value。Key指向當前條目的名字,value是此條目的實際值。
爲了獲得二進制文件的元數據信息,作者特意用Python寫了一個IDA腳本,可以從一個Q_OBJECT類中提取出方法、屬性和枚舉。代碼如下:
# ---------------------------------------------
# MetaData Parser Class
# ---------------------------------------------

# 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 ReadAddress(addr):
    if b64bit == True:
        return (Dword(addr+4) << 32) | Dword(addr)
    return Dword(addr)

class MetaParser:
    
    def __init__(self, stringsaddr, tableaddr):
        self.MetaStrings = stringsaddr
        self.MetaTable = tableaddr
    
    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)
        
    MethodFlags = Enum(             \
        AccessPrivate = 0x00,       \
        AccessProtected = 0x01,     \
        AccessPublic = 0x02,        \
        AccessMask = 0x03,          \
        MethodMethod = 0x00,        \
        MethodSignal = 0x04,        \
        MethodSlot = 0x08,          \
        MethodTypeMask = 0x0c,      \
        MethodCompatibility = 0x10, \
        MethodCloned = 0x20,        \
        MethodScriptable = 0x40)
        
    PropertyFlags = Enum(               \
        Invalid = 0x00000000,           \
        Readable = 0x00000001,          \
        Writable = 0x00000002,          \
        Resettable = 0x00000004,        \
        EnumOrFlag = 0x00000008,        \
        StdCppSet = 0x00000100,         \
        Designable = 0x00001000,        \
        ResolveDesignable = 0x00002000, \
        Scriptable = 0x00004000,        \
        ResolveScriptable = 0x00008000, \
        Stored = 0x00010000,            \
        ResolveStored = 0x00020000,     \
        Editable = 0x00040000,          \
        ResolveEditable = 0x00080000,   \
        User = 0x00100000,              \
        ResolveUser = 0x00200000)


    # ---------------------------------------------
    # methods
    # ---------------------------------------------
    
    def GetClassName(self):
        stringaddr = Dword(self.MetaTable + 4) + self.MetaStrings
        return self.ReadString(stringaddr)
    
    def GetMethodNumber(self):
        return Dword(self.MetaTable + 16)
    
    def GetMethodSignature(self, method_index):
        if method_index >= self.GetMethodNumber():
            return "error: method index out of range"
        
        method_offset = (Dword(self.MetaTable + 20) * 4) + (method_index * (5 * 4))
        
        # get accessibility
        
        access_flags = self.GetMethodAccess(method_index)
        access_type = "private:   "
        if access_flags == self.MethodFlags.AccessProtected:
            access_type = "protected: "
        elif access_flags == self.MethodFlags.AccessPublic:
            access_type = "public:    "
        
        # read return type
        rettype = self.ReadString(Dword(self.MetaTable + method_offset + 8) + self.MetaStrings)
        
        if rettype == "":
            rettype = "void"
        
        # read partial signature
        psign = self.ReadString(Dword(self.MetaTable + method_offset) + self.MetaStrings)
        
        # retrieve argument types
        
        par_index = psign.find("(")
        arg_types = psign[(par_index + 1):(len(psign) - 1)].split(",")
        
        # read argument names
        arg_names = self.ReadString(Dword(self.MetaTable + method_offset + 4) \
            + self.MetaStrings).split(",")
        
        # 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
        
        return access_type + rettype + " " + psign[0:(par_index + 1)] + args + ")"
    
    
    def GetMethodFlags(self, method_index):
        if method_index >= self.GetMethodNumber():
            return -1
        method_offset = (Dword(self.MetaTable + 20) * 4) + (method_index * (5 * 4))
        return Dword(self.MetaTable + method_offset + 16)
    
    def GetMethodType(self, method_index):
        return self.GetMethodFlags(method_index) & self.MethodFlags.MethodTypeMask
    
    def GetMethodAccess(self, method_index):
        return self.GetMethodFlags(method_index) & self.MethodFlags.AccessMask
    
    def GetPropertyNumber(self):
        return Dword(self.MetaTable + 24)
    
    def GetPropertyDecl(self, property_index):
        if property_index >= self.GetPropertyNumber():
            return "error: property index out of range"
        
        property_offset = (Dword(self.MetaTable + 28) * 4) + (property_index * (3 * 4))
        
        # read name
        pr_name = self.ReadString(Dword(self.MetaTable + property_offset) + self.MetaStrings)
        
        # read type
        pr_type = self.ReadString(Dword(self.MetaTable + property_offset + 4) + self.MetaStrings)
        
        return pr_type + " " + pr_name
    
    def GetPropertyFlags(self, property_index):
        if property_index >= self.GetPropertyNumber():
            return -1
        property_offset = (Dword(self.MetaTable + 28) * 4) + (property_index * (3 * 4))
        return Dword(self.MetaTable + property_offset + 8)
    
    def PropertyFlagsToString(self, flags):
        if flags == 0:
            return "Invalid"
        
        fstr = ""
        if flags & self.PropertyFlags.Readable:
            fstr += " | Readable"
        if flags & self.PropertyFlags.Writable:
            fstr += " | Writable"
        if flags & self.PropertyFlags.Resettable:
            fstr += " | Resettable"
        if flags & self.PropertyFlags.EnumOrFlag:
            fstr += " | EnumOrFlag"
        if flags & self.PropertyFlags.StdCppSet:
            fstr += " | StdCppSet"
        if flags & self.PropertyFlags.Designable:
            fstr += " | Designable"
        if flags & self.PropertyFlags.ResolveDesignable:
            fstr += " | ResolveDesignable"
        if flags & self.PropertyFlags.Scriptable:
            fstr += " | Scriptable"
        if flags & self.PropertyFlags.ResolveScriptable:
            fstr += " | ResolveScriptable"
        if flags & self.PropertyFlags.Stored:
            fstr += " | Stored"
        if flags & self.PropertyFlags.ResolveStored:
            fstr += " | ResolveStored"
        if flags & self.PropertyFlags.Editable:
            fstr += " | Editable"
        if flags & self.PropertyFlags.ResolveEditable:
            fstr += " | ResolveEditable"
        if flags & self.PropertyFlags.User:
            fstr += " | User"
        if flags & self.PropertyFlags.ResolveUser:
            fstr += " | ResolveUser"
        
        return fstr[3:]
        
        
    def GetEnumNumber(self):
        return Dword(self.MetaTable + 32)
        
    def GetEnumDecl(self, enum_index):
        if enum_index >= self.GetPropertyNumber():
            return "error: property index out of range"
        
        enum_offset = (Dword(self.MetaTable + 36) * 4) + (enum_index * (4 * 4))
        
        # read name
        enum_name = self.ReadString(Dword(self.MetaTable + enum_offset) + self.MetaStrings)
        
        # read number of items
        items_num = Dword(self.MetaTable + enum_offset + 8)
        
        # items addr
        items_addr = (Dword(self.MetaTable + enum_offset + 12) * 4) + self.MetaTable
        
        decl = "enum " + enum_name + "\n{\n"
        
        # add items
        x = 0
        while x < items_num:
            # read item name
            item_name = self.ReadString(Dword(items_addr) + self.MetaStrings)
            # read data
            item_data = "0x%X" % Dword(items_addr + 4)
            # add
            decl += "    " + item_name + " = " + item_data + ",\n"
            # inc loop
            x += 1
            items_addr += 8
            
        decl += "\n};"
        
        return decl

# ---------------------------------------------
# Display MetaData
# ---------------------------------------------

def DisplayMethod(parser, method_index):
    print(str(method_index) + " - " + parser.GetMethodSignature(method_index))
    
def DisplayProperty(parser, property_index):
    print(str(property_index) + " - " + parser.GetPropertyDecl(property_index))
    flags = parser.GetPropertyFlags(property_index)
    print("    flags: " + parser.PropertyFlagsToString(flags))
    
def DisplayEnum(parser, enum_index):
    print("[" + str(enum_index) + "]\n" + parser.GetEnumDecl(enum_index) + "\n")

def DisplayMetaData(stringsaddr, tableaddr):
    parser = MetaParser(stringsaddr, tableaddr)
    
    print("\n-------------------------------------------------")
    print("--- " + "Qt MetaData Displayer by Daniel Pistelli")
    print("--- " + "metadata of the class: " + parser.GetClassName() + "\n")
    
    num_methods = parser.GetMethodNumber()
    num_properties = parser.GetPropertyNumber()
    num_enums = parser.GetEnumNumber()
    
    # ---------------------------------------------
    # methods
    # ---------------------------------------------
    
    # signals
    
    print("--- Signals:\n")
    
    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
        
    # ---------------------------------------------
    # properties
    # ---------------------------------------------
    
    print("\n--- Properties:\n")
    
    x = 0
    while x < num_properties:
        DisplayProperty(parser, x)
        # increment loop
        x += 1
    
    # ---------------------------------------------
    # enums
    # ---------------------------------------------
    
    print("\n--- Enums:\n")
    
    x = 0
    while x < num_enums:
        DisplayEnum(parser, x)
        # increment loop
        x += 1
    
    print("-------------------------------------------------\n")

# ---------------------------------------------
# Main
# ---------------------------------------------

addrtoparse = ScreenEA()
if addrtoparse != 0:
    stringsaddr = ReadAddress(addrtoparse + AddressSize())
    tableaddr = ReadAddress(addrtoparse + AddressSize() * 2)
    if stringsaddr != 0 or tableaddr != 0:
        DisplayMetaData(stringsaddr, tableaddr)
DisplayMetaData函數接收兩個參數:元數據表地址和元數據字符串地址。這是因爲有時類的結構要在運行時才能確定,就像下面的VC++例子:
.text:00401D60 sub_401D60      proc near   ; DATA XREF: .rdata:00402108
.text:00401D60  push    ebp
.text:00401D61  mov     ebp, esp
.text:00401D63  mov     eax, ds:?staticMetaObject@QObject@@2UQMetaObject@@B 
      ; QMetaObject const QObject::staticMetaObject
.text:00401D68  mov     dword_403070, eax
.text:00401D6D  mov     dword_403074, offset Str2 ; "Counter"
.text:00401D77  mov     dword_403078, offset unk_4021A8
.text:00401D81  mov     dword_40307C, 0
.text:00401D8B  pop     ebp
.text:00401D8C  retn
.text:00401D8C sub_401D60      endp
但有時候,類結構可以靜態確定:
.data:00406000  dd 0                    ; SuperData
.data:00406004  dd offset MetaStrings   ; "Counter"
.data:00406008  dd offset MetaTable
這時,腳本可以直接在SuperData地址上執行,這是QMetaObject中的第一個d結構。Counter類執行腳本後的輸出是:
-------------------------------------------------
--- Qt MetaData Displayer by Daniel Pistelli
--- metadata of the class: Counter

--- Signals:

0 - protected: void valueChanged(int newValue)

--- Slots:

1 - public:    void setValue(int value)

--- Other Methods:


--- Properties:

0 - Priority priority
    flags: Readable | Writable | EnumOrFlag | StdCppSet | Designable | Scriptable | Stored | ResolveEditable

--- Enums:

[0]
enum Priority
{
    High = 0x0,
    Low = 0x1,
    VeryHigh = 0x2,
    VeryLow = 0x3,

};
-------------------------------------------------
是不是很爽?方法的簽名甚至包括了參數的名字(當可用時)。
(作者還測試了QWidget類,這裏略去數百字)……
如何找到類的元數據呢?讓我們看Counter類的結構:
.text:004012F2  mov     eax, ds:_ZN7QObjectC2EPS_
.text:004012F7  mov     [esp+88h+var_84], ecx
.text:004012FB  mov     [ebp+var_3C], 2
.text:00401302  call    eax ; _ZN7QObjectC2EPS_
.text:00401304  mov     eax, [ebp+var_4C]
.text:00401307  mov     dword ptr [eax], offset virtual_ptrs
最後一個彙編指令設置了虛函數表指針。Q_OBJECT類的虛函數表類似於:
.rdata:00402158 off_402158 dd offset metaObject
.rdata:0040215C            dd offset qt_metacast
.rdata:00402160            dd offset qt_metacall
metaObject方法可以獲得元數據表:
.text:00401430 metaObject      proc near             
.text:00401430  push    ebp
.text:00401431  mov     eax, offset dword_406000 ; QMetaObject class layout
.text:00401436  mov     ebp, esp
.text:00401438  pop     ebp
.text:00401439  retn
.text:00401439 metaObject      endp
dword_406000對應QMetaObject類的結構,我們需要它來執行腳本。
獲得類的元數據以後,下一步就是將方法(屬性)的名字和實際反彙編代碼聯繫起來。腳本輸出了每個方法和屬性的索引。要想從索引得到方法的地址,必須考慮moc生成的qt_metacall方法:
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;
    }
#ifndef QT_NO_PROPERTIES
      else if (_c == QMetaObject::ReadProperty) {
        void *_v = _a[0];
        switch (_id) {
        case 0: *reinterpret_cast< Priority*>(_v) = priority(); break;
        }
        _id -= 1;
    } else if (_c == QMetaObject::WriteProperty) {
        void *_v = _a[0];
        switch (_id) {
        case 0: setPriority(*reinterpret_cast< Priority*>(_v)); break;
        }
        _id -= 1;
    } else if (_c == QMetaObject::ResetProperty) {
        _id -= 1;
    } else if (_c == QMetaObject::QueryPropertyDesignable) {
        _id -= 1;
    } else if (_c == QMetaObject::QueryPropertyScriptable) {
        _id -= 1;
    } else if (_c == QMetaObject::QueryPropertyStored) {
        _id -= 1;
    } else if (_c == QMetaObject::QueryPropertyEditable) {
        _id -= 1;
    } else if (_c == QMetaObject::QueryPropertyUser) {
        _id -= 1;
    }
#endif // QT_NO_PROPERTIES
    return _id;
}
這個方法可以解析方法名和屬性get/set方法名。
qt_metacall的地址可以從虛函數表獲得。在分析此函數代碼前,我們先看這個枚舉:
enum Call {
    InvokeMetaMethod,    // 0
    ReadProperty,    // 1
    WriteProperty,    // 2
    ResetProperty,    // 3
    QueryPropertyDesignable,  // 4
    QueryPropertyScriptable,  // 5
    QueryPropertyStored,  // 6
    QueryPropertyEditable,  // 7
    QueryPropertyUser    // 8
};
然後看反彙編:
.text:004014D0 qt_metacall     proc near
.text:004014D0
.text:004014D0 var_28 = dword ptr -28h
.text:004014D0 var_24 = dword ptr -24h
.text:004014D0 var_20 = dword ptr -20h
.text:004014D0 var_1C = dword ptr -1Ch
.text:004014D0 var_10 = dword ptr -10h
.text:004014D0 var_C  = dword ptr -0Ch
.text:004014D0 var_8  = dword ptr -8
.text:004014D0 var_4  = dword ptr -4
.text:004014D0 arg_0  = dword ptr  8
.text:004014D0 arg_4  = dword ptr  0Ch
.text:004014D0 arg_8  = dword ptr  10h
.text:004014D0 arg_C  = dword ptr  14h
.text:004014D0
.text:004014D0  push    ebp
.text:004014D1  mov     ebp, esp
.text:004014D3  sub     esp, 28h
.text:004014D6  mov     [ebp+var_C], ebx
.text:004014D9  mov     eax, [ebp+arg_0]
.text:004014DC  mov     ebx, [ebp+arg_8]
.text:004014DF  mov     [ebp+var_8], esi
.text:004014E2  mov     esi, [ebp+arg_4] ; esi = _c
.text:004014E5  mov     [ebp+var_4], edi
.text:004014E8  mov     edi, [ebp+arg_C]
.text:004014EB  mov     [ebp+var_10], eax
.text:004014EE  mov     [esp+28h+var_20], ebx
.text:004014F2  mov     [esp+28h+var_1C], edi
.text:004014F6  mov     [esp+28h+var_24], esi
.text:004014FA  mov     [esp+28h+var_28], eax
.text:004014FD  call    _ZN7QObject11qt_metacallEN11QMetaObject4CallEiPPv
.text:00401502  test    eax, eax        ; eax = _id
.text:00401504  mov     ebx, eax
.text:00401506  js      short loc_40151C ; _id < 0 ?
.text:00401508  test    esi, esi
.text:0040150A  jnz     short loc_401530 ; _c != InvokeMetaMethod
.text:0040150C  test    eax, eax        ; _id == 0 ?
.text:0040150E  jz      loc_4015B8
.text:00401514  cmp     eax, 1          ; _id == 1 ?
.text:00401517  jz      short loc_401590
假如esi不等於0,那麼就是一個InvokeMetaMethod。考慮"_id == 0"的情況:
.text:004015B8 loc_4015B8:
.text:004015B8  mov     eax, [edi+4]
.text:004015BB  mov     edx, [ebp+var_10]
.text:004015BE  mov     eax, [eax]
.text:004015C0  mov     [esp+28h+var_28], edx
.text:004015C3  mov     [esp+28h+var_24], eax
.text:004015C7  call    sub_401490
由此我們很快就可以確定sub_401490就是valueChanged信號:
.text:00401490 ; void __cdecl signal_valueChanged(int this, int newValue)
.text:00401490 signal_valueChanged proc near
.text:00401490
.text:00401490 var_18 = dword ptr -18h
.text:00401490 var_14 = dword ptr -14h
.text:00401490 var_10 = dword ptr -10h
.text:00401490 var_C  = dword ptr -0Ch
.text:00401490 var_8  = dword ptr -8
.text:00401490 var_4  = dword ptr -4
.text:00401490 this   = dword ptr  8
.text:00401490 newValue = dword ptr  0Ch
.text:00401490
.text:00401490  push    ebp
.text:00401491  xor     ecx, ecx
.text:00401493  mov     ebp, esp
.text:00401495  lea     eax, [ebp+newValue]
.text:00401498  sub     esp, 18h
.text:0040149B  mov     [ebp+var_8], 0
.text:004014A2  mov     edx, offset dword_406000
.text:004014A7  mov     [ebp+var_4], eax
.text:004014AA  lea     eax, [ebp+var_8]
.text:004014AD  mov     [esp+18h+var_C], eax
.text:004014B1  mov     eax, [ebp+this]
.text:004014B4  mov     [esp+18h+var_10], ecx
.text:004014B8  mov     [esp+18h+var_14], edx
.text:004014BC  mov     [esp+18h+var_18], eax
.text:004014BF  call    ds:_ZN11QMetaObject8activateEP7QObjectPKS_iPPv
.text:004014C5  leave
.text:004014C6  retn
接下來就無需解釋了。同樣的方法也可用來解決屬性的get/set。對於一個複雜文件,可以結合前面的元數據腳本,編寫一個解析器自動解析switch塊中的函數。但是要注意的是:switch塊是平臺和編譯器相關的,因此腳本需要頻繁調整。此外,一些小的方法可能被內聯(inline)進qt_metacall方法中。*轉載請註明來自看雪論壇@PEdiy.com 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章