ПОДПРОГРАММА-ФУНКЦИЯ: ОПИСАНИЕ И ВЫЗОВ
Описание подпрограммы-функции ( П-Ф ) и локальный оператор присваивания
Описание П-Ф размещается в рабочем документе перед ее вызовом и включает в себя имя подпрограммы-функции, список формальных параметров (который может отсутствовать) и тело подпрограммы-функции. Для ввода конструкций в тело П-Ф используется палитра инструментов ПРОГРАММИРОВАНИЕ, приведенная на рис. 1.

Рис. 1. Палитра ПРОГРАММИРОВАНИЕ
Каждая П-Ф Math cad имеет оригинальное имя, посредством которого осуществляется обращение к ней. Через это же имя (и только через это имя) «возвращается» результат выполнения П-Ф.
После имени П-Ф идет список формальных параметров, заключенный в круглые скобки. Через формальные параметры «внутрь» П-Ф «передаются» данные, необходимые для выполнения вычислений внутри программы, т.е. все формальные параметры являются входными.
В качестве формальных параметров могут использоваться имена простых переменных, массивов и функций. Формальные параметры отделяются друг от друга запятой.
Замечание 1. П-Ф может не иметь формальных параметров, и тогда данные передаются через имена переменных, определенных выше описания П-Ф.
Тело подпрограммы-функции включает любое число операторов: локальных операторов присваивания, условных операторов и операторов цикла, а также вызов других П-Ф и функций пользователя.
Порядок описания подпрограммы-функции Math cad .
Для ввода в рабочий документ описания П-Ф необходимо выполнить следующие действия:
· ввести имя П-Ф и список формальных параметров, заключенный в круглые скобки
· ввести символ “:” – на экране отображается как “: =”;
· открыть палитру Программирования и щелкнуть кнопкой Add line (см. рис. 1). На экране появится вертикальная черта и вертикальный столбец с двумя полями для ввода операторов, образующих тело П-Ф (см. рис. 2);

Рис. 2. Структура подпрограммы-функции
· перейти в поле 1 (щелкнув на нем мышью или нажав клавишу [Tab]) и ввести первый оператор тела П-Ф.
· затем ввести второй, третий и т.д. операторы, добавляя пустые поля с помощью щелчка на кнопке Add line палитры программирование .
· заполнить самое нижнее поле ввода, введя туда выражение, определяющее возвращаемое через имя П-Ф (см. рис. 3).

Рис. 3. Окончательная структура подпрограммы-функции
Замечание 2. Если результатом работы П-Ф являются несколько величин, то из них в теле П-Ф необходимо сформировать массив и его имя поместить в последней строке тела П-Ф.
Локальный оператор присваивания
Для задания внутри программы значения какой-либо переменной используется так называемый локальный оператор присваивания, имеющий вид:
Обращение к подпрограмме-функции Math cad
Для выполнения П-Ф необходимо обратиться к ее имени с указанием списка фактических параметров (если в описании программы присутствует список формальных параметров), т.е.:
Фактические параметры отделяются друг от друга запятой.
· Очевидно, что между фактическими и формальными параметрами должно быть соответствие по количеству, порядку следования и типу.
Замечание 3. Обращение к П-Ф должно находиться после ее описания, и к моменту обращения фактические параметры должны быть определены.
Пример 4. Обращение к программе f ( x ), приведенной на рис. 4.1.4, может иметь следующий вид:
Заметим, что переменная z никак не связана с «локальной» переменной z , используемой внутри тела П-Ф. ¨
Замечание 4. Передать данные внутрь П-Ф можно, используя внутри подпрограммы переменные, определенные до описания П-Ф (см. пример на рис. 4.2.1).

Рис. 4. Подпрограмма-функция без формальных параметров
Программирование АЛГОРИТМОВ в подпрограмме-функции M ath CAD
Программирование линейных алгоритмов в подпрограмме-функции
Операторы, реализующие этот алгоритм, в теле П-Ф также размещаются последовательно и выполняются все, начиная с первого и заканчивая последним .
Пример 6 . Оформим в виде П-Ф вычисление корней квадратного уравнения ax 2 + bx +c = 0 по формуле
Описание П-Ф root _ poly 2 и обращение к ней приведено на рис. 5.5.1. П-Ф имеет три входных формальных параметра – коэффициенты квадратного уравнения. Выходом является вектор с двумя компонентами. Заметим, что величины x 1, x 2 являются простыми переменными, а не элементами одномерного массива. Поэтому нижние индексы в их именах вводятся после нажатия клавиши [.] – «десятичная точка».
Подпрограмма-функция реализует линейный алгоритм – все операторы выполняются всегда строго последовательно.
Пример программирования линейного алгоритма
Программирование разветвляющихся алгоритмов в подпрограмме-функции
Для программирования разветвляющихся алгоритмов в подпрограмме-функции Math cad можно использовать:
§ условную функции
§ условный оператор if .
Используя эти конструкции, можно «изменить» последовательное выполнение операторов.
Условный оператор. Этот оператор используется только в теле П-Ф и для его ввода необходимо щелкнуть на кнопке if палитры программирование . На экране появляется конструкция с двумя полями ввода, изображенная на рисунке .

