APP下载

基于SWIG的Python仪器驱动封装技术

2018-07-05马宇叶卫东

计测技术 2018年2期
关键词:测试软件自动测试跨平台

马宇,叶卫东

(北京航空航天大学,北京 100191)

0 引言

虚拟仪器是自动测试系统的基础,测试软件是虚拟仪器的核心。目前虚拟仪器和测试软件常见的开发平台包括:NI公司的LabVIEW,LabWindows / CVI,C#,C++和Java等。LabVIEW和LabWindows具有丰富的图形化测试控件库;C#便于开发Windows图形界面程序;C,C++和Java是目前使用最广的编程语言。

在实际的产品测试开发中,这些平台或编程语言也存在一些局限。比如,LabVIEW图形化语言不便于代码管理和维护,LabWindows / CVI使用面向过程的C语言,模块化开发需要大量的编程技巧,开发效率较低,手工管理内存容易导致软件缺陷(如缓冲区溢出)。设计功能较复杂的测试软件时,静态语言在不重新编译的情况下难以对软件的功能进行动态配置。

Python是面向对象的高级编程语言,动态类型、自动内存管理、解释执行、原生跨平台,可拓展性极强,具有丰富的开源库,能快速实现应用程序所需的各种功能[1]。Python在仪器编程方面已有少量应用[2],主要障碍是大量仪器没有提供Python的编程接口。SWIG(Simple Wrapper and Interface Generator)是跨语言接口转换工具,支持Python/Perl/ PHP等动态脚本语言与C,C++,C#,Java等静态编译型语言之间的接口转换[3],Python中的很多拓展库实际上来自SWIG对C库的封装。

本文采用Python设计和开发自动测试软件,提出将SWIG用于仪器驱动的跨语言、跨平台封装,弥补Python在仪器编程方面的短板,希望能够促进Python在虚拟仪器和自动测试领域的推广和应用。

1 系统组成

1.1 硬件结构

整个测试系统由测试计算机、测试服务器和控制主机组成,通过交换机组网,如图1所示。测试计算机采用RS-232,GPIB或PXI等测试总线连接测试资源(模块化仪器或可程控的台式仪器),通过仪器驱动程序控制仪器设备;测试服务器运行数据库和测试应用软件;控制主机实现人机交互,对整个测试系统进行控制。

图1 系统硬件组成

可根据测试需求,调整和缩放系统规模,如将三者合为一体,即以传统的单机方式运行整个测试系统。

1.2 软件组成

用Python开发自动测试软件,其基本层次结构如图2所示。

图2 系统软件层次结构

最上层为测试应用层,负责测试用例执行、数据存储和分析、测试报告生成等具体的测试业务。应用层之下为仪器驱动层,在Python仪器驱动模块中封装和调用底层硬件的API,对测试资源进行配置和管理。

对于支持NI-VISA和NI-IVI标准驱动的测试设备,Python中的开源拓展库pyvisa和pyivi分别提供了二者的API封装,可以直接调用已封装好的与具体仪器无关的函数接口或可互换类驱动接口[4]。

但实际的测试需求往往丰富多变,因为各种原因,测试系统还会使用很多缺少VISA或IVI驱动支持的测试仪器,如各种总线通讯接口卡、数据采集卡、多功能复合仪器,以及一些自研或定制的非标准设备,有时也包括一些暂时无法升级的老旧测试设备。这类不被VISA或IVI驱动支持的测试资源,常被称作专有设备或非标设备。

将Python作为测试开发平台的主要技术障碍,就是如何在Python中对这类专有设备进行编程控制。

2 非标仪器驱动的封装和调用

仪器驱动通常采用C或C++编写,一般会以C语言动态链接库的形式发布,并提供头文件、库文件等供二次开发。常见的仪器驱动程序(或SDK)中包含的文件类型及作用,如表1所示。

表1 非标仪器设备驱动程序的常见结构

用C,C++调用专有仪器驱动的API函数时,一般只需要正确设置编译器的链接路径和链接方式。在C#中,需要用declare语句对API函数的参数和返回值类型等进行声明,之后方可调用仪器API进行编程。有些厂商提供了LabVIEW,C#等环境的驱动接口声明代码(或SDK),以简化编程工作。

