OberonCore

Библиотека  Wiki  Форум  BlackBox  Компоненты  Проекты
Текущее время: Четверг, 28 Март, 2024 15:16

Часовой пояс: UTC + 3 часа




Начать новую тему Ответить на тему  [ Сообщений: 25 ]  На страницу 1, 2  След.
Автор Сообщение
 Заголовок сообщения: #019 SafeAction — защищенные действия
СообщениеДобавлено: Вторник, 07 Январь, 2020 01:47 
Аватара пользователя

Зарегистрирован: Суббота, 16 Февраль, 2008 02:47
Сообщения: 660
Коллеги, в ядре имеются средства для "защищенного" вызова процедур:

Код:
TYPE TryHandler = PROCEDURE (a, b, c: INTEGER)
The type of procedure that can be called with a local trap handler by means of Try.

PROCEDURE Try (h: TryHandler; a, b, c: INTEGER)
Calls the procedure h with parameters a, b, and c with a local trap handler.
If a trap occurs inside h a trap window is displayed and h is terminated but the execution is continued as if Try proceeded normally.

Модуль HostWindows много использовал эти средства, поскольку оконная система должна быть защищена от авостов в клиентских процедурах: иначе "посыпется" оконная система, когда какой-нибудь вид даст сбой, например, во время отработки нажатий.

Очевидны недостатки:

1. чтобы пользоваться Try, приходится забыть о контроле типов и пользоваться SYSTEM.VAL для приведения типов:
Код:
   Kernel.Try(P, SYSTEM.VAL(INTEGER, SomePtr), 0, 0)
   
   PROCEDURE P (a, b, c: INTEGER);
      VAR ptr: SomePtr;
   BEGIN ... ptr := SYSTEM.VAL(SomePtr, a)...
   END P;

2. Параметров всегда три: если не нужны - передавать абы-чего, если нужно больше - изворачиваться.

Я предлагаю решение, которое устранит эти недостатки; оно опробовано в экспериментальном Kernel в составе Tyler:

Код:
MODULE Kernel;

   TYPE SafeAction* = ABSTRACT RECORD
         trapped-: BOOLEAN
      END;

   PROCEDURE (VAR a: SafeAction) Do*, NEW, ABSTRACT;
   PROCEDURE Do* (VAR a: SafeAction);

END Kernel.

MODULE Client;
   
   TYPE SafeHandler = RECORD (Kernel.SafeAction)
      str: ARRAY OF CHAR;
      ptr: SomePtr
   END;
   
   PROCEDURE (VAR h: SafeHandler) Do;
   BEGIN
      PerformSomeRiskyActivity(h.str);
      IF h.ptr # NIL THEN AnotherRiskFactor(ptr) END
   END Do;
   
   PROCEDURE P;
      VAR h: SafeHandler;
   BEGIN
      h.str := "This is a safe handler";
      h.ptr := something;
      Kernel.Do(h);
      IF h.trapped THEN Beda() ELSE Ura() END
   END P;
   
END Client.

Таким образом, решаются обе проблемы Kernel.Try.
Перечислю достоинства:
1. высокоуровневая проверка типов, повышается безопасность и надежность программирования;
2. бОльшая гибкость в передаче параметров;
3. возможность анализировать результат защищенного вызова: поле .trapped;
4. SafeActions объявлены и реализованы как записи (а не указатели), поэтому они могут располагаться в автоматической памяти (стеке), не требуют дорогостоящих обращений к менеджеру памяти, не нуждаются в сборке мусора; при этом допускают размещение и в динамической, и в статической памяти.

Use case: В проекте Тайлер в модуле HostWindows я повсеместно заменил Kernel.Try на Kernel.Do/Kernel.SafeAction, чем существенно ускорил отладку своих правок в HostWindows.

А затем оказалось, что SafeAction гораздо шире может применяться, чем как в HostWindows - на стыке хостовой небезопасной системы и ББ. Поскольку я изменил реализацию окон, процедура открытия окна Windows.Open стала зависеть от вызовов расширений. Т.о. если в расширении происходит авост, "валится" и оконная система - нарушаются инварианты внутренних структур данных. Оказалось удобным небезопасную (зависящую от расширений) часть процедуры Open выделить в TYPE SafeOpen = RECORD (Kernel.SafeAction), и тем самым удалось гарантировать сохранность внутренних инвариантов модуля Windows. Без Kernel.SafeAction это потребовало бы использовать Kernel.Try и SYSTEM.VAL, что нежелательно для высокоуровневого модуля Windows. При этом у SafeOpen множество полей - по сути, параметров процедуры Do.

