В LabVIEW уже много лет существует возможность «прикрутить» Web к VI приборам без каких-либо сложных настроек публикации и серверов со стороны LabVIEW, используя только втроенный сервер ActiveX. Не является исключением и LabVIEW 2020 Community edition.
Для LabVIEW на данный момент момент существует несколько способов публикации виртуальных приборов в Web, требующих разного уровня знаний и предоставляющих разные возможности. В этой статье я не собираюсь их описывать, но хочу познакомить вас с нестандартным использованием встроенного в LabVIEW сервера ActiveX/COM для организации Web доступа к VI, а также управления самой средой LabVIEW. Хотя ActiveX/COM уже старая, но еще продолжающая жить в Windows технология, но именно через встроенный ActiveX сервер можно легко организовать управление LabVIEW и VI приборами, в том числе через Web.
Первое что нужно сделать, это включить в LabVIEW этот самый ActiveX сервер, делается это в настройках среды: Tools->Options->VI Server, флажок ActiveX.

Проверить, что сервер включен и к нему есть доступ, можно простым скриптом на VBScript. Нужно создать на рабочем столе текстовой файл labview_test.vbs и наполнить его следующим содержимым:
Перед выполнением скрипта запустите среду LabVIEW. Впрочем, будет работать и без предварительного запуска среды. На время выполнения скрипта будет запущен экземпляр LabVIEW как ActiveX/COM сервер, а по завершении скрипта экземпляр будет закрыт, так что придется подождать, пока все это «загрузится и выгрузится». В выводе labview_test.vbs будет имя корневого приложения и его версия.

Далее я создал простой VI прибор «ActiveX Server.vi». В нем содержится несколько контролов и вспомогательных функций. Этот VI мы будем загружать и управлять им.