Python语言在虚拟仪器开发中使用比例比较低,大多数仪器厂商并没有为仪器提供Python SDK支持。Python不支持指针操作,完全使用引用类型表示变量、参数等(传递内存地址而不是值拷贝),编译成中间二进制字节码后通过解释器解释运行。Python与C语言虽然语句类似,但在数据类型、内存操作、设计模式、运行方式等方面存在很大的差异,Python无法简单直接地调用针对C语言编程而设计的仪器驱动。

本文对在Python中跨语言调用仪器驱动程序,进行了技术研究和方案验证。

2.1 仪器驱动调用和封装

Python本身是开放、可拓展的,除了可使用大量的第三方开源拓展库,还可自行编写Python拓展模块。借助于一些开源库,可以实现在Python脚本中调用外部DLL动态链接库中的C程序。

1)ctypes库,使用方式与C#中非托管方式调用DLL类似,手工编写接口代码、声明每个API函数的参数和返回值类型。该方案简单易行,但工作量大、难以自动化处理,适合API函数较少的情况。

2)libcffi库提供了比ctypes更友好的编程接口,用更少的代码可完成同样的功能。

3)用CPython将外部DLL库封装为原生的Python拓展模块。该方案需要编写C代码,将仪器驱动头文件中定义的各种API函数、数据类型等转换为相应的Python对象。由于需要了解Python解释器的底层实现机制,工作量和开发难度都很大。

4)SWIG可自动解析C或C++的代码和头文件,提取API函数的参数类型、返回值类型,自动生成CPython接口转换代码。该方案通用性和自动化程度较高,只要熟悉SWIG的配置语法,无需手工编写底层的转换代码,即可快速批量地进行API封装。Python中的pyivi模块,实际上就是NI-IVI的SWIG封装。

在以上4种方案中,可能并不存在绝对的最佳方案。自动测试系统经常搭配使用模块化仪器、台式仪器以及其他程控测试资源,并根据测试需求灵活地增加或置换仪器。在选择驱动封装方案时,建议根据驱动API的数量和复杂度,结合开发人员对相关工具的熟悉程度,使用ctypes,libcffi或SWIG。

本文所设计的测试系统中使用了较多的非标仪器,API数量和类型都比较丰富,因此使用SWIG对仪器驱动进行封装,通过自动化处理提高开发效率。

2.1 用SWIG封装仪器驱动为Python拓展模块

本文以某公司的多功能数据采集模块PXI-nuDAQ2206为例(以下简称DAQ2206),简要介绍将其厂商专有驱动用SWIG转换为Python拓展模块的关键步骤。

DAQ2206为PXI模块仪器,测试资源包括64个AD通道,2个DA通道,2个定时/计数器和24个IO口。在工业控制、自动测试中使用多功能复合测试设备,可以提高资源密度、减小设备体积。但此类多功能复合设备,往往缺少VISA和IVI驱动的支持。

公司提供了专有驱动包D2K_DASK,支持包括DAQ2206在内的多种模块化仪器。用SWIG对其进行接口封装的核心工作是按照SWIG库的配置语法,在拓展名为*.i的API接口描述文件中对特殊的API参数类型进行声明,以便于SWIG能够正确地进行类型转换和封装。示例如下(片段):

%module D2KDASK

%include"D2K_DASK.h"

%include"typemaps.i"

%apply int *OUTPUT { BOOLEAN *Stopped,U32 *AccessCnt};

%apply double *OUTPUT { F64 *voltage };

首先用%include指令包含驱动API的头文件。一般情况下SWIG能自动识别大部分函数原型、变量和常量[5],并将其转换为相应的Python对象。Python采用动态数据类型和自动内存管理,无法通过指针直接操作内存,所以仪器驱动中经常使用的指针类型的参数通常需要特殊处理。

仪器API大多将操作的状态码作为返回值,但由于C / C++函数不支持多返回值,为了输出额外的数据,一般会使用指针作为参数、间接地绕开这一限制。以函数D2K_AI_AsyncCheck为例,其函数原型为I16 D2K_AI_AsyncCheck (U16 CardNumber,BOOLEAN *Stopped,U32*AccessCnt)。其中,指针类型的参数BOOLEAN*Stopped和U32 *AccessCnt,被用于输出数据采集状态和已采集数据的个数。该API函数实际上并不关心这些形参的初始值,只是单向地将输出数据写入指针所指向的内存。

