C語言中使用函數指針構造回調函數的一個典型應用

//////////////////////////////////////
/*
這個例子比較複雜,需要放到專門的編譯器中去看,建議使用VS查看
在後面的帖子中有一個簡單的例子說明回調函數的用法,比較清楚,可以參看
*/

//主函數調用:
int _tmain(int argc, _TCHAR* argv[])
{
Init();
........
AddFileDriver(MiddleWare,NULL);
......
_getch();
return 0;
}

//
//AddFileDriver的代碼
//加載底層驅動程序。典型的回調函數使用<驅動程序DiskCommand通過AddFileDriver獲取參數Parameter信息>
//個人認爲可以改成更通用的形式:AddFileDriver(int8  (* DiskCommand)(void *Parameter), void *RsvdForLow)
//其中參數:Parameter可以獲取調用時的動態信息,比如:命令字、要操作的扇區
//   RsvdForLow可以獲取程序運行過程中的靜態信息,比如:固定的磁盤信息
//   注意:Parameter,RsvdForLow傳遞到DiskCommand函數中具體使用時都要強制類型轉化

//Parameter 用於向驅動函數<DiskCommand指向的函數>傳遞信息,Parameter->RsvdForLow 獲取外部的其他輔助信息
void AddFileDriver(int8  (* DiskCommand)(int8 Command, void *Parameter), void *RsvdForLow)
{
//1.檢測是否有初始化程序<防止傳遞空參數>,初始化是否成功<物理磁盤是否可操作>
//2.獲取當前第一個空閒邏輯盤並掛接<也就是將物理磁盤轉化爲系統內部表示法以便操作>
//3.檢測是否掛接成功。失敗:退出。成功:讀取物理磁盤0扇區(啓動扇區)的值
//4.根據啓動扇區數據重新設置邏輯盤參數(第一次有意義的初始化)
  //**此時需要判斷磁盤是否正確格式化了,如果沒有格式化(Format),那麼此時需要運行Format程序

int8  Buf[SECSIZE] = {0}; //存儲一個扇區的內容
    Disk_Info *Disk = NULL;
Disk_RW_Parameter Pa;

Pa.TempVal = 0;     //初始化,無意義
Pa.SectorIndex = 0;    //初始化,無意義
    Pa.RsvdForLow = RsvdForLow;  //相關輔助信息,有可能用到,如果調用函數RsvdForLow = NULL,那麼這個參數也就無意義
    Pa.Buf = Buf;     //便於調用驅動函數運行時產生的數據,一般是磁盤讀寫數據的緩存
Pa.Drive = EMPTY_DRIVE;   //初始化,當前使用的邏輯盤符

if (DiskCommand == NULL)        // 參數無效退出
    {
        return;
    }

//先掛接再初始化,把流程中所說的1和2有所顛倒,爲了統一接口
Disk = GetEmptyDiskInfoAddr();
if (Disk == NULL)
    {
        return;
    }

//Disk是指向全局變量DiskInfo_G的指針,對其賦值可以把信息保留到程序運行完畢
//因此把程序運行過程中一直需要使用的信息傳給Disk->RsvdForLow和Disk->DiskCommand
//Disk->DiskCommand 一般指向驅動函數,Disk->RsvdForLow 指向主函數中傳遞過來的信息
Disk->RsvdForLow = RsvdForLow;  //接收輔助信息
Disk->DiskCommand = DiskCommand; //指向驅動程序

Pa.Drive = Disk->Drive;
    if (Disk->DiskCommand(DISK_INIT, &Pa) != DISK_INIT_OK)   // 底層驅動初始化不成功退出
    {
        return;
    }

//讀取0扇區的數據,初始化DiskInfo_G[Drive]
Pa.SectorIndex = 0;
    if(Disk->DiskCommand(DISK_READ_SECTOR, &Pa) != DISK_READ_OK)
    {
        return;
    }

#ifdef NF_FORMAT  //如果沒有沒有格式化便格式化
if(FSMount(Disk, Buf) != RETURN_OK)
{
  Format(Disk->Drive);
  if(FSMount(Disk, Buf) != RETURN_OK)
   return;
}
#else
FSMount(Disk, Buf);
#endif

}
//****************Disk_RW_Parameter的結構如下***********************//
typedef struct _Disk_RW_Parameter
{
int32  TempVal;  /* 用於傳遞函數需要的參數或者將函數調用後產生的值傳出來 */
        /* 常用於傳遞一個需要操作的函數或者傳回函數調用後產生的值*/
    uint32      SectorIndex;    /* 操作的扇區,當向下傳遞一個參數的時候也可以使用TempVal替代 */
    void        *RsvdForLow;    /* 保留給底層驅動程序,由_Disk_Info中拷貝過來 */
    int8  *Buf;           /* 數據存儲位置 */
    int8  Drive;
}Disk_RW_Parameter;
//************************************************************************************************************************************//


