Секреты CIN: показать всё, что скрыто

ActiveX, .NET, DLL
Ответить
Аватара пользователя
dadreamer

Activity Professionalism Автор
professor
professor
Сообщения: 3926
Зарегистрирован: 17 фев 2013, 16:33
Награды: 4
Версия LabVIEW: 2.5 — 2022
Благодарил (а): 11 раз
Поблагодарили: 127 раз
Контактная информация:

Секреты CIN: показать всё, что скрыто

Сообщение dadreamer »

0. Альтернативный способ запуска CIN.
Всё, что описано ниже, производилось исключительно ради собственного интереса и для восполнения нехватки информации о внутреннем устройстве LabVIEW (NI порой бывают крайне молчаливы).

Решил разобраться более детально, что же такое CIN и с чем его едят, в результате пришлось выполнить небольшое исследование. О его результатах расскажу в данной теме. Для начала напомню, что LabVIEW сам по себе экспортирует множество функций, среди которых есть такие замечательные функции: REdLoadResFile и REdSaveResFile (за их открытие отдельное спасибо Андрею Дмитриеву). С их помощью можно открыть и сохранить любой ресурс LabVIEW (vi, llb, res и т.д.). Если открыть vi, содержащий CIN, с помощью REdLoadResFile, то будет видно, что искомый LSB-файл (модифицированная DLL) содержится внутри vi и имеет тип LVSB.
При запуске программы LabVIEW извлекает его во временную папку (обычно это C:\WINDOWS\Temp), присваивая случайное имя в формате lvs**.tmp, и затем загружает в память с помощью kernel32.dll:LoadLibraryA как обычную DLL. Причем, если открыть в блокноте или HEX-редакторе этот LSB, то в начале файла будут служебные слова RSRC, LVSBLBVW, i386, а в конце – LVSBLBVW, PLAT, LVSB, [имя исходного LSB], говорящие о том, что LSB – ресурс для LabVIEW. Если эти слова убрать из файла (с помощью HEX-редактора или REdSaveResFile), то получим исходную DLL, получаемую при компиляции проекта по созданию CIN’а.
То есть, программа lvsbutil.exe никак не меняет саму библиотеку, а лишь дописывает к ней идентификаторы ресурса LabVIEW. Поэтому не составило труда создать экстрактор LSB и DLL из vi-программ (см. вложение). Но толку от этих DLL немного, если не знать, что с ними делать дальше. Каждая такая DLL экспортирует всего две функции: GetLVSBHeader и SetLVRTModule (некоторые – только первую). Метод тыка мало что дал, пришлось вооружиться ollydbg и посмотреть, как вызываются CIN-функции.
GetLVSBHeader возвращает адрес в памяти, указывающий на т.н. заголовок/начало CIN’а, которое находится вовсе не в начале файла, а ближе к концу, но если отступить вниз от этого адреса на 10h (16d), то попадём на таблицу _gLVExtCodeDispatchTable, содержащую адреса всех функций в данной DLL. Для получения указателя на ту или иную функцию надо добавить к адресу таблицы некоторое смещение, являющееся постоянным. Для стандартных CIN-функций я составил список этих смещений.
  • CINProperties1Ch (28d)
  • CINRunCh (12d)
  • CINSave14h (20d)
  • CINAbort8h (8d)
  • CINLoad10h (16d)
  • CINInit0h (0d)
  • CINDispose4h (4d)
  • CINUnload18h (24d)
Таким образом, из любой DLL, предназначенной для работы в CIN’е, можно вызвать все стандартные обработчики событий и основную функцию CINRun, исполняемую при запуске vi-программы. Если у вас есть старые проекты, содержащие CIN’ы, но нет исходников или нет желания переписывать код на C, то можно превратить все CIN’ы в DLL и вызывать их из LabVIEW. Но только не напрямую, а через обертку (wrapper), получающую по LVSBHeader’у указатель на CINRun и вызывающую эту функцию. Вот код такой обертки на C/C++:

Код: Выделить всё

extern "C"{
int (__cdecl *LVSB)(void *Num1, void *Num2, void *Sum);

__declspec(dllexport) void CallProc(int LVSBHeader, void *Num1, void *Num2, void *Sum)
{
(int &)LVSB = (*(int *)(*(int *)(LVSBHeader+16)+12)); //CINRun
LVSB(Num1, Num2, Sum); //вызов CINRun
}
}
Ниже – аналогичный код в Delphi:

Код: Выделить всё

var
LVSB: function(Num1: pointer; Num2: pointer; Sum: pointer): integer; cdecl;

procedure CallProc(LVSBHeader: integer; Num1: pointer; Num2: pointer; Sum: pointer); cdecl;
var P: ^integer;
begin
P := Ptr(LVSBHeader + 16); //_gLVExtCodeDispatchTable
P := Ptr(P^ + 12);
LVSB := Ptr(P^); //CINRun
LVSB(Num1, Num2, Sum); //вызов CINRun
end;

exports CallProc name 'CallProc';
Далее буду приводить весь код в Delphi, чтобы не загромождать статью. А вот так выглядит вызов CINRun в LabVIEW:
Call_SampleCIN.jpg
Call_SampleCIN.jpg (30.27 КБ) 7135 просмотров
Call_SampleCIN_FP.jpg
Call_SampleCIN_FP.jpg (11.93 КБ) 7135 просмотров
Теперь несколько фраз о CIN’ах, содержащих внешние процедуры (external subroutines), и о том, как вызываются они. Вообще, внешние процедуры представляют собой LSB-файлы, вызываемые из других LSB-файлов, но не загружаемые в Code Interface Node. В ранних версиях LabVIEW (вплоть до 5.0) можно было компилировать такие файлы с помощью lvsbutil.exe, в версии 7.1 утилита lvsbutil уже не поддерживала создание этих файлов (хотя в LabVIEW программы грузились и работали), а начиная с 8-й версии LabVIEW не разрешает загружать vi, где есть CIN, вызывающий другую (внешнюю) процедуру. При загрузке такой программы появляется сообщение «The CIN referenced from this VI uses an external subroutine. External subroutines are no longer supported in this version of LabVIEW». Однако всё-таки можно запустить внешние процедуры с помощью метода, предложенного выше. Причем не только основную LSB/DLL, но каждую внешнюю LSB/DLL по отдельности.
Для запуска внешней процедуры, нужно превратить её в DLL с помощью экстрактора LSB (см. вложение), затем получить заголовок через GetLVSBHeader, получить указатель на таблицу _gLVExtCodeDispatchTable, и наконец получить указатель на LVSBMain, т.е. основную (и единственную) функцию, аналог CINRun в обычных LSB. В качестве примера я взял программу из мануала Code Interface Reference Manual, содержащую коды двух файлов: calcmean.lsb и sum.lsb. Сама программа находит среднее значение элементов в массиве: sum.lsb суммирует все элементы, а calcmean.lsb делит сумму на количество элементов. sum.lsb является внешней процедурой, чтоб её вызвать, я сделал вот такую обертку:

Код: Выделить всё

var
Sum: function(xArray: pointer; ArrSize: integer): double; cdecl;

procedure CallProc(SumHeader: integer; xArray: pointer; ArrSize: integer; ArrSum: pointer); cdecl;
var P: ^integer;
T: ^double;
begin
P := Ptr(SumHeader + 16);
P := Ptr(P^ + 0);
Sum := Ptr(P^); //<- LVSBMain
T := ArrSum;
T^ := Sum(xArray, ArrSize);
end;

exports CallProc name 'CallProc';
А в LabVIEW получилась вот такая диаграмма:
Call_Sum.jpg
Call_Sum.jpg (32.36 КБ) 7135 просмотров
Call_Sum_FP.jpg
Call_Sum_FP.jpg (27.88 КБ) 7135 просмотров
Как видно из кода выше, единственное отличие этой обертки от той, что вызывает CINRun, – это смещение относительно таблицы _gLVExtCodeDispatchTable, равное нулю, а не Ch (12d).
Теперь о вызове calcmean.lsb. Если попытаться вызвать его при помощи самой первой обертки, то программа выдаст эксепшн (ошибку) и остановится. То же самое (и даже хуже, порой, крах LabVIEW) происходит при удалении строки LIsb из vi-ресурса, говорящей LabVIEW, что загружаемый CIN ссылается на некоторую внешнюю процедуру sum.lsb и надо бы её найти и загрузить. Но загрузить её недостаточно, нужно ещё разместить ссылку на LVSBMain в теле calcmean, чтобы LabVIEW мог перескочить из одного модуля/DLL в другой. Такой ссылки нет, на её месте ключевое слово LVSB, поэтому программа падает. Приведу сразу код wrapper’а для правильного вызова calcmean, а пояснения дам потом.

Код: Выделить всё

var
CalcMean: function(xArray: pointer; mean: pointer): integer; cdecl;

procedure CallProc(SumHeader: integer; CalcMeanHeader: integer; xArray: pointer; mean: pointer); cdecl;
var P,S,X: ^integer;
begin
P := Ptr(SumHeader + 16);
P := Ptr(P^ + 0); //<- LVSBMain

S := Ptr(CalcMeanHeader + 56); //смещение 0x38
//запись ссылки на LVSBMain в память CalcMean
S^ := Integer(Ptr(P^));

X := Ptr(CalcMeanHeader + 16);
X := Ptr(X^ + 12);
CalcMean := Ptr(X^); //CINRun
CalcMean(xArray, mean);
end;

exports CallProc name 'CallProc';
Программа в LabVIEW:
Call_Calcmean.jpg
Call_Calcmean_FP.jpg
Call_Calcmean_FP.jpg (30.06 КБ) 7135 просмотров
Как видно из кода, сперва мы получаем заголовок sum.lsb, потом находим таблицу, после чего получаем адрес на LVSBMain (всё как в примере выше). После этого отступаем от заголовка calcmean.lsb на 38h (56d) и записываем туда адрес, по которому находится основная функция sum.lsb. Далее стандартным способом (см. первый пример) отыскиваем CINRun и вызываем его. Всё то же самое делает и LabVIEW, причем даже больше, с дополнительными проверками, кучей jmp, call’ов и дополнительных операций.
Немного насчёт числа 38h… В LabVIEW оно определяется весьма сложно (upd: на самом деле нет, см. главу 1 "Создание (компиляция) внешних процедур.", пункт "P.S.", ближе к концу), я решил избавить себя от анализа регистров, прописав его в виде константы. По логике вещей, при стыковке одной процедуры с вызывающей библиотекой смещение всегда будет 38h, при стыковке двух процедур для первой смещение будет 38h, для второй 3Ch, т.е. больше на 4h, и так далее. Эксперименты это подтверждают, правда больше двух процедур я не линковал, да и смысла в этом не особо много. Вообще, все эти смещения можно получить из лога компиляции LSB (см. скриншот ниже) и далее использовать во враппере:
13-03-2013 13-49-04.jpg
Пока что на этом всё, позже добавлю ещё кое какую информацию. Буду рад, если эта статья кому-то пригодилась, или кто-нибудь хотя бы понял, о чём здесь речь :) Давно считается, что CIN'ы - устаревшая технология, если NI выпилят их в следующем релизе LV, будет печально. Тогда, скорее всего, эта статья действительно кому-то понадобится.
Вложения
CIN_tests.rar
примеры
(82.75 КБ) 242 скачивания
LSBExtractor3.vi
новая версия экстрактора - корректно работает с внешними процедурами
LV2011
(27.49 КБ) 188 скачиваний
Последний раз редактировалось dadreamer 26 авг 2016, 14:15, всего редактировалось 3 раза.
Аватара пользователя
Pavel Krivozubov

Activity Bronze
professor
professor
Сообщения: 4421
Зарегистрирован: 07 фев 2008, 16:39
Награды: 3
Версия LabVIEW: 7.0 - 2013
Откуда: г. Электросталь
Благодарил (а): 24 раза
Поблагодарили: 9 раз
Контактная информация:

Re: Альтернативный способ запуска CIN (инструкция)

Сообщение Pavel Krivozubov »

