1.1 回調(diào)函數(shù)
1.1.1 回調(diào)函數(shù)設(shè)計方法
在LabWindows/CVI 程序設(shè)計系統(tǒng)中,一個程序可分為若干個程序模塊,每個模塊用來實現(xiàn)一個特定的功能,這些模塊可以是子程序也可以是回調(diào)函數(shù)。一個LabWindows/CVI 應(yīng)用程序由一個主函數(shù)和若干個其他函數(shù)構(gòu)成,由主函數(shù)調(diào)用其他函數(shù),其他函數(shù)之間也可互相調(diào)用,并且可以將一些常用的功能編寫成函數(shù)形式,供其他模塊調(diào)用,以提高代碼利用率,減少程序編寫的工作量。實際上,主程序為用戶功能邏輯的入口點,任何一個C 語言程序都需要通過主函數(shù)進入該程序的消息循環(huán)。
回調(diào)函數(shù)是系統(tǒng)框架設(shè)計中非常重要的一種手段,所謂回調(diào)函數(shù)(callback )是指一個通過函數(shù)指針調(diào)用的函數(shù)?;卣{(diào)函數(shù)可由用戶設(shè)計并被系統(tǒng)所調(diào)用,主要用于截獲消息、獲取系統(tǒng)信息或處理異常事件?;卣{(diào)函數(shù)必須遵守事先規(guī)定好的參數(shù)格式和傳遞方式,否則會引起程序或系統(tǒng)的崩潰。在使用LabWindows/CVI 進行程序設(shè)計時,用框架確定主要的處理流程,而將某些具體的實現(xiàn)交給用戶來做。使用回調(diào)函數(shù)實際上就是在調(diào)用某個函數(shù)時,將一個函數(shù)(這個函數(shù)為回調(diào)函數(shù))的地址作為參數(shù)傳遞給另一個函數(shù)。而另一個函數(shù)在需要時,利用傳遞的地址調(diào)用回調(diào)函數(shù)來處理消息或完成一定的操作。如C 函數(shù)庫中的qsort 函數(shù),它可以接收一個函數(shù)指針做參數(shù)來確定排序的策略,用到的就是回調(diào)函數(shù)的方法。又如,當用Windows 進行系統(tǒng)消息處理時,如果用戶注冊了回調(diào)函數(shù),系統(tǒng)中該消息觸發(fā)時會調(diào)用這個回調(diào)函數(shù),使用戶邏輯得以執(zhí)行。
在LabWindows/CVI 中,采用回調(diào)函數(shù)形式響應(yīng)系統(tǒng)消息循環(huán)。回調(diào)函數(shù)能響應(yīng)產(chǎn)生于用戶界面庫(User Interface Library )的所有事件,其回調(diào)函數(shù)原型定義存儲于userint.h 頭文件中。面板、菜單、控件等都可安裝回調(diào)函數(shù),對于特定的接口對象,LabWindows/CVI 會分配適合的回調(diào)函數(shù)以使程序正常運行。包括系統(tǒng)空閑(Idle)事件和任務(wù)結(jié)束(end-task)事件都可以通過主回調(diào)函數(shù)得到響應(yīng)與執(zhí)行。
在LabWindows/CVI 系統(tǒng)中,一些事件通過GUI 界面產(chǎn)生并傳遞給回調(diào)函數(shù)。如回調(diào)函數(shù)接收到用戶界面的鼠標點擊(EVENT_LEFT_CLICK )事件,連同一些相關(guān)信息可被記錄下來,包括回調(diào)函數(shù)中鼠標的X軸(eventData2)、Y軸(eventData1 )坐標,面板(panel)、控件(control)信息,并可以通過回調(diào)數(shù)據(jù)(callback data )傳遞用戶自定義數(shù)據(jù)。
LabWindows/CVI 中的回調(diào)函數(shù)宏定義為CVICALLBACK 存儲于cvidefs.h 頭文件中,其定義為:#define CVICDECL __cdecl
#define CVICALLBACK CVICDECL
CVICALLBACK 常被用來定義函數(shù)指針,
如:typedef void (CVICALLBACK * MenuDimmerCallbackPtr)(int menuBar, int panel);
值得注意的是,CVICALLBACK 宏定義在進行編譯時優(yōu)先于函數(shù),以保證任何用戶界面庫函
數(shù)以cdecl 方式被編譯,即使stdcall 調(diào)用約定下也是如此。
在LabWindows/CVI 中,由五類對象可通過事件觸發(fā)回調(diào)函數(shù),即控件觸發(fā)、面板觸發(fā)、菜單觸發(fā)、定時器觸發(fā)和主回調(diào)函數(shù)觸發(fā),回調(diào)函數(shù)觸發(fā)優(yōu)先級定義如下。
控件觸發(fā)優(yōu)先級:
●控件回調(diào)函數(shù)
●面板回調(diào)函數(shù)(鍵盤和鼠標事件)
●主回調(diào)函數(shù)
面板觸發(fā)優(yōu)先級:
●面板回調(diào)函數(shù)
●主回調(diào)函數(shù)
菜單觸發(fā)優(yōu)先級:
●菜單項回調(diào)函數(shù)
●主回調(diào)函數(shù)
定時器觸發(fā)優(yōu)先級:
●控件回調(diào)函數(shù)
主回調(diào)函數(shù)觸發(fā)優(yōu)先級:
●主回調(diào)函數(shù)
值得注意的是,EVENT_COMMIT 事件是存放在用戶事件隊列中的,通過GetUserEvent 函數(shù)
傳遞給所有回調(diào)函數(shù)。
1.1.2 回調(diào)函數(shù)程序設(shè)計
(1)面板設(shè)計
編寫一個偽隨機信號發(fā)生器程序,并將產(chǎn)生的數(shù)據(jù)在Graph 控件中顯示出來,將生成程序的文件名在String 控件中顯示。為了使整個面板居中顯示,雙擊面板調(diào)出Edit Panel 對話框,選擇Auto-Center Vertically (when loaded) 和Auto-Center horizontally (when loaded),并點擊“Other Attributes…”按鈕,選擇Movable 、Can Minimize 、Title Bar Visible 、Use Windows Visual Styles for Controls 項。面板設(shè)計如圖1-1 所示,面板中主要控件屬性設(shè)置如表1-1 所示。
圖1-1 回調(diào)函數(shù)面板
表1-1 控件屬性設(shè)置表
(2)程序源代碼
//頭文件聲明,系統(tǒng)自動添加
#include 《ansi_c.h》
#include 《cvirte.h》
#include 《userint.h》
#include “回調(diào)函數(shù).h”
//全局靜態(tài)變量
static int panelHandle;
//主函數(shù)
int main (int argc, char *argv[])
{
//初始化LabWindows/CVI 運行時庫引擎
if (InitCVIRTE (0, argv, 0) == 0)
//如果返回值為0, 則初始化失敗,返回–1
return –1;
//裝載面板,返回面板句柄
if ((panelHandle = LoadPanel (0, “ 回調(diào)函數(shù).uir”, PANEL)) 《 0)
//如果裝載面板失敗,則返回–1
return –1;
//獲得*argv[] 中的字符串,即為文件名
SetCtrlVal (panelHandle, PANEL_STRING, argv[0]);
//顯示面板
DisplayPanel (panelHandle);
//運行用戶界面
RunUserInterface ();
//刪除面板句柄
DiscardPanel (panelHandle);
//主函數(shù)執(zhí)行成功,返回0
return 0;
}
//面板回調(diào)函數(shù)
int CVICALLBACK PanelCB (int panel, int event, void *callbackData,
int eventData1, int eventData2)
{
switch (event)
{
//面板響應(yīng)事件
case EVENT_CLOSE:
// 調(diào)用退出按鈕的EVENT_COMMIT 事件
QuitCallback (panelHandle, PANEL_QUITBUTTON, EVENT_COMMIT, 0, 0, 0);
break;
}
//函數(shù)返回值,0 表示成功
return 0;
}
//退出按鈕
int CVICALLBACK QuitCallback (int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
if (event == EVENT_COMMIT)
{
//退出用戶界面
QuitUserInterface (0);
}
return 0;
}
//顯示按鈕
int CVICALLBACK OkCallback (int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
//定義局部變量
int i;
double datapoints[100];
switch (event)
{
case EVENT_COMMIT:
// 產(chǎn)生100 個隨機數(shù),放入數(shù)組datapoints 中
for (i = 0; i 《 100; i++)
{
datapoints[i] = rand() / 32767.0 * 100.0;
}
// 清除以前Graph 中繪制的波形
DeleteGraphPlot (panelHandle, PANEL_GRAPH, -1, VAL_IMMEDIATE_DRAW);
// 在Graph 中繪制波形
PlotY (panelHandle, PANEL_GRAPH, datapoints, 100, VAL_DOUBLE, VAL_THIN_LINE,
VAL_EMPTY_SQUARE, VAL_SOLID, 1, VAL_RED);
break;
}
return 0;
}
3:程序注釋
① main 函數(shù)
每一個C 程序都必須從一個main 函數(shù)開始,在調(diào)用其他函數(shù)流程后再次回到main 函數(shù),并且在main 函數(shù)中結(jié)束整個程序的運行。實際上,main 函數(shù)可以放在程序的任何地方:有些程序員喜歡把它放在最前面,而另一些程序員把它放在最后面,無論放在哪個地方,以下幾點說明都是適合的。
在C語言中,main 函數(shù)可以有三個參數(shù),即:argc,argv 和env 。
argc :整數(shù)類型,表示傳給main 函數(shù)的命令行參數(shù)個數(shù),一般為1。
*argv[] :二維字符串數(shù)組。在LabWindows/CVI 中,argv[0] 為程序運行時的文件名,與編譯設(shè)置有關(guān),在菜單Build→Configuration 下有兩個選項,即:Release 和Debug。當選擇Release 時,argv[0] 為當前工程名加上“.exe”;當選擇Debug 時,argv[0] 為當前工程名加上“_dbg.exe”。argv[argc] 為NULL 。
*env:二維字符串數(shù)組,為環(huán)境變量。在LabWindows/CVI 中,env[]一般為空字符串且省略不寫。
LabWindows/CVI 啟動時總是把這三個參數(shù)傳遞給main 函數(shù),參數(shù)的傳遞順序為:argc 、argv 、env,可以在用戶程序中加以說明也可以不說明,如果說明了部分或全部參數(shù),它們就成為main 主函數(shù)的局部變量。main 主函數(shù)的聲明方式主要有以下幾種:
main (void)
main (int argc, char *argv[])
main (int argc, char *argv[], char *env[])
② InitCVIRTE 函數(shù)
初始化LabWindows/CVI 運行時(庫)引擎。在使用外部編譯器Visual C++ 、Borland C++ Builder 時調(diào)用,如果不使用外部編譯器,不會影響程序正常運行。函數(shù)原型為:
int InitCVIRTE (void *HInstance, char *Argv[], void *Reserved);
*HInstance:對于main 函數(shù)應(yīng)為0;對于WinMain 函數(shù)應(yīng)為hInstance ;對于DllMain 應(yīng)為
hInstDLL。
*Argv[] :對應(yīng)于main 函數(shù)的*argv[] 參數(shù)。
*Reserved:保留參數(shù),設(shè)置為0。
一般在使用main 函數(shù)、WinMain 函數(shù)、DllMain 函數(shù)時,InitCVIRTE 函數(shù)的參數(shù)設(shè)置稍有不
同,其具體調(diào)用方式如下所示:
main 函數(shù)
int main (int argc, char *argv[])
{
if (InitCVIRTE (0, argv, 0) == 0)
return –1; /* out of memory */ //用戶程序
return 0;
} WinMain 函數(shù)
int __stdcall WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int
nCmdShow)
{
if (InitCVIRTE (hInstance, 0, 0) == 0)
return –1; /* out of memory */ //用戶程序
return 0;
} DllMain 函數(shù)
int __stdcall DllMain (void *hinstDLL, int fdwReason, void *lpvReserved)
{
if (fdwReason == DLL_PROCESS_ATTACH)
{
if (InitCVIRTE (hinstDLL, 0, 0) == 0)
return 0;
//用戶ATTACH 程序
}
else if (fdwReason == DLL_PROCESS_DETACH)
{
//用戶DETACH 程序
CloseCVIRTE ();
}
return 1;
}
LabWindows/CVI 運行時庫引擎主要用在程序發(fā)布,并安裝在其他計算機上獨立運行時。運行時庫包括:User Interface Library、Advanced Analysis Library 、Formatting and I/O Library 、Utility Library、ANSI C Library 、RS-232 Library 、TCP Support Library 、Internet Library、Network Variable Library、DDE Support Library 、ActiveX Library 、DIAdem Connectivity Library 、TDM Streaming Library、.NET Library 等。
③ LoadPanel 函數(shù)裝載用戶界面文件(*.uir)或文本用戶界面(*.tui)到內(nèi)存中。裝載后,面板不可見,需要調(diào)用DisplayPanel 函數(shù)來顯示面板。
函數(shù)原型為:int LoadPanel (int Parent_Panel_Handle, char Filename[], int Panel_Resource_ID); Parent_Panel_Handle :父面板句柄。如果為0,則表示所裝載的面板為頂層窗口;如果為面板句柄,則表示所裝載的面板為該面板的子面板。
Filename[] :用戶界面文件(*.uir )或文本用戶界面(*.tui )的文件名??梢园康穆窂矫蛑话粋€簡單的文件名,如果為簡單的文件名,則必須與工程文件在同一目錄下。Panel_Resource_ID :面板常量名。
返回值:面板句柄。
④ DisplayPanel 函數(shù)將內(nèi)存中裝載的面板顯示出來。函數(shù)原型為:
int DisplayPanel (int Panel_Handle);
Panel_Handle :面板句柄。
⑤ RunUserInterface 函數(shù)
運行用戶界面并響應(yīng)回調(diào)函數(shù)事件。RunUserInterface 在程序開始后始終運行,直到調(diào)用
QuitUserInterface 函數(shù)時才返回。函數(shù)原型為:
int RunUserInterface (void);
返回值:返回由用戶在QuitUserInterface 函數(shù)的參數(shù)中設(shè)置的值。
⑥ QuitUserInterface 函數(shù)
QuitUserInterface 函數(shù)并不直接終止程序的運行,而是使RunUserInterface 函數(shù)返回一個特定值,并進入終止程序運行處理過程。
static int panelHandle;
int main (int argc, char *argv[])
{
int status;
if (InitCVIRTE (0, argv, 0) == 0)
return –1;
if ((panelHandle = LoadPanel (0, “sample.uir”, PANEL)) 《 0)
return –1;
DisplayPanel (panelHandle);
//返回值status 為10,即:QuitUserInterface 函數(shù)的參數(shù)設(shè)置值
status = RunUserInterface ();
DiscardPanel (panelHandle);
return 0;
}
//退出按鈕
int CVICALLBACK QuitCallback (int panel, int control, int event, void *callbackData, int eventData1, int
eventData2)
{
switch (event)
{
case EVENT_COMMIT:
// QuitUserInterface 的參數(shù)值為10,即RunUserInterface 的返回值
QuitUserInterface (10);
break;
}
return 0;
}
⑦ DiscardPanel 函數(shù)釋放面板資源,包含父面板下的子面板。函數(shù)原型為:
int DiscardPanel (int Panel_Handle);
Panel_Handle :面板句柄。
⑧ SetCtrlVal 函數(shù)設(shè)置控件值。函數(shù)原型為:
int SetCtrlVal (int Panel_Handle, int Control_ID, …);
Panel_Handle :面板句柄。
Control_ID:控件常量,通常在頭文件中聲明。…:設(shè)置值。
⑨ rand 函數(shù)產(chǎn)生0~32767 之間的偽隨機數(shù)。函數(shù)原型為:
int rand (void);
返回值:偽隨機數(shù)。
⑩ DeleteGraphPlot 函數(shù)刪除控件所繪制的圖形。函數(shù)原型為:
int DeleteGraphPlot (int Panel_Handle, int Control_ID, int Plot_Handle, int Refresh); Panel_Handle :面板句柄。
Control_ID:控件常量。
Plot_Handle :繪圖句柄,表示所要刪除的圖形,如果為–1,則刪除所有圖形。
Refresh:刷新方式。主要有三種刷新方式,包括:VAL_DELAYED_DRAW 、VAL_IMMEDIATE_ DRAW 、VAL_NO_DRAW 。
? PlotY 函數(shù)沿X軸方向繪制圖形,其中Y軸為數(shù)據(jù)點。函數(shù)原型為:
int PlotY (int Panel_Handle, int Control_ID, void *Y_Array, int Number_of_Points, float Y_Data_Type[], int Plot_Style, int Point_Style, int Line_Style, int Point_Frequency, int Color);
Panel_Handle :面板句柄,指控件所在的面板。
Control_ID:控件常量。
*Y_Array:繪制圖形的數(shù)據(jù)點數(shù)組,其數(shù)據(jù)類型為Y_Data_Type[] 所指定的類型。Number_of_Points :繪制圖形的數(shù)據(jù)點數(shù),*Y_Array 中所包含的數(shù)據(jù)點數(shù)應(yīng)不小于
Number_of_Points 所指定的數(shù)據(jù)點數(shù)。Y_Data_Type[] :數(shù)據(jù)類型,其數(shù)據(jù)類型如表1-2 所示。
表1-2 Y_Data_Type 數(shù)據(jù)類型表
表1-3 Plot_Style 曲線類型表
Point_Style:數(shù)據(jù)點類型。數(shù)據(jù)點的類型決定VAL_CONNECTED_POINTS 或VAL_SCATTER
標記的類型,默認值為VAL_EMPTY_SQUARE 。其主要類型如表1-4 所示。
表1-4 Point_Style 數(shù)據(jù)點類型表
注:LabWindows/CVI 8.0 以上版本中,VAL_EMPTY_SQUARE_WITH_CROSS 不能自動切換,需要手動輸入此值。
Line_Style :線型。其主要類型如表1-5 所示。
表1-5 Line_Style 線型表
Point_Frequency :當曲線類型為VAL_CONNECTED_POINTS 或VAL_SCATTER 時,繪制數(shù)據(jù)點的頻率。默認值為1。
Color:顏色值。為4 個字節(jié)整型RGB 值,用十六進制表示為0x00RRGGBB ,可以使用MakeColor 函數(shù)自定義顏色。
返回值:繪制圖形的句柄。正值表示繪制曲線成功,負值表示產(chǎn)生錯誤。若將Graph 的ATTR_DATA_MODE 屬性設(shè)置為VAL_DISCARD ,則返回值為0。
?函數(shù)的調(diào)用對于控件而言,其回調(diào)函數(shù)原型為:
int CVICALLBACK ControlCallback(int panel, int control, int event, void *callbackData, int eventData1, int eventData2);
panel:控件所在面板句柄。
control:控件常量。
event:控件所響應(yīng)的事件。
*callbackData :回調(diào)數(shù)據(jù)。
eventData1 :對應(yīng)于具體控件響應(yīng)事件的設(shè)置值。
eventData2 :對應(yīng)于具體控件響應(yīng)事件的設(shè)置值。
本程序在面板的EVENT_CLOSE 事件中,調(diào)用了QuitCallback 函數(shù),調(diào)用格式為:
QuitCallback (panelHandle, PANEL_QUITBUTTON, EVENT_COMMIT, 0, 0, 0);
即調(diào)用在panelHandle 這個句柄所在面板的PANEL_QUITBUTTON 常量(退出按鈕)的EVENT_COMMIT 事件(左擊事件)。
?回調(diào)函數(shù)中參數(shù)的傳遞對于退出按鈕,其回調(diào)函數(shù)為:
int CVICALLBACK QuitCallback (int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
if (event == EVENT_COMMIT)
QuitUserInterface (0); } return 0;
}
當有左擊事件發(fā)生時,會將一個常量值傳遞給event 參數(shù),如果值為EVENT_COMMIT 時,則執(zhí)行該函數(shù)。其函數(shù)也可以寫成標準的LabWindows/CVI 形式,兩者功能完全相同,只是形式表現(xiàn)不同。
int CVICALLBACK QuitCallback (int panel, int control, int event,void *callbackData, int eventData1, int eventData2)
{ switch (event) { case EVENT_COMMIT:
QuitUserInterface (0); break;} return 0;
}
? 面板中的熱鍵設(shè)置
面板中的顯示與退出按鈕的表現(xiàn)形式為顯示(S)、退出(Q),設(shè)計時以顯示(__S)、退出(__Q) 來表示,說明可以通過鍵盤或鼠標來進行程序的控制。如要顯示圖形,則可以按下Alt + S 的組合鍵,如果要退出程序,可以按下Alt + Q 鍵,一般將采用Alt 鍵與字母鍵組合的形式稱為熱鍵(Hot Key),與快捷鍵(Shortcut Key )采用的Ctrl 鍵與字母組合的形式稍有不同,例如在Word 中進行的剪切操作,如果用快捷鍵來完成,直接按下Ctrl + X 鍵即可,如果采用熱鍵方式,先按下Alt + E 鍵激活編輯菜單,然后再按下T 鍵完成剪切操作,熱鍵一般要求鍵值在界面中可視,而快捷鍵則可以在不可視情況下應(yīng)用。二者的共同點是通過鍵盤上某幾個特殊鍵組合起來完成一項特定任務(wù),在菜單設(shè)計中較為常見,能夠極大地提高工作效率。
(4)運行效果圖
點擊工具欄中的Debug Project 按鈕,程序開始運行,其效果如圖1-2 所示。
圖1-2 運行效果圖
評論