Структура условного оператора if
В поле 2 вводится логическое выражение УСЛ (в простейшем случае это выражение отношений). В поле 1 вводится конструкция ВЫР1, которая выполняется, если проверяемое логическое выражение принимает значение 1. Если УСЛ = 0, то ВЫР1 не выполняется. Это соответствует условной структуре, называемой ЕСЛИ – ТО .
Для получения условной структуры ЕСЛИ – ТО – ИНАЧЕ используется оператор otherwise , вводимый с палитры ПРОГРАММИРОВАНИЕ, в поле которого размещается конструкция ВЫР2, которая выполняется, если проверяемое логическое выражение принимает значение 0 (см. рисунок). Оператор otherwise непосредственно следует после условного оператора if .

Реализация структуры ЕСЛИ – ТО – ИНАЧЕ
Для ввода ВЫР2 в поле оператора otherwise необходимо:
· выделить поле, стоящее после оператора if ;
· щелкнуть на кнопке otherwise палитры программированиЕ ;
· в появившееся поле оператора otherwise ввести необходимую конструкцию ВЫР2.
Пример 6. Составим описание П-Ф, вычисляющей функцию y(x), заданную выражением
Описание и вызов П-Ф приведены на рисунке
Реализация разветвляющегося алгоритма
Пример 7. Составим описание П-Ф для вычисления переменной z ( t ) по формуле
Описание П-Ф и ее вызов приведены на рисунке
Реализация разветвляющегося алгоритма
Из описания видно, что функция z (t) получит значение ln(t) только тогда, когда не выполняются условия, записанные в двух вышестоящих строках тела П-Ф.
Внимание! Если в строке 3 ввести просто ln ( t ), то это выражение будет вычисляться всегда вне зависимости от выполнения заданных выше условных операторов.
Задание 2 . Составьте описания П-Ф, реализующих следующие разветвляющиеся алгоритмы:
Вариант 1. При выполнении заданного условия УСЛ необходимо выполнить несколько конструкций MathCAD .

Вариант 2. При невыполнении заданного условия УСЛ необходимо выполнить несколько конструкций MathCAD .
В этом случае необходимо выделить поле оператора otherwise , щелкнуть на кнопке Add line палитры ПРОГРАММИРОВАНИЕ нужное число раз и заполнить появившиеся поля.

Пример 8. Составьте описание П-Ф, вычисляющей значения двух полиномов x ( t ), y ( t ) нулевой или первой степени. Порядок полиномов задается переменной n . Если n < 0или n > 1, то значения полиномов равны 0. Описание П-Ф приведено на рисунке.
Реализация алгоритма примера
Пример 9. Даны два числа x , y . Составить описание П-Ф, которая переменной x присваивает максимальное значение из этих двух чисел, а y – минимальное. Описание приведено на рисунке.
Реализация разветвляющегося алгоритма
Вызов подпрограммы-функции arrangement
Задание 3. Даны три числа a , b , c . Составить П-Ф, реализующую следующий алгоритм. Если , то все числа заменить их квадратами, если , то каждое число заменить максимальным значением из этих трех чисел, в противном случае — сменить знаки у чисел.
Задание 4. Координаты точки на плоскости задаются двумя числами x , y . Составить П-Ф, вычисляющую номер четверти на плоскости, в которую попала точка.
Задание 5 . Длина сторон треугольника задается числами a , b , c . Составить П-Ф, вычисляющую значение целой переменной n по следующему правилу: n = 3, если три стороны равны; n = 2, если любые две стороны равны; n = 1, если все три стороны имеют разную длину.
Программирование циклических алгоритмов в подпрограмме-функции
Циклы можно условно разделить на две группы:
· циклы типа арифметической прогрессии;
· итерационные циклы.
Программирование цикла типа арифметической прогрессии
Для программирования таких циклов используется оператор цикла for (часто называемый оператором цикла с параметром). Для ввода такого оператора необходимо выполнить следующие действия:
· щелкнуть на кнопке for палитры ПрограммированиЕ . На экране появятся поля ввода, изображенные на рисунке

