通过ActiveX Server轻松访问LabVIEW VI PHP应用程序

多年以来,LabVIEW仅使用内置的ActiveX服务器即可将Web与VI设备“绑定”在一起,而无需进行任何复杂的LabVIEW发布和服务器设置。LabVIEW 2020社区版也不例外。

目前,对于LabVIEW,有多种方法可以在网络上发布虚拟设备,需要不同程度的知识并提供不同的功能。在本文中,我将不对它们进行描述,而是向您介绍LabVIEW中集成的ActiveX / COM服务器的非标准用法,以组织对VI的Web访问以及控制LabVIEW环境本身。尽管ActiveX / COM已经是可以在Windows上继续使用的旧技术,但是通过集成的ActiveX服务器,您可以轻松地管理LabVIEW和VI设备,包括通过Web。

首先要做的是在LabVIEW中包含相同的ActiveX服务器,这是在以下环境设置中完成的:工具->选项-> VI服务器,ActiveX复选框。


您可以使用VBScript中的简单脚本来验证服务器是否已打开并可以访问它。您需要在桌面上创建文本文件labview_test.vbs,并用以下内容填充它:

Dim obj
Set obj = CreateObject("LabVIEW.Application")
'Dim vi
'Set vi = obj.GetVIReference("C:\Users\Dell\Desktop\LabVIEW Web ActiveX\ActiveX Server Executable _LV2012_NI Verified\Executable as ActiveX Server\ActiveX Server.vi")
WScript.Echo(obj.AppName & " ver: " & obj.Version)
'WScript.Echo(vi.GetControlValue("Count"))
'Set vi = Nothing
Set obj = Nothing

在运行脚本之前,请先运行LabVIEW环境。但是,它无需先启动环境即可工作。在脚本执行过程中,LabVIEW实例将作为ActiveX / COM服务器启动,脚本完成后,实例将被关闭,因此您必须等待所有“加载和卸载”。labview_test.vbs的输出将是根应用程序的名称及其版本。


接下来,我创建了一个简单的VI设备“ ActiveX Server.vi”。它包含几个控件和辅助功能。我们将加载并管理该VI。


在LabVIEW中,我们不需要任何其他功能。现在,您可以从Web层开始。

棘手的道路


首先,我对标准Windows Web服务器Microsoft IIS进行了一些试验。我尝试使用以下内容在VBScript上创建ASP页面:

<% @language = "vbscript" %>
<html><body>
<p>ASP can output HTML tags as well as plain text</p>
<%
	Dim obj
	Set obj = CreateObject("LabVIEW.Application")
	response.write(obj.AppName & " ver: " & obj.Version & "<br>" & vbCr)
	Dim vi
	Set vi = obj.GetVIReference("C:\Users\Dell\Desktop\LabVIEW Web ActiveX\ActiveX Server Executable _LV2012_NI Verified\Executable as ActiveX Server\ActiveX Server.vi")
	response.write(vi.GetControlValue("Count") & vbCr)
	set vi = Nothing
	set obj = Nothing
%>
</body></html>

GetVIReference()方法将VI加载到内存中并与其建立连接。主要参数:所选VI的绝对路径。

脚本输出到浏览器:

LabVIEW.exe ver: 20.0
123 

没错,我不得不修改IIS应用程序池和匿名用户验证的设置,这些设置是我为当前Windows用户配置的。

我决定不深入使用ASP并改用PHP。对于IIS,我配置了PHP FastCGI守护程序。我没有给您这些设置;这些设置对本文的主要部分而言并不重要。PHP还设法通过以下类型访问LabVIEW COM对象:

$obj = new COM('LabVIEW.Application');

在这两种情况下,当LabVIEW都在运行且在其中打开VI时(ActiveX Server.vi)。当请求一个PHP(ASP)脚本时,将并行启动LabVIEW.exe的新实例(并花费相当长的时间),然后使用GetVIReference()方法加载其自己的ActiveX Server.vi实例。 PHP脚本完成后,LabVIEW实例关闭。那些。与已经在运行的LabVIEW环境实例没有交集。使用众所周知的Process Explorer实用程序,可以很好地观察到这一点。带有IIS应用程序池设置的“游戏”也没有给出特殊的结果。就我自己而言,我得出的结论是IIS代表系统充当系统守护进程,因此创建了一个单独的LabVIEW.exe实例,该实例绑定到系统上下文,并且我无法以Windows用户的身份重用已经打开的实例。

