Получить список классов в юните? Потомки TJSONRPCHandler

Вопросы программирования на Free Pascal, использования компилятора и утилит.

Модератор: Модераторы

Получить список классов в юните? Потомки TJSONRPCHandler

Сообщение Rockstein » 04.08.2023 16:21:58

Добрый день,
я играюсь с fpjsonrpc и сделал простой сервер, отвечающий на запросы в json-rcp протоколе. Старт был быстрый и даже заработало. Но вот поднабралость много команд и пытаюсь выкрутиться.

В Lazarus есть возможность визуально накидать TJSONRPCHandler прямо в DataModule. Таким образом при создании датамодуля, все будет инициализовано из .lfm. Но мне визуальный подход не зашел, посколько нужны были потомки TJSONRPCHandler с дополнительными функциями. Например для дополнительной выдачи при запросе api. Вообщем пошел по пути через JSONRPCHandlerManager.

Тут вроде все просто, чтобы зарегистрировать команду TMyCmdClass (потомок TJSONRPCHandler) нужно вызвать функцию менеджера, которая добавляет новый хэндлер в коллекцию TJSONRPCHandlerDefs
Код: Выделить всё
JSONRPCHandlerManager.RegisterHandler('MethodName', TMyCmdClass, ParamCount);


При этом в самом TMyCmdClass перекрывается функц. DoExecute, где и выполняется комманда.
Когда команд набирается много, происходит путаница, даже если комманды в одном юните. Хотелось бы:
1. как-то собрать все комманды, тоесть объявленных потомков TJSONRPCHandler в один массив (предпочтительнее во время компиляции) и потом прогнать в оном цикле JSONRPCHandlerManager.RegisterHandler по этому массиву. У меня "MethodName" и ParamCount хранятся как константы в TMyCmdClass, поэтому от туда и передаются в RegisterHandler

2. Параметры в TMyCmdClass это коллекция TJSONParamDefs, в визуальном варианте она инициализируется из потока, но не в мем случае. Я не нашел ничего лучше, чем сделать рекорд
Код: Выделить всё
 
TCmd_ParamsRec = record
    Name: TJSONStringType;
    DataType: TJSONtype;
    Required: boolean;
end;

и потом в самом классе TMyCmdClass определить массив
Код: Выделить всё
const
    Params: array [0..1] of TCmd_ParamsRec =
    (
      (Name: 'ParamFoo'; DataType: jtNumber; Required: true),
      (Name: 'ParamBar'; DataType: jtString; Required: true)
    );

ну и перекрыть Create от TJSONRPCHandler (<- там где создается пустая коллекция TJSONParamDefs) чтобы из свого Params массива в цикле перекидать в коллекцию через TJSONParamDefs.AddParamDef.
Как проще сразу инициализовать TJSONParamDefs?

По fpjsonrpc в сети очень мало, в сорцах fpc есть очень хорошие демки, которые мне и помогли. Буду признателен, если кто потожет или поделится опытом. Спасибо.
Rockstein
незнакомец
 
Сообщения: 5
Зарегистрирован: 04.08.2023 15:21:12

Re: Получить список классов в юните? Потомки TJSONRPCHandler

Сообщение Снег Север » 04.08.2023 19:56:00

Чтобы не клепать потомков, можно использовать хелперы.
Аватара пользователя
Снег Север
долгожитель
 
Сообщения: 3039
Зарегистрирован: 27.11.2007 16:14:47

Re: Получить список классов в юните? Потомки TJSONRPCHandler

Сообщение delphius » 05.08.2023 00:55:41

Снег Север писал(а):Чтобы не клепать потомков, можно использовать хелперы.

Отличный способ! :!:

Можно еще присмотреться к RTTI, чтобы создавать экземпляры классов на лету и избежать явной регистрации каждого обработчика вручную.
Условно так:
Код: Выделить всё
procedure RegisterAllHandlers;
var
  RttiContext: TRttiContext;
  RttiType: TRttiType;
  CmdClass: TJSONRPCHandlerClass;
begin
  RttiContext := TRttiContext.Create;
  try
    for RttiType in RttiContext.GetTypes do
    begin
      if RttiType.IsInstance and RttiType.BaseType.IsInstance and
         RttiType.BaseType.AsInstance.MetaclassType.InheritsFrom(TJSONRPCHandler) then
      begin
        CmdClass := TJSONRPCHandlerClass(RttiType.AsInstance.MetaclassType);
        JSONRPCHandlerManager.RegisterHandler(CmdClass.MethodName, CmdClass, CmdClass.ParamCount);
      end;
    end;
  finally
    RttiContext.Free;
  end;