Реализация Kernel.Do практически не отличается от текущей реализации Kernel.Try.

ПРЕДЛАГАЮ:
(1) включить приведенные Kernel.SafeAction и Kernel.Do в состав ядра;
(2) объявить Kernel.Try и Kernel.TryHandler устаревшими и неспешно избавиться от них через пару-тройку лет.

P.S.
Мои эксперименты проводил под Linux

P.P.S.
Для одного системного инструмента - трассировщика сообщений - хотелось вызывать защищенную процедуру собственно с сообщением, т.е. передавать ей VAR-параметром запись, размещенную в стеке. Сделал так:

Код:
MODULE Kernel;
   TYPE
      SafeRecAction* = ABSTRACT RECORD (SafeAction)
         prep: BOOLEAN;   (* DoRec => Do; this flags to Do that it was called  *)
         adr, typedesc: INTEGER;   (* address of VAR rec: ANYREC on stack;  *)
      END;
   PROCEDURE (VAR a: SafeRecAction) DoRec* (VAR rec: ANYREC), NEW, ABSTRACT;
   PROCEDURE DoRec* (VAR a: SafeRecAction; VAR rec: ANYREC);
END Kernel.

Реализация DoRec Полагается на Do:
Код:
   PROCEDURE DoRec* (VAR a: SafeRecAction; VAR rec: ANYREC);
   BEGIN a.adr := S.ADR(rec); a.typedesc := S.TYP(rec); a.prep := TRUE; Do(a)
   END DoRec;

Т.е. DoRec просто "шаманит" с адресами, гарантируя клиентам безопасность.

Прилагаю реализацию Do. Зеленым - то, что взято из Try, бирюсовым - то, что добавлено.

(Вы добросовестно дочитали до конца мой лонгрид? Весьма вам признателен :P)


Вложения:
KernelAmndmnt1.txt [3.05 КБ]
Скачиваний: 682
Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Вторник, 07 Январь, 2020 12:56 
Аватара пользователя

Зарегистрирован: Пятница, 25 Ноябрь, 2005 12:02
Сообщения: 8500
Откуда: Троицк, Москва
Серьёзно. Впечатляет.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Среда, 08 Январь, 2020 11:24 
Администратор

Зарегистрирован: Вторник, 15 Ноябрь, 2005 01:14
Сообщения: 4695
Откуда: Россия, Орёл
Только желательно это не на базе Kernel реализовать. По той же самой причине: минимизация импорта Kernel в прикладном коде.
Можно хоть в тот же Services поставить.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Среда, 08 Январь, 2020 11:27 
Администратор

Зарегистрирован: Вторник, 15 Ноябрь, 2005 01:14
Сообщения: 4695
Откуда: Россия, Орёл
Ещё раз на всякий случай.

Services и Meta -- это "человеческое лицо" Kernel. Kernel не гарантирует и не должен гарантировать вам НИКАКИХ интерфейсов.

Цитата:
This module has a private interface, it is only used internally.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Среда, 08 Январь, 2020 11:40 
Аватара пользователя

Зарегистрирован: Суббота, 16 Февраль, 2008 02:47
Сообщения: 660
Да, можно SafeAction в Services отправить, но тогда Try нужно оставить в Kernel - тогда реализация SafeAction Будет опираться на Try. Собсно в Win32 версии я так и сделал внутри Kernel (экспериментально).


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Среда, 08 Январь, 2020 11:47 
Аватара пользователя

Зарегистрирован: Суббота, 16 Февраль, 2008 02:47
Сообщения: 660
Борис Рюмшин писал(а):
Services и Meta -- это "человеческое лицо" Kernel.


Борис, нужно тогда кое-что подправлять, чтобы действительно можно было не импортировать Kernel. В частности, нужно

MODULE Serivces;
TYPE Name* = Kernel.Name;
END Services.