Отличный материал, спасибо!
Только рисунки битые к сожалению*(
можно исправить? тогда поместим в раздел "уроки"
Аватара пользователя
dadreamer

Activity Professionalism Автор
professor
professor
Сообщения: 3926
Зарегистрирован: 17 фев 2013, 16:33
Награды: 4
Версия LabVIEW: 2.5 — 2022
Благодарил (а): 11 раз
Поблагодарили: 127 раз
Контактная информация:

Re: Альтернативный способ запуска CIN (инструкция)

Сообщение dadreamer »

Рад был бы отредактировать, да не могу ( Кнопка редактирования пропала. Жаль, что нельзя размещать больше 5 вложений подряд, и ещё нельзя размещать на внешних источниках, согласно правилам. Пришлось отослать себе несколько ЛС, содержащих залитые картинки. Но, как оказалось, смотреть их можно только мне... Создал альбом на сайте ni.com, надеюсь, оттуда можно постить ссылки? Картинки в этой теме сверху вниз:
Изображение Изображение

Изображение Изображение

Изображение Изображение

Изображение
__________________________________________________________________________________________________________________
Для логического завершения темы публикую новый материал, который вряд ли уже кому-то пригодится, но в качестве исторической справки может быть интересен.

1. Создание (компиляция) внешних процедур.
В этой теме я расписал процесс создания обычного CIN'а и выложил для этого инструменты (cintools) из старых версий :labview: . Однако с помощью них нельзя компилировать внешние процедуры (external subroutines) и CIN'ы, ссылающиеся на них: утилита lvsbutil не поддерживает это, начиная с LV 7.1. Поэтому выкладываю здесь инструменты (cintools) из LV 5.0 с подправленным файлом ntlvsb.mak (добавлен импорт манифеста в компилируемую библиотеку):
cintools__5.rar
(618.79 КБ) 175 скачиваний
Эти инструменты можно использовать также для создания обычных CIN'ов, например, в Microsoft Visual Studio, отличий от cintools версий 7.1 и 8 не так уж много на самом деле (одно из них - в 5-й версии нет экспорта функции SetLVRTModule).
Теперь, собственно, сам процесс создания внешней процедуры на простеньком примере. Возьмём пример из издания LabVIEW Advanced Course, стр. 294-299. Fact.cpp - внешняя процедура, вычисляющая факториал числа n, FactCall.cpp - основной CIN, передающий в Fact число n и получающий факториал этого числа.

Код Fact.cpp:

Код: Выделить всё

#include "extcode.h"

/* prototype */
extern "C" int32 LVSBMain(int32 *n);

/* subroutine */
int32 LVSBMain(int32 *n) {
int32 i, fact;
fact = 1L;
for (i=1; i<*n+1; i++)
fact *= i;
return fact;
}
Код FactCall.cpp:

Код: Выделить всё

#include "extcode.h"

extern "C" {
extern int32 Fact(int32 *n);
CIN MgErr CINRun(int32 *n);
}

CIN MgErr CINRun(int32 *n) {
*n = Fact(n);
/*external call */
return noErr;
}
Допустим, инструменты cintools находятся в папке C:\Program Files (x86)\National Instruments\LabVIEW 2011\cintools__5. Сначала нужно прописать в окружении операционной системы переменную среды CINTOOLS_DIR_5 = C:\PROGRA~2\NATION~1\LABVIE~1\CINTOO~2 (именно в таком формате, т.к. инструменты старые, длинные имена файлов не переваривают). Возможно, придётся поиграть с окончаниями папок, в частности у меня есть Program Files для 64-битных приложений и обычный Program Files (x86), поэтому стоит цифра 2 в названии PROGRA~2. Папок cintools тоже может быть несколько, так же как и версий LabVIEW. Для компиляции обоих CIN'ов я взял среду MS Visual Studio 2005. Для неё требуется прописать некоторые переменные среды, включающие пути к стандартным библиотекам, хэдерам и прочим утилитам Visual Studio. Самое простое - запустить утилиту Visual Studio 2005 Command Prompt, которая автоматом прописывает все необходимые переменные среды. Также это можно сделать вручную:

Код: Выделить всё

Include = C:\Program Files\Microsoft Visual Studio 8\VC\PlatformSDK\Include;C:\Program Files\Microsoft Visual Studio 8\VC\include
Lib = C:\Program Files\Microsoft Visual Studio 8\VC\PlatformSDK\Lib;C:\Program Files\Microsoft Visual Studio 8\VC\lib
Path += C:\Program Files\Microsoft Visual Studio 8\VC\bin;C:\Program Files\Microsoft Visual Studio 8\Common7\IDE
(К переменной Path строку нужно добавить, пометил символом +=)
Дальше, создаём два файла Fact.cpp и FactCall.cpp с вышеприведённым содержимым, например, в корне диска C:\ . Там же создаём make-файлы Fact.lvm и FactCall.lvm:

Fact.lvm:

Код: Выделить всё

name = Fact
type = LVSB
!include $(CINTOOLS_DIR_5)\ntlvsb.mak
FactCall.lvm:

Код: Выделить всё

name = FactCall
type = CIN
subrNames = Fact
!include $(CINTOOLS_DIR_5)\ntlvsb.mak
Открываем Visual Studio 2005 Command Prompt (для простоты) (Пуск -> Все программы -> Microsoft Visual Studio 2005 -> Visual Studio Tools -> Visual Studio 2005 Command Prompt), перемещаемся в директорию с нашими исходниками: Компилируем внешнюю процедуру:

Код: Выделить всё

nmake /f Fact.lvm
Если всё прошло удачно, то вывод утилиты lvsbutil будет таким:
Fact.jpg
Компилируем основной CIN:

Код: Выделить всё

nmake /f FactCall.lvm
Если всё прошло удачно, то вывод утилиты lvsbutil будет таким:
FactCall.jpg
Вот и всё, получили финальные lsb-файлы, теперь можно использовать их в LabVIEW.
LV_Factorial_BD.jpg
LV_Factorial_BD.jpg (12.49 КБ) 6196 просмотров
LV_Factorial_FP.jpg
LV_Factorial_FP.jpg (7.77 КБ) 6196 просмотров
Для успешной компиляции не обязательно, чтобы внешняя процедура и вызывающий её CIN лежали в одной папке. Можно производить компиляцию в разных каталогах и в разное время.

Ещё раз хотелось бы напомнить, что внешние процедуры не поддерживаются в :labview: 8.0 и выше, код загрузки и линковки процедур вырезан из labview.exe и вместо него стоит заглушка (тип ресурса :vi: == LIsb -> окончание загрузки :vi: и вывод диалога об ошибке). Так что для большинства пользователей LabVIEW информация выше может оказаться бесполезной.

P.S. Считаю обязательным дополнить эту главу некоторой важной информацией. При компиляции CIN'а через make-файл подключается дополнительный файл ntlvsb.mak, содержащий указания для компилятора, что собственно нужно делать для того, чтобы получить исходные файлы. Хотел бы кратко разобрать алгоритм работы этого make-файла. Берём CINTOOLS старой версии, чтобы охватить также и генерацию внешних процедур.

Например, у нас имеется
FactCall - основной CIN
Fact - внешняя процедура
Тогда упрощённый make-файл будет выглядеть вот так:
name=FactCall
type=CIN
subrNames = Fact

!include <ntwin32.mak>

MANIFEST_ACTION = mt /manifest $(name).dll.manifest /outputresource:$(name).dll;^#2
CCOMP = cl -nologo -c -W3 -Zi -Zp1 -MD -I$(CINTOOLS_DIR_5)

OBJS = $(name).obj $(CINTOOLS_DIR_5)\win32\labview.lib $(CINTOOLS_DIR_5)\win32\lvsb.lib $(CINTOOLS_DIR_5)\win32\$(type).obj

$(name).lsb : $(name).map
@$(MANIFEST_ACTION)
@$(CINTOOLS_DIR_5)\win32\lvsbutil $(name) -t $(type)

$(name).map : $(OBJS) $(CINTOOLS_DIR_5)\win32\lvsbmain.exp
!ifdef subrNames
@echo "Building link info for $(subrNames)"
@$(CINTOOLS_DIR_5)\win32\genlvinp $(subrNames) >lvsbinp.c
@$(CCOMP) lvsbinp.c
!endif
@link -dll -out:$(name).dll -map:$(name)._mp \
$(CINTOOLS_DIR_5)\win32\lvsbmain.exp $(OBJS) lvsbinp.obj $(olelibsdll)

.cpp.obj :
@$(CCOMP) -O2 $<
Разбираем по порядку компиляцию основного CIN'а. Внешняя процедура компилируется отдельно и аналогично.
1) $(CINTOOLS_DIR_5)\win32\genlvinp Fact >lvsbinp.c
В файл lvsbinp.c записывается вывод утилиты genlvinp.exe. В этом файле будет следующее:
#include "extglue.h"

ExternalSubGlue(Fact)

CommonGlue
extglue.h - это специальная ассемблерная вставка (англ. glue - "клей"), обеспечивающая переход из модуля основного CIN'а в модуль внешней процедуры (т.е., из одной DLL в другую). На Windows этот "клей" будет соответствовать такому коду:

Код: Выделить всё

#define ExternalSubGlue(subrName)
long gLVSB##subrName = 'BSLV';
void subrName(void);	
void subrName(void){	
__asm { pop edi	}
__asm { pop esi }
__asm { pop ebx }
__asm { leave }
__asm { push gLVSB##subrName }
__asm { ret 0 }
}

#define CommonGlue
Вместо subrName подставляется имя внешней процедуры. 'BSLV' - это метка, куда :labview: должен прописать адрес внешней процедуры. Когда основной CIN вызывает внешнюю процедуру, он в итоге вызывает вот эту вставку, а она, исходя из её кода, восстанавливает регистры edi, esi и ebx, выходит из текущей функции (т.е., из кода основного CIN'а), помещает на стек адрес внешней процедуры и, вызывая ret, переходит во внешнюю процедуру. Далее, когда я буду рассматривать примеры CIN'ов на Delphi, станет понятно, как это работает.

2) cl -nologo -c -W3 -Zi -Zp1 -MD -I$(CINTOOLS_DIR_5) lvsbinp.c

Запускаем компилятор C/C++ со следующими ключами:
/nologo - suppress copyright message
/c - compile only, no link
/W3 - Specifies the level of warning to be generated by the compiler. Level 3 displays all level 2 warnings and all other warnings that are recommended for production purposes.
/Zi - enable debugging information
/Zp1 - pack structs on 1-byte boundary
/MD - link with MSVCRT.LIB
/I - add to include search path

Все параметры чувствительны к регистру! На выходе получаем файлы lvsbinp.obj (объектный файл-вставка (клей) для внешней процедуры) и vc80.pdb.

3) cl -nologo -c -W3 -Zi -Zp1 -MD -I$(CINTOOLS_DIR_5) -O2 FactCall.cpp

Запускаем компилятор C/C++ с теми же ключами и ещё вот таким:
/O2 - maximize speed

Все параметры чувствительны к регистру! На выходе получаем объектный файл основного CIN'а FactCall.obj.

4) link -dll -out:FactCall.dll -map:FactCall._mp \
$(CINTOOLS_DIR_5)\win32\lvsbmain.exp \
FactCall.obj \
$(CINTOOLS_DIR_5)\win32\labview.lib \
$(CINTOOLS_DIR_5)\win32\lvsb.lib \
$(CINTOOLS_DIR_5)\win32\cin.obj \
lvsbinp.obj \
ole32.lib uuid.lib oleaut32.lib \
kernel32.lib ws2_32.lib mswsock.lib advapi32.lib \
bufferoverflowu.lib user32.lib gdi32.lib \
comdlg32.lib winspool.lib


Создаём библиотеку (DLL) из имеющихся объектных файлов, подключая также стандартные библиотеки MSVC и библиотеки :labview: .

На выходе получаем файлы
FactCall._mp
FactCall.dll
FactCall.dll.manifest

5) mt /manifest FactCall.dll.manifest /outputresource:FactCall.dll;^#2
Помещаем манифест внутрь библиотеки. Зачем это нужно, вы можете почитать здесь.

6) $(CINTOOLS_DIR_5)\win32\lvsbutil FactCall -t CIN
Преобразуем FactCall.dll в соответствующий LSB-файл для последующей загрузки в :labview: . На выходе получаем файл FactCall.lsb.

Если рядом с входной DLL размещён файл *.map (карта адресов линкера), то утилита lvsbutil прописывает в lsb-ресурс ссылку на внешнюю процедуру, если же файла нет, то создаётся lsb без ссылки. Сама DLL не изменяется.
Карта адресов может выглядеть так:
0003:00000020 _LVSBHead 10003020 CIN.obj
0003:00000058 _gLVSBFact 10003058 lvsbinp.obj
lvsbutil сообщает о создании ссылки так:
global base 10003020 name _LVSBHead
...adding reference to "Fact" at offset 0x38
Смещение (offset) = 10003058 - 10003020 = 0x38. Оно записывается в ресурс LIsb перед путём к внешней процедуре (PTH0). В map-файле должно быть всегда корректное смещение, иначе при загрузке VI в LV внешняя процедура будет прописана не там, где нужно, и LV выдаст access violation при запуске :vi: . Кстати говоря, можно создать map-файл вручную, лишь бы offset равнялся 0x38 для первой внешней процедуры:
0:0 _LVSBHead 0
0:0 _gLVSBFact 38
lvsbutil прекрасно "ест" такой формат. Ранее я утверждал, что поиск смещения 0x38 - дело тёмное, однако как оказалось, это совсем не так. :labview: считывает offset именно из LIsb ресурса, если прописать там вручную что-то другое, то будет прочитано это значение. Никаких защитных проверок здесь не выполняется.
Последний раз редактировалось dadreamer 14 мар 2015, 19:56, всего редактировалось 2 раза.
Аватара пользователя
Pavel Krivozubov

Activity Bronze
professor
professor
Сообщения: 4421
Зарегистрирован: 07 фев 2008, 16:39
Награды: 3
Версия LabVIEW: 7.0 - 2013
Откуда: г. Электросталь
Благодарил (а): 24 раза
Поблагодарили: 9 раз
Контактная информация:

Re: Альтернативный способ запуска CIN (инструкция)

Сообщение Pavel Krivozubov »

Странно, только что попробовал - можно загрузить сколько угодно :dntknw: У нас нет такого ограничения на 5 вложений.
Насчет пропадания кнопки "Правка" - если браузер Хром, то он неадекватно ведет себя с нашим движком, иногда кнопки пропадают, иногда появляются, по моему это связано каким-то образом с очисткой кэша браузера.
Я для этих целей держу Мазиллу и если такая проблема возникает - пользуюсь ей. Там кнопки приварены намертво :super:
Аватара пользователя
dadreamer

Activity Professionalism Автор
professor
professor
Сообщения: 3926
Зарегистрирован: 17 фев 2013, 16:33
Награды: 4
Версия LabVIEW: 2.5 — 2022
Благодарил (а): 11 раз
Поблагодарили: 127 раз
Контактная информация:

Re: Альтернативный способ запуска CIN (инструкция)

Сообщение dadreamer »

Pavel Krivozubov

Смотрите сами :)
12345.png
А насчет браузера... И дома, и на работе стоит Mozilla Firefox, версия 18.0.1. У моего самого свежего сообщения (4 минуты от создания) есть только кнопки "Удалить сообщение" и "Пожаловаться на это сообщение". У старых только последняя кнопка. Вчера видел еще "Редактировать" у новых постов, но сегодня нигде такого не наблюдается :dntknw:
__________________________________________________________________________________________________________________
Продолжаю дополнять статью не менее интересным материалом.

2. Использование Delphi для компиляции CIN'а.
Оказывается, можно использовать не только те среды программирования и компиляторы, что указаны в мануалах Code Interface Reference Manual и Using External Code In LabVIEW, а практически все, что способны создавать Native DLL, соблюдая некоторые условия. Мне долгое время казалось, что кроме C/C++ оболочки в Windows не создать CIN никак, в основном потому, что требуется подключать к проекту объектный файл cin.obj, две библиотеки labview.lib и lvsb.lib, а также файл экспорта функций lvsbmain.def. Файлы *.obj и *.lib в COFF-формате, а Delphi, например, его вообще не воспринимает. Однако, если не использовать labview.lib и разобрать в отладчике остальные файлы, то создать CIN в Delphi проще простого.
Чтобы не мучать читателя техническими подробностями, напишу кратко: для компиляции требуется воссоздать заголовок CIN, прописать в нём ссылки на основные CIN-функции, указать ещё кое какую информацию, а также экспортировать из библиотеки две функции: GetLVSBHeader и SetLVRTModule (вторая нужна больше для удобства). Ну, и соответственно, нужно описать прототипы всех CIN-функций. В результате получилась вот такая заготовка:

Код: Выделить всё

library Test;

uses
  SysUtils,
  Windows;

{$R *.res}

type
  TCINHeader = Packed Record
    Init: Cardinal; //CINInit
    Dispose: Cardinal; //CINDispose
    Abort: Cardinal; //CINAbort
    Run: Cardinal; //CINRun
    Load: Cardinal; //CINLoad
    Save: Cardinal; //CINSave
    Unload: Cardinal; //CINUnload
    Properties: Cardinal; //CINProperties
    LVSBHead: Cardinal;
    Field10: Cardinal;
    TypeString: Cardinal;
    CINToolsVersion: Cardinal;
    gLVExtCodeDispatchTable: Cardinal;
    ReloadCounter: Cardinal;
    Field15: Cardinal;
    CINModuleAddress: Cardinal;
    Field17: Cardinal;
    LVRTTable: Cardinal;
    LVSBHeaderPtr: Cardinal;
    Field20: Cardinal;
    Field21: Cardinal;
    Field22: Cardinal;
    Field23: Cardinal;
    Field24: Cardinal;
    Field25: Cardinal;
    Field26: Cardinal;
    Field27: Cardinal;
end;

var CIN: TCINHeader;
    gLVRTModule: cardinal;

DbgPrintf: function(str: pchar): integer; cdecl;
CINSetArraySize: function(DataHandle: pointer; TDPtr: pointer;
                          ParamNumber: integer;
                          NewNumberOfElements: integer): integer; cdecl;
GetSetLVInfo: function(Action: integer; Data: integer): integer; cdecl;

// CIN-specific Functions //

function GetTDPtr: pointer; cdecl;
begin
Result:=Ptr(GetSetLVInfo(3,0));
end;

function SetCINArraySize(DataHandle: pointer; ParamNumber: integer;
                         NewNumberOfElements: integer): integer; cdecl;
var TD: pointer;
begin
TD := GetTDPtr;
Result := CINSetArraySize(DataHandle,TD,ParamNumber,NewNumberOfElements);
end;

function GetDSStorage: integer; cdecl;
begin
Result:=GetSetLVInfo(0,0);
end;

function SetDSStorage(NewValue: integer): integer; cdecl;
begin
Result:=GetSetLVInfo(1, NewValue);
end;

function GetLVInternals: pointer; cdecl;
begin
Result:=Ptr(GetSetLVInfo(4,0));
end;

// End of CIN-specific Functions //

function CINInit: integer; cdecl;
begin
if (@DbgPrintf<>nil) then DbgPrintf('CINInit');
Result:=0; //mgNoErr
end;

function CINDispose: integer; cdecl;
begin
if (@DbgPrintf<>nil) then DbgPrintf('CINDispose');
Result:=0; //mgNoErr
end;

function CINAbort: integer; cdecl;
begin
if (@DbgPrintf<>nil) then DbgPrintf('CINAbort');
Result:=0; //mgNoErr
end;

function CINRun(Num1: pointer; Num2: pointer; Sum: pointer): integer; cdecl;
var N1,N2,S: ^integer;
begin
if (@DbgPrintf<>nil) then DbgPrintf('CINRun');
N1:=Num1;
N2:=Num2;
S:=Sum;
S^ := N1^ + N2^;
Result:=0; //mgNoErr
end;

function CINLoad(Reserved: cardinal): integer; cdecl;
begin
if (@DbgPrintf<>nil) then DbgPrintf('CINLoad');
Result:=0; //mgNoErr
end;

function CINUnload: integer; cdecl;
begin
if (@DbgPrintf<>nil) then DbgPrintf('CINUnload');
Result:=0; //mgNoErr
end;

function CINSave(Reserved: cardinal): integer; cdecl;
begin
if (@DbgPrintf<>nil) then DbgPrintf('CINSave');
Result:=0; //mgNoErr
end;

function CINProperties(Selector: integer; Arg: pointer): integer; cdecl;
var Data: ^boolean;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINProperties');
Data := Arg;
case Selector of
0: //kCINIsReentrant
begin
Data^ := True;
Result:=0; //mgNoErr
Exit;
end
else
Result:=53; //mgNotSupported
end;
end;

function GetLVSBHeader: Cardinal; cdecl;
begin
Result:=Cardinal(@CIN.LVSBHead);
end;

procedure SetLVRTModule(Module: Cardinal); cdecl;
begin
gLVRTModule:=Module;
//
if (gLVRTModule<>0) then begin
DbgPrintf := GetProcAddress(gLVRTModule, 'DbgPrintf');
//if (@DbgPrintf<>nil) then DbgPrintf('DbgPrintf found.');
CINSetArraySize := GetProcAddress(gLVRTModule, 'CINSetArraySize');
GetSetLVInfo := GetProcAddress(gLVRTModule, 'GetSetLVInfo');
end;
end;

exports
GetLVSBHeader name 'GetLVSBHeader',
SetLVRTModule name 'SetLVRTModule';

begin
CIN.Init:=Cardinal(@CINInit);
CIN.Dispose:=Cardinal(@CINDispose);
CIN.Abort:=Cardinal(@CINAbort);
CIN.Run:=Cardinal(@CINRun);
CIN.Load:=Cardinal(@CINLoad);
CIN.Save:=Cardinal(@CINSave);
CIN.Unload:=Cardinal(@CINUnload);
CIN.Properties:=Cardinal(@CINProperties);
CIN.LVSBHead:=0;
CIN.Field10:=0;
CIN.TypeString:=$204E4943; //String "CIN "
CIN.CINToolsVersion:=$00000004; //cintools Version
CIN.gLVExtCodeDispatchTable:=Cardinal(@CIN.Init);
CIN.ReloadCounter:=0;
CIN.Field15:=0;
CIN.CINModuleAddress:=0;
CIN.Field17:=0;
CIN.LVRTTable:=0;
CIN.LVSBHeaderPtr:=0;
CIN.Field20:=0;
CIN.Field21:=0;
CIN.Field22:=0;
CIN.Field23:=0;
CIN.Field24:=0;
CIN.Field25:=0;
CIN.Field26:=0;
CIN.Field27:=0;
end.
CIN_To_Delphi.rar
(3.09 КБ) 249 скачиваний
В функцию CINRun я поместил простейший код сложения двух чисел для проверки. Этот шаблон можно использовать для создания любого CIN'а в Delphi, банально скопировав код в свой проект. Что интересно, в отличие от MS Visual Studio в Delphi можно вообще не менять опции проекта, а использовать дефолтные настройки! Я даже не менял выравнивание структур (записей), а объявил их как packed record. Всё, что требуется менять при создании нового CIN'а - это типовые функции CINRun, CINInit и т.д. Правда, есть всё же один минус - чтобы использовать встроенные функции самого LabVIEW, требуется прописывать их прототип и получать адрес (см. в качестве примера определение функции DbgPrintf). Это как раз тот недостаток, который появился при отключении labview.lib и заголовочного файла extcode.h. Соответственно, все макросы, облегчающие написание кода, тоже недоступны. Но вряд ли кто-то воспользуется всем функционалом :labview: , обычно нужно применить несколько функций максимум. Для подстраховки можно найти адрес основного модуля в LibMain (между begin и end) и определить нужные функции так:

Код: Выделить всё

LVInstance := GetModuleHandle('lvrt.dll');
if (LVInstance = 0) then
LVInstance := GetModuleHandle(nil);
if (LVInstance <> 0) then begin
DbgPrintf = GetProcAddress(LVInstance, 'DbgPrintf');
if Assigned(DbgPrintf) then
...
else
//ошибка при определении функции
end
Или просто записать LVInstance в глобальную переменную gLVRTModule, а функции определить в SetLVRTModule. Но эти действия излишни, если CIN экспортирует SetLVRTModule, так как в этом случае мы гарантированно получаем адрес основного модуля labview.exe или lvrt.dll (если работаем в Run-Time Engine). Ещё один вариант, который применялся в CIN'ах последних версий - использование LVRTTable, т.е. таблицы со смещениями всех функций относительно базового адреса основного модуля. Рассмотрим этот вариант как-нибудь в другой раз.

Также стоит сказать, что после компиляции полученную библиотеку требуется преобразовать в *.lsb файл, чтобы потом загрузить в :labview: . В Delphi 7 нет опций для задания пользовательских команд после компиляции (в отличие от более поздних версий RAD Studio XE или MS Visual Studio). Так что выход один - вручную (через командную строку) выполнить команду "%CINTOOLS_DIR%\lvsbutil" -c имя_библиотеки. %CINTOOLS_DIR% - переменная среды, равная пути к папке cintools, например C:\Program Files (x86)\National Instruments\LabVIEW 2011\cintools. Имя библиотеки задаётся без расширения *.dll. Конвертация в *.lsb выполняется в папке, где лежит наша DLL. Если всё прошло успешно, то получим вот такое сообщение:
compile.jpg
Далее я приведу пару примеров, подтверждающих нормальную работу CIN'ов, созданных в Delphi. Примеры взяты из изданий "Using External Code In LabVIEW" и "Code Interface Reference Manual".
Пример первый. CIN, вычисляющий векторное произведение двух двумерных массивов типа Double. Дополнительно возвращается статус ошибки, равный True, если перемножить массивы невозможно. Код на С выглядит так:

Код: Выделить всё

/*
* CIN source file
*/
#include "extcode.h"

#define ParamNumber 2
/* The return parameter is parameter 2 */

#define NumDimensions 2
/* 2D Array */
/*
* typedefs
*/

typedef struct {
int32 dimSizes[2];
float64 arg1[1];
} TD1;

typedef TD1 **TD1Hdl;

CIN MgErr CINRun(TD1Hdl ah, TD1Hdl bh, TD1Hdl resulth, LVBoolean *errorp);

CIN MgErr CINRun(TD1Hdl ah, TD1Hdl bh, TD1Hdl resulth, LVBoolean *errorp) {
int32 i,j,k,l;
int32 rows, cols;
float64 *aElmtp, *bElmtp, *resultElmtp;
MgErr err=noErr;
int32 newNumElmts;
if ((k = (*ah)->dimSizes[1]) != (*bh)->dimSizes[0]) {
*errorp = LVTRUE;
goto out;
}
*errorp = LVFALSE;
rows = (*ah)->dimSizes[0];
/* number of rows in a and result */
cols = (*bh)->dimSizes[1];
/* number of cols in b and result */
newNumElmts = rows * cols;
if (err = SetCINArraySize((UHandle)resulth,
ParamNumber, newNumElmts))
goto out;
(*resulth)->dimSizes[0] = rows;
(*resulth)->dimSizes[1] = cols;
aElmtp = (*ah)->arg1;
bElmtp = (*bh)->arg1;
resultElmtp = (*resulth)->arg1;

for (i=0; i<rows; i++)

for (j=0; j<cols; j++)
{
*resultElmtp = 0;

for (l=0; l<k; l++)
*resultElmtp += aElmtp[i*k + l] * bElmtp[l*cols + j];

resultElmtp++;
}

out:
return err;
}
Воссозданный в Delphi код получился таким:

Код: Выделить всё

library CIN_Array;

uses
  SysUtils,
  Windows;

{$R *.res}

type
  TCINHeader = Packed Record
    Init: Cardinal; //CINInit
    Dispose: Cardinal; //CINDispose
    Abort: Cardinal; //CINAbort
    Run: Cardinal; //CINRun
    Load: Cardinal; //CINLoad
    Save: Cardinal; //CINSave
    Unload: Cardinal; //CINUnload
    Properties: Cardinal; //CINProperties
    LVSBHead: Cardinal;
    Field10: Cardinal;
    TypeString: Cardinal;
    CINToolsVersion: Cardinal;
    gLVExtCodeDispatchTable: Cardinal;
    ReloadCounter: Cardinal;
    Field15: Cardinal;
    CINModuleAddress: Cardinal;
    Field17: Cardinal;
    LVRTTable: Cardinal;
    LVSBHeaderPtr: Cardinal;
    Field20: Cardinal;
    Field21: Cardinal;
    Field22: Cardinal;
    Field23: Cardinal;
    Field24: Cardinal;
    Field25: Cardinal;
    Field26: Cardinal;
    Field27: Cardinal;
end;

type
  TD1 = Packed Record
    dimSizes: array[0..1] of integer;
    arg1: array[0..0] of double;
end;

type TD1Ptr = ^TD1;
type TD1Hdl = ^TD1Ptr;

type DblArr = array[0..0] of double;

const ParamNumber = 2;
// The return parameter is parameter 2 //
const NumDimensions = 2;
// 2D Array //

var CIN: TCINHeader;
    gLVRTModule: cardinal;

DbgPrintf: function(str: pchar): integer; cdecl;
CINSetArraySize: function(DataHandle: pointer; TDPtr: pointer;
                          ParamNumber: integer;
                          NewNumberOfElements: integer): integer; cdecl;
GetSetLVInfo: function(Action: integer; Data: integer): integer; cdecl;
DSNewHandle: function(Size: integer): pointer; cdecl;
DSDisposeHandle: function(Handle: pointer): integer; cdecl;

// CIN-specific Functions //

function GetTDPtr: pointer; cdecl;
begin
Result:=Ptr(GetSetLVInfo(3,0));
end;

function SetCINArraySize(DataHandle: pointer; ParamNumber: integer;
                         NewNumberOfElements: integer): integer; cdecl;
var TD: pointer;
begin
TD := GetTDPtr;
Result := CINSetArraySize(DataHandle,TD,ParamNumber,NewNumberOfElements);
end;

function GetDSStorage: integer; cdecl;
begin
Result:=GetSetLVInfo(0,0);
end;

function SetDSStorage(NewValue: integer): integer; cdecl;
begin
Result:=GetSetLVInfo(1, NewValue);
end;

function GetLVInternals: pointer; cdecl;
begin
Result:=Ptr(GetSetLVInfo(4,0));
end;

// End of CIN-specific Functions //

function CINInit: integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINInit');
Result:=0; //mgnoErr
end;

function CINDispose: integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINDispose');
Result:=0; //mgNoErr
end;

