Вызов динамически подключаемых библиотек в среде программирования LabVIEW
Автор: National Instruments
Источник: http://digital.ni.com/
В LabVIEW можно вызывать и создавать процедуры внешнего кода, так называемые динамические библиотеки (DLL), и интегрировать эти процедуры в исполняемые программы. Для присоединения обычного текстового программного кода к виртуальному прибору в LabVIEW присутствует специальная структура, называемая Code Interface Node (CIN, Узел кодового интерфейса). LabVIEW вызывает исполняемый код во время работы узла, передаёт в него входные данные и возвращает данные после исполнения кода обратно в блок-диаграмму. Если вы работаете в Windows, то вы также можете использовать функцию Call Library Function (Вызов библиотечной функции). Из виртуального прибора, созданного в LabVIEW, также можно скомпилировать свою динамическую библиотеку, которую могут использовать другие приложения и системы программирования (например, C++).
Большинство приложений не требуют использования CIN или DLL, т.к. компилятор LabVIEW обычно создаёт достаточно быстрый код. Но при решении критичных по времени задач, в которых обрабатываются большие объёмы данных, CIN и DLL являются полезными инструментами.
Использовать CIN и DLL также есть смысл, если у вас уже много кода, разработанного на традиционных языках программирования. Они также нужны для решения задач, например, по выполнению системных утилит, для которых в LabVIEW не предусмотрено функций. В этой статье рассказано о том, как осуществить вызов DLL в LabVIEW.
Узел Call Library Function
Для вызова DLL в LabVIEW используется узел Call Library Function, расположенный в Functions>>Advanced. Чтобы сконфигурировать узел Call Library Function, щёлкните по нему правой кнопкой мышки и выберите опцию Configure из контекстного меню. В появившемся диалоговом окне(см. рис. 1) необходимо указать нужную библиотеку, вызываемую функцию, её параметры, а также соглашения вызова.
Многопоточный режим
В многозадачных ОС вы можете делать несколько вызовов одной DLL одновременно. По умолчанию, все вызываемые узлом Call Library Function объекты работают в потоке пользовательского интерфейса (UI Thread), при этом сам узел имеет оранжевый цвет. В окне настройки Call Library Function можно выбрать повторно используемый (Reentrant) режим работы. Когда вы вызываете DLL в режиме Reentrant, иконка узла становится жёлтой. При этом вы должны быть уверены, что библиотека будет безошибочно работать при вызове из двух или более отдельных потоков.
Соглашения вызова
Соглашения вызова, которые устанавливаются в пункте Calling Conventions, определяют, каким образом данные передаются в функцию. Вы можете использовать стандартные соглашения вызова _stdcall или соглашения языка С. В Win32 API используются соглашения _stdcall, а в большинстве написанных пользователями DLL – соглашения языка С. Неверный выбор соглашений приведёт к ошибкам при вызове библиотечной функции.
Возвращаемое функцией значение
Для передачи возвращаемого функцией значения предназначена верхняя пара терминалов узла Call Library Function. Если функция ничего не возвращает, в пункте Type окна настройки установите Void, при этом верхняя пара терминалов использоваться не будет. Даже если вызываемая функция возвращает какое-либо значение, вы можете использовать тип Void, при этом возвращаемая величина будет проигнорирована. Можно также задать числовой (Numeric) или строковый (String) типы возвращаемых значений. Если типа данных, возвращаемых вышей функцией, нет в списке, выбирайте равный ему по размеру. Например, если функция возвращает тип char, используйте 8-битное беззнаковое целое.
Параметры функции
Если у вызываемой функции есть параметры, вы должны добавить их, используя кнопки Add Parameter After и Add Parameter Before. Для каждого параметра нужно указать его имя, тип и способ передачи (по значению – by value или по ссылке – by reference). Вы можете выбрать следующие типы параметров:
- Numeric (численный)
- Array (массив)
- String (строка)
- Waveform (сигнальный тип данных)
- Digital Waveform (цифровой сигнальный тип данных)
- Digital Table (цифровая таблица)
- ActiveX
- Adapt to Type (адаптировать к типу данных)
Численный тип данных
Вы должны точно указать, какой численный тип данных вы хотите использовать, выбрав его из перечисленных ниже:
- 8-, 16- или 32-битные целые со знаком и без знака
- 4-байтовые числа одинарной точности
- 8 –байтовые числа двойной точности
После этого в пункте Pass укажите, хотите вы передавать саму величину или указатель на неё.
Массив
Вы должны определить тип данных элементов массива (те же что и для числовых величин), количество его измерений и способ передачи массива. Используйте пункт Array Format для выбора способа передачи массива: через указатель на массив (Array Data Pointer), дескриптор массива (Array Handle) или указатель на дескриптор массива (Array Handle Pointer).
Указатель на массив (Array Data Pointer) обладает следующими свойствами:
- Вы можете задавать число измерений в массиве, однако информацию о количестве элементов в каждом измерении вы должны передавать в DLL как отдельную переменную.
- Никогда не изменяйте размеры массива и не производите операции, которые могут привести к изменению размера массива, поступающего из LabVIEW. Это может привести к сбоям, так как указатель будет смотреть не в выделенный блок памяти.
- Для получения массива данных вы должны выделить в LabVIEW массив достаточного размера и передать этот массив своей функции (см. рис 2, 3). Этот массив будет действовать в качестве буфера. Если данные занимают меньше места, вы сможете вернуть действительный размер в виде отдельного параметра, а затем, используя на блок-диаграмме функцию Array Subset, извлечь массив нужной величины.
После этого в пункте Pass укажите, хотите вы передавать саму величину или указатель на неё.
Помните, что Windows API не использует дескриптор (Array Handle), поэтому с функциями Windows API вы должны использовать только указатель (Array Data Pointer).
При передаче массива с помощью указателя на дескриптор (Array Handle Pointer) нет необходимости вводить отдельный параметр для передачи размера массива (см. рис 4, 5).
Если вы передаёте массив с помощью дескриптора (Array Handle), вы можете использовать функции CIN (Code Interface Node) для изменения его размеров. Чтобы вызывать функции CIN, ваш компилятор должен подключать необходимый файл, расположенный в директории LabVIEW 7.0\cintools:
Компилятор | Файл библиотеки LabVIEW |
Code Warrior | labview.export.stub |
Symantec | labview.sym.lib |
Visual C++ | labview.lib |
Строковый тип
Тип строкового указателя (String Pointer) должен соответствовать тому, который использует ваша функция, иначе появится ошибка. Узел Call Library Function может использовать следующие типы:
C String Pointer – это указатель на строку, с нулевым символом в конце. Этот тип используются в большинстве приложений Win32 API. | т е к с т             \\00 Строка данных Нулевой символ |
Pascal String Pointer – указатель, в начале которого расположен байт с информацией о длине. | \05      т е к с т Длина Строка данных |
LabVIEW String Handle – это дескриптор строки, в начале которого расположены четыре байта с информацией о длине строки. LabVIEW String Handle Pointer – это указатель на дескриптор строки, предваряемый четырьмя байтами информации о длине. | \00 \00 \00 \05      т е к с т Длина строки    Строка данных |
Вы можете представить строку как массив символов. Располагая эти символы в определённом порядке, вы формируете строку. LabVIEW сохраняет строку в специальном формате, в котором первые четыре байта массива символов формируют 32-битное знаковое целое, определяющее количество символов в строке. Таким образом, для сохранения строки из n символов требуется n+4 байта. Преимущество такого типа строковых данных заключается в том, что нулевой символ предшествует строке, то есть она практически неограниченна по длине и может содержать до 231 символов. Если вы передаёте дескриптор LabVIEW String Handle из Call Library Function в DLL, вы можете использовать функции CIN, такие как DSSetHandleSize для измерения размера. При этом, как и в случае массива, вы должны добавить в проект необходимые библиотечные файлы (они были ранее указаны в таблице).
Формат строки Pascal близок к типу LabVIEW, однако длина строки сохраняется как 8- битное целое, что ограничивает её длину 256 символами. Строка Pascal длиной n символов занимает n+1 байт памяти. Что касается строки формата C, то её сходство с обычным массивом чисел станет гораздо более очевидным, если вспомнить, что строки в стиле C определяются как char*, где char – это 8-битное беззнаковое целое. В отличие от LabVIEW или Pascal, в самой строке C информация о её длине явно не содержится. Вместо этого в C используется специальный символ, называемый NULL (нулевой) для индикации конца строки. В формате ASCII этот символ соответствует значению «0». Таким образом, строка C длиной n занимает n+1 байт. Преимущество формата C состоит в том, что строка в этом случае ограничена только размерами памяти. Однако, если вы получаете данные от прибора, который передаёт числа в виде двоичных строк (что является общепринятым для подключаемых последовательно или для GPIB-приборов), вполне возможно, что в строке встретится величина ноль. Если Вы используете С-тип строк, программа может принять эту величину за конец строки, в то время как в действительности прибор передал нулевое значение измеренной величины. Поэтому для двоичных данных, которые могут содержать величину NULL предполагается использование массива беззнаковых восьмибитных целых.
Таким образом, при передаче строк необходимо соблюдать следующие правила:
- Никогда не изменяйте размер, не объединяйте и не производите операции, способные увеличить размер строк, передаваемых в LabVIEW, если вы используете указатели С или Pascal.
- Если необходимо вернуть строковые данные, создайте строку достаточной длины и передайте её в DLL, при этом она будет работать как буфер.
Разрешение проблем с узлом вызова библиотечных функций
Если при вызове DLL в LabVIEW возникает сообщение об ошибке, необходимо произвести следующие действия:
- Проверьте, что указан корректный путь к DLL.
- Если в сообщении говорится, что функция не найдена в библиотеке, повторно проверьте написание имени функции, которую вы хотите вызвать. Помните, что названия функций чувствительны к регистру. Удостоверьтесь, что компилятор не декорирует имя функции.
- Если в сообщении сказано, что не найдена вторичная DLL, даже если вы правильно задали путь к основной DLL, то может оказаться, что этой DLL необходимы функции из дополнительной DLL, которая должна быть помещена в ту же директорию, что и основная.
- Если ВП аварийно завершается, убедитесь, что передаёте функции именно те параметры, которые она ожидает (например, int16, а не int32). Убедитесь, что Вы используете верное соглашение вызова.
- Убедитесь, что все параметры передаются корректным образом: как указатель или как значение.
- Если Вы получаете сообщение memory.cpp error, то в большинстве случаев ошибка содержится в коде DLL, например, запись после того, как закончилась выделенная память. Заметьте, что в случае таких ошибок ВП может и не завершаться аварийно.
Решение проблем с DLL
Помните, при декларировании функции необходимо использовать слова _declspec (dllexport) в заголовке исходного кода или задавать её в секции экспорта .def-файла.
Если вы используете _declspec (dllexport), и соглашения вызова _stdcall, вы должны декларировать имя функции в секции экспорта .def-файла. В противном случае _stdcall может сократить его в непредсказуемом виде, и поэтому функция не будет доступна для приложения, вызывающего DLL.
Если функция экспортировалась неправильно, вам придётся перекомпилировать DLL. Перед перекомпиляцией вы должны закрыть все приложения и ВП, которые могут использовать DLL, в противном случае перекомпиляция не состоится, так как DLL будет находиться в памяти.
После проверки правильности имени функции и её экспортирования определите, используете вы компилятор C или C++? При использовании C++ компилятора имена функций могут изменяться в процессе вызова. Самый лёгкий способ скорректировать искажение – это использовать extern”C”:
extern”C”
{
/прототип функции/ }
Попробуйте использовать отладчик компилятора. Также можно попробовать вызвать DLL из другой программы C, что тоже является способом отладки. При этом вы тестируете DLL независимо от LabVIEW, что помогает выявить проблему быстрее. При вызове DLL, которая передаёт двумерный массив, вы должны сначала декларировать handler variable и инициализировать эту переменную нулём, как показано в примере:
main ()
{
/*Переменная дескриптора LabVIEW для массива*/ TD1HD1 myArray=NULL;
.
.
.
/* Вызов LabVIEW DLL функции*/
DLLFunctional (&myArray);
.
.
.
}
Если вы не инициализируете переменную дескриптора нулем, при вызове DLL будет сгенерирована ошибка General Protection Fault.
Автор: National InstrumentsИсточник: http://digital.ni.com/