文章目录
- 五、HDevelop过程
- 5.1. 过程类型
- 5.2. 文件类型
- 5.2.1. HDevelop程序
- 5.2.2. 过程文件
- 5.2.3. 库
- 5.3. 过程作用域
- 5.4. 过程位置
- 5.5. 过程解析
- 5.6. 受保护的过程
- 5.7. 过程文档
- 5.8. 即时编译🔺
五、HDevelop过程
Procedure:
在HDevelop文档中(或者说在HALCON中),有一个单词 Procedure,我一直好奇应该将它翻译成程序还是过程。
其实现在回过头去看HALCON Quick_Guide(快速向导),里面有句话能一定程度上回答这个问题:
因为Program这个单词,用在计算机领域,翻译成编程或程序是没问题的,作名词时就是程序(不要杠)。所以结合"程序行包含了procedure"这句话和实际应用场景,不难发现Procedure翻译成过程好一些。
从"You can switch between the procedures(including the main procedure)"还能看出,下面列表框中的项就是过程,
学过上一章不难发现,程序窗口的代码,几乎没有底层实现,都是调用算子。既然是一个个算子的调用,调用算子这件事情是不是看成一个过程好一些呢?
而过程的内部就是程序代码,正如下面这句话所说,
所以个人认为还是过程好一些。
HDevelop为过程(procedure)的创建和执行提供了一种机制。过程旨在通过将多个算子调用封装在一个或多个过程调用中来增加HDevelop程序的可读性和模块化。通过将反复使用的功能存储在外部过程中,它使得能更容易地在其他HDevelop程序中重用程序代码。
调过程肯定比调函数、调库,甚至复制大段的代码方便。
而且过程这个东西更能反映整体逻辑(比如上节示例的先阈值化,再找连通区域)。
HDevelop过程由接口和程序体组成。过程接口类似于HALCON算子的接口,即,它们包含图像数据和控制数据的输入输出参数列表。过程体包含算子和过程调用列表。此外,HDevelop提供了大量支持,用结构化文档补充过程。这些文档自动集成在在线帮助系统中。
每个HDevelop程序都由一个或多个过程组成。一个程序总是包含主过程(程序窗口中的main),主过程在程序中具有特殊的地位,因为它在调用层次结构中,始终处于最顶层,且无法从程序中删除。
HDevelop为创建、加载、删除、拷贝、修改、保存和导出过程提供了必要的机制。一旦过程创建,基本上就能像用算子一样使用它:过程调用可以添加到任何程序体中,并使用合适的调用参数执行。一般来讲,在HDevelop中使用过程这个概念是对调用HALCON算子概念的延伸,因为过程和算子接口具有相同的参数类型,并且具有相同的传参规则。
5.1. 过程类型
HDevelop支持不同类型的过程。类型在过程创建时指定,并且要确定过程在(文件)系统中的位置。
- 本地过程(local procedure)
本地过程存储在HDevelop程序中。每个程序至少包含"main" procedure,即主过程。本地过程无法从其它程序或外部过程调用。 - 外部过程(external procedure)
外部过程存储为单独的文件,并能在不同的HDevelop程序之间共享。外部过程的修改会立即影响所有使用它的HDevelop程序。- 过程文件
每个过程文件都包含一个外部过程。文件名决定了过程名。因此,如果外部过程的文件名被更改了,使用它的程序和其他过程也得修改调用名。 - 库
库在单个文件中包含一组(通常是相关的)过程。它们与单个目录中的外部过程集合一样共享相同的属性。创建库的目的是把相关的过程看作一个整体单元。
- 过程文件
如果更改了已有过程的类型,可能会影响过程的解析。
5.2. 文件类型
HDevelop程序、过程和库都以不同格式和扩展名存储在文件中。
5.2.1. HDevelop程序
-
.hdev (HALCON 10及以上版本)
-
这是HDevelop程序的默认文件格式,即,主过程和所有本地过程。它以XML格式存储程序,适用于版本控制软件。
注意,HALCON9及之前的HDevelop版本不支持这种格式的程序。
.dev
- 这是HDevelop9及之前中HDevelop程序的默认文件格式。它不适合用版本控制软件来管理程序。如果你希望能在旧版本的HDevelop中加载HDevelop程序,则需要此格式。该格式现在被标记为遗留格式,不再推荐使用。
5.2.2. 过程文件
-
.hdvp(HALCON10以上版本)
- 这是外部过程文件的默认文件格式。它以XML格式存储外部过程,适用于版本控制软件。扩展名为 .hdvp 的过程始终会覆盖同目录中同名但扩展名为 .dvp 的过程。 .dvp
- 这是HDevelop9以下版本中外部过程文件的默认文件格式。它不适合使用版本控制软件管理外部过程。如果你希望能在旧版本的HDevelop中使用外部过程,则需要此格式。同样,它被标记为了遗留格式,不推荐使用。
文件名(不带扩展名)决定所包含的外部过程的名称。
5.2.3. 库
-
.hdql(HALCON11以上)
- 这是过程库的文件格式。它以XML格式存储库,适用于版本控制软件。过程库是在HALCON 11中引入的,不能在旧版本的HDevelop中使用。要在旧版本的HDevelop中访问,库过程必须转换为外部过程文件。
5.3. 过程作用域
过程的作用域定义一了它对其他过程的可见性。如果一个过程是可见的,那么它就可以被调用并执行。作用域可以是私有的(private),也可以是公共的(public)。
-
Private
- 该过程只能在同一目录或同一文件(程序或库)中看见。本地过程的作用域对当前程序来说始终是私有的,也就是说,外部过程永远无法看到它们。 Public
- 该过程对所有其他过程可见。
5.4. 过程位置
HDevelop按照本节中指定的顺序在一组位置中查找过程。位置可以是目录或库文件。
-
1. 导入位置(import locations)
-
导入位置在程序代码中使用
import 语句指定。
import 语句后面的程序行可以从该位置开始调用过程。
例如,若你想要从目录 C:/Users/Public/procedures/common 中调用过程 config.hdvp ,下面代码可以实现:
导入的位置从导入调用开始到过程结束都有效。... import C:/Users/Public/procedures/common config() ...
2. 会话位置(Session locations)
-
HDevelop可以从命令行启动,启动的命令行后面跟一组位置参数,用于仅在当前会话中搜索过程。
有点拗口,看一下命令格式就知道了:
将路径名称(上面的 path name)中给定的位置添加到搜索过程的位置列表。可以使用分隔符(Windows上是";“,Linux/macOS上是”:")指定多个位置。hdevelop -external_proc_path:<path name(s)>
指定位置的子目录会被递归地搜索。
3. 静态自定义位置(Static user-defined locations)
-
在HDevelop的个性化配置中可以添加或移除任意位置。就像下面的标准过程路径一样,每个用户定义的目录都可以单独启用或禁用。用户定义的目录和位置是持久的(persistent),即,在HDevelop重启后仍然存在。将常用的过程放在这些位置是不错的做法。
同样的,指定位置的子目录会被递归搜寻。
4. 标准过程路径
-
默认情况下,HDevelop被设置为在预定义的目录中查找过程,该目录包含一些很有用的过程。标准过程路径是 * %HALCONROOT%\procedures* (Windows下)和 * $HALCONROOT/procedures* (Linux和macOS下)。由于许多HALCON附带的示例程序都依赖于这些过程,所以标准路径不能被删除,但可以禁用它以使相应的过程不可用。
同样,子目录会被递归搜寻。
5.5. 过程解析
只有可以在过程调用的插入点解析过程,才能调用过程(即想调用过程,必须能够解析过程)。因为HDevelop中允许重复的过程名,所以了解解析的顺序非常重要。解析顺序决定在存在多个同名过程时使用哪个过程。例如,导入的过程优先于本地过程 。
过程仅基于其名称进行解析,而不是基于签名的匹配进行解析。如果已解析的过程具有不同的签名(即参数的数量或类型不同,函数签名的概念),则调用无效。
过程调用按照以下顺序查找给定名称的过程:
-
1. 导入的过程(imported procedures)
- HDevelop首先会查找导入的过程。注意,对包含同名过程的多个导入语句,最后一个导入调用具有高优先级。 2. 调用过程的上下文(context of the calling procedure)
-
如果没有找到导入的过程,则对调用过程的上下文进行过程解析。上下文取决于调用过程的类型:
- 若调用过程是本地过程,就寻找本地过程;
- 若调用过程是过程文件,就寻找同目录下的过程文件;
- 若调用过程是库过程,就寻找同 一库中的过程。
3. 外部过程(external procedure)
-
接下来,HDevelop按照以下顺序寻找外部过程:
- 会话位置,即
hdevelop -external_proc_path:<path name(s)>
- 静态自定义位置
- 标准过程路径
- 会话位置,即
过程调用的工具提示会显示已解析过程的位置。
解析示例
下面是一个例子,一个有三个本地过程的程序。定义了两个过程目录(忽略标准过程路径):
+ C:/Users/Public/
|
+--+ example.hdev
| |
| +--- main
| +--- init
| +--- compute_results
|
+--+ procedures/project/
| |
| +--- init.hdvp public
| +--- local.hdvp private
| +--- setup.hdvp public
| |
| +--+ visualization.hdpl library
| |
| +--- init private
| +--- process private
| +--- setup public
|
+--+ procedures/common/
|
+--- config.hdvp public
In procedure(过程中) | a call to(调用来) | resolves to(解析至) |
---|---|---|
main | init | init(local procedure in example.hdev) |
project/visualization | init | project/visualization.hdpl/init |
common/config | init | project/init |
main | process | - |
project/visualization.hdpl/init | process | project/visualization.hdql/process |
compute_results | local | - |
project/setup | local | project/local |
-
节末小结
- 本小节比较抽象,主要谈的是同名过程解析的顺序,以及过程解析调用的注意项。最后给了一个示例,描述了过程解析至何处。
5.6. 受保护的过程
过程可以通过密码进行保护。所有用户都可以执行受保护的过程。但是,只有提供了正确的密码,才能访问接口、文档和程序代码。
HDevelop程序和库中的所有过程都可以作为一个整体来保护,而不是单独保护单个过程。
受保护的过程在两种状态之间变化:
- 上锁的(locked)
若当前会话中未输入密码,则受保护的过程将被上锁。上锁的程序不能被修改,程序代码在程序窗口中也不可见。 - 解锁的(unlocked)
输入正确密码后,受保护过程将被解锁。解锁的程序可以被修改,程序代码在程序窗口中可见。
5.7. 过程文档
HDevelop过程可以像算子一样被文档化。该文档可以包含过程功能的详细描述、示例代码、到其他过程或算子的链接和每个参数的简明文档。
为了管理大量的过程,可以以一种分层的方式对过程进行排序,也就是说,过程可以像算子一样按章节和段进行排序。
5.8. 即时编译🔺
HDevelop支持过程的即时编译(just-in-time,JIT),以优化HDevelop程序的性能。JIT编译器在首选项对话框中的"experienced user"设置中启用。一旦启用,JIT编译工作无需任何用户干预。在第一次执行过程前,HDevelop会自动将其编译为优化的字节码。额外的编译时间只与当前会话中第一次执行相关。更改相应的过程会触发重新编译。
JIT编译器的消息会被记录到输出控制台。包括指示过程的信息事件,以及指示与JIT编译相关的问题的警告事件。
JIT编译所能达到的加速取决于过程代码的结构。如果过程体只包含连续的算子调用,性能增益可以忽略不计。不过,若一个过程的执行包含相当数量的程序行切换(如,循环),性能增益可能是显著的。
当在程序执行期间用F7进入一个过程时,HDevelop解释器将执行未编译的过程。
若在编译的过程中发生错误,整个过程调用将中止,即,PC将不会指出导致错误的实际程序行。要调试相应的过程,可以使用F7进入过程调用(从而解释其代码)或在首选项中完全禁用JIT编译。
在JIT编译的过程中,try-catch块中的错误消息对话框总是被抑制,无论"experienced user"中相应的设置如何。
在HDevelop中,HALCON系统参数"use_window_thread"默认情况下是激活的,确保在windows系统上所有顶级的HALCON图形和文本窗口都在一个特殊的窗口线程中打开。此设置不能被禁用。否则,HDevelop可能会挂起,例如,在正常执行期间在HALCON图形窗口中显示对象时,该窗口之前在JIT编译执行期间打开过。
JIT编译和语义类型
在JIT编译的代码中,控制变量总是有语义类型"any"。在大多数情况下,这不会引起任何问题,因为语义类型主要用于可视化目的(如,检查变量)。然而,对于由修饰符 par_start 返回的线程ID,语义类型 thread_id 的丢失会导致JIT编译代码的不同运行时行为。这是因为线程的生命周期依赖于仍然应用其线程ID的变量。现在,当具有原始语义类型"thread_id"的控制变量复制到JIT编译过程中的另一个控制变量时,HDevelop会因为缺少语义类型而失去对附加应用的跟踪。