function CINAbort: integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINAbort');
Result:=0; //mgNoErr
end;

function CINRun(ah: TD1Hdl; bh: TD1Hdl; resulth: TD1Hdl; errorp: pointer): integer; cdecl;
var i,j,k,l: integer;
    rows,cols: integer;
    aElmtp, bElmtp: ^DblArr;
    resultElmtp: ^double;
    err: integer;
    newNumElmts: integer;
    Error: ^byte;
label _Out;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINRun');
Error:=errorp;
err:=0; //noErr;
k:=ah^.dimSizes[1];
if (k<>bh^.dimSizes[0]) then begin
Error^:=1;
goto _Out;
end;
Error^:=0;
rows:=ah^.dimSizes[0];
// number of rows in a and result //
cols:=bh^.dimSizes[1];
// number of cols in b and result //
newNumElmts := rows * cols;
err := SetCINArraySize(resulth,ParamNumber,newNumElmts);
if (err<>0) then goto _Out;
resulth^.dimSizes[0] := rows;
resulth^.dimSizes[1] := cols;

aElmtp := @ah^.arg1;
bElmtp := @bh^.arg1;
resultElmtp := @resulth^.arg1;

for i:=0 to rows-1 do
 for j:=0 to cols-1 do begin
  resultElmtp^ := 0;
   for l:=0 to k-1 do
    resultElmtp^ := resultElmtp^ + aElmtp^[i*k + l] * bElmtp^[l*cols + j];
  Inc(resultElmtp);
end;
_Out:
Result:=err;
end;

function CINLoad(Reserved: cardinal): integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINLoad');
Result:=0; //mgNoErr
end;

function CINUnload: integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINUnload');
Result:=0; //mgNoErr
end;

function CINSave(Reserved: cardinal): integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINSave');
Result:=0; //mgNoErr
end;

function CINProperties(Selector: integer; Arg: pointer): integer; cdecl;
//var Data: ^boolean;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINProperties');
{
Data := Arg;
case Selector of
0: //kCINIsReentrant
begin
Data^ := True;
Result:=0; //mgNoErr
Exit;
end
else}
Result:=53; //mgNotSupported
//end;
end;

function GetLVSBHeader: Cardinal; cdecl;
begin
Result:=Cardinal(@CIN.LVSBHead);
end;

procedure SetLVRTModule(Module: Cardinal); cdecl;
begin
gLVRTModule:=Module;
//
if (gLVRTModule<>0) then begin
DbgPrintf := GetProcAddress(gLVRTModule, 'DbgPrintf');
//if (@DbgPrintf<>nil) then DbgPrintf('DbgPrintf found.');
CINSetArraySize := GetProcAddress(gLVRTModule, 'CINSetArraySize');
GetSetLVInfo := GetProcAddress(gLVRTModule, 'GetSetLVInfo');
DSNewHandle := GetProcAddress(gLVRTModule, 'DSNewHandle');
DSDisposeHandle := GetProcAddress(gLVRTModule, 'DSDisposeHandle');
end;
end;

exports
GetLVSBHeader name 'GetLVSBHeader',
SetLVRTModule name 'SetLVRTModule';

begin
CIN.Init:=Cardinal(@CINInit);
CIN.Dispose:=Cardinal(@CINDispose);
CIN.Abort:=Cardinal(@CINAbort);
CIN.Run:=Cardinal(@CINRun);
CIN.Load:=Cardinal(@CINLoad);
CIN.Save:=Cardinal(@CINSave);
CIN.Unload:=Cardinal(@CINUnload);
CIN.Properties:=Cardinal(@CINProperties);
CIN.LVSBHead:=0;
CIN.Field10:=0;
CIN.TypeString:=$204E4943; //String "CIN "
CIN.CINToolsVersion:=$00000004; //cintools Version
CIN.gLVExtCodeDispatchTable:=Cardinal(@CIN.Init);
CIN.ReloadCounter:=0;
CIN.Field15:=0;
CIN.CINModuleAddress:=0;
CIN.Field17:=0;
CIN.LVRTTable:=0;
CIN.LVSBHeaderPtr:=0;
CIN.Field20:=0;
CIN.Field21:=0;
CIN.Field22:=0;
CIN.Field23:=0;
CIN.Field24:=0;
CIN.Field25:=0;
CIN.Field26:=0;
CIN.Field27:=0;
end.
Как видим, код во многом схож с кодом на С, чуть иначе ведётся работа с указателями, присутствуют определения типов TD1Ptr, TD1Hdl и прочих, но в целом алгоритм тот же самый.
Преобразуем *.dll в *.lsb и проверяем в :labview: :
Array_BD.jpg
Array_BD.jpg (18.56 КБ) 5885 просмотров
Array_FP.jpg
Очевидно, что полученный CIN работает прекрасно и практически ничем не отличается от своего "собрата" на С. Желающие могут сравнить сами CIN на С и CIN на Delphi, исходник, получившийся LSB-файл и сам :vi: лежат в архиве.
CIN_Array_d7.rar
lv2011
(39.07 КБ) 167 скачиваний
Пример второй. CIN, вычисляющий скользящее среднее между текущим числом, поданным на вход, и предыдущим. В этом примере используется CIN Data Space - глобальное пространство CIN'а размером 4 байта, куда записывается указатель на структуру, содержащую общую сумму чисел, поданных на вход CIN'а, и количество поданных чисел. В функции CINInit выполняется создание структуры, первичное обнуление и запись в Data Space. В CINDispose структура удаляется из памяти. В CINRun структура считывается из Data Space, дополняется новым числом, а также вычисляется скользящее среднее. Код на С выглядит так:

Код: Выделить всё

/*
* CIN source file
*/
#include "extcode.h"

typedef struct {
float64 total;
int32 numElements;
} dsGlobalStruct;

CIN MgErr CINInit() {
dsGlobalStruct **dsGlobals;
MgErr err = noErr;
if (!(dsGlobals = (dsGlobalStruct **)
DSNewHandle(sizeof(dsGlobalStruct))))
{
/* if 0, ran out of memory */
err = mFullErr;
goto out;
}
(*dsGlobals)->numElements = 0;
(*dsGlobals)->total = 0;
SetDSStorage((int32) dsGlobals);
out:
return noErr;
}

CIN MgErr CINDispose()
{
dsGlobalStruct **dsGlobals;
dsGlobals=(dsGlobalStruct **) GetDSStorage();
if (dsGlobals)
DSDisposeHandle(dsGlobals);
return noErr;
}

CIN MgErr CINRun(float64 *new_num, float64 *avg)
{
dsGlobalStruct **dsGlobals;
dsGlobals=(dsGlobalStruct **) GetDSStorage();
if (dsGlobals) {
(*dsGlobals)->total += *new_num;
(*dsGlobals)->numElements++;
*avg = (*dsGlobals)->total / (*dsGlobals)->numElements;
}
return noErr;
}
Воссозданный в Delphi код получился таким:

Код: Выделить всё

library CIN_Avg;

uses
  SysUtils,
  Windows;

{$R *.res}

type
  TCINHeader = Packed Record
    Init: Cardinal; //CINInit
    Dispose: Cardinal; //CINDispose
    Abort: Cardinal; //CINAbort
    Run: Cardinal; //CINRun
    Load: Cardinal; //CINLoad
    Save: Cardinal; //CINSave
    Unload: Cardinal; //CINUnload
    Properties: Cardinal; //CINProperties
    LVSBHead: Cardinal;
    Field10: Cardinal;
    TypeString: Cardinal;
    CINToolsVersion: Cardinal;
    gLVExtCodeDispatchTable: Cardinal;
    ReloadCounter: Cardinal;
    Field15: Cardinal;
    CINModuleAddress: Cardinal;
    Field17: Cardinal;
    LVRTTable: Cardinal;
    LVSBHeaderPtr: Cardinal;
    Field20: Cardinal;
    Field21: Cardinal;
    Field22: Cardinal;
    Field23: Cardinal;
    Field24: Cardinal;
    Field25: Cardinal;
    Field26: Cardinal;
    Field27: Cardinal;
end;

type
  dsGlobalStruct = Packed Record
    total: double;
    numElements: integer; //CINDispose
end;

type PdsGlobalStruct = ^dsGlobalStruct;
type HdsGlobalStruct = ^PdsGlobalStruct;

var CIN: TCINHeader;
    gLVRTModule: cardinal;

DbgPrintf: function(str: pchar): integer; cdecl;
CINSetArraySize: function(DataHandle: pointer; TDPtr: pointer;
                          ParamNumber: integer;
                          NewNumberOfElements: integer): integer; cdecl;
GetSetLVInfo: function(Action: integer; Data: integer): integer; cdecl;
DSNewHandle: function(Size: integer): pointer; cdecl;
DSDisposeHandle: function(Handle: pointer): integer; cdecl;

// CIN-specific Functions //

function GetTDPtr: pointer; cdecl;
begin
Result:=Ptr(GetSetLVInfo(3,0));
end;

function SetCINArraySize(DataHandle: pointer; ParamNumber: integer;
                         NewNumberOfElements: integer): integer; cdecl;
var TD: pointer;
begin
TD := GetTDPtr;
Result := CINSetArraySize(DataHandle,TD,ParamNumber,NewNumberOfElements);
end;

function GetDSStorage: integer; cdecl;
begin
Result:=GetSetLVInfo(0,0);
end;

function SetDSStorage(NewValue: integer): integer; cdecl;
begin
Result:=GetSetLVInfo(1, NewValue);
end;

function GetLVInternals: pointer; cdecl;
begin
Result:=Ptr(GetSetLVInfo(4,0));
end;

// End of CIN-specific Functions //

function CINInit: integer; cdecl;
var dsGlobals: HdsGlobalStruct;
    err: integer;
label _Out;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINInit');
err := 0; //noErr
dsGlobals := DSNewHandle(SizeOf(dsGlobalStruct));
if not Assigned(dsGlobals) then begin
// if 0, ran out of memory //
err := 2; //mFullErr
goto _Out;
end;
dsGlobals^.numElements:=0;
dsGlobals^.total:=0;
SetDSStorage(Integer(dsGlobals));
_Out:
Result:=err;
end;

function CINDispose: integer; cdecl;
var dsGlobals: HdsGlobalStruct;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINDispose');
dsGlobals := Ptr(GetDSStorage);
if Assigned(dsGlobals) then
  DSDisposeHandle(dsGlobals);
Result:=0; //mgNoErr
end;

function CINAbort: integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINAbort');
Result:=0; //mgNoErr
end;

function CINRun(new_num: pointer; avg: pointer): integer; cdecl;
var NewNumber,Average: ^double;
    dsGlobals: HdsGlobalStruct;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINRun');
NewNumber:=new_num;
Average:=avg;
dsGlobals := Ptr(GetDSStorage);
if Assigned(dsGlobals) then begin
dsGlobals^.total := dsGlobals^.total + NewNumber^;
dsGlobals^.numElements := dsGlobals^.numElements + 1;
Average^ := dsGlobals^.total / dsGlobals^.numElements;
end;
Result:=0; //mgNoErr
end;

function CINLoad(Reserved: cardinal): integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINLoad');
Result:=0; //mgNoErr
end;

function CINUnload: integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINUnload');
Result:=0; //mgNoErr
end;

function CINSave(Reserved: cardinal): integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINSave');
Result:=0; //mgNoErr
end;

function CINProperties(Selector: integer; Arg: pointer): integer; cdecl;
//var Data: ^boolean;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINProperties');
{
Data := Arg;
case Selector of
0: //kCINIsReentrant
begin
Data^ := True;
Result:=0; //mgNoErr
Exit;
end
else}
Result:=53; //mgNotSupported
//end;
end;

function GetLVSBHeader: Cardinal; cdecl;
begin
Result:=Cardinal(@CIN.LVSBHead);
end;

procedure SetLVRTModule(Module: Cardinal); cdecl;
begin
gLVRTModule:=Module;
//
if (gLVRTModule<>0) then begin
DbgPrintf := GetProcAddress(gLVRTModule, 'DbgPrintf');
//if (@DbgPrintf<>nil) then DbgPrintf('DbgPrintf found.');
CINSetArraySize := GetProcAddress(gLVRTModule, 'CINSetArraySize');
GetSetLVInfo := GetProcAddress(gLVRTModule, 'GetSetLVInfo');
DSNewHandle := GetProcAddress(gLVRTModule, 'DSNewHandle');
DSDisposeHandle := GetProcAddress(gLVRTModule, 'DSDisposeHandle');
end;
end;

exports
GetLVSBHeader name 'GetLVSBHeader',
SetLVRTModule name 'SetLVRTModule';

begin
CIN.Init:=Cardinal(@CINInit);
CIN.Dispose:=Cardinal(@CINDispose);
CIN.Abort:=Cardinal(@CINAbort);
CIN.Run:=Cardinal(@CINRun);
CIN.Load:=Cardinal(@CINLoad);
CIN.Save:=Cardinal(@CINSave);
CIN.Unload:=Cardinal(@CINUnload);
CIN.Properties:=Cardinal(@CINProperties);
CIN.LVSBHead:=0;
CIN.Field10:=0;
CIN.TypeString:=$204E4943; //String "CIN "
CIN.CINToolsVersion:=$00000004; //cintools Version
CIN.gLVExtCodeDispatchTable:=Cardinal(@CIN.Init);
CIN.ReloadCounter:=0;
CIN.Field15:=0;
CIN.CINModuleAddress:=0;
CIN.Field17:=0;
CIN.LVRTTable:=0;
CIN.LVSBHeaderPtr:=0;
CIN.Field20:=0;
CIN.Field21:=0;
CIN.Field22:=0;
CIN.Field23:=0;
CIN.Field24:=0;
CIN.Field25:=0;
CIN.Field26:=0;
CIN.Field27:=0;
end.
Опять же отличий от кода на С не так много. Испытаем сразу же полученный код в :labview: :
Average_BD.jpg
Average_BD.jpg (13.93 КБ) 5885 просмотров
Average_FP.jpg
Average_FP.jpg (7.72 КБ) 5885 просмотров
Тестовая последовательность была равна 1, 2, 3, 4, 5. (1 + 2 + 3 + 4 + 5)/5 = 15/5 = 3. Всё работает как часы :) Архив с программой также прилагается:
CIN_Avg_d7.rar
(32.11 КБ) 136 скачиваний
Пример третий (и последний). Для того, чтобы лишний раз не нагружать читателя, возьмём пример из главы 0 "Альтернативный способ запуска CIN". Да, это будет как раз пример с вызовом внешней процедуры: sum.c - внешняя процедура, суммирующая n чисел в массиве типа double, calcmean.c - основной CIN, передающий в sum указатель на массив и размер массива n и получающий сумму элементов этого массива, после чего возвращающий среднее арифметическое всех элементов массива. Код на С выглядит так:
Код sum.c (внешняя процедура):