end;


А для удобства инициализации параметров можно использовать статический метод или процедуру, которая будет инициализировать параметры из массива, условно так:
Код: Выделить всё
type
  TJSONRPCHandler = class
  public
    class procedure InitializeParams(ParamDefs: TJSONParamDefs); virtual;
  end;

class procedure TJSONRPCHandler.InitializeParams(ParamDefs: TJSONParamDefs);
var
  ParamRec: TCmd_ParamsRec;
begin
  for ParamRec in Params do
    ParamDefs.AddParamDef(ParamRec.Name, ParamRec.DataType, ParamRec.Required);
end;


И, соответственно, этот метод можно перекрыть и проинициализировать параметры:
Код: Выделить всё
type
  TMyCmdClass = class(TJSONRPCHandler)
  public
    class procedure InitializeParams(ParamDefs: TJSONParamDefs); override;
  end;

class procedure TMyCmdClass.InitializeParams(ParamDefs: TJSONParamDefs);
begin
  inherited; // вызовем базовый метод для инициализации общих параметров
  ParamDefs.AddParamDef('AdditionalParam', jtString, false); // добавим дополнительные параметры
end;
delphius
постоялец
 
Сообщения: 129
Зарегистрирован: 18.03.2020 13:40:11

Re: Получить список классов в юните? Потомки TJSONRPCHandler

Сообщение Rockstein » 05.08.2023 06:36:00

Снег Север писал(а):Чтобы не клепать потомков, можно использовать хелперы.

Идея интересная, но есть нюансы. К сожалению, хелперы не могут решить задачу. Нельзя перекрыть конструктор, а нужно в конструкторе TJSONRPCHandler инициализовать ParamDefs коллекци. Там в fpjsonrpc.pp особый механизм, сначала при регистрации инстанциируется класс и после этого, его коллекция параметров перенимается в коллекцию handlerdefs. После чего этот объект освобождается. Параметры чекаются уже против коллекции paramdefs в handlerdefs. А еще при обработке команды вызывается виртуальный DoExecute, в родительском TJSONRPCHandler он по счастию ничего не делает (inherited можно похерить). Насколько я правильно прочитал доку, виртуальные методы не перекрываются, но скрываются. Тоесть inherited не сработает.
Да и вообще я немного туплю. А что мне эти хелперы к TJSONRPCHandler дадут? Кто-то же должен будет решать какой метод из какого хелпера по какой команде вызвать. К тому же это совсем не уменьшит количество строк с JSONRPCHandlerManager.RegisterHandler. Не понимаю преимуществ.

delphius писал(а):Можно еще присмотреться к RTTI, чтобы создавать экземпляры классов на лету и избежать явной регистрации каждого обработчика вручную.

Спасибо, я именно так и хотел, по делфийскому rtti примеру, прогнать по контексту и зарегить всех потомков. Но напоролся на то, что в freepascal rttti.pp функуии GetType нужен конкретный тип или класс.
Код: Выделить всё
    function GetType(ATypeInfo: PTypeInfo): TRttiType;
    function GetType(AClass: TClass): TRttiType;

поэтому вот этот финт
Код: Выделить всё
for RttiType in RttiContext.GetTypes do
не прокатывает. Поковырял и не осилил.
Потом была идея, использовать {$MACRO ON} , и тупо составлять
Код: Выделить всё
{$define AllCmdClasses:=TCmd1Class, TCmd2Class, TCmd3Class}
из кусочков. Тоесть рядом с объявлением TCmd1Class дописывать define
Код: Выделить всё
{$define AllCmdClasses:=AllCmdClasses, TCmd1Class}
и так далее. Потом естессвенно подставить AllCmdClasses в инициализацию массива переменный класса.
Код: Выделить всё
const MyAllCmds=array [0..x] of Class of TJSONRPCHandler=(AllCmdClasses);

После чего регистрировать в цикле по массиву. Но препроцессор у фрипаскаль очень слабенький и не понял, что я хочу.

delphius писал(а):А для удобства инициализации параметров можно использовать статический метод или процедуру, которая будет инициализировать параметры из массива

