├── .github └── workflows │ └── cmake-single-platform.yml ├── .gitignore ├── LICENSE ├── README.md ├── gen_pack.sh ├── kk.signals_slots.pdsc ├── signals_slots.c └── signals_slots.h /.github/workflows/cmake-single-platform.yml: -------------------------------------------------------------------------------- 1 | name: Build documentation and pack 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: [published] 7 | 8 | jobs: 9 | pack: 10 | name: Generate pack 11 | runs-on: ubuntu-24.04 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Fetch tags 19 | if: ${{ github.event_name == 'release' }} 20 | run: | 21 | git fetch --tags --force 22 | 23 | - name: Make gen_pack.sh executable 24 | run: chmod +x ./gen_pack.sh 25 | 26 | - name: Generate Pack 27 | uses: Open-CMSIS-Pack/gen-pack-action@main 28 | with: 29 | doxygen-version: 1.9.5 30 | packchk-version: 1.3.96 31 | gen-pack-script: ./gen_pack.sh 32 | gen-pack-output: ./output 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 用C宏打造一个轻量级信号与槽机制 2 | 3 | --- 4 | 5 | ## 一、引言 6 | 7 | 信号与槽机制源自Qt 框架,它是一种强大而优雅的事件驱动模型,旨在促进组件之间的松散耦合通信。该模式允许对象之间无需直接相互引用即可响应彼此的状态变更,从而极大地提高了系统的可扩展性和可维护性。 8 | 9 | ### 1.1 传统事件处理的局限性 10 | 11 | 在早期 C++ 程序中,事件处理通常通过回调函数或观察者模式来实现,但这些方法存在一些问题: 12 | 13 | - **强耦合**:回调函数需要提前注册到特定对象中,导致调用方与被调用方紧密耦合,不利于模块化和重用。 14 | 15 | - **代码复杂度高**:实现和维护复杂的事件处理逻辑较为困难,尤其是在多个对象之间需要进行交互时。 16 | 17 | - **类型检查不足**:回调函数的接口不够灵活,容易引发编译期或运行时错误。 18 | - **`this` 指针的绑定问题**:C++ 成员函数需要显式绑定到对象实例(`this`),导致回调机制实现复杂且易出错。 19 | 20 | ### 1.2 信号与槽机制的优势 21 | 22 | 信号与槽机制提供了一种声明式的方式来描述组件之间的关系,代码更加直观、易读和易维护,降低了程序的复杂度。例如: 23 | 24 | ```c++ 25 | connect(sender, &Sender::signal, receiver, &Receiver::slot); 26 | ``` 27 | 28 | 这一语句清晰地表明了信号与槽之间的关系。 29 | 30 | 信号与槽机制通过 `connect` 函数将事件的发出者和处理者连接起来,实现了组件间的解耦,简化了事件驱动编程。 31 | 32 | ### 1.3 嵌入式 C 编程中的信号与槽的优势 33 | 34 | 虽然信号与槽机制主要源于面向对象语言(如 C++ 和 Qt 框架),但它的思想同样可以借鉴到嵌入式 C 编程中,用于解决某些事件通信和模块解耦问题。 35 | 36 | - **模块解耦**:信号与槽机制允许模块之间通过事件(信号)进行通信,而无需直接调用对方的接口,降低模块间的耦合度。 37 | - **简化事件处理**:使用信号与槽,可以轻松管理事件的分发和处理逻辑,而不需要手动维护复杂的回调函数或状态机。 38 | - **动态性和灵活性**:可以在运行时动态注册或解除信号与槽的绑定,支持灵活的模块间通信。 39 | - **易于扩展**:新模块可以通过注册信号与槽轻松加入系统,无需修改现有模块的实现。 40 | - **提升代码可读性与可维护性**:信号与槽机制的代码逻辑清晰,描述事件触发和响应关系的方式更直观。 41 | - **线程安全**:如果设计得当,信号与槽机制可以用于不同任务(或线程)之间的安全通信,避免复杂的同步机制。 42 | 43 | 44 | 45 | **既然信号槽有如此多的优点,嵌入式开发也想拥有,下边就开始用C语言一步步实现它吧...** 46 | 47 | ## 二、用C宏打造一个轻量级信号与槽机制 48 | 49 | ### 2.1 前景回顾 50 | 51 | 如果说上一篇文章通过[队列](..\queue\queue.md)的实现只是“浅尝辄止”,让我们领略了宏在代码简化和重用上的基本功,那么这篇文章,我们可以更进一步,将目光投向更复杂、更具挑战性的场景。通过深入挖掘宏的强大能力,我们不仅要展示它的“锦上添花”,更要看到它在解决复杂问题时“化繁为简”的真正价值。 52 | 53 | ### 2.2 基本功能封装 54 | 55 | 信号与槽机制的核心在于**信号与槽的动态连接与断开**,以及**通过信号调用槽函数完成事件通知**。 56 | 57 | 为此,我们先无脑定义一些与 Qt 中接口相同的宏,为后续的功能实现奠定基础: 58 | 59 | ```c 60 | #define signals //定义信号 61 | 62 | #define emit //发射信号 63 | 64 | #define slots //定义槽 65 | 66 | #define connect //链接信号与槽 67 | 68 | #define disconnect //断开信号与槽 69 | 70 | 71 | ``` 72 | 73 | 信号槽机制的本质是**发射信号时,通过信号与槽的对应关系找到槽函数并调用它**。我们需要实现的,正是这个机制。 74 | 75 | ### 2.3 实现声明信号的宏 76 | 77 | 在 C 语言中,信号本质上是一个函数,其触发的行为是调用与之绑定的槽函数。因此,我们需要通过 `signals` 宏声明信号的函数类型。 78 | 79 | **实现逻辑**: 80 | 81 | - `signals` 宏通过 `typedef` 声明信号函数类型。 82 | - 发射信号时,会通过调用该函数类型的函数指针,执行槽函数。 83 | 84 | **实现代码**: 85 | 86 | ```c 87 | #define signals(__NAME,...) \ 88 | typedef void __NAME( __VA_ARGS__); 89 | ``` 90 | 91 | **示例**: 92 | 93 | ```c 94 | signals(my_signal, int value, const char *message); 95 | ``` 96 | 97 | 上述代码将定义一个信号 `my_signal`,它可以传递一个 `int` 和一个 `const char *` 参数。 98 | 99 | ### 2.4 实现发射信号的宏 100 | 101 | 发射信号的本质是通过函数指针调用槽函数。在实现发射信号的宏时,我们需要处理以下情况: 102 | 103 | - 动态调用与信号绑定的槽函数。 104 | 105 | - 支持不定长参数传递。 106 | 107 | **实现步骤**: 108 | 109 | 1. 通过信号的函数指针调用槽函数。 110 | 2. 利用参数宏解析 `...` 的参数数量,从而正确调用不同参数数量的槽函数。 111 | 112 | **实现代码**: 113 | 114 | ```c 115 | #define __emit(__OBJ,...) \ 116 | __PLOOC_EVAL(__RecFun_,##__VA_ARGS__) ((__OBJ)->ptRecObj,##__VA_ARGS__); 117 | 118 | #define emit(__NAME,__OBJ,...) \ 119 | do { \ 120 | sig_slot_t *ptObj = &((__OBJ)->tObject); \ 121 | __NAME *__RecFun = ptObj->ptRecFun; \ 122 | __emit(ptObj, __VA_ARGS__) \ 123 | } while(0) 124 | ``` 125 | 126 | 在这里: 127 | 128 | - `__PLOOC_EVAL` 是一个辅助宏,根据参数数量自动选择对应的 `__RecFun_N` 宏。 129 | - `__RecFun_N` 定义了多种参数调用的槽函数适配逻辑。 130 | 131 | ```c 132 | #define __RecFun_0(__OBJ) \ 133 | __RecFun((__OBJ)) 134 | 135 | #define __RecFun_1(__OBJ, __ARG1) \ 136 | __RecFun((__OBJ),(__ARG1)) 137 | 138 | #define __RecFun_2(__OBJ, __ARG1, __ARG2) \ 139 | __RecFun((__OBJ),(__ARG1), (__ARG2)) 140 | 141 | #define __RecFun_3(__OBJ, __ARG1, __ARG2, __ARG3) \ 142 | __RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3)) 143 | 144 | #define __RecFun_4(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4) \ 145 | __RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4)) 146 | 147 | #define __RecFun_5(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4, __ARG5) \ 148 | __RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4), (__ARG5)) 149 | 150 | #define __RecFun_6(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4, __ARG5, __ARG6) \ 151 | __RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4), (__ARG5), (__ARG6)) 152 | 153 | #define __RecFun_7(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4, __ARG5, __ARG6, __ARG7) \ 154 | __RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4), (__ARG5), (__ARG6), (__ARG7)) 155 | 156 | #define __RecFun_8(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4, __ARG5, __ARG6, __ARG7, __ARG8) \ 157 | __RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4), (__ARG5), (__ARG6), (__ARG7), (__ARG8)) 158 | 159 | 160 | ``` 161 | 162 | 163 | 164 | ### 2.5 取代QObject类 165 | 166 | 为了在结构体中存储信号与槽的元信息,我们定义一个 `sig_slot_t` 结构体,用于管理信号与槽的动态连接。 167 | 168 | ```c 169 | typedef struct sig_slot_t sig_slot_t; 170 | typedef struct sig_slot_t { 171 | char chSenderName[SIG_NAME_MAX]; // 信号名称 172 | void *ptSenderObj; // 信号所在对象地址 173 | void *ptRecObj; // 槽所在对象地址 174 | void *ptRecFun; // 槽函数的地址 175 | } sig_slot_t; 176 | 177 | #define SIG_SLOT_OBJ sig_slot_t tObject; 178 | 179 | ``` 180 | 181 | 在需要信号支持的结构体中,添加 `SIG_SLOT_OBJ` 即可。 182 | 183 | **示例**: 184 | 185 | ```c 186 | typedef struct { 187 | SIG_SLOT_OBJ 188 | } my_signal_object_t; 189 | ``` 190 | 191 | ### 2.6 实现connect函数 192 | 193 | `connect` 宏用于动态建立信号与槽的连接关系。 194 | 核心逻辑包括: 195 | 196 | - 检查参数有效性。 197 | 198 | - 将信号的函数地址与槽函数地址绑定。 199 | 200 | ```c 201 | void connect(void *SenderObj, const char *ptSender, void *RecObj, void *RecFun) { 202 | if (SenderObj == NULL || ptSender == NULL || RecObj == NULL || RecFun == NULL) { 203 | return; 204 | } 205 | sig_slot_t *ptMetaObj = SenderObj; 206 | ptMetaObj->ptRecFun = RecFun; 207 | ptMetaObj->ptRecObj = RecObj; 208 | memcpy(ptMetaObj->chSenderName, ptSender, strlen(ptSender)); 209 | } 210 | ``` 211 | 212 | ### 2.7 可选的 slots 宏 213 | 214 | 虽然槽函数在本质上是普通函数,但为了和 `signals` 宏对应,我们可以通过 `slots` 宏为槽函数提供一个语法糖,方便统一管理。 215 | 216 | **实现代码**: 217 | 218 | ``` 219 | #define __slots(__NAME,...) \ 220 | void __NAME(__VA_ARGS__); 221 | 222 | #define slots(__NAME,__OBJ,...) \ 223 | __slots(__NAME,_args(__OBJ,##__VA_ARGS__)) 224 | ``` 225 | 226 | **示例**: 227 | 228 | ``` 229 | slots(my_slot, void *obj, int value, const char *message); 230 | ``` 231 | 232 | ### **2.8 设计总结** 233 | 234 | 到此为止,我们实现了信号与槽的以下核心功能: 235 | 236 | - **信号声明**:通过 `signals` 宏定义信号函数类型。 237 | 238 | - **信号发射**:通过 `emit` 宏调用信号对应的槽函数。 239 | 240 | - **动态连接**:通过 `connect` 宏在运行时动态绑定信号与槽。 241 | 242 | - **统一语法**:使用 `slots` 宏声明槽函数,与信号配合使用。 243 | 244 | 在这一机制下,C 语言通过宏实现了类似 Qt 的信号与槽机制,为模块化和解耦设计提供了强大的工具支持。 245 | 246 | ## 三、完整的代码实现 247 | 248 | 以上代码只是展示核心部分,并且仅实现了一个信号对应一个槽,不能一个信号对应多个信号和槽,还有诸多类型检查,空指针检查等需要优化的地方,完整的代码开源链接:https://github.com/Aladdin-Wang/signals_slots 249 | 250 | 完整代码信号与槽具备以下核心特征: 251 | 252 | - **不定长参数支持**:信号和槽函数可携带任意参数; 253 | 254 | - **多路复用能力**:支持一对多、多对多的灵活连接; 255 | - **连接与断开机制**:运行时动态连接和断开信号与槽; 256 | 257 | - **类型安全与检查**:防止重复连接或非法操作; 258 | - **链表结构**:高效管理信号与槽的关系; 259 | - **宏封装**:简化信号与槽的定义和操作,提升代码可读性和可维护性。 260 | 261 | 262 | 263 | ## 四、使用方法与QT中的区别 264 | 265 | ### 1. SIG_SLOT_OBJ取代QObject 266 | 267 | SIG_SLOT_OBJ取代QObject,且只需要在信号所在的类中定义。 268 | 269 | ### 2. 定义信号不同 270 | 271 | QT在类里面声明信号,signals宏是在结构体外声明信号,并且要指定信号名称,信号所在的对象地址,和一些自定义的参数: 272 | 273 | ```c 274 | signals(__NAME,__OBJ,...) 275 | example: 276 | signals(send_sig,can_data_msg_t *ptThis, 277 | args( 278 | uint8_t *pchByte, 279 | uint16_t hwLen 280 | )); 281 | ``` 282 | 283 | ### 3. 发射信号不同 284 | 285 | emit宏的括号内需要指定信号名称,信号所在的对象地址,和自定义的参数的数据: 286 | 287 | ```c 288 | emit(__NAME,__OBJ,...) 289 | example: 290 | emit(send_sig,&tCanMsgObj, 291 | args( 292 | tCanMsgObj.CanDATA.B, 293 | tCanMsgObj.CanDLC 294 | )); 295 | ``` 296 | 297 | ### 4. 定义槽不同 298 | 299 | 与定义信号语法类似 300 | 301 | ```c 302 | slots(__NAME,__OBJ,...) 303 | example: 304 | slots(enqueue_bytes,byte_queue_t *ptObj, 305 | args( 306 | void *pchByte, 307 | uint16_t hwLength 308 | )); 309 | ``` 310 | 311 | ### 5. 连接信号与槽 312 | 313 | 与QT一样一个信号可以连接多个信号或者槽 314 | 315 | ```c 316 | #define connect(__SIG_OBJ,__SIG_NAME,__SLOT_OBJ,__SLOT_FUN) \ 317 | direct_connect(__SIG_OBJ.tObject,__SIG_NAME,__SLOT_OBJ,__SLOT_FUN) 318 | 319 | example: 320 | connect(&tCanMsgObj,SIGNAL(send_sig),&s_tFIFOin,SLOT(enqueue_bytes)); 321 | 322 | #define disconnect(__SIG_OBJ,__SIG_NAME) \ 323 | auto_disconnect(__SIG_OBJ.tObject,__SIG_NAME) 324 | example: 325 | connect(&tCanMsgObj,SIGNAL(send_sig)); 326 | ``` 327 | 328 | 信号与槽的链接关系是同步调用的关系,不同于发布订阅系统的异步调用。 329 | 330 | **同步调用的特点:** 331 | 332 | - 当一个信号发出时,如果有多个槽连接到该信号,所有槽函数会按顺序被调用。 333 | - 槽函数在信号发出时会立即执行,且不会返回控制权给发出信号的对象,直到所有槽函数执行完毕才会返回。 334 | - 这种调用是同步的,因为发出信号时会等待槽函数的执行完成后才继续进行其他操作。 335 | 336 | **与发布-订阅模式的区别:** 337 | 338 | 发布-订阅模式通常是基于异步通信的,其中发布者发送消息后,不会阻塞等待订阅者的处理结果。订阅者会异步地处理这些消息,并可能在处理完后通过回调等机制通知发布者。 339 | 340 | **下一篇将结合队列,实现一个异步调用的发布-订阅模式,敬请期待。** 341 | 342 | ## 五、信号与槽使用示例 343 | 344 | 信号与槽模块作为我开源代码[MicroBoot](..\..\index.md)的核心机制,不仅提升了代码的可维护性和灵活性,还带来了更好的开发体验。 345 | 346 | 接下来实现一个将CAN接收的数据,存储到环形队列ringbuf的例子: 347 | 348 | can.h文件 349 | 350 | ```c 351 | #include "signals_slots.h" 352 | typedef struct 353 | { 354 | SIG_SLOT_OBJ; 355 | uint8_t CanDLC; 356 | union { 357 | uint8_t B[8]; 358 | uint16_t H[4]; 359 | uint32_t W[2]; 360 | } CanDATA; 361 | }can_data_msg_t; 362 | 363 | signals(send_sig,can_data_msg_t *ptThis, 364 | args( 365 | uint8_t *pchByte, 366 | uint16_t hwLen 367 | )); 368 | 369 | extern can_data_msg_t tCanMsgObj; 370 | ``` 371 | 372 | can.c文件 373 | 374 | ```c 375 | #include "can.h" 376 | can_data_msg_t tCanMsgObj; 377 | void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) 378 | { 379 | HAL_StatusTypeDef status; 380 | CAN_RxHeaderTypeDef rxheader = {0}; 381 | 382 | status = HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rxheader, (uint8_t *)tCanMsgObj.CanDATA.B); 383 | if (HAL_OK != status) 384 | return ; 385 | /* get id */ 386 | if (CAN_ID_STD == rxheader.IDE) 387 | { 388 | tCanMsgObj.CanID_t.CanID = rxheader.StdId; 389 | } 390 | else 391 | { 392 | tCanMsgObj.CanID_t.CanID = rxheader.ExtId; 393 | } 394 | /* get len */ 395 | tCanMsgObj.CanDLC = rxheader.DLC; 396 | 397 | emit(send_sig,&tCanMsgObj, 398 | args( 399 | tCanMsgObj.CanDATA.B, 400 | tCanMsgObj.CanDLC 401 | )); 402 | } 403 | ``` 404 | 405 | main.c文件 406 | 407 | ```c 408 | #include "signals_slots.h" 409 | #include "can.h" 410 | 411 | static uint8_t s_cFIFOinBuffer[1024]; 412 | static byte_queue_t s_tFIFOin; 413 | 414 | MX_CAN1_Init(); 415 | HAL_CAN_Start(&hcan1); 416 | HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING); 417 | QUEUE_INIT(&s_tFIFOin, s_cFIFOinBuffer, sizeof(s_cFIFOinBuffer)); 418 | int main(void) 419 | { 420 | connect(&tCanMsgObj,SIGNAL(send_sig),&s_tFIFOin,SLOT(enqueue_bytes)); 421 | while(1){ 422 | //do something 423 | } 424 | } 425 | ``` 426 | 427 | -------------------------------------------------------------------------------- /gen_pack.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Version: 2.7 3 | # Date: 2023-05-22 4 | # This bash script generates a CMSIS Software Pack: 5 | # 6 | 7 | set -o pipefail 8 | 9 | # Set version of gen pack library 10 | # For available versions see https://github.com/Open-CMSIS-Pack/gen-pack/tags. 11 | # Use the tag name without the prefix "v", e.g., 0.7.0 12 | REQUIRED_GEN_PACK_LIB="0.8.4" 13 | 14 | # Set default command line arguments 15 | DEFAULT_ARGS=(-c "v") 16 | 17 | # Pack warehouse directory - destination 18 | # Default: ./output 19 | # 20 | # PACK_OUTPUT=./output 21 | 22 | # Temporary pack build directory, 23 | # Default: ./build 24 | # 25 | # PACK_BUILD=./build 26 | 27 | # Specify directory names to be added to pack base directory 28 | # An empty list defaults to all folders next to this script. 29 | # Default: empty (all folders) 30 | # 31 | #PACK_DIRS=" 32 | # 33 | #" 34 | 35 | # Specify file names to be added to pack base directory 36 | # Default: empty 37 | # 38 | PACK_BASE_FILES=" 39 | LICENSE 40 | signals_slots.c 41 | signals_slots.h 42 | README.md 43 | " 44 | 45 | # Specify file names to be deleted from pack build directory 46 | # Default: empty 47 | # 48 | # PACK_DELETE_FILES=" 49 | # " 50 | 51 | # Specify patches to be applied 52 | # Default: empty 53 | # 54 | # PACK_PATCH_FILES=" 55 | # 56 | # " 57 | 58 | # Specify addition argument to packchk 59 | # Default: empty 60 | # 61 | # PACKCHK_ARGS=() 62 | 63 | # Specify additional dependencies for packchk 64 | # Default: empty 65 | # 66 | # PACKCHK_DEPS=" 67 | # 68 | # " 69 | 70 | # Optional: restrict fallback modes for changelog generation 71 | # Default: full 72 | # Values: 73 | # - full Tag annotations, release descriptions, or commit messages (in order) 74 | # - release Tag annotations, or release descriptions (in order) 75 | # - tag Tag annotations only 76 | # 77 | # PACK_CHANGELOG_MODE="" 78 | 79 | # 80 | # custom pre-processing steps 81 | # 82 | # usage: preprocess 83 | # The build folder 84 | # 85 | function preprocess() { 86 | # add custom steps here to be executed 87 | # before populating the pack build folder 88 | return 0 89 | } 90 | 91 | # 92 | # custom post-processing steps 93 | # 94 | # usage: postprocess 95 | # The build folder 96 | # 97 | function postprocess() { 98 | # add custom steps here to be executed 99 | # after populating the pack build folder 100 | # but before archiving the pack into output folder 101 | return 0 102 | } 103 | 104 | ############ DO NOT EDIT BELOW ########### 105 | 106 | function install_lib() { 107 | local URL="https://github.com/Open-CMSIS-Pack/gen-pack/archive/refs/tags/v$1.tar.gz" 108 | local STATUS=$(curl -sLI "${URL}" | grep "^HTTP" | tail -n 1 | cut -d' ' -f2 || echo "$((600+$?))") 109 | if [[ $STATUS -ge 400 ]]; then 110 | echo "Wrong/unavailable gen-pack lib version '$1'!" >&2 111 | echo "Check REQUIRED_GEN_PACK_LIB variable." >&2 112 | echo "For available versions see https://github.com/Open-CMSIS-Pack/gen-pack/tags." >&2 113 | exit 1 114 | fi 115 | echo "Downloading gen-pack lib version '$1' to '$2' ..." 116 | mkdir -p "$2" 117 | curl -L "${URL}" -s | tar -xzf - --strip-components 1 -C "$2" || exit 1 118 | } 119 | 120 | function load_lib() { 121 | if [[ -d ${GEN_PACK_LIB} ]]; then 122 | . "${GEN_PACK_LIB}/gen-pack" 123 | return 0 124 | fi 125 | local GLOBAL_LIB="/usr/local/share/gen-pack/${REQUIRED_GEN_PACK_LIB}" 126 | local USER_LIB="${HOME}/.local/share/gen-pack/${REQUIRED_GEN_PACK_LIB}" 127 | if [[ ! -d "${GLOBAL_LIB}" && ! -d "${USER_LIB}" ]]; then 128 | echo "Required gen_pack lib not found!" >&2 129 | install_lib "${REQUIRED_GEN_PACK_LIB}" "${USER_LIB}" 130 | fi 131 | 132 | if [[ -d "${GLOBAL_LIB}" ]]; then 133 | . "${GLOBAL_LIB}/gen-pack" 134 | elif [[ -d "${USER_LIB}" ]]; then 135 | . "${USER_LIB}/gen-pack" 136 | else 137 | echo "Required gen-pack lib is not installed!" >&2 138 | exit 1 139 | fi 140 | } 141 | 142 | load_lib 143 | gen_pack "${DEFAULT_ARGS[@]}" "$@" 144 | 145 | exit 0 -------------------------------------------------------------------------------- /kk.signals_slots.pdsc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | kk 5 | signals_slots 6 | A signal and slot module implemented in C language. 7 | https://github.com/Aladdin-Wang/signals_slots/ 8 | https://github.com/Aladdin-Wang/signals_slots/issues 9 | LICENSE 10 | 11 | 12 | 13 | 14 | https://github.com/Aladdin-Wang/signals_slots.git 15 | 16 | 17 | 18 | 19 | Active development ... 20 | 21 | 22 | 23 | Support connecting one signal to multiple slot functions or multiple signals to one slot function. 24 | 25 | 26 | 27 | Initial release of signals_slots. 28 | 29 | 30 | 31 | 32 | 33 | signals slots 34 | C Language 35 | Data Structures 36 | 37 | 38 | 39 | 40 | 41 | A signal and slot module implemented in C language. 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | A signal and slot module implemented in C language. 53 | 54 | 55 | -------------------------------------------------------------------------------- /signals_slots.c: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2022 kk (https://github.com/Aladdin-Wang) * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | * * 16 | ****************************************************************************/ 17 | 18 | #include "signals_slots.h" 19 | /* 20 | * Function: bool direct_connect(sig_slot_t *ptSender, const char *pchSignal, void *pReceiver, void *pMethod) 21 | * Description: Connects a signal to a slot function in a signal-slot system. 22 | * Parameters: 23 | * - ptSender: Pointer to the signal object from which the signal is emitted. 24 | * - pchSignal: The name of the signal to be connected. 25 | * - pReceiver: Pointer to the object that will receive the signal. 26 | * - pMethod: Pointer to the method or function to be called when the signal is emitted. 27 | * Returns: 28 | * - true: Connection successful. 29 | * - false: Connection failed (if any of the input parameters is NULL or memory allocation fails). 30 | */ 31 | bool direct_connect(sig_slot_t *ptSender, const char *pchSignal, void *pReceiver, void *pMethod) 32 | { 33 | /* Check if any of the input parameters is NULL */ 34 | if (ptSender == NULL || pchSignal == NULL || pReceiver == NULL || pMethod == NULL) { 35 | return false; 36 | } 37 | 38 | /* Create a pointer to the metadata of the signal-slot object */ 39 | sig_slot_t *ptMetaObj = ptSender; 40 | 41 | do { 42 | if(ptMetaObj->ptNext == NULL) { 43 | /* Allocate memory for a new signal object */ 44 | sig_slot_t *ptNewSender = (sig_slot_t *)malloc(sizeof(struct sig_slot_t)); 45 | 46 | /* Check if memory allocation is successful */ 47 | if (ptNewSender == NULL) { 48 | return false; 49 | } 50 | 51 | memset(ptNewSender, 0, sizeof(struct sig_slot_t)); 52 | ptMetaObj->ptNext = ptNewSender; 53 | ptMetaObj->ptNext->ptPrev = ptMetaObj; 54 | } 55 | 56 | ptMetaObj = ptMetaObj->ptNext; 57 | 58 | /* Support connecting one signal to multiple slot functions or multiple signals to one slot function */ 59 | if (strcmp(ptMetaObj->pchSignal, pchSignal) == 0 || strlen(ptMetaObj->pchSignal) > 0) { 60 | /* Allocate memory for a new signal object */ 61 | sig_slot_t *ptNewSender = (sig_slot_t *)malloc(sizeof(struct sig_slot_t)); 62 | 63 | /* Check if memory allocation is successful */ 64 | if (ptNewSender == NULL) { 65 | return false; 66 | } 67 | 68 | memset(ptNewSender, 0, sizeof(struct sig_slot_t)); 69 | 70 | /* Check for duplicate connections */ 71 | if(strcmp(ptMetaObj->pchSignal, pchSignal) == 0 && ptMetaObj->pReceiver == pReceiver && 72 | ptMetaObj->pMethod == pMethod) { 73 | free(ptNewSender); 74 | return false; 75 | } 76 | 77 | /* Traverse to the end of the signal-slot list */ 78 | while (ptMetaObj != NULL && ptMetaObj->ptNext != NULL) { 79 | ptMetaObj = ptMetaObj->ptNext; 80 | 81 | /* Check for duplicate connections */ 82 | if(strcmp(ptMetaObj->pchSignal, pchSignal) == 0 && ptMetaObj->pReceiver == pReceiver && 83 | ptMetaObj->pMethod == pMethod) { 84 | free(ptNewSender); 85 | return false; 86 | } 87 | } 88 | 89 | /* Connect the new signal object to the list */ 90 | ptMetaObj->ptNext = ptNewSender; 91 | ptMetaObj->ptNext->ptPrev = ptMetaObj; 92 | ptMetaObj = ptMetaObj->ptNext; 93 | } 94 | 95 | /* Set the method, receiver, and signal for the current metadata object */ 96 | ptMetaObj->pMethod = pMethod; 97 | ptMetaObj->pReceiver = pReceiver; 98 | memcpy(ptMetaObj->pchSignal, pchSignal, strlen(pchSignal)); 99 | 100 | } while (0); 101 | 102 | /* Connection successful */ 103 | return true; 104 | } 105 | 106 | /** 107 | * Function: void auto_disconnect(sig_slot_t *ptSender, const char *pchSignal, void *pReceiver, void *pMethod) 108 | * Description: Removes a connection between a signal and a slot function in a signal-slot system. 109 | * Parameters: 110 | * - ptSender: Pointer to the signal object from which the signal is emitted. 111 | * - pchSignal: The name of the signal to be disconnected. 112 | * - pReceiver: Pointer to the object or function that is currently connected to the signal. 113 | * - pMethod: Pointer to the method or function that is currently connected to the signal. 114 | * Returns: void 115 | */ 116 | void auto_disconnect(sig_slot_t *ptSender, const char *pchSignal, void *pReceiver, void *pMethod) 117 | { 118 | /* Check if any of the input parameters is NULL */ 119 | if (ptSender == NULL || pchSignal == NULL || pReceiver == NULL || pMethod == NULL) { 120 | return; 121 | } 122 | 123 | /* Create a pointer to the metadata of the signal-slot object */ 124 | sig_slot_t *ptMetaObj = ptSender; 125 | 126 | do { 127 | /* Traverse the signal-slot list */ 128 | while (ptMetaObj != NULL) { 129 | /* Check if the current metadata object matches the specified signal, receiver, and method */ 130 | if (strcmp(ptMetaObj->pchSignal, pchSignal) == 0 && 131 | ptMetaObj->pReceiver == pReceiver && 132 | ptMetaObj->pMethod == pMethod) { 133 | /* Disconnect the signal from the slot function */ 134 | if (ptMetaObj->ptPrev != NULL) { 135 | ptMetaObj->ptPrev->ptNext = ptMetaObj->ptNext; 136 | } 137 | 138 | if (ptMetaObj->ptNext != NULL) { 139 | ptMetaObj->ptNext->ptPrev = ptMetaObj->ptPrev; 140 | } 141 | 142 | memset(ptMetaObj, 0, sizeof(struct sig_slot_t)); 143 | free(ptMetaObj); 144 | /* Exit the loop */ 145 | break; 146 | } 147 | 148 | /* Move to the next metadata object in the list */ 149 | ptMetaObj = ptMetaObj->ptNext; 150 | } 151 | } while (0); 152 | } 153 | 154 | -------------------------------------------------------------------------------- /signals_slots.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aladdin-Wang/signals_slots/5c0b961df5a6b7896267b282b779ad49dff6916a/signals_slots.h --------------------------------------------------------------------------------