Поля оператора цикла for
· в поле ввода 1 ввести имя переменной, являющейся параметром цикла;
· в поле 2 — закон изменения параметра цикла, используя для этого описание дискретной переменной или описание массива ;
· в поле 3 — операторы, составляющие тело цикла. Если одной строки недостаточно, то дополнительные поля ввода (дополнительные строки) создаются щелчком на кнопке Add line палитры программированиЕ, и тогда слева от тела цикла появляется вертикальная черта, охватывающая тело цикла.
Пример 9. Составить описание П-Ф, реализующей алгоритм формирование вектора.
Заметим, что значение системной переменной ORIGIN (начальное значение индексного выражения) задается равным 1.
Подпрограмма-функция формирования вектора
Пример 9. Для x меняющегося от -2 до 2 с шагом 0.5 вычислить значение f(x) = e -x. Cos (2 x ) и сформировать из этих значений вектор y, т.е. y1 = f(-2), y2 = f(-1.5) и т.д.
В этом примере количество повторений тела цикла определяется по формуле
где xk, x0 – конечное и начальное значения параметра цикла, d – шаг его изменения. Подставив значения, получаем (2 –(–2)) /0.5+1=9.
Следовательно, сформированный вектор y будет содержать 9 элементов.
Описание П-Ф и ее вызов приведены на рисунке. Видно, что в теле цикла выполняется два оператора. Первый оператор формирует элемент массива y , а второй изменяет на 1 значение индекса. ¨
Формирование вектора примера 9
Пример 10. Составить описание П-Ф, где значения параметра цикла задаются вектором.
На рисунке приведено описание такой П-Ф.

Задание 5. Составьте описание П-Ф формирования вектора y примера 9, приняв в качестве параметра цикла переменную i .
Программирование итерационных циклов
Для программирования таких циклов используется оператор цикла while . Для ввода этого оператора необходимо выполнить следующие действия:
· щелкнуть на кнопке while палитры ПрограммированиЕ . На экране появляются элементы, показанные на рисунке

Структура оператора цикла while
· в поле 1 ввести условие выполнения цикла;
· в поле 2 ввести операторы тела цикла. В теле цикла должны присутствовать операторы, которые могут изменить значение условия цикла, иначе цикл будет продолжаться бесконечно.
Оператор цикла while выполняется следующим образом : обнаружив оператор while, Math cad проверяет указанное в операторе условие. Если оно равно 1 (т.е. выполняется), то выполняется тело цикла, и снова проверяется условие. Если условие принимает значение 0, то цикл заканчивается.
Пример 11 . Составим П-Ф, реализующую итерационную процедуру приближенного вычисления корня квадратного , используя итерационную процедуру:
В качестве приближенного значения принимается , удовлетворяющее условию:
где – заданная точность вычисления корня квадратного.
Нет необходимости хранить в памяти все приближенные решения x0, x1, x2,. .. Достаточно хранить предыдущее («старое») значение (обозначим его как xc ) и последующее («новое») значение xn.
К сожалению, организация итерационного цикла с помощью оператора while без дополнительных средств контроля может привести к зацикливанию, т.е. повторению тела цикла «бесконечное» число раз. Например, задав при обращении к П-Ф < 0 , получаем зацикливание.
Поэтому в Math cad имеется специальный оператор break , который позволяет выйти из цикла или приостановить исполнение программы при выполнении заданного в операторе break условия.
Оператор break используется в левом поле ввода условного оператор if , а в правом размещается условие, при выполнении которого происходит прекращение работы цикла или программы. Поэтому первоначально вводится оператор if , а затем заполняются поля этого оператора. Следующий пример показывает написание подпрограммы без «зацикливания» с использованием оператора break.
Пример 12.1 . Составим П-Ф, реализующую итерационную про цедуру вычисления корня квадратного без «зацикливания» .

Рис. 5.3.7. Реализация итерационного цикла без «зацикливания»
Пример 12.2 Составить П-Ф, осуществляющую суммирование ряда с бесконечным числом слагаемых. Накопление суммы прекращается, как только очередное слагаемое по абсолютной величине становится меньше заданной погрешности .
Описание П-Ф и ее вызов показаны на рисунке. Заметим, что вторым формальным параметром является имя функции пользователя, определяющей зависимость величины члена ряда от его номера. При вызове этот формальный параметр заменяется фактическим – именем функции пользователя, описанной до обращения к П-Ф.

Вызовы подпрограммы-функции
Программирование двойных циклов
Варианты вложений операторов цикла
Составить описание П-Ф формирующей матрицу по следующему правилу:
Описание и вызов П-Ф приведены на рисунке. В этой П-Ф параметром внешнего цикла является переменная i , а параметром внутреннего — переменная j .

Реализация двойного цикла
Дополнительные операторы, используемые при программировании циклов
Оператор continue . Обычно используется для продолжения выполнения цикла путем возврата в начало тела цикла. Следующий пример поясняет работу этого оператора.
Пример 12.3 Составить описание П-Ф, формирующей новый вектор из положительных проекций исходного вектора.
Описание приведено на рисунке

Оператор return . Прерывает выполнение П-Ф и возвращает значение операнда, стоящего в поле 1 (см. рисунок).

Структура оператора return
Пример 13. Составить описание П-Ф, находящей первую положительную проекцию исходного вектора. Возможны два варианта программной реализации алгоритма решения этой задачи. Вариант B представляется более простым и «элегантным».