Код: Выделить всё

/*
* sum.c
*/
#include "extcode.h"
float64 LVSBMain(float64 *x, int32 n);
float64 LVSBMain(float64 *x, int32 n)
{
int32 i;
float64 sum;
sum = 0.0;
for (i=0; i<n; i++)
sum += *x++;
return sum;
}
Код calcmean.c (основной CIN):

Код: Выделить всё

/*
* CIN source file
*/
#include "extcode.h"
/*
* typedefs
*/
typedef struct {
int32 dimSize;
float64 arg1[1];
} TD1;
typedef TD1 **TD1Hdl;
extern float64 sum(float64 *x, int32 n);
CIN MgErr CINRun(TD1Hdl xArray, float64 *mean);
CIN MgErr CINRun(TD1Hdl xArray, float64 *mean)
{
float64 *x, total;
int32 n;
x = (*xArray)–>arg1;
n = (*xArray)–>dimSize;
total = sum(x, n);
*mean = total/(float64)n;
return noErr;
}
Попробуем теперь реализовать эти коды на Delphi.
Код sum (внешняя процедура):

Код: Выделить всё

library sum;

uses
  SysUtils,
  Windows;

{$R *.res}

type
  TCINHeader = Packed Record
    Main: Cardinal; //LVSBMain
    Field2: Cardinal;
    LVSBHead: Cardinal;
    Field4: Cardinal;
    TypeString: Cardinal;
    CINToolsVersion: Cardinal;
    gLVExtCodeDispatchTable: Cardinal;
    Fied8: Cardinal;
    Fied9: Cardinal;
    CINModuleAddress: Cardinal;
    Field11: Cardinal;
    LVRTTable: Cardinal;
    LVSBHeaderPtr: Cardinal;
    Field14: Cardinal;
    Field15: Cardinal;
    Field16: Cardinal;
end;

var CIN: TCINHeader;
    gLVRTModule: cardinal;

DbgPrintf: function(str: pchar): integer; cdecl; 
CINSetArraySize: function(DataHandle: pointer; TDPtr: pointer;
                          ParamNumber: integer;
                          NewNumberOfElements: integer): integer; cdecl;
GetSetLVInfo: function(Action: integer; Data: integer): integer; cdecl;

// CIN-specific Functions //

function GetTDPtr: pointer; cdecl;
begin
Result:=Ptr(GetSetLVInfo(3,0));
end;

function SetCINArraySize(DataHandle: pointer; ParamNumber: integer;
                         NewNumberOfElements: integer): integer; cdecl;
var TD: pointer;
begin
TD := GetTDPtr;
Result := CINSetArraySize(DataHandle,TD,ParamNumber,NewNumberOfElements);
end;

function GetDSStorage: integer; cdecl;
begin
Result:=GetSetLVInfo(0,0);
end;

function SetDSStorage(NewValue: integer): integer; cdecl;
begin
Result:=GetSetLVInfo(1, NewValue);
end;

function GetLVInternals: pointer; cdecl;
begin
Result:=Ptr(GetSetLVInfo(4,0));
end;

// End of CIN-specific Functions //

function LVSBMain(x: pdouble; n: integer): double; cdecl;
var sum: double;
    i: integer;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('LVSBMain');
sum:=0;
for i:=0 to n-1 do begin
  sum := sum + x^;
  Inc(x);
end;
Result:=sum;
end;

function GetLVSBHeader: Cardinal; cdecl;
begin
Result:=Cardinal(@CIN.LVSBHead);
end;

procedure SetLVRTModule(Module: Cardinal); cdecl;
begin
gLVRTModule:=Module;
//
if (gLVRTModule<>0) then begin
DbgPrintf := GetProcAddress(gLVRTModule, 'DbgPrintf');
//if (@DbgPrintf<>nil) then DbgPrintf('DbgPrintf found.');
CINSetArraySize := GetProcAddress(gLVRTModule, 'CINSetArraySize');
GetSetLVInfo := GetProcAddress(gLVRTModule, 'GetSetLVInfo');
end;
end;

exports
GetLVSBHeader name 'GetLVSBHeader',
SetLVRTModule name 'SetLVRTModule';

begin
CIN.Main:=Cardinal(@LVSBMain);; //LVSBMain
CIN.Field2:=0;
CIN.LVSBHead:=0;
CIN.Field4:=0;
CIN.TypeString:=$4253564C; //String "LVSB"
CIN.CINToolsVersion:=$00000004; //cintools Version
CIN.gLVExtCodeDispatchTable:=Cardinal(@CIN.Main);
CIN.Fied8:=0;
CIN.Fied9:=0;
CIN.CINModuleAddress:=0;
CIN.Field11:=0;
CIN.LVRTTable:=0;
CIN.LVSBHeaderPtr:=0;
CIN.Field14:=0;
CIN.Field15:=0;
CIN.Field16:=0;
end.
Код calcmean (основной CIN):

Код: Выделить всё

library calcmean;

uses
  SysUtils,
  Windows;

{$R *.res}

type
  TCINHeader = Packed Record
    Init: Cardinal; //CINInit
    Dispose: Cardinal; //CINDispose
    Abort: Cardinal; //CINAbort
    Run: Cardinal; //CINRun
    Load: Cardinal; //CINLoad
    Save: Cardinal; //CINSave
    Unload: Cardinal; //CINUnload
    Properties: Cardinal; //CINProperties
    LVSBHead: Cardinal;
    Field10: Cardinal;
    TypeString: Cardinal;
    CINToolsVersion: Cardinal;
    gLVExtCodeDispatchTable: Cardinal;
    ReloadCounter: Cardinal;
    Field15: Cardinal;
    CINModuleAddress: Cardinal;
    Field17: Cardinal;
    LVRTTable: Cardinal;
    LVSBHeaderPtr: Cardinal;
    Field20: Cardinal;
    Field21: Cardinal;
    Field22: Cardinal;
    Field23: Cardinal;
    Field24: Cardinal;
    Field25: Cardinal;
    Field26: Cardinal;
    Field27: Cardinal;
end;

type
  TD1 = Packed Record
    dimSize: integer;
    arg1: array[0..0] of double;
end;

type TD1Ptr = ^TD1;
type TD1Hdl = ^TD1Ptr;

var CIN: TCINHeader;
    gLVRTModule: cardinal;

DbgPrintf: function(str: pchar): integer; cdecl;
CINSetArraySize: function(DataHandle: pointer; TDPtr: pointer;
                          ParamNumber: integer;
                          NewNumberOfElements: integer): integer; cdecl;
GetSetLVInfo: function(Action: integer; Data: integer): integer; cdecl;

function sum(x: pdouble; n: integer): double; cdecl;
begin
Result := 0;
asm
//pop edi
//pop esi
//pop ebx
leave
push CIN.Field23
ret
end;
end;

// CIN-specific Functions //

function GetTDPtr: pointer; cdecl;
begin
Result:=Ptr(GetSetLVInfo(3,0));
end;

function SetCINArraySize(DataHandle: pointer; ParamNumber: integer;
                         NewNumberOfElements: integer): integer; cdecl;
var TD: pointer;
begin
TD := GetTDPtr;
Result := CINSetArraySize(DataHandle,TD,ParamNumber,NewNumberOfElements);
end;

function GetDSStorage: integer; cdecl;
begin
Result:=GetSetLVInfo(0,0);
end;

function SetDSStorage(NewValue: integer): integer; cdecl;
begin
Result:=GetSetLVInfo(1, NewValue);
end;

function GetLVInternals: pointer; cdecl;
begin
Result:=Ptr(GetSetLVInfo(4,0));
end;

// End of CIN-specific Functions //

function CINInit: integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINInit');
Result:=0; //mgNoErr
end;

function CINDispose: integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINDispose');
Result:=0; //mgNoErr
end;

function CINAbort: integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINAbort');
Result:=0; //mgNoErr
end;

function CINRun(xarrayh: TD1Hdl; meanp: pdouble): integer; cdecl;
var x: pdouble;
    total: double;
    n: integer;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINRun');
x := @xarrayh^.arg1;
n := xarrayh^.dimSize;
total := sum(x,n);
meanp^ := total/n;
Result:=0; //mgNoErr
end;

function CINLoad(Reserved: cardinal): integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINLoad');
Result:=0; //mgNoErr
end;

function CINUnload: integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINUnload');
Result:=0; //mgNoErr
end;

function CINSave(Reserved: cardinal): integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINSave');
Result:=0; //mgNoErr
end;

function CINProperties(Selector: integer; Arg: pointer): integer; cdecl;
var Data: ^boolean;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINProperties');
Data := Arg;
case Selector of
0: //kCINIsReentrant
begin
Data^ := True;
Result:=0; //mgNoErr
Exit;
end
else
Result:=53; //mgNotSupported
end;
end;

function GetLVSBHeader: Cardinal; cdecl;
begin
Result:=Cardinal(@CIN.LVSBHead);
end;

procedure SetLVRTModule(Module: Cardinal); cdecl;
begin
gLVRTModule:=Module;
//
if (gLVRTModule<>0) then begin
DbgPrintf := GetProcAddress(gLVRTModule, 'DbgPrintf');
//if (@DbgPrintf<>nil) then DbgPrintf('DbgPrintf found.');
CINSetArraySize := GetProcAddress(gLVRTModule, 'CINSetArraySize');
GetSetLVInfo := GetProcAddress(gLVRTModule, 'GetSetLVInfo');
end;
end;

exports
GetLVSBHeader name 'GetLVSBHeader',
SetLVRTModule name 'SetLVRTModule';

begin
CIN.Init:=Cardinal(@CINInit);
CIN.Dispose:=Cardinal(@CINDispose);
CIN.Abort:=Cardinal(@CINAbort);
CIN.Run:=Cardinal(@CINRun);
CIN.Load:=Cardinal(@CINLoad);
CIN.Save:=Cardinal(@CINSave);
CIN.Unload:=Cardinal(@CINUnload);
CIN.Properties:=Cardinal(@CINProperties);
CIN.LVSBHead:=0;
CIN.Field10:=0;
CIN.TypeString:=$204E4943; //String "CIN "
CIN.CINToolsVersion:=$00000004; //cintools Version
CIN.gLVExtCodeDispatchTable:=Cardinal(@CIN.Init);
CIN.ReloadCounter:=0;
CIN.Field15:=0;
CIN.CINModuleAddress:=0;
CIN.Field17:=0;
CIN.LVRTTable:=0;
CIN.LVSBHeaderPtr:=0;
CIN.Field20:=0;
CIN.Field21:=0;
CIN.Field22:=0;
CIN.Field23:=$42534C56; //String "VLSB" for linking ext. subroutine
CIN.Field24:=0;
CIN.Field25:=0;
CIN.Field26:=0;
CIN.Field27:=0;
end.
Внимательный читатель уже, наверное, обратил внимание на некоторые отличия в коде шаблонов. Для внешней процедуры, во-первых, используется другой заголовок (TCINHeader), в котором присутствует только одна функция LVSBMain. Во-вторых, в заголовке внешней процедуры указан тип "LVSB", а не "CIN ". В-третьих, в заголовке основного CIN'а поле Filed23 не пусто, а содержит метку "VLSB" (см. главу 1 "Создание (компиляция) внешних процедур.", пункт "P.S."), куда :labview: должен прописать адрес внешней процедуры sum при загрузке :vi: . Следующие поля после Field23 могут теоретически содержать ссылки на другие внешние процедуры. Становится понятен смысл смещения 0x38 (56d) - столько нужно отступить от начала заголовка CIN'а (CIN.LVSBHead), чтобы попасть на поле CIN.Field23: 14 полей по 4 байта каждое -> 14 х 4 = 56. И наконец, в-четвёртых, способ вызова внешней процедуры sum из основного CIN'а calcmean. Так как эта внешняя процедура находится в другой библиотеке, то напрямую вызвать её мы не можем. Используем ассемблерную вставку-клей из главы 1 "Создание (компиляция) внешних процедур.", пункт "P.S.":
function sum(x: pdouble; n: integer): double; cdecl;
begin
Result := 0;
asm
//pop edi
//pop esi
//pop ebx
leave
push CIN.Field23
ret
end;
end;
Так как :labview: пропишет в поле Field23 адрес внешней процедуры, то мы, вызывая эту вставку, будем всегда переходить в тело внешней процедуры. Вот так работает эта особая уличная магия! :crazy: Не знаю, кто в NI придумал такой способ коммуникации модулей (DLL), но опредлённо он заслужил медаль за это, ибо я ничего подобного раньше никогда не видел. :D
Компилируем в командной строке оба CIN'а.
Внешняя процедура:
%CINTOOLS_DIR_5%\win32\lvsbutil sum -t LVSB
Основной CIN:
%CINTOOLS_DIR_5%\win32\lvsbutil calcmean -t CIN
Для удобства можно написать батник, чтобы не открывать cmd каждый раз. Надеюсь, про map-файл не забыли перед компиляцией основного CIN'а?
0:0 _LVSBHead 10003020
0:0 _gLVSBsum 10003058
Если забыли, то см. главу 1 "Создание (компиляция) внешних процедур.", пункт "P.S.".
После успешной компиляции обоих CIN'ов проверяем их работу в :labview: . Крайняя версия :labview: , поддерживающего внешние процедуры, - 7.1. Заходя вперёд, скажу, что у меня получилось создать библиотеку, позволяющую запускать CIN'ы с внешними процедурами в :labview: более поздних версий, начиная с LV2010. Но об этом в другой раз.
mean_BD.jpg
mean_BD.jpg (12.03 КБ) 5885 просмотров
mean_FP.jpg
mean_FP.jpg (19.92 КБ) 5885 просмотров
Легко увидеть, что всё работает именно так, как и должно быть: (1 + 2 + 3 + 4)/4 = 10/4 = 2,5. Ну и, по традиции, сами исходники:
ExtTest.rar
(32.92 КБ) 150 скачиваний
Последний раз редактировалось dadreamer 14 мар 2015, 21:08, всего редактировалось 2 раза.
Аватара пользователя
Pavel Krivozubov