А то, как только я хочу с идентификаторами что-то сделать, приходится Kernel импортировать.

И еще, Kernel - не для приложений, но если я системную вещь делаю, его импорт оправдан, кмк.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Среда, 08 Январь, 2020 11:54 
Аватара пользователя

Зарегистрирован: Четверг, 08 Октябрь, 2009 15:00
Сообщения: 3774
adimetrius писал(а):
Да, можно SafeAction в Services отправить, но тогда Try нужно оставить в Kernel - тогда реализация SafeAction Будет опираться на Try. Собсно в Win32 версии я так и сделал внутри Kernel (экспериментально).

Поддерживаю. Try всё равно надо оставить для совместимости.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Среда, 08 Январь, 2020 11:57 
Администратор

Зарегистрирован: Вторник, 15 Ноябрь, 2005 01:14
Сообщения: 4695
Откуда: Россия, Орёл
Для системных вещей оправдан, да. Но, во-первых, его надо стараться минимизировать. Во-вторых, таких случаев реально мало.
Во-третьих, мне непонятно
adimetrius писал(а):
MODULE Serivces;
TYPE Name* = Kernel.Name;
END Services.

Зачем?
При использовании Meta и Services ничего импортировать не надо.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Среда, 08 Январь, 2020 12:00 
Администратор

Зарегистрирован: Вторник, 15 Ноябрь, 2005 01:14
Сообщения: 4695
Откуда: Россия, Орёл
Иван Денисов писал(а):
Поддерживаю. Try всё равно надо оставить для совместимости.

А где я сказал, что Try надо убрать? (по всяком случае не сейчас :D )
Я говорю о том, что не надо рантайм языка втаскивать куда не надо. Это закрытый модуль. Для узкосистемных у вас есть на него документация, для всех остальных -- нет.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Среда, 08 Январь, 2020 12:14 
Аватара пользователя

Зарегистрирован: Суббота, 16 Февраль, 2008 02:47
Сообщения: 660
Борис Рюмшин писал(а):
adimetrius писал(а):
MODULE Serivces;
TYPE Name* = Kernel.Name;
END Services.

Зачем?


MODULE M;
IMPORT Services (* вместо Kernel *);
PROCEDURE P;
VAR name: Services.Name;
...
END P;
END M.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Среда, 08 Январь, 2020 12:20 
Администратор

Зарегистрирован: Вторник, 15 Ноябрь, 2005 01:14
Сообщения: 4695
Откуда: Россия, Орёл
А Name то зачем нужен?


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Пятница, 17 Январь, 2020 13:56 
Аватара пользователя

Зарегистрирован: Суббота, 16 Февраль, 2008 02:47
Сообщения: 660
Коллеги, после обсуждений в Рокетчате и спасибо SovietPony за contribution предлагается такая платформо-независимая формулировка для Services:

Код:
   PROCEDURE (VAR a: SafeAction) Do*, NEW, ABSTRACT;
   PROCEDURE (VAR a: SafeRecAction) DoRec* (VAR rec: ANYREC), NEW, ABSTRACT;
   PROCEDURE (VAR a: SafeRecAction) Do*;
   BEGIN a.DoRec(SYSTEM.THISRECORD(a.adr, a.typ))
   END Do;

(**)   PROCEDURE TryHandler (VAR a: SafeAction; dummy: INTEGER); BEGIN a.Do END TryHandler;
   
   PROCEDURE Try* (VAR a: SafeAction);
      VAR count: INTEGER;
   BEGIN
      count := Kernel.trapCount; a.trapped := FALSE;
      Kernel.Try(SYSTEM.VAL(Kernel.TryHandler, SYSTEM.ADR(TryHandler)), SYSTEM.ADR(a), SYSTEM.TYP(a), -1);
      a.trapped := Kernel.trapCount # count
   END Try;
   
   PROCEDURE TryRec* (VAR a: SafeRecAction; VAR rec: ANYREC);
      VAR count: INTEGER;
   BEGIN
      count := Kernel.trapCount; a.trapped := FALSE;
      a.adr := SYSTEM.ADR(rec); a.typ := SYSTEM.TYP(rec);