Вариант B
Оператор on error . Этот оператор является обработчиком возникающих при выполнении тех или иных вычислений ошибок и записывается в виде:
Оператор выполняется следующим образом. Если при выполнении < конструкция 2 > возникает ошибка, то выполняется . Если ошибка не возникает, то выполняется .
Пример 14. Используем оператор on error для предотвращения появления ошибки «деление на ноль» при вычислении функции angl ( x , y ). Фрагмент представлен на рисунке

Функция error . Используется для вывода диагностических сообщений при возникновении в вычислениях ошибки и записывается в виде:
Имя функции вводится с клавиатуры . Функция используется в левом поле условного оператора if , как показано в следующем примере.
Пример 16. Запрограммируем вывод диагностического сообщения при попытке спроецировать вектор v на нулевой вектор w . Описание П-Ф и ее вызовы приведены на рисунке.
Внедрение кода с пользой

В статье описан способ построения моста между неуправляемым и управляемым кодом на примере математического пакета Mathcad. На картинке показан пример, как бурундук Тот собирается обрабатывать своё изображение средствами математического пакета. Для этого он «использовал» пользовательскую функцию, написанную на VB.Net, в которой реализована возможность подключения к веб-камере и создания снимка. Результат работы функции сразу доступен в рабочем документе.
Исходники
Для нетерпеливых, кто хочет понять всё сразу, пробежав по-диагонали код, указываю хранилище: NetEFI. Там же можно найти тестовые пользовательские библиотеки на трёх языках: c#, vb.net и c++/cli (VS2012, .Net 2.0, x86-32). Пока доступна только 32-разрядная реализация.
Предыстория
В математической программе Mathcad существует возможность подключения сторонних библиотек. Называется этот интерфейс User EFI и был разработан больше 10 лет тому назад. С тех пор он не менялся вообще, хотя сам Mathcad изменился до неузнаваемости. Было время, когда этот интерфейс выкинули из пакета, но старые пользователи затребовали его обратно и в новых версиях Mathcad Prime этот раритетный интерфейс снова живее всех живых.
Существует довольно доходчивое руководство по созданию пользовательских библиотек, я привёл его в конце статьи. Если вкратце, то процесс выглядит примерно так. Мы создаём обычную dll, где в точке входа, т.е. при её загрузке, регистрируем наши функции. При этом, в описателе функции указываем её адрес для последующего вызова из Mathcad напрямую. Кроме этого, ещё можно зарегистрировать одну таблицу с сообщениями об ошибках. Результат, возвращаемый функцией пользователя в случае ошибки, может использоваться для выбора сообщений из этой таблицы. Вот в общем и вся кухня.
Описатель функции выглядит так:
Структура FUNCTIONINFO
typedef LRESULT (* LPCFUNCTION ) ( void * const, const void * const, . ); // The FUNCTIONINFO structure contains the information that Mathcad uses to register a // user function. Refer below for each member and its description. typedef struct tagFUNCTIONINFO < // Points to a NULL-terminated string that specifies the name of the user // function. char * lpstrName; // Points to a NULL-terminated string that specifies the parameters of the // user function. char * lpstrParameters; // Points to a NULL-terminated string that specifies the function description. char * lpstrDescription; // Pointer to the code that executes the user function. LPCFUNCTION lpfnMyCFunction; // Specifies the type of value returned by the function. The values are // COMPLEX_ARRAY or COMPLEX_SCALAR. long unsigned int returnType; // Specifies the number of arguments expected by the function. Must be // between 1 and MAX_ARGS. unsigned int nArgs; // Specifies an array of long unsigned integers containing input parameter // types. long unsigned int argType[ MAX_ARGS ]; >FUNCTIONINFO;
Проблема в том, что сегодня можно было бы гораздо удобнее писать свои функции, если бы мы делали это на .net языках. Но прямой путь для этого лежит через использование C++/CLI. Вариант «обёртывания» каждой пользовательской функции через переходник на C++/CLI или маршалинг структур, думаю, можно сразу отметать как непрактичный и требующий нетривиальных познаний от пользователя математической программы. Я хочу предложить универсальную «обёртку», которую назвал .Net User EFI.
Возникает вопрос, а как создать универсальную функцию, которую можно было бы регистрировать вместо всех функций всех подключаемых сборок, но при этом в точке входа она обладала бы всей необходимой информацией для вызова конкретной функции из конкретной сборки. Библиотека посредник, в которой такая функция находится, должна автоматически работать с любым количеством подключаемых сборок и функций в них.
Для реализации такой универсальности есть одна существенная проблема. Mathcad требует указывать адрес вызываемой функции, сам же прототип объявлен как имеющий переменное количество параметров. Получается, что в точке входа универсальной функции стек с параметрами будет иметь разный размер и передать эту информацию при вызове функции стандартными средствами нет никакой возможности, т.к. она определяется самим скомпилированным кодом. В структуре выше только сам адрес выступает в качестве параметра, по которому мы могли бы отличить вызов одной функции от другой.
И тут наша мысль должна прийти к одному известному решению, которое называется инъекцией кода. На хабре не раз об этом писали, но вот практических полезных примеров использования такой техники можно найти не так много. В каком-то смысле мы тоже будем перехватывать вызовы функций из dll, всё будет выглядеть немного специфичней, но гораздо проще.
Идея
Итак, что же мы будем инъецировать, внедрять, куда и зачем. Ещё раз проясним ситуацию. Мы хотим написать универсальную функцию, которая будет единообразно обрабатывать все вызовы и распределять их в зависимости от типа вызываемой функции. Mathcad не должен ничего «заподозрить», а у нас должна откуда-то взяться дополнительная информация в точке входа универсальной функции о параметрах вызова.
Решение будет в динамическом формировании кода по адресу, который мы регистрируем в Mathcad. Мы зарезервируем в памяти много места под динамический код. Этот код будет осуществлять вспомогательную работу по передаче параметров универсальной функции. Наперёд скажу, что нам достаточно два параметра, это номер сборки в массиве загруженных сборок и номер функции из сборки. Существует два пути передачи параметров: глобальные переменные и стек. Я выбрал первый вариант, т.к. нарушить баланс стека (в котором находятся параметры) легко, а вот восстановить его в нашем случае, я думаю, будет сложно.
Забыл упомянуть, что типов параметров у функции пользователя всего три и все они передаются по указателю: MCSTRING, COMPLEXSCALAR и COMPLEXARRAY. Максимальное их число также ограничено — 10 штук. Это упрощает реализацию разбора параметров в универсальной функции.
Внедрение
Теперь мы морально готовы, чтобы разобрать конкретную последовательность событий, которая должна происходить на этапе внедрения и после него.
Шаг 1. Пользователь создаёт .net класс, реализующей интерфейс IFunction, который содержит необходимую информацию о функции. Компилирует его в сборку и копирует в папку userefi. Также в этой папке должна находиться сборка-посредник, будем называть её netefi.
Шаг 2. При загрузке Mathcad сборка-посредник netefi воспринимается как пользовательская библиотека. Она осуществляет поиск всех .net сборок в текущей папке и перебора функций в них на предмет реализации интерфейса IFunction.
Шаг 3. netefi сохраняет информацию о сборках и функциях в них во внутренних массивах, при этом, чтобы определить функцию, нужно два числа: индекс сборки и индекс функции в ней.
Шаг 4. netefi перебирает все функции и регистрирует их в Mathcad стандартным образом, но в поле адреса структуры FUNCTIONINFO мы записываем ссылку на динамический код, вид которого определяется двумя индексами из предыдущего шага.
Вот так выглядит конкретная реализация метода внедрения:
Динамический код
static int assemblyId = -1; static int functionId = -1; static PBYTE pCode = NULL; #pragma unmanaged LRESULT CallbackFunction( void * out, . ) < return ::UserFunction( & out ); >#pragma managed // TODO: 64-bit. void Manager::InjectCode( PBYTE & p, int k, int n ) < // Пересылка константы (номера сборки) в глобальную переменную. * p++ = 0xB8; // mov eax, imm32 p[0] = k; p += sizeof( int ); * p++ = 0xA3; // mov [assemblyId], eax ( int * & ) p[0] = & assemblyId; p += sizeof( int * ); // Пересылка константы (номера функции) в глобальную переменную. * p++ = 0xB8; // mov eax, imm32 p[0] = n; p += sizeof( int ); * p++ = 0xA3; // mov [functionId], eax ( int * & ) p[0] = & functionId; p += sizeof( int * ); // jmp to CallbackFunction. * p++ = 0xE9; ( UINT & ) p[0] = ( PBYTE ) ::CallbackFunction - 4 - p; p += sizeof( PBYTE ); >
Метод InjectCode() вызывается в цикле при регистрации функций в Mathcad. Глобальные переменные assemblyId и functionId используются для определения типа функции во время её вызова. Работает это так. Mathcad для каждой функции получает ссылку на такой вот динамический код. При этом в assemblyId записывается индекс сборки, известный на момент загрузки (параметр k), в functionId записывается индекс функции — параметр n. Далее идёт безусловный переход на CallbackFunction(), в которой вызывается наша универсальная функция. Это сделано для того, чтобы можно было в UserFunction() вызывать управляемый код. Директивы unmanaged / managed не дадут этого сделать в CallbackFunction().
Заметьте, что параметром универсальной функции является ссылка на стек CallbackFunction(), т.е. на массив параметров (возвращаемое значение находится там же). Динамический код не портит нам стек, поэтому после завершения CallbackFunction() управление вернётся к Mathcad. Вот и вся магия.
Шаг 5. После того, как регистрация завершена, вы можете вызвать пользовательскую функцию в документе Mathcad. Универсальная функция UserFunction() теперь может восстановить тип функции пользователя по глобальным параметрам assemblyId и functionId и разобрать стек, зная количество и тип параметров.
Шаг 6. Каждый неуправляемый тип параметра функции заменяется на аналог: MCSTRING на String, COMPLEXSCALAR на TComplex (я не стал использовать Complex из .Net 4.0, чтобы не было конфликта) и COMPLEXARRAY на TComplex[,].
Шаг 7. Вызывается реализация метода IFunction.NumericEvaluation для функции. Возвращаемый результат проходит обратную последовательность преобразований и отдаётся в Mathcad.
О реализации
Думаю, что этот конкретный способ внедрения я объяснил более менее понятно. Что касается непосредственно самих исходников проекта, то стоит вкратце упомянуть окружение и некоторые детали. В качестве среды разработки используется Visual Studio 2012, язык C++/CLI, .Net Framework 2.0 (выставлен соответствующий режим в свойствах проектов). Поскольку динамический код, вообще говоря, зависит от разрядности и я ещё не знаю точно как привести его к 64-битному представлению, то все проекты настроены на компиляцию для 32-битных машин. Хотя мне говорили, что изменений будет не много.
Использование глобальных переменных нехорошо, но работа в Mathcad не предполагает одновременный вызов нескольких функций. Там всё делается по-порядку, друг за другом.
В сборке-посреднике реализованы ещё некоторые идеи, которые позволяют полно использовать старый интерфейс в новом окружении. Это касается обработки ошибок и об этом нужно писать отдельно. Весь основной код сосредоточен в одном единственном классе Manager (netefi.cpp). Разбирая тестовые примеры, можно понять как работать с интерфейсом IFunction. Все тестовые примеры на разных языках делают одно и то же, и называются почти одинаково.
Примеры тестируются в Mathcad 15 и Mathcad Prime 3.0. Поскольку сам интерфейс User EFI не менялся больше 10 лет (и вряд ли уже изменится), то можно использовать описанный метод и в других версиях Mathcad, начиная, наверное, с 11 версии. В Mathcad Prime 3.0 пользовательским функциям дали новое название — Custom Functions, хотя начинка та же.
Тестовые примеры
Как было указано выше, вы можете найти их тут. Но статья была бы не полной, если не показать конкретный вид .net пользовательских функций для Mathcad.
Посмотрим как будет выглядеть функция «эхо» для одного строкового параметра.
C# вариант
using System; using NetEFI; public class csecho: IFunction < public FunctionInfo Info < get < return new FunctionInfo( "csecho", "s", "return string", typeof( String ), new[] < typeof( String ) >); > > public FunctionInfo GetFunctionInfo( string lang ) < return Info; >public bool NumericEvaluation( object[] args, out object result ) < result = args[0]; return true; >>
VB.Net вариант
Imports NetEFI Public Class vbecho Implements IFunction Public ReadOnly Property Info() As FunctionInfo _ Implements IFunction.Info Get Return New FunctionInfo("vbecho", "s", "return string", _ GetType([String]), New Type() ) End Get End Property Public Function GetFunctionInfo(lang As String) As FunctionInfo _ Implements IFunction.GetFunctionInfo Return Info End Function Public Function NumericEvaluation(args As Object(), ByRef result As Object) As Boolean _ Implements IFunction.NumericEvaluation result = args(0) Return True End Function End Class
С++/CLI вариант
#pragma once using namespace System; using namespace System::Text; using namespace NetEFI; public ref class cppecho: public IFunction < public: virtual property FunctionInfo^ Info < FunctionInfo^ get() < return gcnew FunctionInfo( "cppecho", "s", "return string", String::typeid, gcnew array < String::typeid >); > > virtual FunctionInfo^ GetFunctionInfo(String^ lang) < return Info; >virtual bool NumericEvaluation( array < Object^ >^ args, [Out] Object ^ % result ) < result = args[0]; return true; >>;
Прочее
Хотя основной функционал уже практически готов, есть некоторые недоделки. К примеру, желательно, чтобы работа универсальной функции выполнялась в отдельном потоке. Эта одна из первых вещей, которые надо реализовать. Прерывание работы путём вызова isUserInterrupted никак не отражена в новом интерфейсе. Вся надежда пока на то, что сам Mathcad может прервать работу функции. Над этим буду думать и это перекликается с работой в потоке.
Текущий проект пока работает только на 32-битных системах. Для добавления 64-битных конфигураций нужно протестировать работу динамического кода на 64-битных системах. Пока нет такой возможности.
Работа с COM внутри пользовательской функции сейчас тоже, видимо, невозможна. Столкнулся я с этим, когда реализовывал функцию для создания снимка с веб-камеры. Один из стандартных вариантов предполагал использовать интерфейс к Clipboard, так вот он не заработал, сообщив о том, что поток должен быть с атрибутом STAThreadAttribute. Решил проблему через Graphics.CopyFromScreen. Тоже нужно разбираться.
Загрузка недостающих сборок также пока сделана не достаточно надёжно, т.к. используется Assembly::LoadFile(). Если же использовать Assembly::LoadFrom(), то Mathcad зависает в этом месте. Есть ещё проблема с отладкой смешанного кода. Почему-то она у меня не заработала как надо. Я практически в уме отлаживал код, спасали только логи.
Может быть кто-то делал что-то подобное и мог бы подсказать хорошие идеи, чтобы упростить мой код. Выслушаю все практические варианты. Было бы вообще замечательно, если бы кто заставил мой проект работать под отладчиком студии в смешанном режиме. Пока работают только точки останова в неуправляемом коде. В тестовых примерах бродить по коду можно, конечно.
Ссылки
Обновление (10.02.2021).
Добавлен 64-битный вариант библиотеки. Проверена работа в Mathcad 15 и Mathcad Prime 6.0. В репозиторий также добавлены дополнительные примеры. Код существенно переработан, версия .Net Framework обновлена до 4.0. Используемый тип TComplex заменён на Complex из .Net.
Заставить работать под .Net Core пока не удалось: после точки входа DllMain код уходит в «астрал».
Как из c вызвать функции mathcad
Функции в Mathcad
Произвольные зависимости между входными и выходными параметрами задаются при помощи функций. Функции принимают набор параметров и возвращают значение, скалярное или векторное (матричное). В формулах рабочего листа можно использовать стандартные встроенные функции, а также функции, определенные пользователем.
Чтобы использовать функцию в выражении, ее следует вызвать по имени, указав в значения фактических входных параметров в скобках после имени функции. Имена простейших математических функций можно ввести с панели инструментов Калькулятор (Arithmetic). Информацию о других функциях можно почерпнуть в справочной системе или в приложении. Вставить в выражение стандартную функцию можно при помощи команды Вставка > Функция (Insert > Function). В диалоговом окне (рис. 1.9) слева выбирается категория, к которой относится функция, а справа – конкретная функция. В нижней части окна выдается информация о выбранной функции. При вводе функции через это диалоговое окно автоматически добавляются скобки и заполнители для значений параметров.

