Объявление lisp под NET (nanoCAD 24+) и *error*

Да, в NC24.0 (ну и, соответственно, дальше) появилась возможность объявления lisp-функций через NET. А оно вообще работает? Попробую разобраться. И не пожалею на это отдельной лицензии )

Репозитория на github/gitflic/.. не будет, смысла примерно ноль. Задачка проста и незатейлива - на NET написать функцию округления переданного числа до указанной точности. Выглядеть это должно в лиспе примерно так:

1
(net-round 12.3456789 2) ; 12.35

NET-овский код тоже не блещет сложностью:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System;
using Teigha.DatabaseServices;
using Teigha.Runtime;

namespace NCadLispCheck.LipsDefinitions
{
    public static class RoundLisp
    {
        [LispFunction("net-round")]
        public static double RoundNetLisp(ResultBuffer rb)
        {
            TypedValue[] args = rb.AsArray();
            if (args.Length != 2)
            {
                throw new ArgumentOutOfRangeException(
                    "Аргументов должно быть 2 - первый число, второй - количество знаков после запятой");
            }

            double value = Convert.ToDouble(args[0].Value.ToString());
            int precision = Convert.ToInt32(args[1].Value.ToString());
            return (double)Math.Round((decimal)value, precision, MidpointRounding.AwayFromZero);
        }
    }
}

Ну что ж, создаю проект NET6, подключаю даже не nuget, а напрямую сборки из SDK, запуск...

1
2
3
4
Команда: (net-round 12.3456987 2)
nil
Команда: (setq res (net-round 12.345 2))
nil

Че?! Сначала я подумал, что надо в .csproj прописать не только net6, но и платформу. Поменял. Результат тот же.

Причем, что самое странное - объявление функции-то подхватывается! Но входа в точку останова так и не происходит. Что-то тут не то. При этом создать проект NetFramework со ссылками на host*mgd.dll мне не удалось: при попытке сборки выводятся ошибки типа

1
2
3
Ссылка на тип "MarshalByRefObject" требует его определения в "System.Runtime", но его не удалось найти.

Сборка "hostdbmgd" с удостоверением "hostdbmgd, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" использует "System.Runtime, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" с более высокой версией, чем у сборки "System.Runtime" с удостоверением "System.Runtime, Version=4.1.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", на которую делается ссылка.

Что в принципе логично. Но все равно я не понимаю, что я делаю не так, раз объявленная возможность не работает.

Последняя идея - это подключить dll не из SDK, а от NC24.1 ситуацию не спасло и не изменило. Что ж не так-то?

А если убрать статику хотя бы из объявления класса? Сработало! Хотя, по мне, это дичь полная.

Взаимодействие с *error*
Это задачка, можно сказать, со звездочкой. А как вся эта лабуда будет себя вести, если по ходу дела будут подгружены “левые” коды, в которых может быть переопределена *error*? По слухам, там могут быть не очень приятные варианты…

Напишу несложный лиспик:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
((lambda ()
   (vl-load-com)

   (defun c:kpblc-round (/)
     (net-round 12.3456 3)
   )

   (defun c:loc-error (/ *error*)
     (defun *error* (msg)
       (princ (strcat "\n" msg))
     )

     (/ 50 0.)
     (princ "\nDone!")
   )

   (defun c:unloc-error ()
     (defun *error* (msg)
       (princ (strcat "\n" msg))
     )

     (/ 50 0.)
     (princ "\nDone!")
   )

 (princ)
 )
)

Ну и, чтоб не париться на предмет ручной загрузки, допилю инициализатор приложения:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System.IO;
using HostMgd.ApplicationServices;
using Teigha.Runtime;

namespace NCadLispCheck
{
    public class ExtensionIni : IExtensionApplication
    {
        public void Initialize()
        {
            Document doc = Application.DocumentManager.MdiActiveDocument;
            if (doc != null)
            {
                string lspFile = Path.Combine(Path.GetDirectoryName(typeof(ExtensionIni).Assembly.Location), "kpblc-funs.lsp");
                if (File.Exists(lspFile))
                {
                    doc.SendStringToExecute("(load "" + lspFile.Replace("\", "\\\") + "") ", true, true, true);
                }
            }
        }

        public void Terminate() { }
    }
}

Сборка, запуск, лог командной строки:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Платформа nanoCAD x64 24.1 24.1.6616.4528, © 2019 ООО «Нанософт разработка»


NEW,НОВЫЙ - Создать
Команда: (load "C:\\Users\\kpblc\\source\\repos\\NCadLispCheck\\NCadLispCheck.NET\\bin\\Debug\\net6.0-windows\\kpblc-funs.lsp")
C:UNLOC-ERROR
Команда: KPBLC-ROUND

