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