Что касается инициализации параметров, то TJSONRPCHandler в fcl, я бы его не трогал. И как я писал выше, параметры должны быть инициализированы в конструкторе, такая задумка тех, кто писал. При регистрации команды, класс инстанциируется и из него вытягивается коллекция параметров. В TJSONRPCHandler.InitializeParams вы передаете готовую коллекцию, там модно без цикла, сразу положить. Но моя проблема в том, что для каждой команды свой набор параметров. Причем порядок важен для случая, если в jsonrpc будет массив, а не объект. Тоесть фактически, мне нужно в конструкторе инициализовать все коллекцию параметров для команды.
Я выкрутился секцией const в классе в интерфейсе. Там сделал инициализованный массив записей (MethodName, DataTyp, Requered). Потом как у вас InitializeParams, только в перекрытом конструкторе TMyCmdClass. Тоесть, вся инициализация в интерфейсе. Конечно, можно было и без секции const, а сразу в конструкторе, но мне потом предстоит еще писать доку на api, и хотелось бы все иметь в одном месте. А то получается для доку важные данные размазаны по всему коду: Имя команды в JSONRPCHandlerManager.RegisterHandler . Сам класс комманды в интерфейсе, параметры в конструкторе. Когда три команды, это просто. А если их 100? Я буду делать openrpc документ ручками в блокноте.
Rockstein
незнакомец
 
Сообщения: 5
Зарегистрирован: 04.08.2023 15:21:12

Re: Получить список классов в юните? Потомки TJSONRPCHandler

Сообщение delphius » 05.08.2023 11:20:23

Rockstein писал(а):Когда три команды, это просто.

Ну да, тут еще фабрика бы помогла теоретически, вроде того...
Код: Выделить всё
type
  TJSONRPCHandlerFactory = class
  public
    class function CreateHandler(CommandName: string): TJSONRPCHandler;
  end;

class function TJSONRPCHandlerFactory.CreateHandler(CommandName: string): TJSONRPCHandler;
begin
  Result := nil;
  if CommandName = 'Cmd1' then
    Result := TCmd1Class.Create
  else if CommandName = 'Cmd2' then
    Result := TCmd2Class.Create
  ....
  else
    raise Exception.Create('Unknown command: ' + CommandName);
end;

var
  CommandName: string;
  Handler: TJSONRPCHandler;
begin
  CommandName := ...;
  Handler := TJSONRPCHandlerFactory.CreateHandler(CommandName);
  try
    Handler.DoExecute;
  finally
    Handler.Free;
  end;
end;


Rockstein писал(а): А если их 100

Ну.....словарь...
Код: Выделить всё
type
  TJSONRPCHandlerFactory = class
  private
    class var FHandlerRegistry: TDictionary<string, TJSONRPCHandlerClass>;
  public
    class constructor Create;
    class function CreateHandler(CommandName: string): TJSONRPCHandler;
  end;

class constructor TJSONRPCHandlerFactory.Create;
begin
  FHandlerRegistry := TDictionary<string, TJSONRPCHandlerClass>.Create;
  FHandlerRegistry.Add('Cmd1', TCmd1Class);
  FHandlerRegistry.Add('Cmd2', TCmd2Class);
  ...
end;

class function TJSONRPCHandlerFactory.CreateHandler(CommandName: string): TJSONRPCHandler;
var
  HandlerClass: TJSONRPCHandlerClass;
begin
  if FHandlerRegistry.TryGetValue(CommandName, HandlerClass) then
    Result := HandlerClass.Create
  else
    raise Exception.Create('Unknown command: ' + CommandName);
end;


...или даже динамическая загрузка, нет :?: ...
Код: Выделить всё
type
  TJSONRPCHandlerFactory = class
  public
    class function CreateHandler(CommandName: string): TJSONRPCHandler;
  end;

class function TJSONRPCHandlerFactory.CreateHandler(CommandName: string): TJSONRPCHandler;
var
  HandlerClassName: string;
  HandlerClass: TPersistentClass;
begin
  HandlerClassName := 'TCmd' + CommandName + 'Class';
  HandlerClass := FindClass(HandlerClassName);
  if Assigned(HandlerClass) then
    Result := TJSONRPCHandlerClass(HandlerClass).Create
  else
    raise Exception.Create('Unknown command: ' + CommandName);
end;
delphius
постоялец
 
Сообщения: 129
Зарегистрирован: 18.03.2020 13:40:11


Вернуться в Free Pascal Compiler

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

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

Рейтинг@Mail.ru