KPBLC-ROUND - KPBLC-ROUND
12.346
Команда: LOC-ERROR
LOC-ERROR - LOC-ERROR
ошибка: деление на ноль

Команда: KPBLC-ROUND
KPBLC-ROUND - KPBLC-ROUND
12.346

Команда: UNLOC-ERROR
UNLOC-ERROR - UNLOC-ERROR
деление на ноль

Команда: KPBLC-ROUND
KPBLC-ROUND - KPBLC-ROUND
12.346

При этом независимо ни от чего вход в объявление функции происходит, все срабатывает штатно.

Кстати, прикол. В ACAD такой номер пройдет с баааальшим трудом, а вот в NC24.1 – легко!

1
2
3
4
5
6
Команда: (setq net-round nil)
nil
Команда: KPBLC-ROUND

KPBLC-ROUND - KPBLC-ROUND
функция не определена: NET-ROUND

Поэтому если вдруг у вас в коде где-то идет очистка вызываемой функции / функций, будьте внимательны. Учитывая невозможность пошаговой отладки лиспа в NC, можно получить очень неприятные последствия.

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

Размещено в .NET, nanoCAD · Метки: , , , ,



Комментарии

Есть 7 коммент. к “Объявление lisp под NET (nanoCAD 24+) и *error*”
  1. drz пишет:

    почему ты решил, что должно быть static?
    https://github.com/ADN-DevTech/AutoCAD-Net-Wizards/blob/master/AutoCAD-Plugin-Template/ExamplePlugin/myCommands.cs
    https://github.com/ADN-DevTech/AutoCAD-Net-Wizards/blob/ForAutoCAD2024/AutoCADNetWizardsInstaller/Output/AutoCAD%20CSharp%20plug-in/myCommands.cs
    ===================
    учитывая, что МАЭСТРО не поправил и по видимому все собралось и заработало в АК, этот жеж код работает у меня в нано
    https://adn-cis.org/forum/index.php?topic=10382.msg48321#msg48321

    кстати в SDK АК2016

    using Autodesk.AutoCAD.Runtime;
    using Autodesk.AutoCAD.EditorInput;
    using Autodesk.AutoCAD.ApplicationServices;
    ...
    [LispFunction("c:helloworld")]
    public void hw(ResultBuffer args)
    {
    Editor ed =
    Application.DocumentManager.MdiActiveDocument.Editor;
    ed.WriteMessage('\n' + "Hello World!" + '\n');
    }

    потом появился static

  2. drz пишет:

    статический метод то же работает

    public class TEST
    {
    [CommandMethod("t-HelloWorld")]
    public void HelloWorld()
    {
    Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
    ed.WriteMessage("Hello World");
    }

    #region LISP FUNCTION
    [LispFunction("dr-ttt")]
    public static bool LispTest3(ResultBuffer arguments)
    {
    bool res = false;
    Array args = arguments.AsArray();
    if (args.Length == 1)
    {
    string sText = ((TypedValue)(args.GetValue(0))).Value.ToString();
    Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
    ed.WriteMessage("\nAnswer " + sText);
    res = true;
    }
    return res;
    }
    [LispFunction("_tt")]
    public static bool LispTest2(ResultBuffer arguments)
    {
    bool res = false;
    Array args = arguments.AsArray();
    if (args.Length == 1)
    {
    string sText = ((TypedValue)(args.GetValue(0))).Value.ToString();
    Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
    ed.WriteMessage("Answer " + sText);
    res = true;
    }
    return res;
    }
    [LispFunction("_dr_test")]
    public static bool LispTest(ResultBuffer arguments)
    {
    bool res = false;
    Array args = arguments.AsArray();
    if (args.Length == 1)
    {
    string sText = ((TypedValue)(args.GetValue(0))).Value.ToString();
    Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
    ed.WriteMessage("Answer " + sText);
    res = true;
    }
    return res;
    }

    [LispFunction("kpblc-test-resbuf")]
    public static ResultBuffer KpblcTestResBufList(ResultBuffer arg)
    {
    ResultBuffer res = new ResultBuffer();

    res.Add(new TypedValue((int)LispDataType.ListBegin));

    res.Add(new TypedValue((int)LispDataType.ListBegin));
    res.Add(new TypedValue((int)LispDataType.Text, "key"));
    res.Add(new TypedValue((int)LispDataType.DottedPair));
    res.Add(new TypedValue((int)LispDataType.Text, "value"));

    res.Add(new TypedValue((int)LispDataType.ListEnd));
    res.Add(new TypedValue((int)LispDataType.ListEnd));

    return res;
    }

    [LispFunction("kpblc-test-resbuf2")]
    public static ResultBuffer KpblcTestResBufList2(ResultBuffer arg)
    {
    ResultBuffer res = new ResultBuffer();

    res.Add(new TypedValue((int)LispDataType.ListBegin));
    res.Add(new TypedValue((int)LispDataType.ListBegin));

    res.Add(new TypedValue((int)LispDataType.Text, "key"));
    res.Add(new TypedValue((int)LispDataType.Text, "value"));
    res.Add(new TypedValue((int)LispDataType.ListEnd));

    res.Add(new TypedValue((int)LispDataType.ListBegin));
    res.Add(new TypedValue((int)LispDataType.Text, "key2"));
    res.Add(new TypedValue((int)LispDataType.Int32, 123));
    res.Add(new TypedValue((int)LispDataType.ListEnd));

    res.Add(new TypedValue((int)LispDataType.ListEnd));

    return res;
    }

    [LispFunction("kpblc-test-resbuf1")]
    public static ResultBuffer KpblcTestResBufList1(ResultBuffer arg)
    {
    ResultBuffer res = new ResultBuffer();

    res.Add(new TypedValue((int)LispDataType.ListBegin));

    res.Add(new TypedValue((int)LispDataType.ListBegin));
    res.Add(new TypedValue((int)LispDataType.Text, "key"));
    res.Add(new TypedValue((int)LispDataType.DottedPair));
    res.Add(new TypedValue((int)LispDataType.Text, "value"));
    res.Add(new TypedValue((int)LispDataType.ListEnd));

    res.Add(new TypedValue((int)LispDataType.ListBegin));
    res.Add(new TypedValue((int)LispDataType.Text, "key2"));
    res.Add(new TypedValue((int)LispDataType.DottedPair));
    res.Add(new TypedValue((int)LispDataType.Int32, 123));
    res.Add(new TypedValue((int)LispDataType.ListEnd));

    res.Add(new TypedValue((int)LispDataType.ListEnd));

    return res;
    }

    #endregion
    }

    в том году на старом форуме в начале февраля разбирались

  3. Кулик Алексей aka kpblc пишет:

    Ну, во-первых, на многих исхониках (которые я видел в свое время) команды и лисп-функции прописывались в статических классах. И, во-вторых, если объявить команду не статической, она может быть вызывана в NC с ком.строки и без документа, насколько я помню. И плевать на регистрацию команды в меню )

  4. drz пишет:

    нет, static не влияет
    по умолчанию команда всегда выполняется в контексте документа
    очевидно, что эти команды без документа работать не будут


    using Win = System.Windows;
    /////
    [Rtm.CommandMethod("drz_Test1")]
    public static void CmdPTest1()
    {
    Win.MessageBox.Show("static Test1");
    }
    [Rtm.CommandMethod("drz_Test2")]
    public void CmdPTest2()
    {
    Win.MessageBox.Show("Test2");
    }

    флаг CommandFlags.Sessionуказывает, что контекст выполнения команды документ
    так без документа команды будут работать

    [Rtm.CommandMethod("drz_Test3",Rtm.CommandFlags.Session)]
    public static void CmdPTest3()
    {
    Win.MessageBox.Show("static Session Test3");
    }
    [Rtm.CommandMethod("drz_Test4",Rtm.CommandFlags.Session)]
    public void CmdPTest4()
    {
    Win.MessageBox.Show("Session Test2");
    }

    мы с тобой уже обсуждали этот момент год назад)))
    на убиенном форуме, я то же забыл пришлось вспоминать

  5. drz пишет:

    более того
    для команды

    [Rtm.CommandMethod("drz_Test5")]
    public void CmdPTest5()
    {
    Document doc = Application.DocumentManager.MdiActiveDocument;
    if (doc == null)
    {
    return;
    }
    Editor ed = doc.Editor;
    ed.WriteMessage("Проверка документа на null не нужна, команда и так не будет выполнена без документа");
    }

    проверка наличия активного документа не требуется
    ======

    а вот здесь обязательна проверка, команда выполняется в контексте приложения (мы жэж не только с документом можем работать)))

    [Rtm.CommandMethod("drz_Test6", Rtm.CommandFlags.Session)]
    public void CmdPTest6()
    {
    Document doc = Application.DocumentManager.MdiActiveDocument;
    if (doc == null)
    {
    return;
    }
    Editor ed = doc.Editor;
    ed.WriteMessage("Проверка документа на null обязательна, иначе без дакумента выхватим эксепшен");
    }

    как видишь static совсем не при делах),
    по крайней мере в нане

  6. drz пишет:

    флаг CommandFlags.Sessionуказывает, что контекст выполнения команды документ
    заболтался, конечно жэ контент выполнения приложение)))

  7. Кулик Алексей aka kpblc пишет:

    М-да, как-то про CommandFlags я упустил из виду. Спасибо )

Поделитесь своим мнением


Я не робот.