然后,出现了尝试以当前用户的名义尝试在简单应用程序模式下运行的第三方Web服务器的想法。选择权在于NGINX,此外,我已经将其用作LabVIEW WebServices的反向代理。

Nginx的


我们采用适用于Windows的nginx的当前版本。目前为nginx-1.17.10。要将PHP与NGINX连接,我使用了以下描述

简单的最小NGINX设置。我有一个文件:c:\ nginx-1.17.10 \ conf \ nginx.conf
将根目录列表添加到浏览器:

nginx.conf:

location / {
	root   html;
	index  index.html index.htm;
	autoindex on;
}

在服务器根目录中通过FastCGI启用PHP:

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
location ~ \.php$ {
	root           html;
	fastcgi_pass   127.0.0.1:9000;
	fastcgi_index  index.php;
	fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
	include        fastcgi_params;
}

p


我们采用Windows的最新版本的PHP 。我使用了php-7.4.5-nts-Win32-vc15-x64.zip,我将它放在c中:\ php-7.4.5-nts-Win32-vc15-x64

重命名并配置php.ini(即php。档案中的ini-development)。进行以下更改:

php.ini:

short_open_tag = On
html_errors = On
error_reporting = E_ALL & ~E_NOTICE
extension_dir = "ext"
extension=gd2
extension=php_com_dotnet.dll

此处连接了用于处理图像的GD库(如果需要从LabVIEW获取一些图像)和用于在PHP中使用ActiveX / COM对象的php_com_dotnet.dll模块已连接。

在PHP中使用COM时,在正文中使用包含0x0字符的字符串(VT_BSTR)时发现了一个令人不愉快的错误。通过使用修复程序重新编译替换php_com_dotnet.dll可以解决此问题。可以在此处找到错误说明和修补程序补丁。不幸的是,它仍未在PHP中正式修复。我重建了php_com_dotnet.dll(适用于php-7.4.5-nts-Win32-vc15-x64),可以在链接中找到固定的php_com_dotnet.dll 。可以在此处找到自构建PHP和扩展的指南

默认情况下,NGINX将在TCP端口80(端口9000的PHP FastCGI守护程序)上运行,请检查是否没有其他正在使用这些端口的应用程序。

启动和停止NGINX和PHP FastCGI守护程序可以通过多种方式进行组织。为了满足我的调试需求,这些cmd脚本已成形:在后台启动/重新启动(没有打开恶魔窗口)start-restart-all.cmd和停止kill-all.cmd,这些命令已放置在NGINX目录中。使用的“运行隐藏控制台”实用程序,取自描述

start-restart-all.cmd:

rem @echo off
set PHP_FCGI_MAX_REQUESTS=0
@echo Shutting down servers...
taskkill /f /IM nginx.exe
taskkill /f /IM php-cgi.exe
@timeout 1
@echo Starting servers...
@rem start /b /D "C:\php-7.4.5-nts-Win32-vc15-x64" php-cgi.exe -b 127.0.0.1:9000
RunHiddenConsole.exe "C:\php-7.4.5-nts-Win32-vc15-x64\php-cgi.exe" -b 127.0.0.1:9000
start /b /D "c:\nginx-1.17.10\" nginx.exe
@timeout 3

kill-all.cmd:

taskkill /f /IM nginx.exe
taskkill /f /IM php-cgi.exe
pause

我要注意环境变量PHP_FCGI_MAX_REQUESTS。默认情况下,它是500。在请求500之后,PHP FastCGI守护程序将完成其工作,因此我禁用了此计数器进行调试。这是来自文档的引言,以进行反思:

可以通过将PHP_FCGI_MAX_REQUESTS设置为0来禁用此PHP行为,但是如果PHP应用程序泄漏资源,则可能会出现问题。或者,可以将PHP_FCGI_MAX_REQUESTS设置为比默认值高得多的值,以减少出现此问题的频率。

我编写了2个PHP测试脚本labview.php,labview_png.php,这些脚本必须放在Web服务器C的根目录中:\ nginx-1.17.10 \ html labview.php-
这是主要的示例脚本
labview_png.php-从从LabVIEW ActiveX服务器读取的VT_BSTR类型的字符串返回PNG图像。

