zoukankan      html  css  js  c++  java
  • 宏Q_OBJECT

    所有QObject的派生类在官方文档中都推荐在头文件中放置宏Q_OBJECT,那么该宏到底为我们做了哪些工作?在qobjectdef.h中有下面的代码:


    1. #define Q_OBJECT \  
    2. public: \  
    3.   Q_OBJECT_CHECK \  
    4.   static const QMetaObject staticMetaObject; \  
    5.   Q_OBJECT_GETSTATICMETAOBJECT \  
    6.   virtual const QMetaObject *metaObject() const; \  
    7.   virtual void *qt_metacast(const char *); \  
    8.   QT_TR_FUNCTIONS \  
    9.   virtual int qt_metacall(QMetaObject::Call, intvoid **); \  
    10. private:  


    首先定义了一个Q_OBJECT_CHECK宏,这个宏在Q_OBJECT宏的上方定义:

    1. #define Q_OBJECT_CHECK \  
    2.   template <typename T> inline void qt_check_for_QOBJECT_macro(const T &_q_argument) const \  
    3.   { int i = qYouForgotTheQ_OBJECT_Macro(this, &_q_argument); i = i; }  


    Q_OBJECT_CHECK实现了一个模板函数,该函数调用了qYouForgotTheQ_OBJECT_Macro的函数,qYouForgotTheQ_OBJECT_Macro在宏Q_OBJECT_CHECK下面有定义:

    1. template <typename T>  
    2. inline int qYouForgotTheQ_OBJECT_Macro(T, T) { return 0; }  
    3. template <typename T1, typename T2>  
    4. inline void qYouForgotTheQ_OBJECT_Macro(T1, T2) {}  


    从返回值情况上可以看到,调用的是有int返回值的那个模板函数。但是很不明白,为什么之后还要添加一句 i=i?,刨根之后,发现Q_OBJECT_CHECK宏并没有做什么工作。

    然后Q_OBJECT又帮我们定义了一个静态的元对象,

    1. static const QMetaObject staticMetaObject; \  
    2. Q_OBJECT_GETSTATICMETAOBJECT \  

    Q_OBJECT_GETSTATICMETAOBJECT宏从名字上看就是为了获取这个元对象,确实在定义了Q_NO_DATA_RELOCATION宏的情况下,Q_OBJECT_GETSTATICMETAOBJECT宏就为我们定义了获取静态元对象的方法。

    1. #ifdef Q_NO_DATA_RELOCATION  
    2. #define Q_OBJECT_GETSTATICMETAOBJECT static const QMetaObject &getStaticMetaObject();  
    3. #else  
    4. #define Q_OBJECT_GETSTATICMETAOBJECT  
    5. #endif  

    之后Q_OBJECT宏又给我们定义了连个虚函数用来获取元对象指针和设置源对象。

    1. virtual const QMetaObject *metaObject() const; \  
    2. virtual void *qt_metacast(const char *); \  


    而定义的  QT_TR_FUNCTIONS 则表示是否支持I18N

    1. #ifndef QT_NO_TRANSLATION  
    2. # ifndef QT_NO_TEXTCODEC  
    3. // full set of tr functions  
    4. // ### Qt 5: merge overloads  
    5. # define QT_TR_FUNCTIONS \  
    6.   static inline QString tr(const char *s, const char *c = 0) \  
    7.       { return staticMetaObject.tr(s, c); } \  
    8.   static inline QString trUtf8(const char *s, const char *c = 0) \  
    9.       { return staticMetaObject.trUtf8(s, c); } \  
    10.   static inline QString tr(const char *s, const char *c, int n) \  
    11.       { return staticMetaObject.tr(s, c, n); } \  
    12.   static inline QString trUtf8(const char *s, const char *c, int n) \  
    13.       { return staticMetaObject.trUtf8(s, c, n); }  
    14. # else  
    15. // no QTextCodec, no utf8  
    16. // ### Qt 5: merge overloads  
    17. # define QT_TR_FUNCTIONS \  
    18.   static inline QString tr(const char *s, const char *c = 0) \  
    19.       { return staticMetaObject.tr(s, c); } \  
    20.   static inline QString tr(const char *s, const char *c, int n) \  
    21.       { return staticMetaObject.tr(s, c, n); }  
    22. # endif  
    23. #else  
    24. // inherit the ones from QObject  
    25. # define QT_TR_FUNCTIONS  
    26. #endif  


    只要有所实现,tr的方法就通过静态对象staticMetaObject的tr方法来实现。最后Q_OBJECT又定义了一个虚函数

    1. virtual int qt_metacall(QMetaObject::Call, intvoid **);   

    看名字定义,应该是用来对元对象的调用。

    合起来看所有的Q_OBJECT定义,都是为了操作元对象,并没有所谓的信号和槽,属性等内容,很显然,QObject对象能够支持这些功能,必然是通过QMetaObject这个元对象来实现的。尽管QMetaObject对象的实现有些庞大,但这个是所有的QOBject中最核心的一个实现,因此需要仔细分析该对象的每个定义:

    1. struct Q_CORE_EXPORT QMetaObject  
    2. {  
    3.   const char *className() const;                                    // 类名  
    4.   const QMetaObject *superClass() const;                            // 父类的元对象  
    5.   QObject *cast(QObject *obj) const;                                // 强制一个对象  
    6. #ifndef QT_NO_TRANSLATION  
    7.   // ### Qt 4: Merge overloads  
    8.   QString tr(const char *s, const char *c) const;                       // 翻译函数  
    9.   QString trUtf8(const char *s, const char *c) const;  
    10.   QString tr(const char *s, const char *c, int n) const;  
    11.   QString trUtf8(const char *s, const char *c, int n) const;  
    12. #endif // QT_NO_TRANSLATION  
    13.   int methodOffset() const;                                 // 方法偏移量  
    14.   int enumeratorOffset() const;                             // 枚举偏移量  
    15.   int propertyOffset() const;                                   // 属性偏移量  
    16.   int classInfoOffset() const;                                  // 类信息数目  
    17.   int constructorCount() const;                             // 构造函数数目  
    18.   int methodCount() const;                                  // 方法数目  
    19.   int enumeratorCount() const;                              // 枚举数据  
    20.   int propertyCount() const;                                    // 属性数目  
    21.   int classInfoCount() const;                                   // 类信息数目  
    22.   int indexOfConstructor(const char *constructor) const;            // 构函函数索引  
    23.   int indexOfMethod(const char *method) const;                  // 方法索引  
    24.   int indexOfSignal(const char *signal) const;                  // 信号量索引  
    25.   int indexOfSlot(const char *slot) const;                      // 槽索引  
    26.   int indexOfEnumerator(const char *name) const;                // 枚举索引  
    27.   int indexOfProperty(const char *name) const;                  // 属性索引  
    28.   int indexOfClassInfo(const char *name) const;                 // 类信息索引  
    29.   QMetaMethod constructor(int index) const;                 // 构造方法  
    30.   QMetaMethod method(int index) const;                      // 方法  
    31.   QMetaEnum enumerator(int index) const;                        // 枚举  
    32.   QMetaProperty property(int index) const;                      // 属性  
    33.   QMetaClassInfo classInfo(int index) const;                    // 类属性信息      
    34.   QMetaProperty userProperty() const;                           // 用户属性  
    35. ///////////////////////////////////////////////////////////////////////////////////////////////////////////////  
    36.   static bool checkConnectArgs(const char *signal, const char *method);  
    37.   static QByteArray normalizedSignature(const char *method);  
    38.   static QByteArray normalizedType(const char *type);  
    39.   // internal index-based connect  
    40.   static bool connect(const QObject *sender, int signal_index,  
    41.                       const QObject *receiver, int method_index,  
    42.                       int type = 0, int *types = 0);  
    43.   // internal index-based disconnect  
    44.   static bool disconnect(const QObject *sender, int signal_index,  
    45.                          const QObject *receiver, int method_index);  
    46.   static bool disconnectOne(const QObject *sender, int signal_index,  
    47.                             const QObject *receiver, int method_index);  
    48.   // internal slot-name based connect  
    49.   static void connectSlotsByName(QObject *o);  
    50.   // internal index-based signal activation  
    51.   static void activate(QObject *sender, int signal_index, void **argv); //obsolete  
    52.   static void activate(QObject *sender, int from_signal_index, int to_signal_index, void **argv); //obsolete  
    53.   static void activate(QObject *sender, const QMetaObject *, int local_signal_index, void **argv);  
    54.   static void activate(QObject *sender, const QMetaObject *, int from_local_signal_index, int to_local_signal_index, void **argv); //obsolete  
    55.   // internal guarded pointers  
    56.   static void addGuard(QObject **ptr);  
    57.   static void removeGuard(QObject **ptr);  
    58.   static void changeGuard(QObject **ptr, QObject *o);  
    59.   static bool invokeMethod(QObject *obj, const char *member,  
    60.                            Qt::ConnectionType,  
    61.                            QGenericReturnArgument ret,  
    62.                            QGenericArgument val0 = QGenericArgument(0),  
    63.                            QGenericArgument val1 = QGenericArgument(),  
    64.                            QGenericArgument val2 = QGenericArgument(),  
    65.                            QGenericArgument val3 = QGenericArgument(),  
    66.                            QGenericArgument val4 = QGenericArgument(),  
    67.                            QGenericArgument val5 = QGenericArgument(),  
    68.                            QGenericArgument val6 = QGenericArgument(),  
    69.                            QGenericArgument val7 = QGenericArgument(),  
    70.                            QGenericArgument val8 = QGenericArgument(),  
    71.                            QGenericArgument val9 = QGenericArgument());  
    72.   static inline bool invokeMethod(QObject *obj, const char *member,  
    73.                            QGenericReturnArgument ret,  
    74.                            QGenericArgument val0 = QGenericArgument(0),  
    75.                            QGenericArgument val1 = QGenericArgument(),  
    76.                            QGenericArgument val2 = QGenericArgument(),  
    77.                            QGenericArgument val3 = QGenericArgument(),  
    78.                            QGenericArgument val4 = QGenericArgument(),  
    79.                            QGenericArgument val5 = QGenericArgument(),  
    80.                            QGenericArgument val6 = QGenericArgument(),  
    81.                            QGenericArgument val7 = QGenericArgument(),  
    82.                            QGenericArgument val8 = QGenericArgument(),  
    83.                            QGenericArgument val9 = QGenericArgument())  
    84.   {  
    85.       return invokeMethod(obj, member, Qt::AutoConnection, ret, val0, val1, val2, val3,  
    86.               val4, val5, val6, val7, val8, val9);  
    87.   }  
    88.   static inline bool invokeMethod(QObject *obj, const char *member,  
    89.                            Qt::ConnectionType type,  
    90.                            QGenericArgument val0 = QGenericArgument(0),  
    91.                            QGenericArgument val1 = QGenericArgument(),  
    92.                            QGenericArgument val2 = QGenericArgument(),  
    93.                            QGenericArgument val3 = QGenericArgument(),  
    94.                            QGenericArgument val4 = QGenericArgument(),  
    95.                            QGenericArgument val5 = QGenericArgument(),  
    96.                            QGenericArgument val6 = QGenericArgument(),  
    97.                            QGenericArgument val7 = QGenericArgument(),  
    98.                            QGenericArgument val8 = QGenericArgument(),  
    99.                            QGenericArgument val9 = QGenericArgument())  
    100.   {  
    101.       return invokeMethod(obj, member, type, QGenericReturnArgument(), val0, val1, val2,  
    102.                                val3, val4, val5, val6, val7, val8, val9);  
    103.   }  
    104.   static inline bool invokeMethod(QObject *obj, const char *member,  
    105.                            QGenericArgument val0 = QGenericArgument(0),  
    106.                            QGenericArgument val1 = QGenericArgument(),  
    107.                            QGenericArgument val2 = QGenericArgument(),  
    108.                            QGenericArgument val3 = QGenericArgument(),  
    109.                            QGenericArgument val4 = QGenericArgument(),  
    110.                            QGenericArgument val5 = QGenericArgument(),  
    111.                            QGenericArgument val6 = QGenericArgument(),  
    112.                            QGenericArgument val7 = QGenericArgument(),  
    113.                            QGenericArgument val8 = QGenericArgument(),  
    114.                            QGenericArgument val9 = QGenericArgument())  
    115.   {  
    116.       return invokeMethod(obj, member, Qt::AutoConnection, QGenericReturnArgument(), val0,  
    117.               val1, val2, val3, val4, val5, val6, val7, val8, val9);  
    118.   }  
    119.   QObject *newInstance(QGenericArgument val0 = QGenericArgument(0),  
    120.                        QGenericArgument val1 = QGenericArgument(),  
    121.                        QGenericArgument val2 = QGenericArgument(),  
    122.                        QGenericArgument val3 = QGenericArgument(),  
    123.                        QGenericArgument val4 = QGenericArgument(),  
    124.                        QGenericArgument val5 = QGenericArgument(),  
    125.                        QGenericArgument val6 = QGenericArgument(),  
    126.                        QGenericArgument val7 = QGenericArgument(),  
    127.                        QGenericArgument val8 = QGenericArgument(),  
    128.                        QGenericArgument val9 = QGenericArgument()) const;  
    129.   enum Call {  
    130.       InvokeMetaMethod,  
    131.       ReadProperty,  
    132.       WriteProperty,  
    133.       ResetProperty,  
    134.       QueryPropertyDesignable,  
    135.       QueryPropertyScriptable,  
    136.       QueryPropertyStored,  
    137.       QueryPropertyEditable,  
    138.       QueryPropertyUser,  
    139.       CreateInstance  
    140.   };  
    141.   int static_metacall(Call, intvoid **) const;  
    142.   static int metacall(QObject *, Call, intvoid **);  
    143. #ifdef QT3_SUPPORT  
    144.   QT3_SUPPORT const char *superClassName() const;  
    145. #endif  
    146.   struct { // private data  
    147.       const QMetaObject *superdata;                         // 父类  
    148.       const char *stringdata;                               // 类名  
    149.       const uint *data;                                     // 任意指向数据的指针  
    150.       const void *extradata;                                    // 扩展数据  
    151.   } d;  
    152. };  


    QMetaObject就内部一个结构体对象,包含了四个部分,分别是父类对象,自己元对象的名称,以及一个指针任意值的指针数据和扩展数据,一般情况下,d.data表示的是QMetaObjectPrivate对象指针,在priv函数中可以找到一些痕迹:

    1. static inline const QMetaObjectPrivate *priv(const uint* data)  
    2. return reinterpret_cast<const QMetaObjectPrivate*>(data); }  

    按照Qt的实现惯例,使用Private对象做一些具体的工作,于是就有了QMetaObjectPrivate和QObjectPrivate

    1. struct QMetaObjectPrivate                               // 注意是一个结构体  
    2. {  
    3.   int revision;                                         // 版本  
    4.   int className;                                        // 类名,注意类型  
    5.   int classInfoCount, classInfoData;                        // 类信息数据和数量  
    6.   int methodCount, methodData;                          // 方法数据和数量  
    7.   int propertyCount, propertyData;                      // 属性数据和数量  
    8.   int enumeratorCount, enumeratorData;                  // 枚举数据和数量  
    9.   int constructorCount, constructorData; //since revision 2     // 构造函数数据和数量      
    10.   int flags; //since revision 3                             // 标记  
    11.   int signalCount; //since revision 4                       // 信号量数目  
    12.   // revision 5 introduces changes in normalized signatures, no new members  
    13.   // 从元数据中获取QMetaObjectPrivate对象指针,从data中得到!!  
    14.   static inline const QMetaObjectPrivate *get(const QMetaObject *metaobject)  
    15.   { return reinterpret_cast<const QMetaObjectPrivate*>(metaobject->d.data); }   
    16.   static int indexOfSignalRelative(const QMetaObject **baseObject,  
    17.                                    const char* name,  
    18.                                    bool normalizeStringData);  
    19.   static int indexOfSlot(const QMetaObject *m,  
    20.                          const char *slot,  
    21.                          bool normalizeStringData);  
    22.   static int originalClone(const QMetaObject *obj, int local_method_index);  
    23. #ifndef QT_NO_QOBJECT  
    24.   //defined in qobject.cpp  
    25.   enum DisconnectType { DisconnectAll, DisconnectOne };  
    26.   static bool connect(const QObject *sender, int signal_index,  
    27.                       const QObject *receiver, int method_index,  
    28.                       int type = 0, int *types = 0);  
    29.   static bool disconnect(const QObject *sender, int signal_index,  
    30.                          const QObject *receiver, int method_index,  
    31.                          DisconnectType = DisconnectAll);  
    32.   static inline bool disconnectHelper(QObjectPrivate::Connection *c,  
    33.                                       const QObject *receiver, int method_index,  
    34.                                       QMutex *senderMutex, DisconnectType);  
    35. #endif  
    36. };  


    ==================================================================

    1. class Q_CORE_EXPORT QObjectPrivate : public QObjectData  
    2. {  
    3.   Q_DECLARE_PUBLIC(QObject)  
    4. public:  
    5.   struct ExtraData                      // 定义额外数据  
    6.   {  
    7.       ExtraData() {}  
    8. #ifndef QT_NO_USERDATA  
    9.       QVector<QObjectUserData *> userData;  
    10. #endif  
    11.       QList<QByteArray> propertyNames;                    // 属性名列表  
    12.       QList<QVariant> propertyValues;                     // 属性值    
    13.   };  
    14.   struct Connection                                     // 定义连接信息  
    15.   {  
    16.       QObject *sender;                                  // 发送者  
    17.       QObject *receiver;                                    // 接受者  
    18.       int method;                                           // 方法  
    19.       uint connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking  
    20.       QBasicAtomicPointer<int> argumentTypes;             // 参数类型  
    21.       // The next pointer for the singly-linked ConnectionList  
    22.       Connection *nextConnectionList;                       // 下一个连接对象列表  
    23.       //senders linked list  
    24.       Connection *next;                                 // 下一个连接  
    25.       Connection **prev;                                    // 上一个连接  
    26.       ~Connection();  
    27.   };  
    28.   // ConnectionList is a singly-linked list  
    29.   struct ConnectionList {                                   // 定义连接列表  
    30.       ConnectionList() : first(0), last(0) {}                 
    31.       Connection *first;                                    // 连接首  
    32.       Connection *last;                                 // 连接尾  
    33.   };  
    34.   struct Sender                                         // 定义发送者  
    35.   {  
    36.       QObject *sender;                                  // 发送者  
    37.       int signal;                                           // 信号  
    38.       int ref;                                              // 引用计数  
    39.   };  
    40.   QObjectPrivate(int version = QObjectPrivateVersion);  
    41.   virtual ~QObjectPrivate();  
    42.   void deleteChildren();                                        // 删除所有子节点  
    43.   void setParent_helper(QObject *);                             // 设置父节点  
    44.   void moveToThread_helper();                                   // 转到某个线程中  
    45.   void setThreadData_helper(QThreadData *currentData, QThreadData *targetData); // 设置线程数据  
    46.   void _q_reregisterTimers(void *pointer);                          // 设置注册时间  
    47.   bool isSender(const QObject *receiver, const char *signal) const// 判断信号量是否为发送者  
    48.   QObjectList receiverList(const char *signal) const;               // 接受信号的对象列表  
    49.   QObjectList senderList() const;                               // 发出信息列表  
    50.   void addConnection(int signal, Connection *c);                // 添加一个信号连接  
    51.   void cleanConnectionLists();                              // 清理连接列表  
    52. #ifdef QT3_SUPPORT  
    53.   void sendPendingChildInsertedEvents();  
    54.   void removePendingChildInsertedEvents(QObject *child);  
    55. #endif  
    56.   static inline Sender *setCurrentSender(QObject *receiver,  
    57.                                   Sender *sender);          // 设置当前发送者  
    58.   static inline void resetCurrentSender(QObject *receiver,  
    59.                                  Sender *currentSender,  
    60.                                  Sender *previousSender);   // 重置当前发送者  
    61.   static int *setDeleteWatch(QObjectPrivate *d, int *newWatch); // 设置删除监听  
    62.   static void resetDeleteWatch(QObjectPrivate *d, int *oldWatch, int deleteWatch);  // 重置删除监听  
    63.   static void clearGuards(QObject *);                   // 清除管理者  
    64.   static QObjectPrivate *get(QObject *o) {          // 从QObject对象中获取QObjectPrivate    
    65.       return o->d_func();  
    66.   }  
    67.   int signalIndex(const char *signalName) const;            // 信号索引  
    68.   inline bool isSignalConnected(uint signalIdx) const;      // 判断是否为信号连接  
    69. public:  
    70.   QString objectName;                                   // 对象名字  
    71.   ExtraData *extraData;   // extra data set by the user     // 额外数据  
    72.   QThreadData *threadData; // id of the thread that owns the object     // 线程数据  
    73.   QObjectConnectionListVector *connectionLists;                 // 连接列表数组  
    74.   Connection *senders;    // linked list of connections connected to this object  // 发送者连接  
    75.   Sender *currentSender;  // object currently activating the object         // 当前发送者  
    76.   mutable quint32 connectedSignals[2];                      // 连接信号  
    77. #ifdef QT3_SUPPORT  
    78.   QList<QObject *> pendingChildInsertedEvents;  
    79. #else  
    80.   // preserve binary compatibility with code compiled without Qt 3 support  
    81.   // keeping the binary layout stable helps the Qt Creator debugger  
    82.   void *unused;                                     // 保留  
    83. #endif  
    84.   QList<QPointer<QObject> > eventFilters;  
    85.   union {  
    86.       QObject *currentChildBeingDeleted;                // 当前子节点被删除  
    87.       QAbstractDeclarativeData *declarativeData; //extra data used by the declarative module  
    88.   };  
    89.   // these objects are all used to indicate that a QObject was deleted  
    90.   // plus QPointer, which keeps a separate list  
    91.   QAtomicPointer<QtSharedPointer::ExternalRefCountData> sharedRefcount; // 共享引用计数  
    92.   int *deleteWatch;         // 删除监听者  
    93. };  


    QObjectPrivateQObjectData派生下来:

    1. class  
    2. #if defined(__INTEL_COMPILER) && defined(Q_OS_WIN)  
    3. Q_CORE_EXPORT  
    4. #endif  
    5. QObjectData {  
    6. public:  
    7.   virtual ~QObjectData() = 0;  
    8.   QObject *q_ptr;                               // 当前指向的QOBject  
    9.   QObject *parent;                              // 指向的QObject父类   
    10.   QObjectList children;                         // 孩儿们  
    11.   uint isWidget : 1;                                // 是否为widget的标记   
    12.   uint pendTimer : 1;                           // 开启时钟  
    13.   uint blockSig : 1;                                // 阻塞信号标记  
    14.   uint wasDeleted : 1;                          // 是否参数标记  
    15.   uint ownObjectName : 1;                       // 是否含有对象名标记  
    16.   uint sendChildEvents : 1;                     // 发送到子对象时间标记  
    17.   uint receiveChildEvents : 1;                  // 接受子对象时间标记  
    18.   uint inEventHandler : 1;                      // 是否有事件句柄标记  
    19.   uint inThreadChangeEvent : 1;                 // 线程更改时间标记  
    20.   //是否有守护标记  
    21.   uint hasGuards : 1; //true iff there is one or more QPointer attached to this object  
    22.   uint unused : 22;                             // 保留  
    23.   int postedEvents;                             // 发送的数据  
    24.   QMetaObject *metaObject; // assert dynamic            // 元对象  
    25. };  


    QObjectData保留一些了基本的数据信息

    1. #define Q_DECLARE_PUBLIC(Class)                                   \  
    2.   inline Class* q_func() { return static_cast<Class *>(q_ptr); } \  
    3.   inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \  
    4.   friend class Class;  


    在元对象中获取类名和父类的方式很简单,直接即可。

    1. inline const char *QMetaObject::className() const  
    2. return d.stringdata; }  
    3. inline const QMetaObject *QMetaObject::superClass() const  
    4. return d.superdata; }  


    元对象中cast函数的实现

    1. QObject *QMetaObject::cast(QObject *obj) const  
    2. {  
    3.   if (obj) {  
    4.       const QMetaObject *m = obj->metaObject();  
    5.       do {  
    6.           if (m == this)  
    7.               return const_cast<QObject*>(obj);  
    8.       } while ((m = m->d.superdata));  
    9.   }  
    10.   return 0;  
    11. }  

    如果对象obj存在,则对每一个obj的父类的源对象进行转换,如果是当前的元对象,则进行返回,否则返回0,也就是cast函数是在obj中查找当前元对象所在的QObject对象。


    转自:http://blog.csdn.net/liuysheng/article/details/6828221

  • 相关阅读:
    TCPUDPSocket调试工具v2.2
    C#高性能Socket服务器IOCP实现
    c#使用HttpListener监听HTTP请求
    Winform Socket通信
    C# 方法中的this参数(扩展方法)
    C# 两种方法实现HTTP协议迷你服务器
    C#访问HTTP请求
    Socket通信原理
    C#数据decimal保留两位小数
    单机网站架构云化后架构图
  • 原文地址:https://www.cnblogs.com/hnrainll/p/2387601.html
Copyright © 2011-2022 走看看