嵌入式軟體架構設計-函式呼叫

大橙子瘋發表於2022-07-04

1 前言

函式呼叫很好理解,即使剛學沒多久的朋友也知道函式呼叫是怎麼實現的,即呼叫一個已經封裝好的函式,實現某個特定的功能。

把一個或者多個功能通過函式的方式封裝起來,對外只提供一個簡單的函式介面,然後在其他地方呼叫即可


2 函式呼叫方式

函式呼叫難道還能怎麼呼叫?不就封裝好直接呼叫嗎???

函式呼叫方式分為兩種:直接呼叫間接呼叫

2.1 直接呼叫

直接呼叫就是我們平常使用的方式,下面的方式就屬於直接呼叫了。

int SumFun(int a, int b)
{
    return a + b;
}

int main()
{
    // 直接呼叫定義好的函式
    int sum = SumFun(5, 6);
    printf("sum=%d", sum);
    return 0;
}

2.1 間接呼叫

間接呼叫在初學時很難使用到,這是通過函式指標的方式實現的。

函式指標本質是一個指標變數,是一個指向函式的指標(函式本身也是有地址的,指向的是函式入口);
指標函式本質是一個函式,其返回值為指標。

函式指標的用法如下:

typedef int (*FunctionCB)(int, int);

int SumFun(int a, int b)
{
    return a + b;
}

int main()
{
    // 將定義好的函式賦值給函式指標
    FunctionCB pfnSum = SumFun;

    // 通過函式指標間接呼叫
    int sum = pfnSum(5, 6);
    printf("sum=%d", sum);
    return 0;
}

 

3 什麼場景下使用

函式指標在軟體架構分層設計中十分重要,因為分層設計中有一個設計原則,那就是下層函式不能直接呼叫上層函式,那麼可以通過函式指標的方式實現;一般稱上層通過函式指標賦值給下層的函式為回撥函式

什麼情況會存在需要下層程式需要呼叫上層程式的呢?
比如串列埠資料接收,雖然可以通過查詢的方式接收,但是遠不及通過串列埠中斷的方式接收及時,當接收完成時,需要立即通知上層讀取資料進行處理,而不是等待上層程式查詢讀取。

如何實現呢?
比如硬體抽象層/驅動層中的串列埠模組實現函式

/************* UART.c 檔案 ****************/
static UartRecvCB sg_pfnUartRecv;

// 設定資料幀接收處理回撥函式
void UART_SetRecvCallback(UartRecvCB pfnUartRecv)
{
    sg_pfnUartRecv = pfnUartRecv;
}

void UART_Task(void)
{
    if (RecvEnd)
    {
        // 資料一幀接收完成立即呼叫
        if (sg_pfnUartRecv != NULL)
        {
            sg_pfnUartRecv(UartRecvBuf, UartRecvLength);
        }
    }
}

/************* UART.h 檔案 ****************/
typedef void (*UartRecvCB)(const char *, int);

extern void UART_SetRecvCallback(UartRecvCB pfnUartRecv);
extern void UART_Task(void);

應用層程式碼中實現回撥函式,並呼叫下層函式。

// 回撥函式:串列埠資料處理
void OnUartRecvProcess(const char *pBuf, int length)
{
    // 處理串列埠資料
    printf("Recv: %s", pBuf);
}

int main()
{
    UART_SetRecvCallback(OnUartRecvProcess);

    while(1)
    {
        if (TimeFlag)
        {
            UART_Task();
        }
    }
}

上述示例中通過函式指標的方式間接呼叫了應用層的函式,而且並不違背分層設計原則。
如果看程式碼不能立即理解的話,可以嘗試通過下圖理解:

 Callback 是一個函式指標型別的變數,通過函式 FML_TIME_Attach 拿到了應用層程式碼函式 OnFunction(...) 的函式地址,之後在定時器中斷函式中根據觸發條件呼叫 Callback 即可,呼叫方式和直接呼叫 OnFunction(...) 沒有太大差異,只不過名字不一樣(可以理解成取了一個別名),為了保證系統執行安全,呼叫前要確保  Callback 不為 NULL,否則會引起程式異常。

 

相關文章