(**)      Kernel.Try(SYSTEM.VAL(Kernel.TryHandler, SYSTEM.ADR(TryHandler)), SYSTEM.ADR(a), SYSTEM.TYP(a), -1);
      a.trapped := Kernel.trapCount # count
   END TryRec;


То, что отмечено (**) - это "трюк", основанный на том, что в реализации VAR a: SafeRec - передается как два целых: адрес записи и адрес дескриптора типа. SovietPony предлагал иную реализацию этой части:

Код:
   PROCEDURE SafeCall (VAR a: SafeAction); BEGIN a.Do END SafeCall;
   PROCEDURE TryHandler (actionAdr, actionTyp: ADDRESS; dummy: INTEGER);
   BEGIN SafeCall(SYSTEM.THISRECORD(actionAdr, actionTyp))
   END TryHandler;
   ...
   Kernel.Try(TryHandler, SYSTEM.ADR(a), SYSTEM.TYP(a), -1);


Вопрос к компиляторщикам (Дмитрий В. Дагаев, Олег Н. Чер): что скажете на это? Как ваши компиляторы отнесутся к такому коду?


Последний раз редактировалось adimetrius Пятница, 17 Январь, 2020 14:06, всего редактировалось 1 раз.

Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Пятница, 17 Январь, 2020 14:05 
Аватара пользователя

Зарегистрирован: Суббота, 16 Февраль, 2008 02:47
Сообщения: 660
Вот второй вариант полностью, для ясности:

Код:
   PROCEDURE (VAR a: SafeAction) Do*, NEW, ABSTRACT;
   PROCEDURE (VAR a: SafeRecAction) DoRec* (VAR rec: ANYREC), NEW, ABSTRACT;
   PROCEDURE (VAR a: SafeRecAction) Do*;
   BEGIN a.DoRec(SYSTEM.THISRECORD(a.adr, a.typ))
   END Do;

   PROCEDURE SafeCall (VAR a: SafeAction); BEGIN a.Do END SafeCall;
   PROCEDURE TryHandler (actionAdr, actionTyp: ADDRESS; dummy: INTEGER);
   BEGIN SafeCall(SYSTEM.THISRECORD(actionAdr, actionTyp))
   END TryHandler;

   PROCEDURE Try* (VAR a: SafeAction);
      VAR count: INTEGER;
   BEGIN
      count := Kernel.trapCount; a.trapped := FALSE;
      Kernel.Try(SYSTEM.VAL(Kernel.TryHandler, SYSTEM.ADR(TryHandler)), SYSTEM.ADR(a), SYSTEM.TYP(a), -1);
      a.trapped := Kernel.trapCount # count
   END Try;
   
   PROCEDURE TryRec* (VAR a: SafeRecAction; VAR rec: ANYREC);
      VAR count: INTEGER;
   BEGIN
      count := Kernel.trapCount; a.trapped := FALSE;
      a.adr := SYSTEM.ADR(rec); a.typ := SYSTEM.TYP(rec);
      Kernel.Try(TryHandler, SYSTEM.ADR(a), SYSTEM.TYP(a), -1);   
      a.trapped := Kernel.trapCount # count
   END TryRec;


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Пятница, 17 Январь, 2020 17:53 
Аватара пользователя

Зарегистрирован: Вторник, 28 Август, 2007 00:55
Сообщения: 519
Откуда: Украина, Днепропетровская обл.
Ну чего сказать... нормальный код. Но в Ofront'е+ нет SYSTEM.TYP и SYSTEM.THISRECORD.

Но можно сделать, если надо.


Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: Версия 1.8
СообщениеДобавлено: Суббота, 08 Февраль, 2020 22:22 
Аватара пользователя

Зарегистрирован: Суббота, 16 Февраль, 2008 02:47
Сообщения: 660
Коллеги, после обсуждения на этом форуме и в чате Chat.oberone.org был реализован интерфейс Services.SafeActions в ветке работы над версией ББ 1.8. Посмотреть и попробовать можно здесь же на https://blackbox.oberon.org/download.

Документация, вероятно, еще не попала в загружаемый файл, поэтому привожу ее здесь.

TYPE SafeAction
ABSTRACT
SafeActions are objects whose Do procedures can be called in a 'protected' fashion: if a trap occurs during the execution of Do, control returns to the caller (as opposed to terminating the whole running command).

