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 运行效果图
评论
查看更多