Activity Bronze
professor
professor
Сообщения: 4421
Зарегистрирован: 07 фев 2008, 16:39
Награды: 3
Версия LabVIEW: 7.0 - 2013
Откуда: г. Электросталь
Благодарил (а): 24 раза
Поблагодарили: 9 раз
Контактная информация:

Re: Альтернативный способ запуска CIN (инструкция)

Сообщение Pavel Krivozubov »

Да, точно стояло ограничение. Наверное на администраторов оно не действует :crazy:
Исправил количество вложений на 100.
Попробуй сейчас.
Аватара пользователя
dadreamer

Activity Professionalism Автор
professor
professor
Сообщения: 3926
Зарегистрирован: 17 фев 2013, 16:33
Награды: 4
Версия LabVIEW: 2.5 — 2022
Благодарил (а): 11 раз
Поблагодарили: 127 раз
Контактная информация:

Re: Альтернативный способ запуска CIN (инструкция)

Сообщение dadreamer »

Сейчас всё ок, но тему переправить по-прежнему не могу. Был бы признателен, если бы вы: а) удалили её; или б) сами подправили, если не сложно, конечно; или в) что-то с форумом сделали, чтоб кнопки появились :D
__________________________________________________________________________________________________________________

3. Создание 64-битных CIN'ов.
Как оказалось, такое тоже возможно сделать, причём не сложнее, чем компилировать CIN'ы в Delphi. Официально cintools и входящая в их состав программа lvsbutil не портированы под :labview: x64, однако код загрузки и линковки CIN'ов полностью работоспособен. Для компиляции CIN'а можно не использовать заголовочные файлы и lib'ы (также как в гл. 2. "Использование Delphi для компиляции CIN'а."), а определить необходимые функции динамически. После этого единственным препятствием для загрузки CIN'а в :labview: 64-bit будет неверный тип платформы в ресурсе *.lsb. "Заворачиванием" DLL в lsb-обёртку занимается lvsbutil, так что пришлось малость подкорректировать её работу. Я поменял тип платформы, прописываемый в PLAT-секцию lsb: было i386, стало wx64.
lvsbutil64.rar
(123.11 КБ) 166 скачиваний
Теперь создаваемый CIN грузится в :labview: , но программа вылетает, т.к. нужно поменять типы некоторых полей заголовка CIN'а. Раз IDE 64-битная, то на указатели приходится не 4 байта, а 8. С учётом этого, а также исходя из того, что :labview: x64 всегда выравнивает записи/структуры по границе 8 байт, мы получаем вот такой шаблон для создания CIN'а (используется среда программирования Embarcadero® Delphi XE7):

Код: Выделить всё

library cin64;

uses
  System.SysUtils,
  Winapi.Windows;

{$R *.res}

type
  TCINHeader = Packed Record
    Init: UInt64; //CINInit
    Dispose: UInt64; //CINDispose
    Abort: UInt64; //CINAbort
    Run: UInt64; //CINRun
    Load: UInt64; //CINLoad
    Save: UInt64; //CINSave
    Unload: UInt64; //CINUnload
    Properties: UInt64; //CINProperties
    LVSBHead: Cardinal;
    Field10: Cardinal;
    TypeString: Cardinal;
    CINToolsVersion: Cardinal;
    gLVExtCodeDispatchTable: UInt64;
    ReloadCounter: UInt64;
    Field15: UInt64;
    CINModuleAddress: UInt64;
    Field17: UInt64;
    LVRTTable: UInt64;
    LVSBHeaderPtr: UInt64;
    Field20: Cardinal;
    Field21: Cardinal;
    Field22: Cardinal;
    Field23: Cardinal;
    Field24: Cardinal;
    Field25: Cardinal;
    Field26: Cardinal;
    Field27: Cardinal;
end;

var CIN: TCINHeader;
    gLVRTModule: UInt64;

DbgPrintf: function(Str: PAnsiChar): Integer; cdecl;
CINSetArraySize: function(DataHandle: Pointer; TDPtr: Pointer;
                          ParamNumber: Integer;
                          NewNumberOfElements: Integer): Integer; cdecl;
GetSetLVInfo: function(Action: Integer; Data: Int64): Int64; cdecl;

// CIN-specific Functions //

function GetTDPtr: Pointer; cdecl;
begin
Result:=Pointer(GetSetLVInfo(3,0));
end;

function SetCINArraySize(DataHandle: Pointer; ParamNumber: Integer;
                         NewNumberOfElements: Integer): Integer; cdecl;
var TD: Pointer;
begin
TD := GetTDPtr;
Result := CINSetArraySize(DataHandle, TD, ParamNumber, NewNumberOfElements);
end;

function GetDSStorage: Int64; cdecl;
begin
Result:=GetSetLVInfo(0,0);
end;

function SetDSStorage(NewValue: Int64): Int64; cdecl;
begin
Result:=GetSetLVInfo(1, NewValue);
end;

function GetLVInternals: pointer; cdecl;
begin
Result:=Pointer(GetSetLVInfo(4,0));
end;

// End of CIN-specific Functions //

function CINInit: Integer; cdecl;
begin
if (@DbgPrintf<>nil) then DbgPrintf('CINInit');
Result:=0; //mgNoErr
end;

function CINDispose: Integer; cdecl;
begin
if (@DbgPrintf<>nil) then DbgPrintf('CINDispose');
Result:=0; //mgNoErr
end;

function CINAbort: Integer; cdecl;
begin
if (@DbgPrintf<>nil) then DbgPrintf('CINAbort');
Result:=0; //mgNoErr
end;

function CINRun(Num1: PInteger; Num2: PInteger; Sum: PInteger): Integer; cdecl;
begin
if (@DbgPrintf<>nil) then DbgPrintf('CINRun');
Sum^ := Num1^ + Num2^;
Result:=0; //mgNoErr
end;

function CINLoad(Reserved: Cardinal): Integer; cdecl;
begin
if (@DbgPrintf<>nil) then DbgPrintf('CINLoad');
Result:=0; //mgNoErr
end;

function CINUnload: Integer; cdecl;
begin
if (@DbgPrintf<>nil) then DbgPrintf('CINUnload');
Result:=0; //mgNoErr
end;

function CINSave(Reserved: Cardinal): Integer; cdecl;
begin
if (@DbgPrintf<>nil) then DbgPrintf('CINSave');
Result:=0; //mgNoErr
end;

function CINProperties(Selector: Integer; Data: PBoolean): Integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINProperties');
case Selector of
0: //kCINIsReentrant
begin
Data^ := True;
Result:=0; //mgNoErr
Exit;
end
else
Result:=53; //mgNotSupported
end;
end;

function GetLVSBHeader: UInt64; cdecl;
begin
Result:=UInt64(@CIN.LVSBHead);
end;

procedure SetLVRTModule(Module: UInt64); cdecl;
begin
gLVRTModule:=Module;
//
if (gLVRTModule<>0) then begin
DbgPrintf := GetProcAddress(gLVRTModule, 'DbgPrintf');
//if (@DbgPrintf<>nil) then DbgPrintf('DbgPrintf found.');
CINSetArraySize := GetProcAddress(gLVRTModule, 'CINSetArraySize');
GetSetLVInfo := GetProcAddress(gLVRTModule, 'GetSetLVInfo');
end;
end;

exports
GetLVSBHeader name 'GetLVSBHeader',
SetLVRTModule name 'SetLVRTModule';

begin
CIN.Init:=UInt64(@CINInit);
CIN.Dispose:=UInt64(@CINDispose);
CIN.Abort:=UInt64(@CINAbort);
CIN.Run:=UInt64(@CINRun);
CIN.Load:=UInt64(@CINLoad);
CIN.Save:=UInt64(@CINSave);
CIN.Unload:=UInt64(@CINUnload);
CIN.Properties:=UInt64(@CINProperties);
CIN.LVSBHead:=0;
CIN.Field10:=0;
CIN.TypeString:=$204E4943; //String "CIN "
CIN.CINToolsVersion:=$4; //cintools Version
CIN.gLVExtCodeDispatchTable:=UInt64(@CIN.Init);
CIN.ReloadCounter:=0;
CIN.Field15:=0;
CIN.CINModuleAddress:=0;
CIN.Field17:=0;
CIN.LVRTTable:=0;
CIN.LVSBHeaderPtr:=0;
CIN.Field20:=0;
CIN.Field21:=0;
CIN.Field22:=0;
CIN.Field23:=0;
CIN.Field24:=0;
CIN.Field25:=0;
CIN.Field26:=0;
CIN.Field27:=0;
end.
При этом используются дефолтные настройки самой IDE, в т.ч. Record field alignment = Quad word (8 байт). При желании можно выставить выравнивание на Off или Byte (1 байт), а у объявлений записей убрать ключевое слово packed - это не должно повлиять на функционирование CIN'а.
Также в Delphi XE появилась возможность выполнения пользовательских команд после компиляции проекта. Это значит, что мы можем кинуть lvsbutil в папку с проектом и прописать в пункте Build Events настроек проекта следующее:
cd "$(OutputDir)"
"lvsbutil64" -c "$(OutputName)"
2015-03-29_17-09-12.jpg
Теперь после компиляции библиотеки будет выполняться автоматическое преобразование в lsb-ресурс. В рабочих проектах рекомендую компилировать сразу в Release-конфигурации:
2015-03-29_17-29-51.jpg
2015-03-29_17-29-51.jpg (56.91 КБ) 5868 просмотров
Это обеспечит значительно меньший размер выходного файла (почти в 7 раз).
Исходник шаблона для компиляции 64-битного CIN'а:
cin64.rar
Delphi XE7
(1.3 МБ) 155 скачиваний
Теперь с помощью данного шаблона можем внести соответствующие правки и переделать примеры 1 и 2 из главы 2. "Использование Delphi для компиляции CIN'а.", чтобы запустить их в :labview: x64. Без долгих прелюдий покажу сразу получившиеся исходники.
Пример первый:

Код: Выделить всё

library arr64;

uses
  System.SysUtils,
  Winapi.Windows;

{$R *.res}

type
  TCINHeader = Packed Record
    Init: UInt64; //CINInit
    Dispose: UInt64; //CINDispose
    Abort: UInt64; //CINAbort
    Run: UInt64; //CINRun
    Load: UInt64; //CINLoad
    Save: UInt64; //CINSave
    Unload: UInt64; //CINUnload
    Properties: UInt64; //CINProperties
    LVSBHead: Cardinal;
    Field10: Cardinal;
    TypeString: Cardinal;
    CINToolsVersion: Cardinal;
    gLVExtCodeDispatchTable: UInt64;
    ReloadCounter: UInt64;
    Field15: UInt64;
    CINModuleAddress: UInt64;
    Field17: UInt64;
    LVRTTable: UInt64;
    LVSBHeaderPtr: UInt64;
    Field20: Cardinal;
    Field21: Cardinal;
    Field22: Cardinal;
    Field23: Cardinal;
    Field24: Cardinal;
    Field25: Cardinal;
    Field26: Cardinal;
    Field27: Cardinal;
end;

type
  TD1 = Packed Record
    dimSizes: array[0..1] of Integer;
    arg1: array[0..0] of Double;
end;

type TD1Ptr = ^TD1;
type TD1Hdl = ^TD1Ptr;

type DblArr = array[0..0] of Double;

const ParamNumber = 2;
// The return parameter is parameter 2 //
const NumDimensions = 2;
// 2D Array //

var CIN: TCINHeader;
    gLVRTModule: UInt64;

DbgPrintf: function(Str: PAnsiChar): Integer; cdecl;
CINSetArraySize: function(DataHandle: Pointer; TDPtr: Pointer;
                          ParamNumber: Integer;
                          NewNumberOfElements: Integer): Integer; cdecl;
GetSetLVInfo: function(Action: Integer; Data: Int64): Int64; cdecl;
DSNewHandle: function(Size: Integer): Pointer; cdecl;
DSDisposeHandle: function(Handle: Pointer): Integer; cdecl;

// CIN-specific Functions //

function GetTDPtr: Pointer; cdecl;
begin
Result := Pointer(GetSetLVInfo(3,0));
end;

function SetCINArraySize(DataHandle: Pointer; ParamNumber: Integer;
                         NewNumberOfElements: Integer): Integer; cdecl;
var TD: Pointer;
begin
TD := GetTDPtr;
Result := CINSetArraySize(DataHandle, TD, ParamNumber, NewNumberOfElements);
end;

function GetDSStorage: Int64; cdecl;
begin
Result := GetSetLVInfo(0,0);
end;

function SetDSStorage(NewValue: Int64): Int64; cdecl;
begin
Result := GetSetLVInfo(1, NewValue);
end;

function GetLVInternals: Pointer; cdecl;
begin
Result := Pointer(GetSetLVInfo(4,0));
end;

// End of CIN-specific Functions //

function CINInit: Integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINInit');
Result:=0; //mgnoErr
end;

function CINDispose: Integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINInit');
Result:=0; //mgnoErr
end;

function CINAbort: Integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINAbort');
Result:=0; //mgNoErr
end;

function CINRun(ah: TD1Hdl; bh: TD1Hdl; resulth: TD1Hdl; Error: PByte): Integer; cdecl;
var i, j, k, l: Integer;
    rows, cols: Integer;
    aElmtp, bElmtp: ^DblArr;
    resultElmtp: PDouble;
    err: Integer;
    newNumElmts: Integer;
label _Out;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINRun');
err := 0; //noErr;
k := ah^.dimSizes[1];
if (k<>bh^.dimSizes[0]) then begin
Error^ := 1;
goto _Out;
end;
Error^ := 0;
rows := ah^.dimSizes[0];
// number of rows in a and result //
cols := bh^.dimSizes[1];
// number of cols in b and result //
newNumElmts := rows * cols;
err := SetCINArraySize(resulth, ParamNumber, newNumElmts);
if (err<>0) then goto _Out;
resulth^.dimSizes[0] := rows;
resulth^.dimSizes[1] := cols;

aElmtp := @ah^.arg1;
bElmtp := @bh^.arg1;
resultElmtp := @resulth^.arg1;

