# 如何看懂Open Source : 帶你閱讀一座程式II ###### tags: `libmodbus` Copyright 2021, [月下麒麟](https://hackmd.io/@YMont/note-catalog) --- ## 概述 有關結構在[第一篇](https://hackmd.io/@YMont/ryGK1J-lt)就有提及相關實作, 此篇會著重的技巧**function pointer**與**callback function**及自我理解 在理解source過程中,發現多處出現**modbus_t**的使用 經trace之後,發現它是某個名為 **\_modbut_t** struct所定義而成 沿著這個思路去理解function pointer與callback function ## 觀念補充 callback function- `*`A callback function is a function that is passed to another function as an argument. `*`Two types of callback functions: `-->`Synchronous: executed immediately. `-->`Asynchronous: executed later. `*`Commonly used in event-driven systems. `*`In C, implement using function pointers. Reference:[Callback Functions | C Programming](https://www.youtube.com/watch?v=Hm1OjzTa_MY) ## Source Code 讓我們就先來之前看原始碼 Reference:[libmodbus/src/modbus-private.h](https://github.com/stephane/libmodbus/blob/master/src/modbus-private.h#L67) ```c= typedef struct _modbus_backend { unsigned int backend_type; unsigned int header_length; unsigned int checksum_length; unsigned int max_adu_length; int (*set_slave) (modbus_t *ctx, int slave); int (*build_request_basis) (modbus_t *ctx, int function, int addr, int nb, uint8_t *req); int (*build_response_basis) (sft_t *sft, uint8_t *rsp); int (*prepare_response_tid) (const uint8_t *req, int *req_length); int (*send_msg_pre) (uint8_t *req, int req_length); ssize_t (*send) (modbus_t *ctx, const uint8_t *req, int req_length); int (*receive) (modbus_t *ctx, uint8_t *req); ssize_t (*recv) (modbus_t *ctx, uint8_t *rsp, int rsp_length); int (*check_integrity) (modbus_t *ctx, uint8_t *msg, const int msg_length); int (*pre_check_confirmation) (modbus_t *ctx, const uint8_t *req, const uint8_t *rsp, int rsp_length); int (*connect) (modbus_t *ctx); void (*close) (modbus_t *ctx); int (*flush) (modbus_t *ctx); int (*select) (modbus_t *ctx, fd_set *rset, struct timeval *tv, int msg_length); void (*free) (modbus_t *ctx); } modbus_backend_t; ``` Reference: [libmodbus/src/modbus-rtu.c](https://github.com/stephane/libmodbus/blob/master/src/modbus-rtu.c#L1199) ```c= const modbus_backend_t _modbus_rtu_backend = { _MODBUS_BACKEND_TYPE_RTU, _MODBUS_RTU_HEADER_LENGTH, _MODBUS_RTU_CHECKSUM_LENGTH, MODBUS_RTU_MAX_ADU_LENGTH, _modbus_set_slave, _modbus_rtu_build_request_basis, _modbus_rtu_build_response_basis, _modbus_rtu_prepare_response_tid, _modbus_rtu_send_msg_pre, _modbus_rtu_send, _modbus_rtu_receive, _modbus_rtu_recv, _modbus_rtu_check_integrity, _modbus_rtu_pre_check_confirmation, _modbus_rtu_connect, _modbus_rtu_close, _modbus_rtu_flush, _modbus_rtu_select, _modbus_rtu_free }; ``` 看完上面的code,想必會覺得這兩段長得很像 Reference:[libmodbus/src/modbus-rtu.c](https://github.com/stephane/libmodbus/blob/master/src/modbus-rtu.c#L304) ```c= static int _modbus_rtu_receive(modbus_t *ctx, uint8_t *req) { int rc; modbus_rtu_t *ctx_rtu = ctx->backend_data; //... return rc; } ``` 延伸:其中(\*receive)的實作範例 ## 解析 於typedef struct...的地方先定義出了 **\_modbus_backend**結構的細節, 與該結構變數**modbus_backend_t** 並在**modbus_rtu.c**裡, 使用結構變數(modbus_backen_t)作為 **\_modbus_rtu_backend**的型態, 進行初始值的賦予,只是初值內容是**macro**與**匿名函式** **小結:** modbus_backend_t作為prototype, 提供結構成員操作時可依據宣告的初值去實作細節。 ### 實作 #1 Reference:[Pointers as Structure Member in C](https://overiq.com/c-programming-101/pointers-as-structure-member-in-c/) ```c= #include <stdio.h> typedef struct _calculator{ int sum; int sub; int mul; int div; char *total[4]; } calculator_t; int main() { // declared initialization value calculator_t cal_implement = { 8+5, 8-5, 8*5, 8/5, {"8+5", "8-5", "8*5", "8/5"} }; // used pointer to get struct's value calculator_t* ptr_cal = &cal_implement; // #1 struct get member's value printf("Accessing members using structure variable: \n"); printf("sum:%d\n",cal_implement.sum); printf("sub:%d\n",cal_implement.sub); printf("mul:%d\n",cal_implement.mul); printf("div:%d\n",cal_implement.div); for(int i=0;i<4;i++){ printf("total:%s\n",cal_implement.total[i]); } // #2 pointer get member's value printf("\nAccessing members using pointer variable: \n"); printf("sum:%d\n",ptr_cal->sum); printf("sub:%d\n",ptr_cal->sub); printf("mul:%d\n",ptr_cal->mul); printf("div:%d\n",ptr_cal->div); for(int i=0;i<4;i++){ printf("total_ptr:%s\n",ptr_cal->total[i]); } return 0; } ``` ``` Output: Accessing members using structure variable: sum:13 sub:3 mul:40 div:1 total:8+5 total:8-5 total:8*5 total:8/5 Accessing members using pointer variable: sum:13 sub:3 mul:40 div:1 total_ptr:8+5 total_ptr:8-5 total_ptr:8*5 total_ptr:8/5 ``` 可以觀察到利用結構成員與**function pointer**皆可取得宣告初值的內容 ### 實作 #2 Reference:[Youtube: Callback Functions | C Programming](https://www.youtube.com/watch?v=Hm1OjzTa_MY) ```c= #include <stdio.h> float square(int num){ return num*num; } float circular(int num){ return num*num*3.14; } float rectangle(int num){ return num*num; } typedef float (*op_t)(int); float cal_call(int num, op_t op){ return op(num); } int main() { printf("square:\t\t%.2f\n",cal_call(9,square)); printf("circular:\t%.2f\n",cal_call(6,circular)); printf("rectangle:\t%.2f\n",cal_call(10,rectangle)); return 0; } ``` ``` square: 81.00 circular: 113.04 rectangle: 100.00 ``` 發現到**callback function**就是利用函式做為指標 typedef float (\*op_t)(int); 可理解op_t為指向函式的函式指標且帶有一個整數引數值,並回傳值為浮點數格式 ### 實作 #3 ```c= #include <stdio.h> #include <stdlib.h> int compar(const void* p1, const void* p2){ int i1 = *(int*)p1; int i2 = *(int*)p2; if(i1 < i2){ return -1; } else if (i1 == i2){ return 0; } else { return 1; } } int main(void) { int arr[5] = { 8, 6, 2, 1, 9 }; printf("initial array:\t"); for(int i=0 ; i<5 ; i++){ printf("%d ",arr[i]); } printf("\n"); qsort(arr, sizeof(arr)/sizeof(arr[0]), sizeof(int), compar); printf("sorted array:\t"); for(int i=0 ; i<5 ; i++){ printf("%d ",arr[i]); } printf("\n"); return 0; } ``` ``` initial array: 8 6 2 1 9 sorted array: 1 2 6 8 9 ``` 該實作是利用[cplusplus: qsort ](https://www.cplusplus.com/reference/cstdlib/qsort/)函式本身就使用到callback function, 從其引數(argument)可發現到**int (\*compar) (const void* p1, const void* p2);** 利用qsort這個prototype然後依序對arr的元素位址取值比較大小,再將其重新排列。 >當有需要排列的list,將其丟入(呼叫)qsort函式, >當qsort排列完後,會再將結果吐出來(回傳), >並再回到主程式接續執行, >這一動作可理解為callback function。 >(另外,該qsort也有應用到function pointer概念) ### 補充 #### #1 ```c= int arr1[10]; int s1 = sizeof(arr1); int s2 = sizeof(arr1)/sizeof(arr1[0]); printf("arr1-s1:%d, arr1-s2:%d\n",s1 , s2); int* arr2[10]; int t1 = sizeof(arr2); int t2 = sizeof(arr2)/sizeof(arr2[0]); printf("arr2-t1:%d, arr2-t2:%d\n",t1 , t2); ``` ``` arr1-s1:40, arr1-s2:10 arr2-t1:80, arr2-t2:10 ``` 上面可觀察到, s1或t1就是求得 **(array size \* type size)**,改變型別會有不同結果 s2或t2就是求得陣列大小, sizeof(arr)/sizeof(**arr[0]**),等價於 sizeof(arr)/sizeof(**int**) 但是前者的寫法比較好,若改變陣列元素型別,後續程式無須再做變更。 #### #2 Reference:[Function Pointers and Callbacks in C](https://bit.ly/3sflHz2) 這篇的範例,滿清楚的,但我稍微修改,將它合併在一個main.c裡 ```c= #include <stdio.h> typedef void (*callback)(void); // 宣告 prototype void register_callback(callback ptr_reg_callbcak); // 宣告 register_callback void my_callback(void){ printf("5\n"); printf("inside my_callback\n"); //此時,才真正呼叫此函式 } int main(void){ printf("1\n"); callback ptr_my_callback = my_callback; // ptr_my_callback 先記錄下(位址) my_callback,程式繼續往下走 printf("2\n"); printf("This is a program demonstrating function callback\n"); printf("3\n"); register_callback(ptr_my_callback); //把剛剛 ptr_my_callback 紀錄的東西(位址)丟到 register_callback函式 printf("7\n"); printf("back inside main program\n"); printf("8\n"); return 0; } void register_callback(callback ptr_reg_callback) { printf("4\n"); printf("inside register_callback\n"); // 進入register_callback (*ptr_reg_callback)(); // 將ptr_my_callback參照prototype,執行於my_callback printf("6\n"); } ``` ``` 1 2 This is a program demonstrating function callback 3 4 inside register_callback 5 inside my_callback 6 7 back inside main program 8 ``` 可參考這段程式,更能體會callback function的意義。 #### #3 ```c= #include<stdio.h> int add(void); int main(void){ printf("%d\n",add()); printf("%p\n",add); printf("%p\n",*(add)); return 0; } int add(void){ return 42; } ``` ``` 42 0x400560 0x400560 ``` 呼叫函式與取得位址 --- **結論:** callback的精神在於第一次遇到它時,只去取得它的**位址** (**注意函式後面沒括號**) 所以將其位址保留在變數ptr_my_callback 接著,等到你想去**執行它**時,再**將其位址丟到負責呼叫別人做事的函式**裏頭 它就會真正去做事了 日常比喻的話,你在掃地掃到一半(主程式), 外面下雨(事件發生), 你家人就呼叫你去收衣服(系統要主程式緊急的先執行), 收完衣服,你回來繼續完成你的打掃(回到主程式)。 最後,引述一段參考文章的話: **Just remember, callback is nothing but passing the function pointer to the code from where you want your handler/callback function to be invoked.** --- (真好,今天又進步一點點了, 另外,感謝師仔的耐心提點與詳細解釋,才有這篇文章的產出, 內容細節編排上,還是相當凌亂,還有很多進步的空間, 希望能幫助路過於此,正在學習的你)