ボンネットの下で信号とスロットはどのように実装されますか?
-
05-07-2019 - |
質問
この質問はこのフォーラムで既に質問されていますが、概念がわかりません。
読み返してみると、シグナルとスロットは関数ポインターを使用して実装されているようです。つまり、シグナルは内部で接続されているすべてのスロット(関数ポインター)を呼び出す1つの大きな関数です。これは正しいです?ストーリー全体で生成されたmocファイルの役割は何ですか?シグナル関数がどのスロットを呼び出すか、つまりどのスロットがこのシグナルに接続されているかをどのように認識するのかわかりません。
お時間をいただきありがとうございます
解決
Qtは、これらのことを、解釈された言語に似た方法で実装します。つまりシグナル名を関数ポインターにマップするシンボルテーブルを構築し、それらを維持し、必要に応じて関数名で関数ポインターを検索します。
信号を出すたびに、つまり書き込み
emit something();
実際に something()
関数を呼び出します。この関数は、メタオブジェクトコンパイラによって自動的に生成され、 *。moc
ファイルに配置されます。この関数内では、この信号が現在どのスロットに接続されているかがチェックされ、適切なスロット関数(独自のソースで実装した)がシンボルテーブルを介して順番に呼び出されます(上記の方法で)。また、 emit
は、他のQt固有のキーワードと同様に、 *。moc
が生成された後、C ++プリプロセッサによって破棄されます。実際、Qtヘッダーの1つ( qobjectdefs.h )、そのような行があります:
#define slots
#define signals protected
#define emit
接続関数( connect
)は、 *。moc
ファイル内に保持されているシンボルテーブルと、それに渡される引数( SIGNAL()
および `SLOTマクロ)もテーブルに一致するように前処理されます。
これが一般的な考え方です。別の回答では、ジョージ はリンクを提供します trolltechメーリングリストおよびこのトピックに関する別のSO質問。
他のヒント
次を追加する必要があると思います。
別のリンクされた質問-< href = "http://www.ntcore.com/files/qtrev.htm" rel = "nofollow noreferrer">非常に優れた記事は、回答; この記事は再びここにあります、改善されています(まだ完璧ではありません)コード構文の強調表示。
ここに私の短い言い直しがあります、それは間違いを起こしやすいかもしれません)
基本的に、クラス定義に Q_OBJECT
マクロを挿入すると、プリプロセッサはそれを静的な QMetaObject
インスタンス宣言に展開します。これは、同じクラス:
class ClassName : public QObject // our class definition
{
static const QMetaObject staticMetaObject; // <--= Q_OBJECT results to this
// ... signal and slots definitions, other stuff ...
}
このインスタンスは、初期化時に、信号とスロットの署名(&quot; methodname(argtype1、argtype2)&quot;
)を格納します。 indexOfMethod()
呼び出しの実装を許可します。これは、署名文字列によってメソッドのインデックスを返します:
struct Q_CORE_EXPORT QMetaObject
{
// ... skip ...
int indexOfMethod(const char *method) const;
// ... skip ...
static void activate(QObject *sender, int signal_index, void **argv);
// ... skip ...
struct { // private data
const QMetaObject *superdata; // links to the parent class, I guess
const char *stringdata; // basically, "string1\0string2\0..." that contains signatures and other names
const uint *data; // the indices for the strings in stringdata and other stuff (e.g. flags)
// skip
} d;
};
moc
がQtクラスヘッダー headername.h
の moc_headername.cpp
ファイルを作成すると、署名文字列と d
構造の正しい初期化に必要なその他のデータ。次に、このデータを使用して staticMetaObject
シングルトンの初期化コードを書き込みます。
もう1つの重要なことは、オブジェクトの qt_metacall()
メソッドのコードの生成です。このメソッドは、オブジェクトのメソッドIDと引数ポインターの配列を受け取り、長いを介してメソッドを呼び出します。このようなスイッチ
:
int ClassName::qt_metacall(..., int _id, void **_args)
{
// ... skip ...
switch (_id) {
case 0: signalOrSlotMethod1(_args[1], _args[2]); break; // for a method with two args
case 1: signalOrSlotMethod2(_args[1]); break; // for a method with a single argument
// ... etc ...
}
// ... skip ...
}
最後に、 moc
はすべてのシグナルに対して、 QMetaObject :: activate()
呼び出しを含む実装を生成します:
void ClassName::signalName(argtype1 arg1, argtype2 arg2, /* ... */)
{
void *_args[] = { 0, // this entry stands for the return value
&arg1, // actually, there's a (void*) type conversion
&arg2, // in the C++ style
// ...
};
QMetaObject::activate( this,
&staticMetaObject,
0, /* this is the signal index in the qt_metacall() map, I suppose */
_args
);
}
最後に、 connect()
呼び出しは、文字列メソッドのシグネチャを整数ID( qt_metacall()
で使用されるもの)に変換し、シグナル送信先のリストを維持します-スロット接続;シグナルが発信されると、 activate()
コードはこのリストを通過し、適切なオブジェクト「スロット」を呼び出します。 qt_metacall()
メソッドを介して。
要約すると、静的な QMetaObject
インスタンスは「メタ情報」を保存します。 (メソッド署名文字列など)、生成された qt_metacall()
メソッドは、「メソッドテーブル」を提供します。インデックスによって任意のシグナル/スロットを呼び出すことができます。 moc
によって生成されたシグナル実装は、 activate()
を介してこれらのインデックスを使用し、最後に connect()
は、信号からスロットへのインデックスマップのリストを管理します。
*注意:異なるスレッド間でシグナルを配信したい場合に使用されるこのスキームの複雑さ( blocking_activate()
コードを見る必要があると思います)が、一般的な考え方が変わらないことを願っています)
これはリンクされた記事を非常に大まかに理解しているため、簡単に間違っている可能性があるため、直接読んで直接読むことをお勧めします
PS。 Qt実装の理解を深めたいので、改めて矛盾がある場合は教えてください!
他の(以前の)回答が熱心なエディターによって削除されたので、ここにテキストを追加します(Pavel Shvedの投稿に組み込まれていない いくつかの詳細が欠落しています。誰が答えを削除したかを気にかけます。
@Pavel Shved:
Qtヘッダーのどこかに次の行が存在することを確信しています:
#define emit
ただ