for i:=0 to rows-1 do
 for j:=0 to cols-1 do begin
  resultElmtp^ := 0;
   for l:=0 to k-1 do
    resultElmtp^ := resultElmtp^ + aElmtp^[i*k + l] * bElmtp^[l*cols + j];
  Inc(resultElmtp);
end;
_Out:
Result := err;
end;

function CINLoad(Reserved: Cardinal): Integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINLoad');
Result:=0; //mgNoErr
end;

function CINUnload: Integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINUnload');
Result:=0; //mgNoErr
end;

function CINSave(Reserved: Cardinal): Integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINSave');
Result:=0; //mgNoErr
end;

function CINProperties(Selector: Integer; Data: PBoolean): Integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINProperties');
case Selector of
0: //kCINIsReentrant
begin
Data^ := True;
Result:=0; //mgNoErr
Exit;
end
else
Result:=53; //mgNotSupported
end;
end;

function GetLVSBHeader: UInt64; cdecl;
begin
Result:=UInt64(@CIN.LVSBHead);
end;

procedure SetLVRTModule(Module: UInt64); cdecl;
begin
gLVRTModule:=Module;
//
if (gLVRTModule<>0) then begin
DbgPrintf := GetProcAddress(gLVRTModule, 'DbgPrintf');
//if (@DbgPrintf<>nil) then DbgPrintf('DbgPrintf found.');
CINSetArraySize := GetProcAddress(gLVRTModule, 'CINSetArraySize');
GetSetLVInfo := GetProcAddress(gLVRTModule, 'GetSetLVInfo');
DSNewHandle := GetProcAddress(gLVRTModule, 'DSNewHandle');
DSDisposeHandle := GetProcAddress(gLVRTModule, 'DSDisposeHandle');
end;
end;

exports
GetLVSBHeader name 'GetLVSBHeader',
SetLVRTModule name 'SetLVRTModule';

begin
CIN.Init:=UInt64(@CINInit);
CIN.Dispose:=UInt64(@CINDispose);
CIN.Abort:=UInt64(@CINAbort);
CIN.Run:=UInt64(@CINRun);
CIN.Load:=UInt64(@CINLoad);
CIN.Save:=UInt64(@CINSave);
CIN.Unload:=UInt64(@CINUnload);
CIN.Properties:=UInt64(@CINProperties);
CIN.LVSBHead:=0;
CIN.Field10:=0;
CIN.TypeString:=$204E4943; //String "CIN "
CIN.CINToolsVersion:=$4; //cintools Version
CIN.gLVExtCodeDispatchTable:=UInt64(@CIN.Init);
CIN.ReloadCounter:=0;
CIN.Field15:=0;
CIN.CINModuleAddress:=0;
CIN.Field17:=0;
CIN.LVRTTable:=0;
CIN.LVSBHeaderPtr:=0;
CIN.Field20:=0;
CIN.Field21:=0;
CIN.Field22:=0;
CIN.Field23:=0;
CIN.Field24:=0;
CIN.Field25:=0;
CIN.Field26:=0;
CIN.Field27:=0;
end.
CIN_Array_64.rar
Delphi XE7
(1.31 МБ) 154 скачивания
Пример второй:

Код: Выделить всё

library avg64;

uses
  System.SysUtils,
  Winapi.Windows;

{$R *.res}

type
  TCINHeader = Packed Record
    Init: UInt64; //CINInit
    Dispose: UInt64; //CINDispose
    Abort: UInt64; //CINAbort
    Run: UInt64; //CINRun
    Load: UInt64; //CINLoad
    Save: UInt64; //CINSave
    Unload: UInt64; //CINUnload
    Properties: UInt64; //CINProperties
    LVSBHead: Cardinal;
    Field10: Cardinal;
    TypeString: Cardinal;
    CINToolsVersion: Cardinal;
    gLVExtCodeDispatchTable: UInt64;
    ReloadCounter: UInt64;
    Field15: UInt64;
    CINModuleAddress: UInt64;
    Field17: UInt64;
    LVRTTable: UInt64;
    LVSBHeaderPtr: UInt64;
    Field20: Cardinal;
    Field21: Cardinal;
    Field22: Cardinal;
    Field23: Cardinal;
    Field24: Cardinal;
    Field25: Cardinal;
    Field26: Cardinal;
    Field27: Cardinal;
end;

type
  dsGlobalStruct = Packed Record
    total: double;
    numElements: integer; //CINDispose
end;

type PdsGlobalStruct = ^dsGlobalStruct;
type HdsGlobalStruct = ^PdsGlobalStruct;

var CIN: TCINHeader;
    gLVRTModule: UInt64;

DbgPrintf: function(Str: PAnsiChar): Integer; cdecl;
CINSetArraySize: function(DataHandle: Pointer; TDPtr: Pointer;
                          ParamNumber: Integer;
                          NewNumberOfElements: Integer): Integer; cdecl;
GetSetLVInfo: function(Action: Integer; Data: Int64): Int64; cdecl;
DSNewHandle: function(Size: Integer): Pointer; cdecl;
DSDisposeHandle: function(Handle: Pointer): Integer; cdecl;

// CIN-specific Functions //

function GetTDPtr: Pointer; cdecl;
begin
Result := Pointer(GetSetLVInfo(3,0));
end;

function SetCINArraySize(DataHandle: Pointer; ParamNumber: Integer;
                         NewNumberOfElements: Integer): Integer; cdecl;
var TD: Pointer;
begin
TD := GetTDPtr;
Result := CINSetArraySize(DataHandle, TD, ParamNumber, NewNumberOfElements);
end;

function GetDSStorage: Int64; cdecl;
begin
Result := GetSetLVInfo(0,0);
end;

function SetDSStorage(NewValue: Int64): Int64; cdecl;
begin
Result := GetSetLVInfo(1, NewValue);
end;

function GetLVInternals: Pointer; cdecl;
begin
Result := Pointer(GetSetLVInfo(4,0));
end;

// End of CIN-specific Functions //

function CINInit: Integer; cdecl;
var dsGlobals: HdsGlobalStruct;
    err: integer;
label _Out;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINInit');
err := 0; //noErr
dsGlobals := DSNewHandle(SizeOf(dsGlobalStruct));
if not Assigned(dsGlobals) then begin
// if 0, ran out of memory //
err := 2; //mFullErr
goto _Out;
end;
dsGlobals^.numElements:=0;
dsGlobals^.total:=0;
SetDSStorage(Int64(dsGlobals));
_Out:
Result:=err;
end;

function CINDispose: Integer; cdecl;
var dsGlobals: HdsGlobalStruct;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINDispose');
dsGlobals := Pointer(GetDSStorage);
if Assigned(dsGlobals) then DSDisposeHandle(dsGlobals);
Result:=0; //mgNoErr
end;

function CINAbort: Integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINAbort');
Result:=0; //mgNoErr
end;

function CINRun(NewNumber, Average: PDouble): Integer; cdecl;
var dsGlobals: HdsGlobalStruct;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINRun');
dsGlobals := Pointer(GetDSStorage);
if Assigned(dsGlobals) then begin
dsGlobals^.total := dsGlobals^.total + NewNumber^;
dsGlobals^.numElements := dsGlobals^.numElements + 1;
Average^ := dsGlobals^.total / dsGlobals^.numElements;
end;
Result:=0; //mgNoErr
end;

function CINLoad(Reserved: Cardinal): Integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINLoad');
Result:=0; //mgNoErr
end;

function CINUnload: Integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINUnload');
Result:=0; //mgNoErr
end;

function CINSave(Reserved: Cardinal): Integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINSave');
Result:=0; //mgNoErr
end;

function CINProperties(Selector: Integer; Data: PBoolean): Integer; cdecl;
begin
//if (@DbgPrintf<>nil) then DbgPrintf('CINProperties');
case Selector of
0: //kCINIsReentrant
begin
Data^ := True;
Result:=0; //mgNoErr
Exit;
end
else
Result:=53; //mgNotSupported
end;
end;

function GetLVSBHeader: UInt64; cdecl;
begin
Result:=UInt64(@CIN.LVSBHead);
end;

procedure SetLVRTModule(Module: UInt64); cdecl;
begin
gLVRTModule:=Module;
//
if (gLVRTModule<>0) then begin
DbgPrintf := GetProcAddress(gLVRTModule, 'DbgPrintf');
//if (@DbgPrintf<>nil) then DbgPrintf('DbgPrintf found.');
CINSetArraySize := GetProcAddress(gLVRTModule, 'CINSetArraySize');
GetSetLVInfo := GetProcAddress(gLVRTModule, 'GetSetLVInfo');
DSNewHandle := GetProcAddress(gLVRTModule, 'DSNewHandle');
DSDisposeHandle := GetProcAddress(gLVRTModule, 'DSDisposeHandle');
end;
end;

exports
GetLVSBHeader name 'GetLVSBHeader',
SetLVRTModule name 'SetLVRTModule';

begin
CIN.Init:=UInt64(@CINInit);
CIN.Dispose:=UInt64(@CINDispose);
CIN.Abort:=UInt64(@CINAbort);
CIN.Run:=UInt64(@CINRun);
CIN.Load:=UInt64(@CINLoad);
CIN.Save:=UInt64(@CINSave);
CIN.Unload:=UInt64(@CINUnload);
CIN.Properties:=UInt64(@CINProperties);
CIN.LVSBHead:=0;
CIN.Field10:=0;
CIN.TypeString:=$204E4943; //String "CIN "
CIN.CINToolsVersion:=$4; //cintools Version
CIN.gLVExtCodeDispatchTable:=UInt64(@CIN.Init);
CIN.ReloadCounter:=0;
CIN.Field15:=0;
CIN.CINModuleAddress:=0;
CIN.Field17:=0;
CIN.LVRTTable:=0;
CIN.LVSBHeaderPtr:=0;
CIN.Field20:=0;
CIN.Field21:=0;
CIN.Field22:=0;
CIN.Field23:=0;
CIN.Field24:=0;
CIN.Field25:=0;
CIN.Field26:=0;
CIN.Field27:=0;
end.
CIN_Avg_64.rar
Delphi XE7
(1.31 МБ) 140 скачиваний
Скриншоты FP и BD в :labview: я здесь не привожу, т.к. они аналогичны скринам из гл. 2. "Использование Delphi для компиляции CIN'а.". Желающие могут проверить работоспособность CIN'ов в :labview: x64 самостоятельно. Естественно, в другой IDE, позволяющей компилировать 64-битные DLL, также можно создать CIN'ы, придерживаясь указанных в статье рекомендаций. Особых подводных камней я здесь не обнаружил.
__________________________________________________________________________________________________________________

4. "Несколько слов" о Watcom'овских CIN'ах.
Мало кто знает о том, что в старых версиях :labview: , включая 4 и 5, существовал особый тип CIN'а, создаваемый при помощи компилятора Watcom. В издании "Code Interface Reference Manual" сказано, что такой CIN можно компилировать на Windows 3.1 и он будет работать на Windows NT и выше, а также этот CIN может быть создан непосредственно на Win NT и выше с рядом функциональных ограничений. Под Windows 3.1 мне не довелось скомпилировать всё это дело, но на Windows XP я попробовал и, как ни странно, всё заработало. Были испробованы компиляторы Watcom C++ 11.0c и Open Watcom 1.9 с одинаковым результатом. Сама библиотека после компиляции получается в формате .REX (Phar Lap relocatable executable), и заголовок файла начинается с символов "MQ" (вместо стандартных "MZ" для EXE/DLL). Подробного описания этого формата я найти не смог, да и не сильно хотел найти, т.к. технология очень старая (мягко говоря), досталась в наследство от 16-битных ОС, и смысл её применения на современных системах стремится к нулю. Единственное, что я сделал - воспроизвёл алгоритм загрузки Watcom'овского CIN'а в :labview: (см. далее). Что примечательно, подобные CIN'ы не извлекаются во временную папку при загрузке :vi: . Вместо этого :labview: копирует в отдельную область памяти весь код CIN'а (заголовок + основные функции: CINRun и т.д.). В заголовке первоначально вместо адресов функций содержатся константы-смещения. Добавляя к ним действующие адреса в запущенном экземпляре :labview: , получаем верные адреса в памяти. Эти адреса прописываются на старое место. Когда требуется запуск конкретной функции (CINRun и т.д.), :labview: просто вызывает блок с кодом CIN'а, берёт из заголовка адрес функции и командой CALL вызывает содержимое по этому адресу. В этом отличие от обычных CIN'ов, которые по сути представляют собой DLL со специфической таблицей экспорта, и нужно их загружать через LoadLibrary, получать адреса через GetProcAddress и прочее. Так что именно Watcom'овские CIN'ы должны быть relocatable (как говорится в Code Interface Reference Manual и Using External Code In LabVIEW), что следует из их природы.
Немного упрощённый алгоритм загрузки и линковки MQ CIN'ов можно изучить с помощью данного :vi: :
Full_MQ_Alg_LV13.vi
алгоритм линковки MQ CIN'ов
(52.6 КБ) 164 скачивания
Компилируется Watcom'овский CIN довольно просто:
cd C:\[папка с исходниками]
"C:\WATCOM\binw\wmake.exe" /f [имя make-файла].lvm
При этом make-файл должен выглядеть так:
name = [имя make-файла]
type = CIN (или LVSB для внешней процедуры)
!include $(%CINTOOLS_DIR_5)\generic.mak
Как видно, вместо nmake используется wmake, а в make-файл включается вместо ntlvsb.mak файл generic.mak. Кстати, и cintools для таких CIN'ов можно использовать только старых версий (см. гл. 1. "Создание (компиляция) внешних процедур.").
Для желающих взглянуть на MQ CIN'ы я залил примитивный :vi: :
Dbg.rar
lv2011
(5.33 КБ) 156 скачиваний
Также более детально с ними можно ознакомиться в :labview: начиная с 2.5 и заканчивая 5.0 включительно (папка vi.lib -> analysis - большинство :vi: здесь содержат CIN или несколько CIN'ов; в папке lvsb лежат внешние процедуры, которые используются некоторыми CIN'ами). Для извлечения lsb из :vi: может потребоваться LSBExtractor (см. гл. 0 "Альтернативный способ запуска CIN").
Ещё один :vi: в качестве примера взят из демо-версии :labview: 4.0 (её можно скачать отсюда):
Mean.rar
lv2011
(6.73 КБ) 168 скачиваний
Последний раз редактировалось dadreamer 29 мар 2015, 20:31, всего редактировалось 3 раза.
Аватара пользователя
Pavel Krivozubov