借助于SWIG的指针类型处理模块typemaps.i,通过指令%apply int *OUTPUT { BOOLEAN *Stopped,U32 *AccessCnt },可将对应的参数声明为Python整数类型,参数用途为OUTPUT。

编写好SWIG接口文件后,调用swig命令可自动生成接口的包装代码(如D2KDASK_wrap.c),将其编译为动态链接库(Windows下还需要修改拓展名为*.pyd),即得到仪器驱动的Python拓展模块。在Python中可直接import导入仪器驱动拓展模块。受益于Python语法简单、多返回值、动态类型、自动内存管理等特性,无需繁琐的定义、声明和底层操作,可以简洁、自然地调用驱动模块中的资源,如:

>>>from D2K_DASK import D2K_AI_AsyncCheck

>>>status,stop,count=D2K_AI_AsyncCheck(0)

>>>status,stop,count

(0,1,50)

以上只是使用SWIG封装仪器驱动的简单示例。除typemaps.i外,SWIG还提供了windows.i,cpointer.i,carrays.i,cstring.i,cmalloc.i,cdata.i等拓展库,能够处理Windows编程中使用的各种头文件,在Python脚本中操作函数指针、数组、字符串、结构体和联合体等C语言数据类型,通过malloc动态申请和释放内存,直接对内存进行不受保护地读写。SWIG大大丰富和扩充了Python的底层编程能力,基本能满足用Python进行仪器编程的需求。

2.2 进一步功能封装

将仪器驱动封装为Python模块后,还可参考IVI可互换类驱动的实现机制,利用Python面向对象的特性,将具体的底层API操作封装在类内部,对外抽象出与仪器无关的高级操作接口,逐步将测试软件与底层仪器API解耦,提高仪器的可互换性。

2.3 实现分布式和跨平台调用

设计和开发测试系统时,有时需要在测试软件中集中管理和操作连接到多台测试计算机的仪器资源。可能的原因包括:计算机接口类型和数量有限,仪器设备空间分布较广,系统中不同的测试设备所要求的软件运行环境无法统一等。随着计算机软硬件平台不断升级,测试设备会逐渐过时,在需要对老旧的测试系统进行升级维护时,上述问题可能会更加突出[6]。将仪器设备接入到多台测试计算机后,传统的测试开发平台或编程语言往往难以用比较简单的方式,解决在分布式、跨平台的环境下,对仪器驱动进行远程操作和远程调试等问题。

Python具有数量众多且功能强大的网络编程库。其中,RPyC(Remote Python Call)库采用对象代理(Object Proxying)技术,可以像操作本地对象一样操作远程主机上的Python模块和程序。Python作为弱类型的动态语言,允许在运行时修改和替换对象,该技术被称为“猴子补丁”(MonkeyPatch),可用于在不改变源码的情况下、对软件功能进行追加或变更。

结合RPyC库和“猴子补丁”,通过本地测试软件中的代理对象,可通过RPyC库,透明地操作远程计算机所连接的测试设备,如图3所示。

图3 实现远程、跨平台调用仪器驱动

>>>import rpyc

>>>server_ip=′192.168.1.22′

>>>server=rpyc.classic.connect(host=server_ip,port=18812)

>>>D2K_AI_AsyncCheck=server.D2K_DASK.D2K_AI_AsyncCheck

>>>status,stop,count=D2K_AI_AsyncCheck(0)

在实现远程调用的同时,以上方案还支持跨平台操作,即远程计算机和本地计算机可分别使用不同类型的操作系统(Windows/Linux/Unix等),不同版本的Python,SWIG。

使用pyvisa,pyivi以及SWIG封装的Python拓展调用仪器驱动,结合RPyC和“猴子补丁”,可将单机测试软件无缝迁移到分布式、跨平台的网络环境中。该方案能非常好地解决测试软件设计中远程调试、远程操作以及运行环境无法统一的问题,为老旧测试设备的联网升级改造、多台测试机的组网运行,提供了一种简单易行的技术方案。

3 性能测试与分析

Python脚本在运行时首先会被编译为中间字节码,再通过解释器解释执行,执行过程中解释器会进行大量的类型检查、自省等操作,导致Python代码的运行效率和实时性表现较差[7]。虚拟仪器程序和自动测试软件,对运行性能和实时性往往有较高的要求。调用经SWIG封装的仪器驱动模块时,隐含的类型转换、数据拷贝等跨语言调用,必然会引入一定的封装延迟。因此,有必要定量测量和研究经SWIG封装后的Python仪器驱动模块的运行效率。

