C語言實現(xiàn)信號槽的項目實踐
一.前言
前段時間使用qt做了個項目,了解到信號槽這種機制,在解耦合和程序設計的方便程度上體現(xiàn)出極度的優(yōu)雅與從容.
現(xiàn)在回到單片機程序開發(fā)上來,感受到之前的單片機程序?qū)懙倪€不夠優(yōu)雅,使用函數(shù)指針能夠?qū)崿F(xiàn)解耦合,但需要設置回調(diào)函數(shù),而且不同的模塊設計的回調(diào)函數(shù)不盡相同,最大的痛點是實現(xiàn)異步調(diào)用非常復雜,與優(yōu)雅二字基本是毫無關(guān)聯(lián)了.
網(wǎng)上查閱了一遍,發(fā)現(xiàn)別人設計的信號槽要么只支持同步調(diào)用,要么實現(xiàn)過程復雜,代碼量巨大,要么代碼量巨大還只支持同步調(diào)用,要么居然還要自己去實現(xiàn)信號,信號不是給個函數(shù)聲明就行了嗎?還有的是閉源代碼,好像是需要購買?
介于以上原因,本人打算自己實現(xiàn)信號槽機制,應用于單片機編程.實現(xiàn)的完整代碼不到300行,支持同步異步混合調(diào)用,一對多多對一多對多等.當然,本文程序也有局限性,就是只支持單片機.因為只測試了單片機.
二.平臺及運行環(huán)境
CPU | STM32F407 |
---|---|
內(nèi)存 | 128K+64K |
操作系統(tǒng) | rt-thread |
編譯器 | keil |
moc腳本 | python |
對單片機的配置還是有一定的要求,內(nèi)存太小的話能支持的信號和槽的數(shù)量就會有限制,使用體驗上就會捉襟見肘;由于信號槽調(diào)用比回調(diào)函數(shù)速度慢,在信號槽數(shù)量多的情況下如果CPU算力不夠可能會影響業(yè)務邏輯.本文代碼不會限制信號和槽的數(shù)量,只要內(nèi)存夠.
由于異步調(diào)用需要操作系統(tǒng)支持,本文程序基于rt-thread,如需支持其他操作系統(tǒng),需要做相應修改
三.代碼實現(xiàn)
1.定義相關(guān)數(shù)據(jù)類型
在 signal.h 文件中聲明數(shù)據(jù)類型如下:
// 聲明信號和發(fā)送宏 #define signal void #define emit // 槽列表 typedef struct _slot_list{ struct _slot_list *next;// 下一個槽 void *fun;// 槽函數(shù) void *mb;// 槽函數(shù)運行的線程 void *obj;// 槽函數(shù)所在類 }slot_list; // 信號 typedef struct{ const char *name;// 信號名 const char *file;// 信號所在文件 void *signal_;// 信號 slot_list *head;// 槽列表 }signal_def; // 消息 typedef struct{ void *fun;// 要執(zhí)行的函數(shù) void *src;// 消息源 int param_num;// 參數(shù)個數(shù) uint32_t param[0];// 參數(shù)列表 }slot_msg_def; // 異步調(diào)用 typedef struct{ void *mb; int run; }slot_run_def; // 線程 typedef void * sig_thread;
2.信號定義相關(guān)
為了使系統(tǒng)知道程序中有哪些信號,還需要在信號的定義上下功夫,既要使程序能正常執(zhí)行,又需要使用戶在編程使又只需要聲明信號,而不需要去實現(xiàn).本文程序仿照qt中的做法,使用moc腳本來自動生成信號的實現(xiàn).
在 signal.h 中聲明宏:
// 這個宏用于聲明信號,聲明了之后系統(tǒng)就能找到這個信號 // 這個宏用戶編程時不需要使用 #define signal_export(name_) \ const static char __sig_##name_##_name[] SECTION(".rodata.sigstr") = #name_; \ const static char __sig_##name_##_file[] SECTION(".rodata.sigstr") = __FILE__; \ RT_USED static signal_def _signal_##name_ SECTION("signalstruct")= \ {\ .name=__sig_##name_##_name,\ .file=__sig_##name_##_file,\ .signal_=name_,\ .head=0,\ };
信號聲明示例:
用戶編程時只需要在需要使用信號的頭文件中,使用 signal 關(guān)鍵字聲明信號原型即可,moc腳本會自動生成信號的實現(xiàn),保證c語言程序結(jié)構(gòu)正常.如下:
#include "signal.h" signal pmcu_test_signal(int *a,int b);
信號實現(xiàn)示例:
在此展示一下moc腳本生成的信號實現(xiàn)長什么樣,這部分用戶使用過程中無需關(guān)心:
#include "stdlib.h" #include "signal.h" void pmcu_test_signal(int *a,int b) { uint32_t *param=malloc(sizeof(uint32_t)*2); param[0]=(uint32_t)a; param[1]=(uint32_t)b; _signal_emit(pmcu_test_signal,param,2); free(param); } signal_export(pmcu_test_signal);
3.相關(guān)函數(shù)實現(xiàn)
signal.c 文件實現(xiàn)如下:
其中 param_check(s) 是斷言 參數(shù)s是否為0的宏
#include "signal.h" #include "board.h" #include "stdlib.h" #include "stdio.h" typedef struct{ void *mutex; }self_def; static self_def g_self; // 在系統(tǒng)啟動時初始化信號量系統(tǒng) int signal_init(void) { self_def *s=&g_self; if(s->mutex==0) { s->mutex=rt_mutex_create("signal_",RT_IPC_FLAG_FIFO); } return 0; } // 4字節(jié)拷貝 static void cpy4byte(uint32_t *dst,uint32_t *src,int num_4byte) { for(int i=0;i<num_4byte;i++) { dst[i]=src[i]; } } // 調(diào)用這個宏執(zhí)行槽函數(shù) #define SLOT_FUN_RUN(fun,param) \ ((slot_fun_def)(fun))(param[0],param[1],param[2],\ param[3],param[4],param[5],param[6],param[7]) // 槽函數(shù)定義 typedef void (*slot_fun_def)(uint32_t a,uint32_t b,uint32_t c,uint32_t d,uint32_t e,uint32_t f,uint32_t g,uint32_t h); // 異步調(diào)用線程 static void slot_run(void *t) { param_check(t); slot_run_def *s=t; int msg_size=sizeof(slot_msg_def)+sizeof(uint32_t)*8; slot_msg_def *msg=malloc(msg_size); while(s->run) { rt_mq_recv(s->mb,msg,msg_size,RT_WAITING_FOREVER); SLOT_FUN_RUN(msg->fun,msg->param); } free(msg); } // 創(chuàng)建一個線程 sig_thread thread_creat(int pro) { static int count=0; char name[20]={0}; slot_run_def *run=malloc(sizeof(slot_run_def)); run->run=1; sprintf(name,"sig_mq#%d",count); run->mb=rt_mq_create(name,(sizeof(slot_msg_def)+sizeof(uint32_t)*8),20,RT_IPC_FLAG_FIFO); sprintf(name,"sig_t#%d",count); rt_thread_t rt_t=rt_thread_create(name,slot_run,run,2048,pro,20); rt_thread_startup(rt_t); count++; return run->mb; } void thread_delete(sig_thread t) { // 刪除線程需要刪除與此線程相關(guān)的所有信號槽 // 刪除消息隊列 param_check(0); } // 連接信號和槽參數(shù)分別為:信號,槽線程,槽所在類,槽函數(shù) int connect(void *signal_,sig_thread t,void *slot_obj,void *slot) { self_def *s=&g_self; signal_def *sig; sig=signal_find(signal_); if(sig==0) return -1; rt_mutex_take(s->mutex,RT_WAITING_FOREVER); slot_list *slo=malloc(sizeof(slot_list)); param_check(slo); slo->fun=slot; slo->mb=t; slo->next=0; slo->obj=slot_obj; if(sig->head==0) { sig->head=slo; } else{ slot_list *next=sig->head; while(next->next!=0) { next=next->next; } next->next=slo; } rt_mutex_release(s->mutex); return 0; } // 信號和槽斷開連接,參數(shù)同上 int disconnect(void *signal_,sig_thread t,void *slot_obj,void *slot) { self_def *s=&g_self; signal_def *sig; sig=signal_find(signal_); if(sig==0) return -1; rt_mutex_take(s->mutex,RT_WAITING_FOREVER); slot_list *next=sig->head; slot_list *prev=0; while(next!=0) { if(next->fun==slot&&next->mb==t&&next->obj==slot_obj) { if(prev) prev->next=next->next; else sig->head=next->next; free(next); } prev=next; next=next->next; } rt_mutex_release(s->mutex); return -1; } // 查找信號結(jié)構(gòu)體 signal_def *signal_find(void *signal_) { extern const int signalstruct$$Base; extern const int signalstruct$$Limit; signal_def *start=(signal_def *)&signalstruct$$Base; signal_def *end=(signal_def *)&signalstruct$$Limit; for(signal_def *t=start;t<end;t++) { if(t->signal_==signal_) { return t; } } return 0; } // 發(fā)送信號,這個函數(shù)用戶無需關(guān)心 // 用戶編程發(fā)送信號使用 emit signal_fun(); 語法即可 int _signal_emit(void *signal_,uint32_t *param,int param_num) { self_def *s=&g_self; signal_def *sig=signal_find(signal_); if(sig==0) return -1; if(param_num>7) return -2; int size=sizeof(slot_msg_def)+sizeof(uint32_t)*(8); slot_msg_def *m=malloc(size); rt_mutex_take(s->mutex,RT_WAITING_FOREVER); slot_list *h=sig->head; m->param_num=param_num; m->src=signal_; while(h) { m->fun=h->fun; if(h->obj) { cpy4byte(m->param+1,param,param_num); m->param[0]=(uint32_t)h->obj; }else{ cpy4byte(m->param,param,param_num); } if(h->mb){ rt_mq_send(h->mb,m,size); }else{ SLOT_FUN_RUN(m->fun,m->param); } h=h->next; } rt_mutex_release(s->mutex); free(m); return 0; }
signal.h 文件中添加函數(shù)聲明:
sig_thread thread_creat(int pro); void thread_delete(sig_thread t); signal_def *signal_find(void *signal_); int connect(void *signal_,sig_thread t,void *slot_obj,void *slot); int disconnect(void *signal_,sig_thread t,void *slot_obj,void *slot); int signal_init(void); // 這個函數(shù)用戶無需關(guān)心 int _signal_emit(void *signal_,uint32_t *param,int param_num);
四.moc腳本實現(xiàn)
moc腳本的工作就是定義信號函數(shù),在每次編譯之前必須調(diào)用moc腳本,否則編譯報錯.完整代碼如下:
import os MOD_FILE="mod_signals.c" MOD_PATH="./source/task/" def find_file(path:str,fix:str): file_list=[] for file_name in os.listdir(path): if file_name.endswith(fix): file_list.append(os.path.join(path, file_name)) return file_list def find_signal_def(file): list_signal=[] with open(file) as f: list_str=f.readlines() for i in list_str: if(i[0:6]=="signal"): list_signal.append(i) return list_signal # 截取變量名 def split_par_name(par_str:str): ret_str="" if(par_str.count('*')>0): ret_str=par_str.split("*")[1].strip() else: ret_str=par_str.split(" ")[1] return ret_str def def_signal_fun(line:str): # 刪除多余空格 line=' '.join(line.split()) # print(line) list_str=line.split('(') fun_name=list_str[0].split(' ')[1] param_str=list_str[1].split(')')[0] params=[] # 有","則至少有兩個參數(shù)否則可能有一個參數(shù),可能沒有 if(param_str.count(',')>0): params=param_str.split(',') for i in range(len(params)): params[i]=params[i].strip() else: t_str=param_str.strip() if(len(t_str)>0)and(t_str!="void"): params.append(t_str) # print(fun_name,params) params_num=len(params) fun_str="" fun_str+="void "+fun_name+"(" for i in range(params_num): fun_str+=params[i] if(i<len(params)-1): fun_str+="," fun_str+=")\n{\n" fun_str+=" uint32_t *param=malloc(sizeof(uint32_t)*vvxyksv9kd);\n".format(d=params_num) for i in range(params_num): fun_str+=" param[{d1}]=(uint32_t){d2};\n".format(d1=i,d2=split_par_name(params[i])) fun_str+=" _signal_emit({d1},param,{d2});\n".format(d1=fun_name,d2=params_num) fun_str+=" free(param);\n}\n" fun_str+="signal_export(vvxyksv9kd);\r\n\r\n\r\n\r\n".format(d=fun_name) # print(fun_str) return fun_str def ergodic_signal_fun(path): fun_str="" list_file=find_file(path,".h") for i in list_file: list_signal=find_signal_def(i) for j in list_signal: fun_str+=def_signal_fun(j) return fun_str def mod_fine_creat(file_path): with open(file_path,"w+") as f: f.write("#include \"stdlib.h\"\n") f.write("#include \"signal.h\"\r\n\r\n\r\n\r\n") f.write(ergodic_signal_fun(MOD_PATH)) mod_fine_creat(MOD_PATH+MOD_FILE) print("mod_file creat success.\n")
使用moc腳本:
keil中做如下設置,可在每次編譯前自動調(diào)用moc腳本:
五.使用
1.測試程序編寫
編寫 main.c 如下:
// 使用信號槽功能需要包含此文件 #include "signal.h" // 此文件聲明了pmcu_test_signal 信號,如前文所述 #include "prot_mcu.h" // 定義槽函數(shù) void slot_test(int *a,int b) { int int_array[20]={0}; int_array[0]=a[0]; int_array[1]=a[1]; int_array[2]=a[2]; int_array[3]=a[3]; int_array[4]=a[4]; int_array[5]=b; } // 定義槽函數(shù),假設a是函數(shù)所在類 void slot_test2(int *a,int *b,int c) { int int_array[20]={0}; int_array[0]=a[0]; int_array[1]=a[1]; int_array[2]=a[2]; int_array[3]=a[3]; int_array[4]=a[4]; int_array[5]=c; } int main() { // 定義數(shù)據(jù),異步調(diào)用時需保證數(shù)據(jù)在函數(shù)執(zhí)行時還有效 int int_array[20]={4,3,5,2,1}; // 創(chuàng)建異步調(diào)用線程,如不需要異步調(diào)用,則無需創(chuàng)建線程 sig_thread t=thread_creat(20); // 連接信號和槽,異步調(diào)用,這個槽將在線程t中運行 connect(pmcu_test_signal,t,0,slot_test); // 同步調(diào)用,這個槽將在調(diào)用信號的線程中運行 connect(pmcu_test_signal,0,0,slot_test); // 異步調(diào)用,假設int_array是槽函數(shù)所在類 connect(pmcu_test_signal,t,int_array,slot_test2); while(1) { rt_thread_mdelay(1000); // 發(fā)送信號,emit不是必須的,但出于可讀性要求,還是加上比較好 emit pmcu_test_signal(int_array,8); } }
程序中 pmcu_test_signal 信號連接了3個槽,因此發(fā)送信號時,每個槽都會執(zhí)行一遍,其中沒有所屬類的槽函數(shù) slot_test 會執(zhí)行兩遍,一次在main函數(shù)線程,一次在異步線程; slot_test2函數(shù)有所屬類,其第一個參數(shù)就是所屬類的指針,執(zhí)行在異步線程.
2.執(zhí)行結(jié)果
進入調(diào)試界面:
通過調(diào)試信息分析:
此時槽 slot_test 在異步線程中執(zhí)行,并且參數(shù)傳入正常
此時槽 slot_test 在同步線程中執(zhí)行,并且參數(shù)傳入正常
此時槽 slot_test2 在異步線程中執(zhí)行,并且參數(shù)傳入正常
六.總結(jié)
寥寥幾行就實現(xiàn)了類于類之間的解耦合,異步調(diào)用實現(xiàn)更是簡單,基于此模型的編程各模塊只需要關(guān)注自己內(nèi)部的業(yè)務邏輯,在合適的時候發(fā)出信號就行了,至于有沒有模塊需要,哪些模塊需要,就不需要關(guān)心了.
到此這篇關(guān)于C語言實現(xiàn)信號槽的項目實踐的文章就介紹到這了,更多相關(guān)C語言 信號槽內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于C/C++中typedef的定義與用法總結(jié)
在C還是C++代碼中,typedef都使用的很多,在C代碼中尤其是多,typedef與#define有些相似,其實是不同的,特別是在一些復雜的用法上,需要的朋友可以參考下2012-12-12C++ 模擬實現(xiàn)list(迭代器)實現(xiàn)代碼
這篇文章主要介紹了C++ 模擬實現(xiàn)list(迭代器)實現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2017-05-05關(guān)于C++ string和c類型字符數(shù)組的對比
下面小編就為大家?guī)硪黄P(guān)于C++ string和c類型字符數(shù)組的對比。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-07-07