trapped-: BOOLEAN
TRUE if the last execution of Do ended in a trap; FALSE if Do returned successfully.
Valid only after Try.

PROCEDURE (a: SafeAction) Do
NEW, ABSTRACT
Do is called in a protected fashion by Try.

TYPE SafeRecAction
ABSTRACT
SafeRecActions are like SafeActions, but their DoRec procedure has a reference record variable parameter.

trapped-: BOOLEAN
TRUE if the last execution of DoRec ended in a trap; FALSE if DoRec returned successfully.
Valid only after TryRec.

PROCEDURE (a: SafeRecAction) DoRec (VAR rec: ANYREC)
NEW, ABSTRACT
DoRec is called in a protected fashion by TryRec.


PROCEDURE Try (VAR a: SafeAction)
Calls a.Do. Try returns even if a trap occurs during the execution of Do. a.trapped can be analyzed to check if Do trapped or returned successfully.

PROCEDURE TryRec (VAR a: SafeAction; VAR rec: ANYREC)
Calls a.DoRec(rec). TryRec returns even if a trap occurs during the execution of DoRec. a.trapped can be analyzed to check if Do trapped or returned successfully.



Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Вторник, 11 Февраль, 2020 23:15 
Аватара пользователя

Зарегистрирован: Четверг, 08 Октябрь, 2009 15:00
Сообщения: 3774
Антон, документация добавилась.

Товарищи, ждем ваши отзывы.
https://blackbox.oberon.org/unstable/de ... a1.010.zip
Там есть модуль
VarSafeActionTest

Также высказано мнение, что с их применением и использованием HALT(128) возможно строить системы обработки исключений не многим хуже популярных языков.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Среда, 12 Февраль, 2020 00:19 
Аватара пользователя

Зарегистрирован: Суббота, 16 Февраль, 2008 02:47
Сообщения: 660
О, нет, НИЧУТЬ не хуже, в смысле не менее мощные. Ток, похоже, в нашем КомПасном мире не нужны они никому - эти структурные исключения.
И, кстати, там же приложена была статья из далеких 90х про очень, кмк, оригинальный и минималистичный подход к исключениям на основе метапрограммирования, который использовали в одной из Оберонов.


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Вторник, 01 Декабрь, 2020 18:06 
Аватара пользователя

Зарегистрирован: Четверг, 08 Октябрь, 2009 15:00
Сообщения: 3774
Вынесу сюда код SovietPony из чата, как пример организации обработки исключений.

SovietPony писал(а):
Вот примерно так можно сделать исключения на базе SafeAction. Для простоты сделал прямо в самом SafeAction, что бы не нужно было плодить больше типов. Дорабатывать есть куда. Можно добавить в Try генерацию исключений для перехвата системных исключений (деление на ноль, etc).


Код:
  TYPE
    ADDRESS* = INTEGER;

    SafeAction* = ABSTRACT RECORD
      trapped-: BOOLEAN
    END;

    Exception* = ABSTRACT RECORD END;

  VAR
    aAdr, aTyp: ADDRESS;
    guard: BOOLEAN;

  PROCEDURE (VAR a: SafeAction) Do*, NEW, ABSTRACT;

  PROCEDURE (VAR a: SafeAction) Handle- (VAR e: Exception), NEW, EMPTY;

  PROCEDURE TryHandler (actionAdr, actionTyp, dummy: ADDRESS);

    PROCEDURE SafeCall (VAR a: SafeAction);
    BEGIN a.Do
    END SafeCall;

  BEGIN
    SafeCall(SYSTEM.THISRECORD(actionAdr, actionTyp))
  END TryHandler;

  PROCEDURE Try* (VAR a: SafeAction);
    VAR count: INTEGER; oAdr, oTyp: ADDRESS; oGuard: BOOLEAN;
  BEGIN
    oAdr := aAdr; oTyp := aTyp; oGuard := guard;
    aAdr := SYSTEM.ADR(a); aTyp := SYSTEM.TYP(a); guard := FALSE;
    count := Kernel.trapCount; a.trapped := FALSE;
    Kernel.Try(TryHandler, SYSTEM.ADR(a), SYSTEM.TYP(a), -1);
    aAdr := oAdr; aTyp := oTyp; guard := oGuard;
    a.trapped := Kernel.trapCount # count
  END Try;

  PROCEDURE Raise* (VAR e: Exception);

    PROCEDURE Handle (VAR a: SafeAction);
    BEGIN
      guard := TRUE;
      a.Handle(e);
      HALT(128)
    END Handle;

  BEGIN
    ASSERT(guard = FALSE, 100); (* recursion not allowed *)
    Handle(SYSTEM.THISRECORD(aAdr, aTyp))
  END Raise;


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Вторник, 01 Декабрь, 2020 21:22 
Аватара пользователя