//
//MiddleWare的代碼
int8 MiddleWare(int8 Command, void *Parameter)
{
    uint8 state;    //用於返回值,表示函數操作完畢之後的狀態
    Disk_RW_Parameter * Dp;  //用於接收加載驅動時傳遞的參數
   
    Dp = (Disk_RW_Parameter *)Parameter;
   
    // 如果所要求的命令沒有在這裏實現,則state =  BAD_DISK_COMMAND
    switch (Command)
    {
  default:
            state = BAD_DISK_COMMAND;
            break;        
  case DISK_INIT:
            // 初始化驅動程序,必須實現 //
            // Parameter沒有使用 //
   //state= DISK_INIT_OK 或 DISK_INIT_NOT_OK
            //state = PHDisk_Initialize();爲了明確顯示採用以下方式:
   if(PHDisk_Initialize() == PH_DISK_INIT_OK)
    state = DISK_INIT_OK;
   else
    state = DISK_INIT_NOT_OK;
            break;
  case DISK_READ_SECTOR:
            // 讀物理扇區,必須實現 //
            // Dp->Buf:存儲讀到的數據 //
            // Dp->SectorIndex:物理扇區索引 //
            // state=DISK_READ_OK或DISK_READ_NOT_OK//
   if(PHDiskReadSec(Dp->SectorIndex, Dp->Buf) == PH_DISK_READ_OK)
    state = DISK_READ_OK;
   else
    state = DISK_READ_NOT_OK;
            break;
.......
.......
    }
    return state;
}

注意紅色和大字體部分,可以放到VS中去看,網頁上顯示的比較亂!
之所以貼出代碼是因爲這段代碼實在是太經典了,用好
void AddFileDriver(int8  (* DiskCommand)(int8 Command, void *Parameter), void *RsvdForLow)
中的void *Parameter和void *RsvdForLow,可以實現強大的變參函數。而且函數接口具有良好的擴展性。 

舉一個簡單的回調函數的例子吧(出自《C和指針》13.3.1節,網上有電子版下載,圖書館好像也有此書)

//使用函數指針構造回調函數
//在一個單鏈表中查找一個指定的值(*value)
search_list(Node *node, void const *value, int (*compare)(void const *, void const *) )
{

         while(node != NULL)
         {
                 if(compare(&node->value, value) == 0 )
                          break;
                 node = node->link;
         }
         return node;
}


//函數的定義
int compare_ints (void const *a, void const *b)
{
         if(*(int*)a == *(int*)b )
                return 0;
         else
                return 1;
}

//回調函數的使用
desired_node = search_list(root, &desired_value, compare_ints);


***這是一個簡單的例子,雖然說明了回調函數的構造方法和使用,但是隻是個簡單的應用,還沒有完全體現出來回調函數的強大功能。這個函數的擴展性很好,大家可以看見傳遞的參數都是(void*),也就是一個通用性指針,可以指向任意類型。但是沒有體現出來被調用函數可以從回調函數接收參數這一點,但是在第一個例子中具有這種思想(在回調函數AddFileDriver(...)中給(Disk_RW_Parameter Pa) 賦值,然後再傳給被調用函數MiddleWare(...),也就是*DiskCommand指向的函數)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章