Qt uses pre-compilers and macros to ensure powerful cross-platform capabilities. The signal mechanism is the most subtle. This article analyzes several common signal processing mechanisms, and then introduces the signal/Slot Mechanism of QT in detail.
The signal mentioned here is not the signal for inter-process communication in UNIX. The signals here are more closely related to the input and output of the graphic interface (of course, they can be invisible operations ). Since computer programs have changed from a character interface to a graphical interface, user input has suddenly become complicated and rich, different Input locations, different input devices, different focus locations, and different input values constitute many signals. All of a sudden, this world has become colorful. What are the current three major operating systems ?? Windows, UNIX, and Mac provide a visual GUI. Although they come from different companies and have many branches, they are similar in terms of graphic operations and management: they all have desktops, icons, rules, and irregular windows, the window contains titles, borders, menus, buttons, and other controls. You can enter the content in the current focus on the keyboard, and click any window and control with the mouse. In terms of initiative, it is the next step by the user-led program, rather than the character era. This is also called "event-driven ". In an event-driven system, neither windows nor UNIX can break away from the following processing frameworks:
When an application receives an event from the operating system, it determines who should handle the event. The processing itself may cause a new event, which tells the operating system what signal I sent. In this way, the water of Qingshan flow. So how does a specific signal trigger a function corresponding to it? Most systems adopt callback mechanisms. The so-called "Callback" is actually a pointer to a function. In C language, the number of letters is actually a pointer, so the callback is actually a pointer to the pointer. There are some minor differences in callback implementation in different development frameworks or sdks. When I first came into contact with QT, I had been wondering how it handled signal calls from various platforms. Although the C language is platform independent, the signal mechanism varies depending on an operating system or a development kit. Signals are an important part of an object-oriented development environment. To design a class library or program framework, you must consider the differences between different platforms. After being connected to QT, I feel that QT has chosen a special way to handle it ?? Signals/slot. The Chinese name is tentatively set to "signal/reaction slot ". In the internal design of QT, callback is well encapsulated through the use of signal/reaction slot. To better understand this mechanism, let's take a look at several other commonly used signal-related procedures.
1. Win32
Win32 programs are always executed from winmain. In winmain code, there are generally three main functions: one is to register the window class, the other is to display the window on the screen, and the third is to implement the message loop. The function of message ring is to retrieve the messages put into the operating system from the application queue, so as to realize interaction between users and programs (including non-user input messages such as timers ). Applications occasionally wait for messages to arrive in the message ring. // Message ring
While (getmessage (& MSG, null, 0, 0 ))
{
Translatemessage (& MSG );
Dispatchmessage (& MSG );
}
This program includes three basic APIs for forming a standard message ring: getm essage (), translatemessage (), and ispatchmessage ()
When the acceleration key and non-mode dialog box are used, the structure of the message ring is changed accordingly. In Windows, getmessage () is the core of multiple tasks. Before a message appears in the message queue of an application, this function does not return anything. The wait for getmessage () blocks the current process and thus provides the opportunity to check the private message ring for other running applications. After a message appears, getmessage () retrieves the message and stores the message in a MSG data structure. Getmessage () returns true for each message that forces the exit of the message ring and stops the process (except wm_quit. Generally, a return statement is followed by a message ring to force winmain () to return to the system. Follow the translatemessage () of getmessage () to process the MSG and modify the content of the data block. Dispatchmessage () is used to find which window should be called. This selection is based on the window identified by hwnd in MSG. The Window Process processes messages,
After completion, return to the message ring and run getmessage () again (). As shown in:
To process a message of interest, the window must provide a message callback function during creation, whether explicitly called or implicitly generated by other API functions. In this callback function, the user must judge and process each message of interest. From the perspective of C language, a window process (callback function) is a function that accepts four parameters, returns an lresult value. A switch statement occupies a large amount of code during the process to complete each action.
2. MFC
Although programs developed using Win32 APIs are highly efficient and well-organized, the development process is complicated and the maintenance time is also high, therefore, most applications developed using C ++ in Windows use the MFC class library provided by Microsoft. It is an object-oriented design. Although its programming style is quite different from that of Win32 at first glance, it is a highly encapsulated result and its internal implementation is no different from that of Win32. One of the Dominant Design Concepts of MFC is the view/document model (cframewnd) under the program framework, and many macros are defined to simplify programming, the message transmission is also closely related to macros (the anatomy of MFC can be seen in the second edition of Mr Hou Jie's "deep dive into MFC ). By using these macros, the application itself will maintain this potentially expensive message ing table. For programmers, they only need to click the mouse to complete the above work, and the development efficiency has been greatly improved. 3. Linux
A major difference between Linux (including other UNIX) and Windows is that the management of the GUI is separated from the kernel and is responsible for graphic operations (including keyboard, mouse, and other event capturing) the module is X Window. Note that the "window" here is irrelevant to Microsoft Windows. X Window consists of three parts: Server (xserver), client (X client), and Protocol (X protocol), as shown below: the graphic interface program developed in Linux is generally the client program in X Window, and the corresponding library is X Lib. X Lib is the lowest interface library in X Window, which is equivalent
API. This library encapsulates access to X protocol and provides over 610 functions. Because X protocol can be transmitted over the network, the server and client in X Window may not be on one machine, which is very different from Microsoft Windows. Comparing the Processing Methods of X lib and Win32 APIs, we can find that although the frameworks and styles of the two are different, the process processing is similar.
4. QT
Nearly half of the class libraries in QT are inherited from the base class qobject. The signals/slot mechanism is used to communicate between the qobject class and its subclasses. As a general processing mechanism, the signal and reaction slot are flexible and can carry any number of parameters. The parameter types are also customized by the user. At the same time, it is also type-safe. Any user class inherited from qobject or its subclass can use signal and reaction slots. Signals serve as messages in windows. In QT, for the object that sends the signal, it does not know who received the signal. Such a design may be inconvenient in some places, but it eliminates tight coupling, which is advantageous for the overall design. The reaction slot is used to receive signals,
But it is actually a common function. A programmer can call a reaction slot just like a common function. Similar to the signal, the owner of the reaction slot does not know who sent a signal to it. In the program design process, multiple signals can be connected to one reaction slot. Similarly, one signal can be connected to multiple reaction slots, or even one signal can be connected to another signal. In Windows, if we need to activate a function for multiple menus, we usually write a shared function first and then call this function in each menu event. If you want to implement the same function in QT, you can write the implementation part in a menu, and then combine other menus with the menu level. Although the signal/reaction slot mechanism has many advantages and is easy to use
It is not a defect. The biggest drawback is to sacrifice a little bit of performance. Based on trolltech's self-testing, on a PC with an Intel PentiumII 500 MHz CPU, a single signal can be called 2 million times per second for a connection corresponding to a reaction slot; A single signal can be called 1.2 million times per second for the connection of two reaction tanks. This speed is a tenth of the speed of direct callback without connection. Note that the 10-in-1 speed ratio here is the call speed, rather than the execution time of a complete function. In fact, in general, the total execution time of a function is mostly in the execution part, and only a small part is in the call part, because this speed is acceptable. This is similar to object-oriented programming compared with structured programming in earlier years: the execution efficiency of programs has not improved, but has declined. but now everyone is using object-oriented programming.
Program. It is worthwhile to use part of the execution efficiency to switch back to the development efficiency and maintenance efficiency. Moreover, it is now the mainstream era of P4. Let's take a look at a simple example:
Class Demo: Public qobject
{
Q_object
Public:
Demo ();
Int value () const {return val ;};
Public slots:
Void setvalue (INT );
Signals:
Void valuechanged (INT );
PRIVATE:
Int val;
};
As shown in the example, there are two keywords slots and signals in the class definition, and a macro q_object. If the signal and reaction slot is used in the QT program, the macro must be declared in the class definition. However, if you declare the macro but there is no signal or reaction slot in the program, it will not affect the program, so we suggest you add this macro when writing a program using QT. Slots defines the implementation of the signal function, that is, the reaction slot, for example:
Void Demo: setvalue (int v)
{
If (V! = Val ){
Val = V;
Emit valuechanged (v );
}
}
This program indicates that when setvalue is executed, it will release the valuechanged signal. The following procedure demonstrates the connection between signals and reaction tanks between different objects.
Demo A, B;
Connect (& A, signal (valuechanged (INT), & B, slot (setvalue (INT )));
B. setvalue (11 );
A. setvalue (7 array );
B. Value (); // The value of B will be 7array rather than the original 11
In the above program, once the signal is connected to the reaction slot, when. setvalue (7 array) releases a valuechanged (INT) signal, and object B receives the signal and triggers the setvalue (INT) function. When B executes the setvalue (INT) function, it also releases the valuechanged (INT) signal. Of course, B's signal is not received, so nothing is done. Note: In the example, the signal is released only when the input variable V is not equal to Val.
A and B do not result in an endless loop. Because the keyword and macro specific to QT are used in the example, QT itself does not include the C ++ compiler, therefore, if you use a popular Compilation Program (such as the visual compiler in Windows) to convert the code into a C ++ code without special keywords and macros, You can parse, compile, and link these compilation programs.
The above Code defines CITIC numbers and reaction slots in the class. So, can a non-class member function, for example, a global function do the same? The answer is no. This signal can be sent only when the signal class or its subclass is defined. Different signals of an object can be connected to different objects. When a signal is released, the reaction slot connected to it will be executed immediately, just like directly calling this function in a program. The signal releasing process is blocked, which means that the signal releasing process is returned only after the reaction slot is executed. If a signal is connected to multiple reaction tanks, these reaction tanks are executed sequentially, And the sorting process is arbitrary. Therefore, if the program has strict requirements on the sequence of execution of these reaction tanks, special attention should be paid. When using signals, note that the signal definition process is implemented in the class definition process, that is, the header file. For the normal operation of the intermediate compilation tool MOC, do not define signals in the source file (. cpp), and the signal itself should not return any data type, that is, a null value (void ). If you want to design a common class or control, you should try to use common data in the parameters of the signal or reaction slot to increase universality. The valuechanged parameter in the code above is int type. If it uses a special type such as qrangecontrol: range, this signal can only be connected to the reaction slot in rangecontrol. As mentioned above, the slot is also a common function, and there is no difference between the execution of user functions with no slots defined. However, signals cannot be connected with conventional functions in the program. Otherwise, the release of signals will not cause the execution of corresponding functions. Even if the intermediate Compilation Program MOC does not report errors in this case, the C ++ Compilation Program will not report errors. It is easy for beginners to ignore this point. It is often because the program has been compiled without errors and is logically correct, but the running result is not displayed as expected, at this time, check whether this is caused by negligence. Qt designers make such an estimation to ensure the strict matching between signals and reaction tanks. Since there is no difference between the reaction slot and conventional functions during execution, it can also be defined as a public reaction slot (Public
Slots), Protection slots, and private slots ). If necessary, we can also define the reaction slot as a virtual function so that sub-classes can be implemented differently, which is very useful. We will only discuss how to use the signal and reaction slot. Since qt's X11 free edition provides the source code, let's take a look at the implementation of connect in qobject. QT is a cross-platform development library. To work with compilers on different platforms, it defines an intermediate class qmetaobject, this class stores information about the signal/reaction slot and the object itself. This class is used internally by QT and should not be used by users.
The following is the definition of qmetaobject (some minor code is deleted for convenient browsing ):
Class q_export qmetaobject
{
Public:
Qmetaobject (const char * const class_name, qmetaobject * superclass,
Const qmetadata * const slot_data, int n_slots,
Const qmetadata * const signal_data, int n_signals );
Virtual ~ Qmetaobject ();
Int numslots (bool super = false) const;/* Number of reaction slots */
Int numsignals (bool super = false) const;/* Number of signals */
Int findslot (const char *, bool super = false) const;
/* Locate the index in the list based on the reaction slot name */
Int findsignal (const char *, bool super = false) const;
/* Locate the index in the list based on the signal name */
Const qmetadata * slot (INT index, bool super = false) const;
/* Obtain the response slot data based on the Index */
Const qmetadata * signal (INT index, bool super = false) const;
/* Obtain the signal data based on the Index */
Qstrlist slotnames (bool super = false) const;
/* Obtain the reaction slot list */
Qstrlist signalnames (bool super = false) const;
/* Obtain the signal list */
Int slotoffset () const;
Int signaloffset () const;
Static qmetaobject * metaobject (const char * class_name );
PRIVATE:
Qmemberdict * Init (const qmetadata *, INT );
Const qmetadata * slotdata;/* Data Pointer of the reaction slot */
Qmemberdict * slotdict;/* Data Dictionary pointer of the reaction slot */
Const qmetadata * signaldata;/* signal Data Pointer */
Qmemberdict * signaldict;/* signal data dictionary pointer */
Int signaloffset;
Int slotoffset;
};
Let's take a look at the implementation of connect in qobject. The function exposes a more refined function: connectinternal. What does the function do? Let's take a look:
Void qobject: connectinternal (const qobject * sender, int signal_index,
Const qobject * receiver,
Int membcode, int member_index)
{
Qobject * s = (qobject *) sender;
Qobject * r = (qobject *) receiver;
If (! S-> connections ){
/* If an object has a signal or reaction slot but no connection is established, no connection list will be established, which can reduce unnecessary resource consumption */
S-> connections = new qsignalvec (7 );
S-> connections-> setautodelete (true );
/* When no connection exists, the connection list will be automatically deleted */
}
Qconnectionlist * clist = s-> connections-> at (signal_index );
If (! Clist ){
/* Create a list of receiving objects corresponding to one of the signal sources */
Clist = new qconnectionlist;
Clist-> setautodelete (true );
S-> connections-> insert (signal_index, clist );
}
Qmetaobject * rmeta = r-> metaobject ();
Switch (membcode ){
/* Obtain the Data Pointer of the signal or reaction slot */
Case qslot_code:
Rm = rmeta-> slot (member_index, true );
Break;
Case qsignal_code:
Rm = rmeta-> signal (member_index, true );
Break;
}
Qconnection * c = new qconnection (R, member_index,
Rm? Rm-> name: "qt_invoke", membcode );
/* Create a new signal/reaction cell connection */
Clist-> append (c);/* Add this pair of connections to the signal source */
If (! R-> senderobjects ){
/* Similar to the signal source, the connection list of the reaction slot is also dynamically created */
R-> senderobjects = new qobjectlist;
}
R-> senderobjects-> append (s);/* Add the connection to the reaction slot */
}
At this point, the connection between the signal and the reaction slot has been established. How does one trigger the reaction slot when the signal is generated? From the definition of qobject, we can see that it has multiple activate_signal member functions, all of which are protected, that is, they can only be used by themselves or subclasses. Let's take a look at its implementation:
Void qobject: activate_signal (qconnectionlist * clist, quobject * O)
{
If (! Clist)/* validity check */
Return;
Qobject * object;
Qconnection * C;
If (clist-> count () = 1 ){
/* For a specific signal of an object, there is usually only one reaction slot connected to it, so you can determine in advance to speed up processing */
C = clist-> first ();
Object = C-> Object ();
Sigsender = this;
If (c-> membertype () = qsignal_code)
Object-> qt_emit (c-> member (), O);/* signal-level connection */
Else
Object-> qt_invoke (c-> member (), O);/* call the slot function */
} Else {
Qconnectionlistit (* clist );
While (C = it. Current () {/* scan multiple connections one by one */
++ It;
Object = C-> Object ();
Sigsender = this;
If (c-> membertype () = qsignal_code)
Object-> qt_emit (c-> member (), O);/* signal-level connection */
Else
Object-> qt_invoke (c-> member (), O);/* call the slot function */
}
}
}
So far, we have a basic understanding of the process of qt citic/reaction tanks. Let's take a look at the newly added Syntax of QT: three keywords: slots, signals, and emit. Three macros: slot (), signal (), and q_object. In the header file qobjectdefs. H, we can see that the new syntax is defined as follows:
# Define slots // slots: in class
# Define signals protected // signals: in class
# Define emit // emit Signal
# Define slot (a) "1" #
# Define signal (a) "2" #
We can see that there is nothing to do with the three keywords, and the slot () and signal () macros simply add a single character before the string, so that
The program can identify who is a signal and who is a reaction slot only by name. The intermediate Compilation Program moc.exe can "translate" corresponding functions based on these keywords and macros for compilation in the C ++ compiler. The remaining macro q_object is complex. Its definition is as follows:
# Define q_object \
Publi \
Virtual qmetaobject * metaobject () const {\
Return staticmetaobject ();\
}
\
Virtual const char * classname () const ;\
Virtual void * qt_cast (const char *);\
Virtual bool qt_invoke (INT, quobject *);\
Virtual bool qt_emit (INT, quobject *);\
Qt_prop_functions
\
Static qmetaobject * staticmetaobject ();\
Qobject * qobject () {return (qobject *) This ;}\
Qt_tr_functions
\
PRIVATE :\
Static qmetaobject * metaobj;
From the definition, we can see that the macro has two functions: one is to declare the qmetaobject intermediate class operations related to itself, the other is to declare the signal release operation and the activation operation of the reaction slot. After the moc.exe file is pre-compiled, a source file that can be compiled by the C ++ compiler is generated. The demo class described above is used as an example. Assume that its code files are d e m o. H and d e m o. C p, respectively.
Moc_demo.cpp:
Qmetaobject * Demo: metaobj = 0;
Void Demo: initmetaobject ()
{
If (metaobj)
Return;
If (strcmp (qobject: classname (), "qobject ")! = 0)
Badsuperclasswarning ("Demo", "qobject ");
(Void) staticmetaobject ();
}
Qmetaobject * Demo: staticmetaobject ()
{
If (metaobj)
Return metaobj;
(Void) qobject: staticmetaobject ();
Typedef void (Demo: * m1_t0) (INT );
M1_t0 v1_0 = q_ampersand Demo: setvalue;/* locate the portal of the reaction slot */
Qmetadata * slot_tbl = qmetaobject: new_metadata (1 );
/* Create a New reaction slot */
Qmetadata: Access * slot_tbl_access = qmetaobject: new_metaaccess (1 );
Slot_tbl [0]. Name = "setvalue (INT)";/* reaction slot name */
Slot_tbl [0]. PTR = * (qmember *) & v1_0 );
/* Find the portal pointer of the reaction tank by using the reaction tank name */
Slot_tbl_access [0] = qmetadata: public;/* permission type */
Typedef void (Demo: * m2_t0) (INT );
M2_t0 v2_0 = q_ampersand Demo: valuechanged;/* positioning signal entry */
Qmetadata * signal_tbl = qmetaobject: new_metadata (1);/* Create signal data */
Signal_tbl [0]. Name = "valuechanged (INT)";/* signal name */
Signal_tbl [0]. PTR = * (qmember *) & v2_0 );
/* The signal name can be used to find the signal entry pointer */
Metaobj = qmetaobject: new_metaobject (
/* Create a qmetaobject object related to the demo class */
"Demo", "qobject ",
Slot_tbl, 1,
Signal_tbl, 1,
0, 0 );
Metaobj-> set_slot_access (slot_tbl_access);/* Set permissions */
Return metaobj;
}
// Activate the corresponding reaction slot or another signal when there is a signal
Void Demo: valuechanged (INT T0)
{
Activate_signal ("valuechanged (INT)", T0 );
}
This file does not have the keywords unique to QT, nor has a special macro definition. It fully complies with the common C ++ syntax, so it can be compiled and linked smoothly.
Source: http://www.sudu.cn/info/html/edu/20070102/286735.html