Activity Bronze
professor
professor
Сообщения: 4421
Зарегистрирован: 07 фев 2008, 16:39
Награды: 3
Версия LabVIEW: 7.0 - 2013
Откуда: г. Электросталь
Благодарил (а): 24 раза
Поблагодарили: 9 раз
Контактная информация:

Re: Альтернативный способ запуска CIN (инструкция)

Сообщение Pavel Krivozubov »

В Мазилле все нормально (последняя версия 19.0.2.).
test.png
У вас могут стоять какие-то плагины и тд, которые могут в итоге повлиять на отображение.
Аватара пользователя
dadreamer

Activity Professionalism Автор
professor
professor
Сообщения: 3926
Зарегистрирован: 17 фев 2013, 16:33
Награды: 4
Версия LabVIEW: 2.5 — 2022
Благодарил (а): 11 раз
Поблагодарили: 127 раз
Контактная информация:

Re: Альтернативный способ запуска CIN (инструкция)

Сообщение dadreamer »

Действительно, виноват был Flashblock, только непонятно было, почему он удаляет кнопки на сайте. Добавил форум в белый список, всё стало нормально. Тему отредактировал, можете опубликовать.
__________________________________________________________________________________________________________________

5. Запуск внешних процедур в :labview: 8.0 и выше.
Как и обещал, в этой главе поделюсь способом запуска внешних процедур (external subroutines) в LV последних версий, начиная с LV 8.0. Как уже неоднократно говорилось, последняя версия :labview: , где работают внешние процедуры - 7.1. В остальных :labview: после 7.1 попытка запуска :vi: с внешней процедурой приводит к выдаче следующего диалога:
2015-04-04_14-36-39.jpg
2015-04-04_14-37-22.jpg
Кнопка запуска :vi: при этом перечёркнута и запуск программы не представляется возможным. На мой взгляд, NI поступили крайне неосмотрительно, выпилив этот функционал (но оставив в работе основной механизм загрузки-линковки CIN'ов), пусть он использовался крайне редко и прежде, но факт в том, что старые наработки оказались "за бортом" прогресса и не существовало никакой возможности их применения, кроме полного переписывания кода из исходников.

Это одна из причин, благодаря которой я решил исправить это недоразумение. Вторая - обычное любопытство и желание запустить старый код и посмотреть, как он работает. Собственно коррекция выполнена следующим образом. Т.к., загрузка-линковка CIN'а, ссылающегося на внешнюю процедуру, выпилена на программном уровне из экзешника :labview: (см. гл. 1. "Создание (компиляция) внешних процедур."), то пришлось воссоздать весь этот код и "подсунуть" его :labview:, чтобы он вместо заглушки-диалога об ошибке начал рекурсивно загружать основной CIN и все связанные с ним внешние процедуры. Код был воссоздан путём изучения алгоритма загрузки CIN'ов в :labview: старых версий. Перенаправление механизма загрузки CIN'ов в :labview: с дефолтного на нужный выполнено путём модификации кода в оперативной памяти :labview: . Это делается единоразово в момент загрузки пользовательской DLL. После выгрузки DLL код загрузки CIN'ов меняется на дефолтный. Никаких изменений на файловом уровне не производится, что предотвращает потенциальное повреждение файлов :labview: или пользовательских файлов.

Описывать весь алгоритм работы этого загрузчика я не стану, т.к. он достаточно большой, а кому интересно - можете изучить исходники библиотеки. Я постарался тщательно оттестировать загрузчик на разных версиях :labview: и на разных CIN'ах с разными внешними процедурами. Было исправлено бесчисленное количество багов. Но возможно, что я что-то пропустил. С этой целью создан отладочный вывод, который в режиме создания программ выглядит как окно Debugging Window, куда выводится весь процесс загрузки-линковки CIN'ов. В режиме RTE (exe) такое окно недоступно, поэтому отладка пишется в специальный текстовый файл.

Итак, вот сама DLL (upd: последняя версия библиотеки размещена в конце поста).

Библиотека полностью написана на Delphi 7. В комплекте лежит :vi: Load DLL.vi, при открытии которого библиотека загружается в память и модифицирует образ :labview: . Визуально это выглядит так:
2015-04-04_15-12-36.jpg
Пользователь может и сам загрузить lvsb.dll в любой момент, для этого достаточно кинуть на БД CLFN, выбрать в нём lvsb.dll и указать функцию ShowGlobalList. Ни параметров, ни возвращаемого значения функция не имеет. Однако, если уже загружены какие-либо CIN'ы с внешними процедурами, функция выведет в окно отладки имена этих внешних процедур, физические пути к их файлам и т.д.:
2015-04-04_15-24-52.jpg
Как видно на скрине, основной CIN "caller "вызывает внешнюю процедуру "change", а она в свою очередь вызывает две других - "addtwo" и "multiplythree". Значения в круглых скобках - указатели на LVSBHeader'ы, т.е. на заголовки внешних процедур. В квадратных скобках содержится информация о "родителе" процедуры (кем она вызывается), путь к файлу процедуры и смещение в LVSBHeader'е "родителя", по которому лежит ссылка на текущую процедуру. Иерархия CIN'ов с внешними процедурами является древовидной, в данном примере все взаимосвязи можно показать так:
lvsb_struct.jpg
lvsb_struct.jpg (35.92 КБ) 5863 просмотра
Причём каждая процедура может вызывать какое угодно число остальных процедур, из-за чего вызовы могут выполняться не линейно сверху вниз, а хоть как: либо в рамках одного уровня (влево/вправо), либо с уровня на уровень (и сверху вниз, и снизу вверх). Поэтому оптимальный подход при работе с такой струтурой - рекурсивный перебор всех "ветвей" основного CIN'а. Данный подход и был реализован в lvsb.dll.

Теперь то, что касается версий. Поддерживаются :labview: с 8.0 по 2016. Возможна поддержка вверх (2017 и т.д.) в будущем при условии, что код загрузки-линковки CIN'ов не претерпит существенных изменений. 64-битные версии :labview: поддерживаться не будут, так как нет нормального 64-битного отладчика и, самое главное, уймы времени, необходимого на исследования и испытания.

В остальном обеспечена полная (насколько это возможно) поддержка: загрузка :vi: (load), сохранение :vi: (save) (в т.ч. в старые версии), очистка/удаление CIN'а (purge). Также в полной мере работает компиляция проекта в exe и работа в RTE. Про RTE требуется сказать "пару слов". В отличие от работы в режиме отладки lvsb.dll нужно грузить не в exe, а в lvrt.dll. Поэтому возможно два варианта:
1. Каждый раз при запуске экзешника проекта открывать Load DLL.vi, а затем основной :vi: проекта. Это довольно неудобно, но имеет место быть.
2. Пропатчить lvrt.dll, чтобы он грузил lvsb.dll каждый раз при своей инициализации. Это можно сделать в программе Stud_PE или CFF Explorer. Перед этими манипуляциями лучше сделать копию оригинального lvrt.dll на всякий пожарный случай.

Stud_PE:
Открываем файл lvrt.dll, лежащий по адресу C:\Program Files (x86)\National Instruments\Shared\LabVIEW Run-Time\[версия LV] и переходим на вкладку Functions.
2015-04-04_16-18-19.jpg
Жмём ПКМ в левом поле со списком зависимостей -> Add New Import. Появляется следующее окно:
2015-04-04_16-21-36.jpg
2015-04-04_16-21-36.jpg (44.79 КБ) 5863 просмотра
Кладём lvsb.dll в ту же папку, где лежит lvrt.dll. Нажимаем Dll Select -> выбираем lvsb.dll. Жмём Select func. -> выбираем ShowGlobalList. После этого нажимаем Add to list.
2015-04-04_16-25-40.jpg
2015-04-04_16-25-40.jpg (58.34 КБ) 5863 просмотра
Теперь нажимаем кнопку ADD и происходит модификация списка импорта lvrt.dll. В конец этого списка добавляется lvsb.dll:
2015-04-04_16-27-36.jpg
Теперь жмём ОК и получаем пропатченную lvrt.dll.

CFF Explorer:
Открываем файл lvrt.dll, лежащий по адресу C:\Program Files (x86)\National Instruments\Shared\LabVIEW Run-Time\[версия LV] и переходим на вкладку Import Adder.
2015-04-04_16-33-42.jpg
Кладём lvsb.dll в ту же папку, где лежит lvrt.dll. Нажимаем Add -> выбираем lvsb.dll. Выбираем ShowGlobalList -> жмём Import By Name.
2015-04-04_16-43-46.jpg
Теперь нажимаем кнопку Rebuild Import Table и происходит модификация списка импорта lvrt.dll. Появляется окно с сообщением об успешной модификации:
2015-04-04_16-39-36.jpg
2015-04-04_16-39-36.jpg (24.19 КБ) 5863 просмотра
Сохраняем изменённый файл (File -> Save) с перезаписью и выходим (Exit) - lvrt.dll пропатчена.

После этого все экзешники, скомпилированные в :labview: соответствующей версии, будут нормально грузить CIN'ы с внешними процедурами. В RTE отладочная информация пишется в текстовый файл lvsb_debug.txt, находящийся в той же папке, что и lvsb.dll и lvrt.dll. Данные в файле соответствуют одному запущенному сеансу приложения, при следующем запуске старая информация стирается. При каких-либо проблемах можно изучить этот файл или даже скопировать его содержимое на форум.

Напоследок размещу здесь пару CIN'ов с внешними процедурами, чтобы было на чём испытывать.
Пример, уже упоминавшийся мной выше - "calling external from external". Можно взять здесь или здесь, но там :vi: слишком старый (4-я версия), поэтому я пересохранил в более свежей версии:
calling external from external.rar
(19.01 КБ) 138 скачиваний
Также упоминавшийся в других главах пример - CalcMean:
CalcMean.rar
(12.67 КБ) 132 скачивания
Пример из LV 5.0 - :vi: для вычисления спектра мощности входного сигнала - "Power Spectrum". Исходника CIN'а нет, т.к. этот инструмент распространялся вместе с LabVIEW, но можно изучить запутанную структуру CIN'а с помощью ShowGlobalList. Стоит отметить, что этот CIN в .REX формате (см. гл. 4. "Несколько слов о Watcom'овских CIN'ах.").
spectrum.rar
(10.79 КБ) 169 скачиваний
_________________________________________

update: Последняя версия библиотеки lvsb.dll - 1.2.0.0.
lvsbdll_1.2.0.0.rar
(208.01 КБ) 135 скачиваний
Следующие баги исправлены:
- при нулевом пути к текущему :vi: / .lsb CIN не загружался в память (сообщение "LSB / VI Path is empty!"); теперь CIN будет загружаться при условии, что ссылка на ресурс не нулевая
- при сохранении :vi: генерировался не совсем корректный LIsb ресурс, из-за чего LV 7.1 не мог прочитать такой :vi:

Кроме прочего, выполнены следующие изменения:
- добавлена поддержка :labview: 8.0, 8.2, 8.5, 8.6 и 2009; таким образом, поддерживается вся линейка :labview: , из которых внешние процедуры были функционально "выпилены" (все версии LV после 7.1) (только 32-разрядные версии!)

Известные баги и проблемы:
- см. аналогичный пункт для 1.1.0.0

Предыдущие версии:
lvsbdll_1.1.0.0.rar
(202.64 КБ) 95 скачиваний
Следующие баги исправлены:
- :labview: вылетал при следующей последовательности действий: открытие :vi: с CIN'ом -> очистка CIN'а на БД (через Purge Bad Code Resource) -> загрузка того же .lsb ресурса (Load Code Resource) -> запуск :vi:
- :labview: вылетал при следующей последовательности действий: открытие :vi: с CIN'ом -> сохранение :vi: в предыдущей версии -> запуск текущего (открытого) :vi:
- :labview: вылетал при следующей последовательности действий: открытие :vi: с CIN'ом -> запуск билда .exe из :vi: -> закрытие открывшегося окна с параметрами билда кнопкой Cancel -> запуск :vi:
- различные мелкие баги в коде библиотеки

Кроме прочего, выполнены следующие изменения:
- изменён механизм определения пути к текущему :vi: / .lsb / .exe: вместо констант-смещений, определяемых для каждой версии :labview: экспериментально, используется отслеживание пути по дескриптору открытого файла-ресурса; этот метод потенциально более надёжен
- внесены некоторые модификации в алгоритмы загрузки (Load), сохранения (Save) и очистки (Purge) иерархии "Основной (мастер) CIN" - "подчинённые внешние процедуры"
- отладочный вывод в обоих режимах (IDE/RTE) вместо DbgPrintf / .txt теперь пишется в консоль (по запросу возможна компиляция lvsb.dll без отладочных сообщений)
- добавлена поддержка :labview: 2016; работа в последующих версиях (2017 и т.п.) не гарантируется, т.к. код 16-й версии уже местами отличается от предыдущих версий LV

Известные баги и проблемы:
- :labview: вылетает при следующей последовательности действий: открытие :vi: с CIN'ом -> копирование CIN'а на БД через зажатую клавишу Ctrl + перетаскивание -> очистка одного (любого) из CIN'ов (через Purge Bad Code Resource) -> сохранение :vi: ; это баг не lvsb.dll, а самого LabVIEW (новые версии), так как воспроизводится на любых CIN'ах без загрузки lvsb.dll; фикса не будет, т.к. технология CIN'ов более не поддерживается
- при компиляции в .exe внешние процедуры в .llb не встраиваются (и не будут, т.к. работа с .llb не реализована), потому все внешние процедуры должны лежать рядом с созданным .exe, если для них не указаны постоянные пути
- по той же причине (отсутствие поддержки .llb) не рекомендуется встраивать внешние процедуры в ваши .llb (они не будут загружены при загрузке основного :vi: / .lsb); :vi: с CIN'ами без внешних процедур загружаются корректно, находясь в .llb
lvsbdll.rar
(181.26 КБ) 141 скачивание
- первоначальная версия — 0.0.0.0
Ответить
  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «Коммуникация с приложениями»