Try   HackMD

如何看懂Open Source : 帶你閱讀一座程式II

tags: libmodbus

Copyright 2021, 月下麒麟


概述

有關結構在第一篇就有提及相關實作,
此篇會著重的技巧function pointercallback 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

Source Code

讓我們就先來之前看原始碼
Reference:libmodbus/src/modbus-private.h

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

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

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

#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

#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

#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 函式本身就使用到callback function,
從其引數(argument)可發現到int (*compar) (const void* p1, const void* p2);
利用qsort這個prototype然後依序對arr的元素位址取值比較大小,再將其重新排列。

當有需要排列的list,將其丟入(呼叫)qsort函式,
當qsort排列完後,會再將結果吐出來(回傳),
並再回到主程式接續執行,
這一動作可理解為callback function。
(另外,該qsort也有應用到function pointer概念)

補充

#1

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
這篇的範例,滿清楚的,但我稍微修改,將它合併在一個main.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

#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.


(真好,今天又進步一點點了,
另外,感謝師仔的耐心提點與詳細解釋,才有這篇文章的產出,
內容細節編排上,還是相當凌亂,還有很多進步的空間,
希望能幫助路過於此,正在學習的你)