Skip to content

Создание DLL и импорт функций из неё

edited March 2022 in Tutorials

В OverScript есть базовые функции, которые вшиты в интерпретатор. Дополнительно к ним можно импортировать функции из своей DLL. Можно обходится без импортирования, вызывая функции из библиотек через рефлекшн, но такие вызовы в 7 раз медленнее вызова базовых функций.
Импорт функций из DLL делается директивой #import: #import "MyLib.dll". Можно полный путь указывать: #import @"D:\files\MyLib.dll". Собака указывает, что escape-последовательности в строке не обрабатываются. В пути можно использовать любые переменные среды, а также специальные:
1. %APPDIR% - путь к папке, в которой лежит выполняемый скрипт;
2. %MODDIR% - путь к папке модулей (modules в папке интерпретатора);
3. %OVSDIR% - путь к интерпретатору;
4. %CURDIR% - путь к текущей рабочей папке (Environment.CurrentDirectory).

Например: #import @"%MODDIR%\MyLib.dll" загружает MyLib.dll из папки modules. Из этой папки можно загружать и так: #import <MyLib.dll>.

Сразу покажу пример очень простой библиотеки:

using System;
using OverScript;

namespace OSAdditionalFuncs
{

    [Import] //помечаем атрибутом класс, в котором находится функция импорта
    public class MainClass
    {
        [Import] //помечаем атрибутом функцию импорта
        public static void AddFuncs(Action<string,BF> addFunc)
        {
            addFunc("Hello", new BF().Add(Hello_string, TypeID.String)); //добавление функции Hello к базовым функциям. new BF() - это создание новой базовой функции, а метод Add добавляет делегат с указанием типов параметров функции (в данном примере один string-параметр).

        }
        private static string Hello_string(EvalUnit[] fnArgs, int scope, ClassInstance srcInst, ClassInstance inst, CallStack cstack, EvalUnit csrc)
        {
            string name = fnArgs[0].EvalString(scope, inst, cstack); //вычисляем первый аргумент (по индексу 0)

            return $"Hello, {name}. Today is {DateTime.Now}.";
        }

    }
}

Положили нашу OSAdditionalFuncs.dll в папку со скриптом и тестим:

#import "OSAdditionalFuncs.dll"

WriteLine(Hello("Jack")); //Hello Jack. Today is 06.02.2022 17:39:04.

Интерпретатор ищет в DLL тип с атрибутом [Import], а потом ищет у этого типа метод с атрибутом [Import], и найдя, выполняет этот метод. В метод импорта передаётся делегат addFunc, с помощью которого можно добавлять функции. Метод который мы добавляем - Hello_string. Все добавляемые методы должны иметь набор параметров как у него. Что это за параметры:
1. EvalUnit[] fnArgs - аргументы, передаваемые функции. Это не значения, а эвал-юниты (единицы вычисления: выражения или просто переменные). Не знаю, как их назвать по-другому, поэтому так и буду называть;
2. scope - номер области видимости, из которой вызывается функция;
3. ClassInstance srcInst - не используется, т.к. нужен только при вызове небазовых функций;
4. ClassInstance inst - экземпляр, из которого делается вызов;
5. CallStack cstack - стек вызовов
6. EvalUnit csrc - выражение, из которого делается вызов.

Чтобы получить значение аргумента нужно вычислить эвал-юнит. В примере это делается в строке: string name = fnArgs[0].EvalString(scope, inst, cstack);.

В директиве импорта можно задать что-то вроде пространства имён для доступа к функции:

#import "OSAdditionalFuncs.dll" as Lib

WriteLine(Lib@Hello("Jack")); //Hello Jack. Today is 06.02.2022 17:39:04.

Теперь имя функции - Lib@Hello. Как видим, используется не точка, как обычно, а собака. Это позволяет визуально сразу определить, что делается вызов импортированной функции. С точкой можно было бы принять за вызов метода экземпляра.
А можно так:

#import "OSAdditionalFuncs.dll" as Lib_*

WriteLine(Lib_Hello("Jack")); //Hello Jack. Today is 06.02.2022 17:39:04.

Т.е. так ко всем именам функций при импорте добавится Lib_.

В директиве импорта можно указать имя класса импорта:

#import "OSAdditionalFuncs.dll" as Lib, from=MainClass

Можно указать и имя метода импорта:

#import "OSAdditionalFuncs.dll" as Lib, from=MainClass, call=AddFuncs

Можно указывать несколько функций импорта:

#import "OSAdditionalFuncs.dll" as Lib, call=AddFuncs|AddFuncs2|AddFuncs3

Можно указать имена функций, которые импортировать:

#import "OSAdditionalFuncs.dll" as Lib, funcs=Hello|SomeFunc|Test

Можно разрешить замену уже существующих функций:

#import "OSAdditionalFuncs.dll" as Lib, override=true

Подробно про добавление функций

В примере выше есть строка:

addFunc("Hello", new BF().Add(Hello_string, TypeID.String));

Это самый простой вариант, в котором для функции с именем "Hello" добавляется одна перегрузка. Можно добавлять несколько перегрузок. Допишем в библиотеке новые перегрузки:

using System;
using OverScript;
using static OverScript.BasicFunctions; //чтобы можно было метод EvalArgs использовать

namespace OSAdditionalFuncs
{

    [Import] 
    public class MainClass
    {
        [Import] 
        public static void AddFuncs(Action<string,BF> addFunc)
        {
            addFunc("Hello", new BF().Add(Hello_string, TypeID.String)
                                     .Add(Hello_string_int, TypeID.String, TypeID.Int)
                                     .Add(Hello_string_long, TypeID.String, TypeID.Long)
                                     .Add(Hello_string_objectParams, TypeID.String).HasParams(TypeID.Object)
                    );
        }
        private static string Hello_string(EvalUnit[] fnArgs, int scope, ClassInstance srcInst, ClassInstance inst, CallStack cstack, EvalUnit csrc)
        {
            string name = fnArgs[0].EvalString(scope, inst, cstack);
            return $"Hello, {name}. Today is {DateTime.Now}.";
        }
        private static string Hello_string_int(EvalUnit[] fnArgs, int scope, ClassInstance srcInst, ClassInstance inst, CallStack cstack, EvalUnit csrc)
        {
            string name = fnArgs[0].EvalString(scope, inst, cstack); //вычисляем первый аргумент (по индексу 0)
            int num = fnArgs[1].EvalInt(scope, inst, cstack); //вычисляем второй аргумент (по индексу 1)
            return $"Hello, {name}. Int arg is {num}.";
        }
        private static string Hello_string_long(EvalUnit[] fnArgs, int scope, ClassInstance srcInst, ClassInstance inst, CallStack cstack, EvalUnit csrc)
        {
            string name = fnArgs[0].EvalString(scope, inst, cstack); 
            long num = fnArgs[1].EvalLong(scope, inst, cstack); 
            return $"Hello, {name}. Long arg is {num}.";
        }
        private static string Hello_string_objectParams(EvalUnit[] fnArgs, int scope, ClassInstance srcInst, ClassInstance inst, CallStack cstack, EvalUnit csrc)
        {
            string name = fnArgs[0].EvalString(scope, inst, cstack); 
            object[] arr =EvalArgs(1, fnArgs, scope, inst, cstack); //EvalArgs возвращает массив значений аргументов начиная с заданного индекса (1) 
            //int[] arr = EvalArgs<int>(1, fnArgs, scope, inst, cstack); //если парамсы, например, int, а не не object
            return $"Hello, {name}. Params: {string.Join(", ", arr)}.";
        }

    }
}

Тестим:

#import "OSAdditionalFuncs.dll"

WriteLine(Hello("Jack")); //Hello, Jack. Today is 21.02.2022 18:44:28.
WriteLine(Hello("Jack", 123)); //Hello, Jack. Int arg is 123.
WriteLine(Hello("Jack", 567L)); //Hello, Jack. Long arg is 567.
WriteLine(Hello("Jack", 777, "test", 4.9)); //Hello, Jack. Params: 777, test, 4,9.

У Hello есть 4 перегрузки. Перегрузка Hello_string_objectParams принимает params. Можно и так их передать:

WriteLine(Hello("Jack", new object[]{777, "test", 4.9}));

В AddFuncs для этой перегрузки params устанавливаются методом HasParams:

.Add(Hello_string_objectParams, TypeID.String).HasParams(TypeID.Object)

Перегрузки, Hello_string_int и Hello_string_long могут принимать не только int и long:

WriteLine(Hello("Jack", byte\123)); //Hello, Jack. Int arg is 123.
WriteLine(Hello("Jack", decimal\123)); //Hello, Jack. Long arg is 123.

Значение типа byte принимает Hello_string_int, а decimal - Hello_string_long (если передать число больше long.MaxValue, то будет ошибка при выполнении). В C# функция с long параметром не может принимать decimal.
Можно задать строгое совпадение типа параметра и аргумента:

.Add(Hello_string_int, TypeID.String, TypeID.Int).Strict(1) //1 - индекс аргумента (можно несколько указывать). Если вообще не указывать, то все параметры будут принимать строго значения своего типа.

Теперь для Hello_string_int второй аргумент должен быть строго типа int.

Sign In or Register to comment.