下面描述的行为是从单个进程的角度来看的。一个GenTL生产者实现必须确保允许访问资源在硬件上有这个独立的视图,而不需要知道其他相关进程。有关C功能和数据类型的详细说明,请参阅第6章软件接口。有关如何配置特定模块或获得事件通知的信息,请参阅第4章配置和信号。
1.1 设置
打开系统模块并在GenTL上执行任何操作之前,生产者驱动程序必须调用GCInitLib函数。此操作必须执行一次。关闭系统模块后(比如,GenTL消费者关闭)必须调用GCCloseLib函数才能正确释放所有资源。如果库在调用GCCloseLib后使用,必须再次调用GCInitLib。GCInitLib的单个进程中没有引用计数。因此,如果在单个进程空间内,GCInitLib被调用两次,但没有调用GCCloseLib,那么第二个调用将返回错误GC_ERR_RESOURCE_IN_USE。这个在该进程中首次调用GCCloseLib将释放所有资源。如果有多个对GCCloseLib的调用,而没有相应的对GCInitLib的调用,也会报错。
系统模块始终是GenTL消费者调用进入GenTL生产者的入口点。使用系统模块提供的功能,所有可用的硬件接口都可以以接口模块的形式枚举出来。
通过调用TLOpen函数可以获取TL_HANDLE句柄,它用来处理系统模块的函数。从成功调用TLOpen函数获得来的TL_HANDLE句柄将用来对属于系统模块的其他功能进行所有后续调用。
在此之前,可能会调用GCGetInfo函数来检索关于GenTL 生产者实现的基本信息,而无需打开系统模块。
每个GenTL Producer驱动程序在进程空间中,仅公开操作系统中的单个系统实例。如果GenTL生产商允许从多个进程访问,则必须小心处理进程间通信,与实例化系统模块的记帐。如果不允许这种访问,则每当试图从另一个操作系统进程,创建第二个系统模块实例时,必须返回相应的错误代码。
系统模块在单个进程内不进行引用计数。因此,即使在单个进程空间内请求两次系统模块句柄,第二次调用将返回错误GC_ERR_RESOURCE_IN_USE。来自在此进程中的对close函数第一次调用,将释放所有资源,并关闭模块。
在枚举子接口之前,TLUpdateInterfaceList函数必须被调用。系统模块持有的接口列表不得更改其内容,除非再次调用此函数。对TLUpdateInterfaceList的任何调用都不会影响实例化的接口句柄。它只会更改通过访问的内部列表的顺序TLGetInterfaceID。可以使用已知id实例化以前没有枚举的子接口。建议调用TLUpdateInterfaceList重新配置系统模块后,以反映可能的变化。
GenTL消费者必须确保对TLUpdateInterfaceList的调用函数和访问列表的函数不会从多个线程并发,并且所有线程在执行时都知道更新操作。GenTL生产者必须确保以线程安全的方式进行任何列表的访问。
在内部生成可用接口列表后,TLGetNumInterfaces函数返回系统中已知的当前接口数量。此系统列表中不包含IF_HANDLEs句柄本身,而是包含它们各自的唯一ID接口。要检索这样的ID,必须调用TLGetInterfaceID函数。这间接级别允许枚举多个接口,而无需真正打开它们,这样可以节省资源和时间。
如果需要其他信息来决定要打开哪个接口,则可以调用TLGetInterfaceInfo函数。此功能启用GenTL消费者可以在单个界面上查询信息,而无需打开它。
要打开特定接口,将该接口的唯一ID传递给TLOpenInterface函数。如果在调用之前知道ID,则可以使用此ID直接打开接口,无需通过查询可用接口列表TLUpdateInterfaceList。这意味着两个ID在这两个会话当中,必须保持相同。仅仅在硬件没有任何变化的情况下,才能保证这一点。尽管如此,仍可以调用TLUpdateInterfaceList函数来创建系统内部可用接口列表。
GenTL生产者可能在需要的情况下,在模块实例化时调用TLUpdateInterfaceList。GenTL消费者必须在调用TLGetNumInterfaces或TLGetInterfaceID之前,先调用一下TLUpdateInterfaceList。模块实例化成功后GenTL使用者可以调用TLUpdateInterfaceList函数,以便了解此列表中的任何更改。
为方便起见,GenTL生产者不仅可以使用其唯一ID打开接口模块,还可以使用任何其他与ID对应的名称来打开接口模块。如果GenTL消费者请求模块ID,则GenTL生产者必须返回其唯一ID,而不是用于最初请求模块句柄的方便使用的名称。例如,这允许GenTL消费者使用网络的IP地址接口(如果是GigE Vision GenTL Producer驱动程序)来实例化模块使用唯一ID。
当不再需要GenTL 生产者驱动程序时,必须调用TLClose函数来关闭系统模块以及仍处于打开状态且与此相关的所有其他系统模块。
关闭系统模块后,可以再次打开该模块,处理模块方式可能与第一次实例化不同。
接口模块表示特定的硬件接口,如网络接口卡或图像数据采集卡。接口含义的确切定义留给GenTL生产者实现。从系统模块中获取IF_HANDLE句柄后,就可以枚举所有连接到系统上的设备。
系统模块提供的接口列表的大小和顺序在仅在调用TLUpdateInterfaceList函数后更新。接口模块可能以随机顺序关闭,其顺序可能不同于实例化时的顺序。模块不进行引用计数。如果接口模块句柄在一个进程空间内请求第二次调用,那么第二次调用将返回错误GC_ ERR_RESOURCE_IN_USE。从该进程中调用IFClose函数将释放所有资源并在此进程中关闭模块。
每个接口都由系统模块范围的唯一ID标识,而不是由索引标识。这个此ID的内容由GenTL生产者决定,仅由其解释,不得由GenTL消费者解释。
为了创建或更新所有可用设备的内部列表可以调用IFUpdateDeviceList函数。设备的内部列表不得更改其内容,除非再次调用此函数。建议调用IFUpdateDeviceList定期更新,并在接口模块配置后调用IFUpdateDeviceList,以反映可能的更改。
GenTL消费者必须确保调用IFUpdateDeviceList函数访问列表的函数不是从多个线程并发的,而且线程知道更新操作。GenTL生产者必须确保访问是以线程安全的方式进行的,因为对列表的访问可以从多个线程,并且这些列表的存储不是线程本地的。因此,更新列表来自一个线程可能会影响另一个线程中使用的索引。
内部生成的设备列表入口数是通过调用IFGetNumDevices函数获得的。与系统模块的接口列表一样,此列表不会保留设备的DEV_HANDLE句柄,但保留它们的唯一IDs。从列表中检索ID,是通过调用IFGetDeviceID函数得到的。通过不要求打开设备枚举后,可以在不同的进程中使用不同的设备。这只是GenTL生产者支持从不同进程访问的情况。
在打开设备模块之前,可能需要有关它的更多信息。要检索该信息调用IFGetDeviceInfo函数。
要打开设备模块,请使用IFOpenDevice函数。与接口ID一样如果在调用之前已知设备ID,则可以通过调用直接打开设备IFOpenDevice。ID在两个会话之间不得更改。这个尽管如此,仍可以调用IFUpdateDeviceList函数来创建接口可用设备的内部列表。必须在对IFGetNumDevices或IFGetDeviceID的任何调用之前调用IFUpdateDeviceList。如果设备实例化模块在没有内部设备列表的情况下调用IFOpenDevice之前没有调用IFUpdateDeviceList。设备无法在系统中枚举,例如,连接GigE Vision摄像头的网络接口通过WAN。如果需要的话,GenTL生产者可以在模块处调用IFUpdateDeviceList实例化。模块实例化成功后,IFUpdateDeviceList只能由GenTL消费者调用,以便其了解该列表中的任何更改。调用IFUpdateDeviceList不会影响任何实例化的设备模块及其句柄,只有内部列表的顺序可能会受到影响。为方便起见,GenTL 生产者实现可能不仅仅允许具有其唯一ID打开设备模块,而且具有任何其他定义的名称。如果GenTL消费者然后请求此类模块上的ID,GenTL生产者必须返回其唯一ID,而不是最初用于请求模块句柄的“名称”。这允许GenTL消费者GigE Vision GenTL生产商使用远程设备IP地址的示例驱动程序来实例化设备模块,而不是使用唯一ID。当不再需要接口时,必须使用IFClose函数将其关闭。这释放此接口的资源,并且所有子设备模块仍处于打开状态。关闭接口模块后,可以再次打开该模块,并且模块可能与第一个实例化不同。
设备模块表示远程设备上GenTL 生产者驱动程序的视图。如果设备能够输出流数据此模块用于枚举可用数据流。可用数据流的数量首先受到远程设备的限制,其次是由GenTL生产者实现。可能取决于实现可能只能获取多个流信道中的一个,甚至只能获取第一个流信道。
如果GenTL消费者在同一个进程请求已实例化的设备,但该设备尚未关闭,则接口返回错误。如果实例是在另一个进程空间中创建,GenTL生产者明确希望授予对此访问权限应限制为只读。在一个进程空间内,模块没有参考计数。如果第二次从请求设备模块句柄,第二个调用将返回错误GC_ERR_RESOURCE_IN_USE。该进程内对DevClose的第一次调用函数将释放所有资源并关闭模块,包括该进程中的所有子模块。
每个设备都不是由索引标识的,而是由接口模块范围内的唯一ID标识的。建议为特定设备提供通用唯一标识符。GenTL设备的ID应不同于远程设备ID。此ID的内容取决于GenTL生产者,仅由其解释,不由任何GenTL消费者解释。
为方便起见,GenTL生产者可能不仅允许通过其唯一ID打开设备。其他表示可能是用户定义的名称或传输层技术相关的ID表示,例如需要基于IP的设备的IP地址。
要获取可用数据流的数量,DevGetNumDataStreams函数是使用从接口模块返回的DEV_HANDLE句柄调用。与接口和设备列表相比,此列表保存可用流的唯一ID。数据流的数量或数据流ID在单个会话期间可能不会更改。数据流的ID必须在所有会话之间修复。
要访问与设备关联的端口对象,函数DevGetPort必须被调用。
数据流模块可以使用DevPendDataStream函数进行实例化。用先前讨论模块的ID打开数据流。ID不能在不同会话之间更改。获取数据的唯一ID,调用DevGetDataStreamID函数。
如果给定GenTL生产者不提供数据流,那么可用的流通道数目必须返回0。在这种情况下,调用DevPenDataStream和all名称中以DS开头的数据流相关函数将失败。这就是所谓的“非流媒体实现”。它只覆盖负责枚举和与设备的通信。
如果不再需要设备,请调用DevClose函数释放设备模块的资源及其依赖的子数据流(如果它们仍处于打开状态)。关闭设备模块后,可以再次打开该模块,并打开模块,这时候句柄可能与第一次实例化不同。
数据流模块不枚举其子模块。本模块的主要目的是第5章采集引擎中详细描述的数据采集。缓冲区由调用GenTL的消费者引入,因此不需要枚举它们。
每个流不是由索引标识的,而是由设备模块范围的唯一ID标识的。ID的内容由GenTL生产者决定,仅由GenTL生产者解释,与消费者无关。
任何GenTL消费者。当不再需要数据流模块时,必须调用DSClose函数释放其资源。这会自动停止正在运行的采集,刷新所有缓冲区,并撤销它们。
不建议从其他进程空间访问。模块没有参考计数机制。这意味着即使第二次请求数据流模块句柄在一个进程空间内,第二次调用将返回错误GC_ERR_RESOURCE_IN_USE。该进程内对close函数的第一次调用将释放所有资源并在此过程中关闭模块。关闭数据流模块后,可以再次打开该模块。但模块的句柄可能与第一次实例化不同。
缓冲区用作采集引擎数据的目的地。每个缓冲区不是由索引标识的,而是由从DSAnnounceBuffer或DSAllocAndAnnounceBuffer函数返回的唯一句柄标识的。
缓冲区可以由GenTL消费者或GenTL生产者分配。消费者分配的缓冲区通过调用DSAnnounceBuffer,返回此缓冲区的BUFFER_HANDLE句柄。生产者分配的缓冲区通过调用DSAllocAndAnnounceBuffer,返回BUFFER_HANDLE句柄。这两种方法不能在单个数据流模块中。GenTL生产者必须实现这两种方法,即使其中一种方法性能较差。DSAllocAndAnnounceBuffer最简单的实现是平台SDK中的malloc。
如果通过调用DSAnnounceBuffer在单个流上两次声明相同的缓冲区,就会返回错误GC_ERR_RESOURCE_IN_USE。缓冲区可以向多个流声明。在这种情况下,将返回每个流的各个句柄。一般来说,没有定义两个流之间的同步或锁定机制。GenTL生产者尽管提供了防止数据丢失的特殊功能。如果GenTL生产者不是能够处理向多个流通告缓冲区,它可能会拒绝通告,并且返回GC_ERR_RESOURCE_IN_USE。
必须从数据流模块获取所需的缓冲区大小或从相关远程设备宣布缓冲区(有关更多信息,请参阅第5.2.1章详细信息)。
为了允许采集引擎将数据流式传输到缓冲区,必须将其放入输入缓冲池,通过调用DSQueueBuffer函数,带有通过公告功能获取的Buffer_HANDLE。
由DSAnnounceBuffer或DSAllocAndAnnounceBuffer获取的BUFFE_HANDLE句柄可以通过调用DSRevokeBuffer来释放。一个仍在采集引擎中的输入缓冲池或输出缓冲队列中的缓冲区无法调用,尝试调用时,就会返回错误。一个内存缓冲区只能是向单个流声明通告一次。
1.7 枚举模块列表概览
本章旨在强调与清单维护相关的与系统中可用的GenTL模块(接口、设备)可能问题。它提供了以下内容的摘要:本规范其他章节中列出的原则。
由设备实现的数据流模块集是静态的,并且保持不变在本地GenTL设备模块的整个生命周期内,而在接口上发现的系统和设备是动态的,可能会根据请求进行更新由GenTL消费者提供。
更新列表的显式请求可以通过C接口发出(TLUpdateInterfaceList和IFUpdateDeviceList函数)或通过父模块的相应命令(InterfaceUpdateList、DeviceUpdateList)。
值得注意的是,对于“当前”“可用”模块,我们将在接口:
- 物理连接到接口的真实设备。如果在运行(或通电)时,连接了新设备,GenTL生产者可能知道也可能不知道。这取决于它是否主动监视接口。但它不会发布到GenTL Consumer通过C接口,也不会通过结点映射nodemap,直到Consumer显式请求更新列表。类似地,如果设备在物理上断开连接(或电源关闭),它也不会从发布给GenTL消费者的列表中删除(IFGetNumDevices/DeviceSelector),直到执行下一次列表更新。
- 上次请求更新时在给定接口上发现的设备列表设备列表(中的IFUpdateDeviceList函数或在nodemap中的DeviceUpdateList命令),并通过C接口发布给GenTL消费者(IFGetNumDevices)和nodemap(DeviceSelector)。而GenTL生产者只维护一个列表,并通过两个接口以相同的方式发布。视角可能暂时与GenTL消费者的视角不同。如果列表为从nodemap(使用DeviceUpdateList命令)更新,它由通过C接口直接映射nodemap。如果列表是从C接口更新的(IFUpdateDeviceList函数),它可能由于GenApi缓存效应,不会直接由nodemap反映。最后,这两个视图(C接口和节点映射)可能都是GenTL消费者独立使用。它可能通过以下方式查询信息关于一个设备的C接口,而用户选择了在节点映射中的另一个设备(DeviceSelector)。
- 当前打开的本地设备模块,例如GenTL消费者拥有有效句柄(IFOpenDevice)。这通常是已发布列表的子集通过C接口和nodemap。然而,规范要求实例化的句柄不受任何列表更新请求的影响。这意味着如果设备在运行时物理断开连接(而使用者拥有它),句柄保持有效,直到显式关闭(DevClose)–即使大多数对该句柄的操作将完全失败。如果请求更新设备列表,从父接口发布的列表中删除此类设备。每当其父(或祖父母)模块已关闭。请注意,规范允许打开设备(IFOpenDevice,与接口类似),直接使用已知的设备ID(ID应该是唯一的不能在会话之间更改)而不首先调用IFUpdateDeviceList。在里面在这种情况下,GenTL生产者可能需要(重新)在上执行设备发现过程它自己连接到设备,为GenTL消费者提供句柄在下一次列表更新之前,已发布的设备列表保持不变(甚至可能为空)要求。
1.8 例子
下面的程序片段代码显示了如果实例化连接到第一个接口上的第一个设备的第一个数据流。
1.8.1 基本设备存取
本节用到的函数都列在下面的子段中,在大括号{}了。
{
InitLib();
TL_HANDLE hTL = OpenTL();
IF_HANDLE hIface = OpenFirstInterface(hTL);
DEV_HANDLE hDevice = OpenFirstDevice( hIface );
DS_HANDLE hStream = OpenFirstDataStream( hDevice );
// 到此,我们就饿可以获取并处理数据了
CloseDataStream( hStream);
CloseDevice( hDevice );
CloseInterface( hIface );
CloseTL( hTL );
CloseLib();
}
1.8.2 InitLib
初始化 GenTL 生产者
Void InitLib( void )
{
GCInitLib();
}
1.8.3 OpenTL
获取传输层句柄
TL_HANDLE OpenTL( void )
{
TLOpen( hTL );
}
1.8.4 OpenFirstInterface
获取第一个接口句柄。
IF_HANDLE OpenFirstInterface( hTL )
{
TLUpdateInterfaceList( hTL );
TLGetNumInterfaces( hTL, NumInterfaces );
If( NumInterfaces > 0)
{
TLGetInterfaceID( hTL, 0, IfaceID, &bufferSize );
TLOpenInterface( HTL, IfaceID, hNewIface);
Return hNewIface;
}
}
1.8.5 OpenFirstDevice
获取第一个设备句柄
DEV_HANDLE OpenFirstDevice( hIF )
{
IFUpdateDeviceList( hIF );
IFGetNumDevices( hTL, NumDevices );
If( NumDevices > 0)
{
IFGetDeviceID(hIF, 0, DeviceID, &bufferSize);
IFOpenDevice( hIF, DeviceID, hNewDevice);
Return hNewDevice;
}
}
1.8.6 OpenFirstDataStream
获取第一个数据流
DS_HANDLE OpenFirstDataStream( hDev )
{
// Retrieve the number of Data Stream
DevGetNumDataStreams( hDev, NumStreams );
If( NumStreams > 0)
{
DevGetDataStreamID(hdev, 0, StreamID, buffersize);
DevOpenDataStream(hDev, StreamID, hNewStream);
}
}
1.8.7 CloseDataStream
关闭数据流。
Void CloseDataStream( hStream)
{
DSClose( hStream );
}
1.8.8 CloseDevice
关闭设备
Void CloseDevice( hDevice )
{
DevClose( hDevice );
}
1.8.9 CloseInterface
Close Interface.
Void CloseInterface( hIface)
{
IFClose( hIface);
}
1.8.10 CloseTL
关闭系统模块
Void CloseTL(hTL)
{
TLClose(hTL);
}
1.8.11 CloseLib
关闭GenTL 生产者
Void CloseLib(void)
{
GCCloseLib();
}