我在 LabVIEW 中经常使用的一种编程结构是Event Structure。这让我不必通过轮询不必要地浪费 CPU 周期,而只在生成我感兴趣的事件时执行操作。
作为一个对 C 有相当了解的经验丰富的 LabVIEW 程序员,我很好奇如何在 C 中模拟 LabVIEW 的事件结构。最好在Linux下。一个小代码示例(如上面链接中的那个)说明如何完成将不胜感激。此外,如果已经存在 3rd 方库(用于 Linux)来将此事件框架添加到 C,那也很高兴知道。谢谢。
我在 LabVIEW 中经常使用的一种编程结构是Event Structure。这让我不必通过轮询不必要地浪费 CPU 周期,而只在生成我感兴趣的事件时执行操作。
作为一个对 C 有相当了解的经验丰富的 LabVIEW 程序员,我很好奇如何在 C 中模拟 LabVIEW 的事件结构。最好在Linux下。一个小代码示例(如上面链接中的那个)说明如何完成将不胜感激。此外,如果已经存在 3rd 方库(用于 Linux)来将此事件框架添加到 C,那也很高兴知道。谢谢。
事件结构实际上只是对您隐藏执行线程的抽象。必须在计算机的某个地方运行一些代码来检查这些事件,然后调用您的事件处理程序。在 C 语言中,您需要自己提供此代码(程序的“主循环”)。此代码将检查您感兴趣的各种事件源并调用您的事件处理函数。
然后诀窍就变成了如何不让这个主循环疯狂地旋转 CPU。一个简单的技巧是让主循环休眠一段时间,然后检查是否需要处理任何事件,然后再次休眠。这具有引入延迟的缺点。如果适用,一个更好的技巧是让操作系统将这些检查作为其正常操作的一部分,然后在发生有趣的事情时唤醒应用程序的主循环。在 Linux 中,这是通过 'select' 系统调用完成的,但 select 有一个限制,它只能指定可以与文件描述符关联的资源,因此设备、标准输入、文件、网络端口都可以。
编辑:为我的反对者澄清:我并不否认硬件中断的存在。是的,如果代码可以直接访问它希望处理的所有事件的硬件中断(例如嵌入式系统或设备驱动程序),您可以编写真正的“事件驱动”代码,其中包含多个不忙等待或休眠的入口点. 但是,在 Linux 下运行的普通应用程序级 C 程序中,这种代码架构实际上并不存在,而是在应用程序级进行仿真。任何 Linux 应用程序都会有一个主循环和至少一个执行线程。该线程可能会被调度程序暂停,但它始终存在并且始终在特定指令处具有指令指针。如果代码离开 main() 程序结束。代码无法从 main 返回并稍后从内核获取回调。该代码只有一个入口点,并且必须手动调用其各种事件处理程序。除了在设备驱动程序(或使用信号的非常特定的系统代码)中,如果用户单击某个菜单项,您不能让内核或硬件自动调用某个函数,而是您的代码正在运行,检测到此事件本身,并调用正确的事件处理程序。
你可以告诉 LabView “当 XX 发生时调用这个函数”。在 C 中,您告诉自己的事件调度代码“当 XX 发生时调用此函数”。
我想说的(很糟糕?)是事件框架架构不是 C / Linux 应用程序的本机。它必须由您的代码模拟,方法是拥有一个主调度线程,该线程提供事件驱动框架的外观。您可以手动执行此操作,也可以使用在幕后执行此操作的事件库来呈现事件驱动模型的外观。LabView 采用第二种方法,因此当没有事件发生时似乎没有代码在运行,但实际上有 LabView 自己的 C++ 代码在运行管理事件队列。这并不意味着它一直在忙着等待,就像我之前所说的那样,代码可以在无工作时使用 select 和 sleep 等系统调用来让出 cpu 时间,但代码不能简单地停止执行。
假设您想编写一个带有两个事件处理程序的“事件驱动”程序。每 10 秒调用一次的函数称为 tick(),每次按下键时调用的函数称为 key(),每次键入单词“foobar”时调用的函数称为 foobar()。您可以定义这三个事件处理程序,但除此之外,您还需要一些基本上可以执行的调度主线程
while not quitting
If 10 seconds have elapsed, call tick()
If Key has been Pressed
call key()
add save the key to our key buffer
If buffer now contains "foobar" call foobar() and clear buffer
Wait()
如果你关心的所有事件都是系统级事件或时间级事件,你可以 Wait() 可以简单地告诉内核“当这些事情之一发生时叫醒我”,所以我不需要“忙等待” , 但是你不能简单地告诉内核“当“foobar 被按下”时调用 foobar()。你必须有模拟事件结构的应用程序级调度代码。你的 C 程序只有一个来自内核的入口点对于每个执行线程。如果您查看提供事件分派模型的库,例如 Qt,您会发现它们在后台是这样工作的。
我喜欢libev这种事情。
如果您只关心键盘输入,那么 C 标准 I/O 就是您想要的。默认情况下,输入流是缓冲的,并且会停止你的程序,直到接收到输入。使用scanf
, getchar
, 中的任何其他内容<stdio.h>
。
如果您想要鼠标输入,则需要更具体地了解您的平台,因为 C/C++ 没有对鼠标或窗口的本机支持。
与 LabVIEW 事件结构的一个很好的类比是 Win32 的“事件拉取”功能GetMessage()
。GetMessage()
一直等到 GUI 事件发生。Windows 中的事件甚至对于每个子窗口(LabVIEW:控件或指示器)都比 LabVIEW 中的要多得多。GetMessage()
只需在每个事件上返回,精细过滤(如在 LabVIEW 中)必须稍后完成,通常使用DispatchMessage()
Window 的事件处理程序WindowProc()
及其或多或少的大型switch()
语句。
大多数takeits使用不适合事件结构的“事件推送”样式。中断驱动程序也是如此。
如果使用超时,请认为MsgWaitForMultipleObjects()
在之前调用零文件句柄PeekMessage()
。当在给定的时间范围内没有事件到达时,超时情况适用。
实际上,LabVIEW 的事件结构应该在一个单独的循环中。一个单独的循环是一个线程。对于典型的 Win32 编程,GetMessage()
在主线程中使用,并且根据需要由用户交互生成附加(“工作”)线程。
LabVIEW 无法轻松创建线程。只能通过调用异步子 VI 来实现。真的!因此,大多数LabVIEW程序使用第二个while循环作为永久可用的工作线程,当必须完成某事时运行,否则阻塞(即停止消耗CPU功率)。为了指示必须在后台完成的操作,使用了队列。作为一个不好的副作用,当工作线程做某事时,用户不能在后台做其他事情,因为只有一个工作线程。
LabVIEW 的事件结构与其他编程语言有很大的不同:LabVIEW 事件可以有多个使用者!如果使用多个事件结构,一切都会继续正常工作(具有布尔返回值的事件除外)。在 Windows 中,事件被发布到特定的线程,主要是 Windows 的线程。要提供多个线程,必须多次发布事件。类似于其他编程语言。那里的事件由类似于LabVIEW“队列”相关函数的东西处理:如果有人接收到事件,它就会被排除在队列之外。
多目标要求每个消费者以某种方式向生产者注册自己。对于 GUI 事件,这是自动完成的。对于用户事件,这必须以编程方式完成。请参见 LabVIEW 示例。
在 Windows 中使用 DDE 实现了将事件分发给多个侦听器,但这仅适用于进程而不是线程。使用DdeConnect()
或类似方法注册到线程,并将事件推送到回调函数。(更确切地说,Win32 是如何工作的,GetMessage()
接收 DDE 消息,并DispathcMessage()
实际调用回调函数。)