От LabVIEW нам больше ничего не потребуется. Теперь можно приступать к слоям Web.
Сначала я немного поэкспериментировал с штатным Windows Web-сервером Microsoft IIS. Пробовал создавать страницы ASP на VBScript приблизительно следующего содержания:
Метод GetVIReference() загружает VI в память и устанавливает с ним связь. Основной параметр: абсолютный путь к выбранному VI.
Вывод скрипта в браузер:
Правда, пришлось немного повозиться с настройками пула приложений IIS и анонимной проверки пользователей, которые я настроил на текущего пользователя Windows.
Я решил не углублятся в ASP и переключился на PHP. Для IIS настроил PHP FastCGI демон. Настройки не привожу, они не важны для основной части этой статьи. В PHP также удалось получить доступ к COM объекту LabVIEW, по типу:
В обоих случаях при запущенной среде LabVIEW и открытом в ней VI (ActiveX Server.vi). При запросе PHP (ASP) скрипта параллельно запускался (и значительное время) новый экземпляр LabVIEW.exe, далее в нем c помощью метода GetVIReference() загружался свой экземпляр «ActiveX Server.vi». По завершении работы PHP скрипта экземпляр LabVIEW закрывался. Т.е. тут не было никакого пересечения с уже запущенным экземпляром среды LabVIEW. С помощью известной утилиты Process Explorer это хорошо наблюдается. «Игра» с настройками пула приложений IIS тоже не дала особого результата. Для себя я сделал вывод, что IIS работает как системный демон от имени system, и поэтому создается отдельный экземпляр LabVIEW.exe, привязанный к контексту system, и переиспользование уже открытого экземпляра от имени пользователя Windows мне не удастся получить.
Тогда возникла мысль попробовать сторонний web-сервер, запущенный в простом режиме приложения от имени текущего пользователя. Выбор пал на NGINX, притом я его уже использовал в качестве обратного proxy для LabVIEW WebServices.
Берем доступную текущую версию nginx под Windows. На данный момент nginx-1.17.10. Для связи PHP с NGINX я использовал следующее описание.
Несложная минимальная настройка NGINX. У меня файл: c:\nginx-1.17.10\conf\nginx.conf
Добавление листинга корневого каталога в браузер:
nginx.conf:
Включение PHP через FastCGI в корневом каталоге сервера:
Берем актуальную версию PHP для Windows. Я использовал 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:
Тут подключается библиотека GD для работы с изображениями (если нужно получать какие-то изображения из LabVIEW) и модуль php_com_dotnet.dll для работы с ActiveX/COM объектами в PHP.
В процессе работы с COM в PHP обнаружился неприятный баг при работе со строками (VT_BSTR), содержащими в теле 0x0 символы. Решается он заменой php_com_dotnet.dll на перекомпилированный с исправлением. Описание бага и патч с исправлением можно найти тут. К сожалению, он до сих пор официально не исправлен в PHP. Я пересобрал php_com_dotnet.dll (для php-7.4.5-nts-Win32-vc15-x64), исправленный php_com_dotnet.dll можно найти по ссылке. Руководство для самостоятельной сборки PHP и расширений можно найти тут.
По умолчанию NGINX будет запущен на 80 TCP порту, PHP FastCGI демон на порту 9000, проверьте, что нет других работающий приложений, использующих эти порты.
Запуск и остановку NGINX и PHP FastCGI демона можно организовать разными способами. У меня для нужд отладки оформились вот такие cmd скрипты: запускающий/перезапускающий в фоне (без открытых окон демонов) start-restart-all.cmd и останавливающий kill-all.cmd, которые я положил в каталог NGINX. Используется Run Hidden Console утилита, взятая из описания.
start-restart-all.cmd:
kill-all.cmd:
Хочу обратить внимание на переменную окружения PHP_FCGI_MAX_REQUESTS. По умолчанию она равна 500. И через 500 запросов демон PHP FastCGI завершит свою работу, поэтому у себя для отладки я отключил этот счетчик. Вот цитата из документации для размышления:
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.
Я написал 2 тестовых скрипта PHP labview.php, labview_png.php, которые необходимо разместить в корне web-сервера C:\nginx-1.17.10\html
labview.php — это основной скрипт примера
labview_png.php — возвращает изображение PNG из читаемой из LabVIEW ActiveX сервера строки типа VT_BSTR.
Выполнять скрипты лучше при запущенной среде LabVIEW, в этом случае скрипты будут переиспользовать уже открытый экземпляр LabVIEW. А не создаваться и закрывать COM экземпляр при каждом вызове скрипта. В моем скрипте используется немного AJAX и «перезапуск», а не переиспользование LabVIEW выльется в «черепаший марафон» последовательный запусков и завершений labview.exe.
Видеообзор:
Некоторое время назад я немного экспериментировал с WebServices LabVIEW (по правде сказать на довольно старой версии LabVIEW). Тогда обнаружил, что у страниц (ресурсов WebServices) нет никакого простого разграничения доступа. Предлагалось настраивать пользователей в Application Server и использовать «мертвый» Microsoft Silverlight. А мне нужен был какой-нибудь простой вариант, типа проверки пароля HTTP Basic access authentication.
Я воспользовался NGINX и настроил его в качестве обратного web proxy c включенной проверкой auth_basic. Используя приведенные ниже настройки, при обращении на адрес http://server_name:5500 после ввода пароля пользователь получает доступ к WebService приложению, работающему по адресу http://127.0.0.1:8001/webservice1/.
Защищаются все ресурсы приложения webservice1.
nginx.conf:
и файл htpasswd с паролями пользователей:
Развивая эту мысль дальше, можно включить доступ к proxy NGINX по HTTPS, а от NGINX к LabVIEW оставить HTTP.
Для LabVIEW на данный момент момент существует несколько способов публикации виртуальных приборов в Web, требующих разного уровня знаний и предоставляющих разные возможности. В этой статье я не собираюсь их описывать, но хочу познакомить вас с нестандартным использованием встроенного в LabVIEW сервера ActiveX/COM для организации Web доступа к VI, а также управления самой средой LabVIEW. Хотя ActiveX/COM уже старая, но еще продолжающая жить в Windows технология, но именно через встроенный ActiveX сервер можно легко организовать управление LabVIEW и VI приборами, в том числе через Web.
Первое что нужно сделать, это включить в LabVIEW этот самый ActiveX сервер, делается это в настройках среды: Tools->Options->VI Server, флажок 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. Пробовал создавать страницы ASP на VBScript приблизительно следующего содержания:
<% @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 также удалось получить доступ к COM объекту LabVIEW, по типу:
$obj = new COM('LabVIEW.Application');
В обоих случаях при запущенной среде LabVIEW и открытом в ней VI (ActiveX Server.vi). При запросе PHP (ASP) скрипта параллельно запускался (и значительное время) новый экземпляр LabVIEW.exe, далее в нем c помощью метода GetVIReference() загружался свой экземпляр «ActiveX Server.vi». По завершении работы PHP скрипта экземпляр LabVIEW закрывался. Т.е. тут не было никакого пересечения с уже запущенным экземпляром среды LabVIEW. С помощью известной утилиты Process Explorer это хорошо наблюдается. «Игра» с настройками пула приложений IIS тоже не дала особого результата. Для себя я сделал вывод, что IIS работает как системный демон от имени system, и поэтому создается отдельный экземпляр LabVIEW.exe, привязанный к контексту system, и переиспользование уже открытого экземпляра от имени пользователя Windows мне не удастся получить.
Тогда возникла мысль попробовать сторонний web-сервер, запущенный в простом режиме приложения от имени текущего пользователя. Выбор пал на NGINX, притом я его уже использовал в качестве обратного proxy для LabVIEW WebServices.
NGINX
Берем доступную текущую версию nginx под Windows. На данный момент 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; }
Включение PHP через FastCGI в корневом каталоге сервера:
# 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
Берем актуальную версию PHP для Windows. Я использовал 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_com_dotnet.dll для работы с ActiveX/COM объектами в PHP.
В процессе работы с COM в PHP обнаружился неприятный баг при работе со строками (VT_BSTR), содержащими в теле 0x0 символы. Решается он заменой php_com_dotnet.dll на перекомпилированный с исправлением. Описание бага и патч с исправлением можно найти тут. К сожалению, он до сих пор официально не исправлен в PHP. Я пересобрал php_com_dotnet.dll (для php-7.4.5-nts-Win32-vc15-x64), исправленный php_com_dotnet.dll можно найти по ссылке. Руководство для самостоятельной сборки PHP и расширений можно найти тут.
По умолчанию NGINX будет запущен на 80 TCP порту, PHP FastCGI демон на порту 9000, проверьте, что нет других работающий приложений, использующих эти порты.
Запуск и остановку NGINX и PHP FastCGI демона можно организовать разными способами. У меня для нужд отладки оформились вот такие cmd скрипты: запускающий/перезапускающий в фоне (без открытых окон демонов) start-restart-all.cmd и останавливающий kill-all.cmd, которые я положил в каталог NGINX. Используется Run Hidden Console утилита, взятая из описания.
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 завершит свою работу, поэтому у себя для отладки я отключил этот счетчик. Вот цитата из документации для размышления:
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.
Я написал 2 тестовых скрипта PHP labview.php, labview_png.php, которые необходимо разместить в корне web-сервера C:\nginx-1.17.10\html
labview.php — это основной скрипт примера
labview_png.php — возвращает изображение PNG из читаемой из LabVIEW ActiveX сервера строки типа VT_BSTR.
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.
Видеообзор:
Appendix. Конфигурация NGINX в качестве обратного proxy с HTTP Basic access authentication для работы с WebServices LabVIEW
Некоторое время назад я немного экспериментировал с WebServices LabVIEW (по правде сказать на довольно старой версии LabVIEW). Тогда обнаружил, что у страниц (ресурсов WebServices) нет никакого простого разграничения доступа. Предлагалось настраивать пользователей в Application Server и использовать «мертвый» Microsoft Silverlight. А мне нужен был какой-нибудь простой вариант, типа проверки пароля HTTP Basic access authentication.
Я воспользовался NGINX и настроил его в качестве обратного web proxy c включенной проверкой auth_basic. Используя приведенные ниже настройки, при обращении на адрес http://server_name:5500 после ввода пароля пользователь получает доступ к WebService приложению, работающему по адресу http://127.0.0.1:8001/webservice1/.
Защищаются все ресурсы приложения 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
Развивая эту мысль дальше, можно включить доступ к proxy NGINX по HTTPS, а от NGINX к LabVIEW оставить HTTP.
