Easy Web Access to LabVIEW VI PHP Applications via ActiveX Server

For many years, LabVIEW has been able to “tie” the Web to VI devices without any complicated publishing and server settings from LabVIEW, using only the built-in ActiveX server. LabVIEW 2020 Community edition is no exception.

For LabVIEW at the moment there are several ways to publish virtual devices on the Web, requiring different levels of knowledge and providing different capabilities. In this article I’m not going to describe them, but I want to introduce you to the non-standard use of the ActiveX / COM server integrated in LabVIEW to organize Web access to VI, as well as to control the LabVIEW environment itself. Although ActiveX / COM is already old, but still continuing to live on Windows technology, it is through the integrated ActiveX server that you can easily manage LabVIEW and VI devices, including via the Web.

The first thing to do is to include this same ActiveX server in LabVIEW, this is done in the environment settings: Tools-> Options-> VI Server, the ActiveX checkbox.


You can verify that the server is turned on and has access to it, using a simple script in VBScript. You need to create the text file labview_test.vbs on the desktop and fill it with the following contents:

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

Before running the script, run the LabVIEW environment. However, it will work without first launching the environment. During the execution of the script, the LabVIEW instance will be launched as an ActiveX / COM server, and upon completion of the script the instance will be closed, so you will have to wait until all this "loads and unloads". The output of labview_test.vbs will be the name of the root application and its version.


Next, I created a simple VI device “ActiveX Server.vi”. It contains several controls and auxiliary functions. This VI we will load and manage it.


From LabVIEW we do not need anything else. Now you can start with the Web layers.

Thorny path


At first I experimented a bit with the standard Windows Web server Microsoft IIS. I tried creating ASP pages on VBScript with the following contents:

<% @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>

The GetVIReference () method loads the VI into memory and establishes a connection with it. Main parameter: absolute path to the selected VI.

Script output to browser:

LabVIEW.exe ver: 20.0
123 

True, I had to tinker a bit with the settings of the IIS application pool and anonymous user verification, which I configured for the current Windows user.

I decided not to go deep into ASP and switched to PHP. For IIS, I configured the PHP FastCGI daemon. I don’t give you the settings; they are not important for the main part of this article. PHP also managed to access the LabVIEW COM object by type:

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

In both cases, when LabVIEW is running and VI is open in it (ActiveX Server.vi). When requesting a PHP (ASP) script, a new instance of LabVIEW.exe was launched in parallel (and for a considerable amount of time), then using the GetVIReference () method, it loaded its own instance of ActiveX Server.vi. Upon completion of the PHP script, the LabVIEW instance was closed. Those. there was no intersection with an already running instance of the LabVIEW environment. Using the well-known Process Explorer utility, this is well observed. The "game" with the settings of the IIS application pool also did not give a special result. For myself, I concluded that IIS works as a system daemon on behalf of system, and therefore a separate instance of LabVIEW.exe is created that is bound to the system context, and I cannot get to reuse an already open instance as a Windows user.

Then the idea arose to try a third-party web server running in the simple application mode on behalf of the current user. The choice fell on NGINX, moreover, I already used it as a reverse proxy for LabVIEW WebServices.

Nginx


We take the available current version of nginx for Windows. Currently nginx-1.17.10. To connect PHP with NGINX, I used the following description .

Simple minimal NGINX setup. I have a file: c: \ nginx-1.17.10 \ conf \ nginx.conf
Adding the root directory listing to the browser:

nginx.conf:

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

Enabling PHP via FastCGI in the server root:

# 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;
}

Php


We take the current version of PHP for Windows . I used php-7.4.5-nts-Win32-vc15-x64.zip, I will have it located in c: \ php-7.4.5-nts-Win32-vc15-x64

Rename and configure php.ini (which is php. ini-development from the archive). Make the following changes:

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

Here the GD library for working with images is connected (if you need to get some images from LabVIEW) and the php_com_dotnet.dll module for working with ActiveX / COM objects in PHP.

While working with COM in PHP, an unpleasant bug was discovered when working with strings (VT_BSTR) containing 0x0 characters in the body. It is solved by replacing php_com_dotnet.dll with recompiled with the fix. The bug description and patch with the fix can be found here . Unfortunately, it is still not officially fixed in PHP. I rebuilt php_com_dotnet.dll (for php-7.4.5-nts-Win32-vc15-x64), the fixed php_com_dotnet.dll can be found at the link . A guide for self-building PHP and extensions can be found here .

By default, NGINX will be running on TCP port 80, the PHP FastCGI daemon on port 9000, check that there are no other working applications using these ports.

Starting and stopping the NGINX and PHP FastCGI daemons can be organized in many ways. These cmd scripts took shape for my debugging needs: start-restart-all.cmd that starts / restarts in the background (without demon windows open) and stops kill-all.cmd that I put in the NGINX directory. Used Run Hidden Console utility, taken from the description .

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

I want to pay attention to the environment variable PHP_FCGI_MAX_REQUESTS. By default, it is 500. And after 500 requests, the PHP FastCGI daemon will complete its work, so I disabled this counter for debugging. Here is a quote from the documentation for reflection:

This PHP behavior can be disabled by setting PHP_FCGI_MAX_REQUESTS to 0, but that can be a problem if the PHP application leaks resources. Alternatively, PHP_FCGI_MAX_REQUESTS can be set to a much higher value than the default to reduce the frequency of this problem.

I wrote 2 PHP test scripts labview.php, labview_png.php, which must be placed in the root of the web server C: \ nginx-1.17.10 \ html
labview.php - this is the main example script
labview_png.php - returns a PNG image from a string of type VT_BSTR read from the LabVIEW ActiveX server.

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;
?>


It is better to execute scripts when the LabVIEW environment is running, in which case the scripts will reuse an already open instance of LabVIEW. Instead of creating and closing a COM instance with every script call. My script uses a little AJAX and "restart", and not reusing LabVIEW will result in a "tortoise marathon" of sequential starts and terminations of labview.exe.

Video review:


Appendix. Configuring NGINX as a reverse proxy with HTTP Basic access authentication to work with WebServices LabVIEW


Some time ago I experimented a bit with WebServices LabVIEW (to tell the truth on a rather old version of LabVIEW). Then I discovered that pages (WebServices resources) have no simple access control. It was proposed to configure users in the Application Server and use the "dead" Microsoft Silverlight. And I needed some simple option, such as HTTP Basic access authentication password verification.

I used NGINX and configured it as a reverse web proxy with auth_basic enabled. Using the settings below, when accessing the address http: // server_name: 5500 after entering the password, the user gets access to the WebService application running at http://127.0.0.1:8001/webservice1/.
All resources of the webservice1 application are protected.

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;
    }
}

and the htpasswd file with user passwords:

admin:{PLAIN}1

Further developing this idea, you can enable access to the NGINX proxy via HTTPS, and leave HTTP from NGINX to LabVIEW.

All Articles