Зарегистрирован: Четверг, 08 Октябрь, 2009 15:00
Сообщения: 3774
Пример, что можно отключить просмотр дампов стека при авостах, и заниматься вручную обработкой исключений

Код:
MODULE DemoTry;
   
   IMPORT Log, Services, Kernel;

   TYPE
      SafeAction = RECORD (Services.SafeAction) END;
      TrapCleaner = POINTER TO RECORD (Kernel.TrapCleaner) END;
      
   VAR
      array: ARRAY 2 OF INTEGER;
      pos, res: INTEGER;
      cleaner: TrapCleaner;
      
   PROCEDURE Trap;
   BEGIN
   END Trap;
   
   PROCEDURE (c: TrapCleaner) Cleanup;
   BEGIN
      Log.String("cleaner"); Log.Ln;
   END Cleanup;
   
   PROCEDURE (VAR s: SafeAction) Do;
   BEGIN
      res := array[pos];
   END Do;
   
   PROCEDURE Start*;
   VAR s: SafeAction;
   BEGIN
      Kernel.PushTrapCleaner(cleaner);
      array[0] := 0; array[1] := 1;
      pos := 2;
      Services.Try(s);
      IF s.trapped THEN
         Log.String("error"); Log.Ln;
      ELSE
         Log.Int(res); Log.Ln;
      END;
      Kernel.PopTrapCleaner(cleaner);
   END Start;

BEGIN
   Kernel.InstallTrapViewer(Trap);
   NEW(cleaner);
   
END DemoTry.

DemoTry.Start


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Среда, 06 Январь, 2021 18:07 
Аватара пользователя

Зарегистрирован: Четверг, 08 Октябрь, 2009 15:00
Сообщения: 3774
Оказывается в Блэкбоксе предусмотрена возможность на время отключать вывод дампов после аварийных остановок с помощью:

Код:
Kernel.SetTrapGuard(TRUE);


Так что используя SafeAction возможно делать так:

Код:
MODULE DemoTry;
   
   IMPORT Log, Services, Kernel;

   TYPE
      SafeAction = RECORD (Services.SafeAction) END;
      
   VAR
      array: ARRAY 2 OF INTEGER;
      pos, res: INTEGER;

   
   PROCEDURE (VAR s: SafeAction) Do;
   BEGIN
      res := array[pos];
   END Do;
   
   PROCEDURE Start*;
   VAR s: SafeAction;
   BEGIN
      Kernel.SetTrapGuard(TRUE);
      array[0] := 0; array[1] := 1;
      pos := 2;
      Services.Try(s);
      IF s.trapped THEN
         Log.String("error"); Log.Ln;
      ELSE
         Log.Int(res); Log.Ln;
      END;
      Kernel.SetTrapGuard(FALSE);
   END Start;

END DemoTry.

DemoTry.Start


Вернуться к началу
 Профиль  
 
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 25 ]  На страницу 1, 2  След.

Часовой пояс: UTC + 3 часа


Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 13


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Вся информация, размещаемая участниками на конференции (тексты сообщений, вложения и пр.) © 2005-2024, участники конференции «OberonCore», если специально не оговорено иное.
Администрация не несет ответственности за мнения, стиль и достоверность высказываний участников, равно как и за безопасность материалов, предоставляемых участниками во вложениях.
Без разрешения участников и ссылки на конференцию «OberonCore» любое воспроизведение и/или копирование высказываний полностью и/или по частям запрещено.
Powered by phpBB® Forum Software © phpBB Group
Русская поддержка phpBB