Пользовательские функции должны быть сначала определены. Определение задается при помощи оператора присваивания. В левой части указывается имя пользовательской функции и, в скобках, формальные параметры – переменные, от которых она зависит. Справа от знака присваивания эти переменные должны использоваться в выражении, например

Если для вычисления пользовательской функции необходимо выполнить несколько операций, то в теле функции формируют необходимое число строк с помощью кнопки Add line панели Программирование и в появившиеся заполнители помещают формулы. Отметим, что в операторах тогда допустима операция внутреннего присваивания, задаваемая стрелкой, а все вычисленные подобным образом переменные являются локальными и за пределами функции будут не определенны. Так, на рис. 1.10 функция fct вычисляется за три действия, а переменные с2k и c3k будут видимыми только внутри функции.

При применении пользовательской функции в последующих формулах ее имя вводят вручную. В диалоговом окне Вставка функции оно не отображается.
Как из c вызвать функции mathcad
Чтобы помочь вам приступить к написанию пользовательских функций для PTC Mathcad , имеется ряд примеров кода. См. файл MULTIPLY.C , который находится в подпапке Custom Functions/multiply папки установки PTC Mathcad . MULTIPLY.C содержит функцию с двумя аргументами, которая умножает скаляр на массив. Когда вы скомпилируете и правильно скомпонуете ее, новая функция multiply(a, M) станет доступной после перезапуска PTC Mathcad .
Подробное описание кода в MULTIPLY.C следует далее. Чтобы увидеть код полностью, откройте файл в Visual Studio или любом текстовом редакторе.
#include «mcadincl.h»
Ваша программа C должна включать файл заголовков mcadincl.h , находящийся в подпапке Custom Functions папки установки PTC Mathcad . Этот файл содержит структуры данных, которые позволяют выполнять следующие действия.
• Определять функции, которые может читать PTC Mathcad .
• Выделять и освобождать скаляры и массивы совместимым с PTC Mathcad способом.
• Создавать сообщения об ошибках, которые могут быть возвращены в PTC Mathcad с помощью графического пользовательского интерфейса.
Сообщения об ошибках
Как правило, следующая часть программы определяет коды ошибок для типов ошибок, возникновения которых можно ожидать. Обеспечьте перехват ошибок для несоответствующих типов данных, поскольку это один из наиболее часто встречающихся случаев неправильного применения функций PTC Mathcad . Они определяются совместно, поэтому легко подсчитать общее количество ошибок. Общая сумма требуется функцией CreateUserErrorMessageTable .
#define INTERRUPTED 1
#define INSUFFICIENT_MEMORY 2
#define MUST_BE_REAL 3
#define NUMBER_OF_ERRORS 3
// table of error messages
// if your function never returns an error — you do not need to create this table
char * myErrorMessageTable[NUMBER_OF_ERRORS] =
«interrupted»,.
«insufficient memory»,
«must be real»
>;
PTC Mathcad перехватывает следующие исключения в операциях с плавающей запятой: переполнение, деление на нуль и недопустимая операция. В случае этих исключений PTC Mathcad отображает сообщение об ошибке операции с плавающей запятой под функцией. Когда возникает одна из таких ошибок, PTC Mathcad также освобождает всю выделенную память.
Далее следует код, который выполняет ваш алгоритм. Если вы преобразуете код из существующей библиотеки, необходимо привести его к типам COMPLEXARRAY , COMPLEXSCALAR и MCSTRING , передаваемым и ожидаемым PTC Mathcad .
Первый аргумент в алгоритме является указателем на возвращаемое значение, в данном случае Product . Остальные аргументы являются указателями на входные значения, поступающие из PTC Mathcad .
// this code executes the multiplication
LRESULT MultiplyRealArrayByRealScalar( COMPLEXARRAY * const Product,
LPCCOMPLEXSCALAR Scalar, LPCCOMPLEXARRAY Array )
unsigned int row, col;
// check that the scalar argument is real
if ( Scalar->imag != 0.0)
// if it is not, display «must be real» error message
// under the scalar argument ( the 1st argument )
return MAKELRESULT( MUST_BE_REAL, 1)
// check that the array argument is real
if ( Array->hImag != NULL )
// if it is not, display «must be real» error message
// under the array argument ( the 2nd argument )
return MAKELRESULT( MUST_BE_REAL, 2);
// allocate memory for the product
if( !MathcadArrayAllocate( Product, Array-rows,Array-cols,
TRUE, // allocate memory for the real part
FALSE )) // do not allocate memory for the imaginary part
// if allocation is not successful, return with the appropriate error code
return INSUFFICIENT_MEMORY;
// if all is well so far — go ahead and perform the multiplication
for ( col = 0; col < Product->cols; col++ )
// check that a user has not tried to interrupt the calculation
if (isUserInterrupted())
// if user has interrupted — free the allocated memory
MathcadArrayFree( Product );
// and return with an appropriate error code
return INTERRUPTED;
>
for ( row = 0; row < Product->rows; row++ )
Product->hReal[col][row] =
Scalar-> real*Array-> hReal[col][row];
>
// normal return
return 0;
>
Регистрация функции в PTC Mathcad
Заполните структуру FUNCTIONINFO информацией, необходимой для регистрации функции в PTC Mathcad . Эта структура определяет имя функции в PTC Mathcad , а не имя алгоритма MultiplyRealArrayByRealScalar . Кроме того, она определяет параметры и описание, а также указатель на используемый алгоритм.
FUNCTIONINFO multiply =
// name by which Mathcad will recognize the function
«multiply»,
// description of «multiply» parameters to be used
«a,M»,
// description of the function
«returns the product of real scalar a and real array M»,
// pointer to the executable code
// i.e. code that should be executed when a user types «multiply(a,M) wwID0EVABOB» >Выполните динамическую компоновку библиотеки
DLL обозначает библиотеку динамической компоновки. Следующий код делает эту библиотеку доступной для других функций Windows, в частности PTC Mathcad , через DLL-интерфейс Windows. Необходимо также указать ссылку на точку входа через программную среду. Когда DLL загружается, точка входа DLL вызывается операционной системой. PTC Mathcad требует, чтобы вы регистрировали свои пользовательские функции и свою таблицу сообщений об ошибках во время загрузки DLL.
// DLL entry point code
// the _CRT_INIT function is needed if you are using Microsoft’s 32-bit compiler
BOOL WINAPI _CRT_INIT(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved);
BOOL WINAPI DllEntryPoint (HINSTANCE hDLL, DWORD dwReason, LPVOID lpReserved)
switch (dwReason)
<
case DLL_PROCESS_ATTACH:
// DLL is attaching to the address space of the current process
if (!_CRT_INIT(hDLL, dwReason, lpReserved))
return FALSE;
Регистрация таблицы сообщений об ошибках и функции
Если ваша функция никогда не возвращает ошибку, таблица сообщений об ошибках не требуется. Можно зарегистрировать только одну таблицу сообщений об ошибках на DLL, но можно зарегистрировать несколько функций на DLL. Необходимо также очистить любые остающиеся процессы или определения.
if ( CreateUserErrorMessageTable( hDLL, NUMBER_OF_ERRORS, myErrorMessageTable ) )
// and if the errors register properly, register the user function
CreateUserFunction( hDLL, &multiply );
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
if (!_CRT_INIT(hDLL, dwReason, lpReserved))
return FALSE;
break;
>
return TRUE;
>
#undef INTERRUPTED
#undef INSUFFICIENT_MEMORY
#undef MUST_BE_REAL
#undef NUMBER_OF_ERRORS
Дополнительная информация
• Все значения, передаваемые в и из PTC Mathcad , являются комплексными, имеющими и действительную, и мнимую части в своей структуре (см. mcadincl.h ). Необходимо разделить действительную и мнимую части, если вы намереваетесь обрабатывать их независимо.
• Массивы индексируются сначала по столбцу, а затем по строке в отличие от порядка индексов в PTC Mathcad (сначала по строке, затем по столбцу).
• Все массивы предполагаются двумерными. Если нужно сослаться на вектор, задайте для первого индекса массива (столбец) значение 0.