簡短的switch-case還是繼續用吧,但是對於分支太多的長switch-case最好能想辦法化解開,那麼什麼算長什麼算短呢?我也不知道,就以在最低分辨率的顯示器上能夠在一個窗口中完整顯示整個switch-case塊爲判斷依據吧。化解長switch-case的方法有很多種,用函數封裝或者宏取代case塊是治標不治本的方法,使用表驅動通常是治療這種頑症的有效方法,本文將介紹如何用表驅動方法化解長switch-case。
還是用例子說明問題吧,假設我們要爲一個系統編寫驅動,系統已經定義好了如下所示的複用接口(MUX):
STATUS DriverIoControl(UINT function_no, PVOID para_in, PVOID para_out)
用戶層程序通過複用接口調用驅動,功能號就是function_no,驅動程序負責實現具體的DriverIoControl()函數完成相應的功能。這是使用switch-case的典型場景,先看一個使用switch-case的方案:
STATUS DriverIoControl(UINT function_no, PVOID para_in, PVOID para_out)
{
STATUS rc;
switch(function_no)
{
case PROCESSA:
rc = ProcessA(para_in,para_out);
break;
case PROCESSB:
rc = ProcessB(para_in,para_out);
break;
case PROCESSC:
rc = ProcessC(para_in,para_out);
break;
..........
default:
rc = UN_SUPPORT;
break
}
return rc;
}
STATUS ProcessA(PVOID para_in, PVOID para_out)
{
//一些代碼....
}
STATUS ProcessB(PVOID para_in, PVOID para_out)
{
//一些代碼....
}
STATUS ProcessC(PVOID para_in, PVOID para_out)
{
//一些代碼....
}
................
這個方案中規中矩,但是如果驅動很複雜,功能很多,那麼DriverIoControl函數代碼的長度是相當可觀的,好像已經聞到壞味道了,呵呵。現在換成使用宏的解決方案:
#define DISPATCH_BEGIN(func) switch(func) /
{
#define DISPATCH_FUNCTION(func_c, function) case func_c: /
rc = function(para_in,para_out); /
break;
#define DISPATCH_END(code) default: /
rc = code; /
}
STATUS DriverIoControl(UINT function_no, PVOID para_in, PVOID para_out)
{
STATUS rc;
DISPATCH_BEGIN(function_no)
DISPATCH_FUNCTION(PROCESSA,ProcessA)
DISPATCH_FUNCTION(PROCESSB,ProcessB)
DISPATCH_FUNCTION(PROCESSC,ProcessC)
........................
DISPATCH_END(UN_SUPPORT)
return rc;
}
嗯,好一點,但好不到哪裏去,只是用一行代替多行而已,並不能改變代碼隨着功能增多線性增長的趨勢。羅嗦一下,我不喜歡宏的原因很簡單,目前很少有(說實話,是我確實沒有見過)調試器支持對宏的展開調試。這很麻煩,當一段摻雜着宏的代碼沒有達到預期的目的時,你不得不一遍一遍地在心裏展開你的宏,以確定它是沒有問題的(或者,你根本不能確定,只能假設它沒有問題)。
現在看看錶驅動方案,如果系統約定的function_no是個連續的線性數字,或者是某個區間上的連續線性數字,那麼驅動表可以簡單就是一個線性數組,看看例子:
typedef STATUS (*ProcessFuncPtr)(PVOID para_in, PVOID para_out);
typedef struct tagDispatchItem
{
ProcessFuncPtr func_ptr;
}DISPATCH_ITEM;
DISPATCH_ITEM dispatch_table[MAX_DISPATCH_ITEM];
STATUS DriverIoControl(UINT function_no, PVOID para_in, PVOID para_out)
{
/*或者需要對function_no調整,使其區間落到[0 - MAX_DISPATCH_ITEM)*/
if((function_no >= 0) && (function_no < MAX_DISPATCH_ITEM))
return dispatch_table[function_no].func_ptr(para_in,para_out);
else
return UN_SUPPORT;
}
如果function_no不是線性的,那就需要查表:
typedef struct tagDispatchItem
{
UNIT func_no;
ProcessFuncPtr func_ptr;
}DISPATCH_ITEM;
DISPATCH_ITEM dispatch_table[MAX_DISPATCH_ITEM];
STATUS DriverIoControl(UINT function_no, PVOID para_in, PVOID para_out)
{
int i,find = -1;
for(i = 0; i < MAX_DISPATCH_ITEM; i++)
{
if(function_no == dispatch_table[i].func_no)
{
return dispatch_table[i].func_ptr(para_in,para_out);
}
}
return UN_SUPPORT;
}
使用表驅動的好處就是DriverIoControl的代碼就這幾行,添加新功能,只需要維護驅動表dispatch_table就行了,就這樣擺脫了冗長乏味的switch-case。
前面例子中的switch-case語句中各個case分支參數比較簡單整齊,也就是各個case分支都是用相同的參數para_in和para_out,如果各個分支使用的參數不整齊怎麼辦?那就需要封裝,通常是用struct和union結合定義一個統一的數據結構做爲接口參數,不同的分支dispatch函數內部根據需要從這個統一的數據結構中提取相應的數據。具體方法就是首先定義統一的dispatch函數接口,例如:
typedef STATUS (*ProcessFuncPtr)(DISPATCH_DATA *para);
然後用struct和union相結合定義DISPATCH_DATA,DISPATCH_DATA通常有這樣的結構:
typedef struct tagDISPATCH_DATA
{
//此處聲明所有的分支公用的數據
union
{
//此處聲明processA分支使用的數據
}processA;
union
{
//此處聲明processA分支使用的數據
}processB;
union
{
//此處聲明processA分支使用的數據
}processC;
......
}DISPATCH_DATA;
做過Windows驅動程序的朋友肯定對IRP不陌生,IRP就是一個典型的例子。
就這樣吧,打完,收工。