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