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