labview.php
<?php
if(strpos(exec('tasklist /FI "IMAGENAME eq LabVIEW.exe" /NH'), 'LabVIEW.exe') === false)
	exit("  LabVIEW.exe");?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>LabVIEW PHP COM example</title>
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
	<script>
		// setTimeout(function(){
		//	window.location.reload(1);
		// }, 3000);
		// setInterval(function() {
		//	var myImageElement = document.getElementById('myImage');
		// 	myImageElement.src = 'labview_png.php?rand=' + Math.random();
		//}, 200);
	 
		$(document).ready(function(){
			setInterval(function(){
				$("#png").attr('src', 'labview_png.php?rand=' + Math.random());
				$("#auto").load(location.href + " #auto");
			}, 1000);
		});
</script>
	
</head> 
<body>
<?php
//phpinfo();
echo '_GET val: ';
foreach ($_GET as $key => $value)
	echo "$key=$value, ";
echo '<br>', PHP_EOL;

echo '_POST val: ';
foreach ($_POST as $key => $value)
	echo "$key=$value, ";
echo '<br>', PHP_EOL;

define('FPStateInfo', ['Invalid', 'Standard', 'Closed', 'Hidden', 'Minimized', 'Maximized']);
define('ExecStateInfo', ['eBad 0 VI has errors; it cannot run', 'eIdle 1 VI is not running, but the VI is in memory.', 'eRunTopLevel 2 VI is running as a top-level VI', 'eRunning 3 VI is running as a subV']);

$obj = new COM('LabVIEW.Application');
//com_print_typeinfo($obj);

$vi = $obj->GetVIReference('C:\Users\Dell\Desktop\LabVIEW Web ActiveX\ActiveX Server Executable _LV2012_NI Verified\Executable as ActiveX Server\ActiveX Server.vi');

//$vi->OpenFrontPanel();

echo '<form action="" method="post">';
echo '<input type="button" value="Refresh page" onClick=\'window.location.href=window.location.href\'>', PHP_EOL;

$fpstate = $vi->FPState();
$vistate = $vi->ExecState();

if ($_POST['action']==='run_vi' && $vistate <= 1) {
	$vi->Run(true); // async Boolean If TRUE, you do not need to wait for the VI to finish running. The default is FALSE.
} elseif ($_POST['action']==='stop_vi' && $vistate > 1) {
	//$vi->SetControlValue('stop', true);
	//sleep(1);
	$vi->Abort();
} elseif ($_POST['action']==='open_fp' && $fpstate==2) {
	$vi->OpenFrontPanel();
} elseif ($_POST['action']==='close_fp' && $fpstate!=2) {
	$vi->CloseFrontPanel();
}

if ($_POST['Count2']) {
	$vi->SetControlValue('Count2', $_POST['Count2']);
}

echo '<h3>SetControlValue(\'Count2\'):</h3>', PHP_EOL;
echo '<input onchange="this.form.submit()" type="number" name="Count2" value="', $vi->GetControlValue('Count2'), '">', PHP_EOL;

echo '<div id="auto">';

echo '<h3>AppName / Version:</h3>', PHP_EOL;
echo $obj->AppName(), ' / ', $obj->Version(), '<br>', PHP_EOL;

echo '<h3>ExportedVIs:</h3>', PHP_EOL;
foreach ($obj->ExportedVIs() as $value)
	echo $value, '<br>', PHP_EOL;

echo '<h3>FPState:</h3>', PHP_EOL;
$fpstate = $vi->FPState();
echo $fpstate, ', ', FPStateInfo[$fpstate], PHP_EOL;

echo '<button name="action" type="submit" value="open_fp">OpenFrontPanel</button>', PHP_EOL;
echo '<button name="action" type="submit" value="close_fp">CloseFrontPanel</button>', PHP_EOL;

echo '<h3>ExecState:</h3>', PHP_EOL;
$vistate = $vi->ExecState();

if ($vistate > 1) {
	echo '<font color="blue">', $vistate, ', ', ExecStateInfo[$vistate], '</font>', PHP_EOL;
} else {
	echo $vistate, ', ', ExecStateInfo[$vistate], PHP_EOL;
}