本文选取了DAQ2206的5个API函数,分别用C语言直接调用和用Python调用经SWIG封装后的驱动模块,统计执行耗时、计算封装开销。

在C程序中精确测量时间的原理是:北桥提供了高精度性能计数器,调用QueryPerformanceCounter和QueryPerformanceFrequency这两个API可分别获取其计数值和计数频率。在被测函数前后插桩、获取计数差值,除以计数频率,即得到函数的执行耗时。

表2 测试环境

嵌入式控制器 cPCI-2510计数频率测得约为1.46 MHz(硬件决定),相当于计时分辨力可达1 μs以上。测试次数1000次,数据统计如表3所示。

表3 性能测试数据 μs

可以看出,SWIG接口转换引入的封装耗时约为2~15 μs。Register_Card,Release_Card的延迟相对其他API较高,但由于只在初始化阶段和程序退出时调用,封装开销对性能的影响基本可忽略。其他3个API函数的封装开销为2~3 μs,与API本身的执行时间没有明显关联。受操作系统任务调度影响,Windows软件的实时性指标往往只能达到10 ms左右,因此微秒级的调用开销一般不会对测试任务产生严重影响。

在对程序性能要求非常严格的场合,不建议非常频繁(如每秒数千次以上)地调用SWIG封装后的API函数(如AI_VoltScale)。此时,可以将最耗时的底层关键代码用C语言实现,一并编译、封装到Python仪器驱动拓展模块中,作为整体进行调用,这样既可以使用Python语言进行高效率的开发,也不会由于SWIG封装和Python解释运行而导致软件整体的实时性受到破坏。

在100 Mbps局域网环境下,远程调用驱动API会再引入约2 ms的传输延迟,可能对软件的执行效率产生一定的影响。因此,远程调用一般更适用于软件调试、低速数据采集等实时性要求相对较低的场景。

4 总结

测试软件是虚拟仪器和自动测试系统的核心,传统的测试开发平台使用中存在较大的局限性。本文通过SWIG将仪器驱动程序转换为Python拓展模块,弥补了Python在底层编程方面的不足,解决了用Python进行仪器编程的主要障碍。

受益于Python的自动内存管理、动态类型、面向对象以及丰富的拓展库,用Python开发测试软件,可提高编程效率,降低在分布式、跨平台的环境下设计和开发测试软件的难度,缩短复杂测控系统的开发时间,一定程度上也有助于提高仪器的可互换性。在对软件实时性、仪器操作性能等有较高要求的场合,可采用C语言和Python混合编程,在软件开发效率和运行效率之间,取得比较好的平衡。

[1] 丁未.将工业与科技世界的运行统一在Python语言的开源框架中[J].中国仪器仪表,2013(08):23-25.

[2] Hughes J M.真实世界的Python仪器监控 : Real world instrumentation with Python:数据采集与控制系统自动化[M].北京:电子工业出版社,2013.

[3] Beazley D M.SWIG: An Easy to Use Tool for Integrating Scripting Languages with C and C++[C]// Usenix Tcl/tk Workshop,1996.

[4] 黄建军,李宥谋,刘婧,等.基于Python语言的自动化测试系统的设计与实现[J].现代电子技术,2017,40(4):39-43.

[5] Beazley D M.Automated scientific software scripting with SWIG[J].Future Generation Computer Systems,2003,19(5):599-609.

[6] Weltzin C,Schlonsky S.Reducing obsolescence of Linux-based ATEs with virtualization[J].Instrumentation & Measurement Magazine IEEE,2011,14(4):1-3.

[7] 范浩杰.面向Python程序源代码的分析与编译优化研究[D].北京:北京信息科技大学,2015.

猜你喜欢

测试软件自动测试跨平台
跨层级网络、跨架构、跨平台的数据共享交换关键技术研究与系统建设
一款游戏怎么挣到全平台的钱?
网络自适应测试软件运行方法设计
JXG-50S型相敏轨道电路接收器自动测试台
关于某型雷达自动测试系统的几点认识和建议
自动化检测EPU10A板卡系统设计与实现
基于C++语言的跨平台软件开发的设计
基于B/S的跨平台用户界面可配置算法研究
基于ATE与BIT组合的NAMP自动测试与故障诊断系统设计
远程开放教育学生自主学习能力评价的研究