echo '<button name="action" type="submit" value="run_vi">Run VI</button>', PHP_EOL;
echo '<button name="action" type="submit" value="stop_vi">Abort VI</button>', PHP_EOL;
echo '</form>', PHP_EOL;

echo '<h3>GetControlValue(\'Count\') / GetControlValue(\'Count2\'):</h3>', PHP_EOL;
echo $vi->GetControlValue('Count'), ' / ', $vi->GetControlValue('Count2'), PHP_EOL;
//echo $vi->SetControlValue('Count2', $vi->GetControlValue('Count')+1), PHP_EOL;

echo '<h3>Array1:</h3>', PHP_EOL;
foreach ($vi->GetControlValue('Array1') as $value)
	echo $value, '<br>', PHP_EOL;

//$png_data = new variant(null, VT_UI1);
//$png_data = variant_set_type($vi->GetControlValue('png data'), VT_UI1);
//echo variant_cast($vi->GetControlValue('png1'), VT_BSTR), PHP_EOL;
//echo mb_strlen($vi->GetControlValue('String1')), PHP_EOL;
//echo variant_get_type($vi->GetControlValue('png1')), PHP_EOL;

echo '<h3>PNG data:</h3>', PHP_EOL;
$png_data = $vi->GetControlValue('PNG data');
echo 'PNG size:' , strlen($png_data), '<br>', PHP_EOL;


echo '</div>';

if ($vistate > 1 && $fpstate!=2) {
	echo '<img src="labview_png.php" id="png">';
}

// variant_set_type($variant, VT_BSTR)
//$png_data = variant_cast($vi->GetControlValue('png data'), VT_U1);


//echo  variant_get_type($png_data), PHP_EOL;
echo $vi->SetControlValue('String1', "123\x00555321");
//com_print_typeinfo($vi);
$obj = null;
?>
</body>
</html>


labview_png.php
<?php
if(strpos(exec('tasklist /FI "IMAGENAME eq LabVIEW.exe" /NH'), 'LabVIEW.exe') === false)
	exit("  LabVIEW.exe");
$obj = new COM('LabVIEW.Application');
$vi = $obj->GetVIReference('C:\Users\Dell\Desktop\LabVIEW Web ActiveX\ActiveX Server Executable _LV2012_NI Verified\Executable as ActiveX Server\ActiveX Server.vi');

$data = $vi->GetControlValue('PNG data');

$im = imagecreatefromstring($data);
if ($im !== false) {
    header('Content-Type: image/png');
    imagepng($im);
    imagedestroy($im);
}
else {
    echo ' .';
}
$obj = null;
?>


最好在LabVIEW环境运行时执行脚本,在这种情况下,脚本将重用已经打开的LabVIEW实例。而不是每个脚本调用都创建和关闭COM实例。我的脚本使用了一些AJAX和“重新启动”,并且不重复使用LabVIEW将导致labview.exe的顺序启动和终止的“乌龟马拉松”。

影片评论:


附录。使用HTTP Basic访问身份验证将NGINX配置为反向代理,以与WebServices LabVIEW一起使用


不久前,我对WebServices LabVIEW进行了一些实验(在相当老的LabVIEW版本上实话实说)。然后,我发现页面(WebServices资源)没有简单的访问控制。建议在Application Server中配置用户并使用“失效” Microsoft Silverlight。我需要一些简单的选项,例如HTTP Basic访问身份验证密码验证。

我使用NGINX并将其配置为启用了auth_basic的反向Web代理。使用以下设置,在输入密码后访问地址http:// server_name:5500时,用户可以访问运行在http://127.0.0.1:8001/webservice1/的WebService应用程序。
webservice1应用程序的所有资源均受保护。

nginx.conf:

server {
    listen       5500;
    server_name  localhost;
    location / {
        auth_basic "Unauthorized";
        auth_basic_user_file htpasswd;
        root html;
        #autoindex on;
        #index index.html index.htm;
        proxy_pass http://127.0.0.1:8001/webservice1/;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }
}

以及带有用户密码的htpasswd文件:

admin:{PLAIN}1

进一步发展这个想法,您可以启用通过HTTPS对NGINX代理的访问,并将HTTP从NGINX保留到LabVIEW。

All Articles