////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2001-2023 Alexey Kuryakin daqgroup@mail.ru under MIT license //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// This file is part of the CRW-DAQ project by DaqGroup - component CRWKIT.   //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Purpose:                                                                   //
// SMI Proxy server for CRW-DAQ Finite State Machines.                        //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// History:                                                                   //
// 2020xxxx - Created (A.K.)                                                  //
// 20230907 - Modified for FPC (A.K.)                                         //
////////////////////////////////////////////////////////////////////////////////

program smiproxy; // SMI Proxy

{$I _crw_sysdef.inc}

{$I _crw_sysmode.inc}

{-$WARN 5024 off : Parameter "$1" not used}

{$R *.res}

uses
 //////////////////////////////////////////////////////
 {$I _crw_uses_first.inc} // NB: MUST BE FIRST USES !!!
 //////////////////////////////////////////////////////
 {$IFDEF WINDOWS} shellapi, {$ENDIF}
 sysutils, classes, math, forms, interfaces,
 _crw_alloc, _crw_cmdargs, _crw_str, _crw_base64, _crw_fio, _crw_fifo,
 _crw_rtc, _crw_polling, _crw_task, _crw_ascio, _crw_az, _crw_proc,
 _crw_dim, _crw_smirtl;

 //
 // General variables and constants
 //
const
 Terminated       : Boolean        = false; // Program should be terminated
 smi_cmd_count    : TAtomicCounter = nil;   // Command counter.
 smi_err_count    : TAtomicCounter = nil;   // Error counter.
 smi_attach_res   : Integer        = 0;     // Attach result.
 smi_attach_obj   : LongString     = '';    // Attached object.
 opt_dns          : LongString     = '';    // Option -dns=...
 opt_obj          : LongString     = '';    // Option -obj=...
 opt_set          : LongString     = '';    // Option -set=...
 opt_vol          : Boolean        = false; // Option -vol
 opt_verb         : Boolean        = false; // Option -verb
 //
 // OS abstraction layer routines
 //
{$IFDEF SKIP_DRAFT}
// TODO: for UNIX
{$ELSE ~SKIP_DRAFT}
const
 THREAD_PRIORITY_IDLE          = -15;
 THREAD_PRIORITY_LOWEST        = -2;
 THREAD_PRIORITY_BELOW_NORMAL  = -1;
 THREAD_PRIORITY_NORMAL        = 0;
 THREAD_PRIORITY_ABOVE_NORMAL  = 1;
 THREAD_PRIORITY_HIGHEST       = 2;
 THREAD_PRIORITY_TIME_CRITICAL = 15;
 function GetCurrentProcess:THandle;
 begin
  Result:=0;
  {$IF DEFINED (WINDOWS)}
  Result:=windows.GetCurrentProcess;
  {$ELSEIF DEFINED (UNIX)}
  Result:=GetCurrentProcessId;
  {$ENDIF }
 end;
 function GetPriorityClass(hProcess:THandle):DWORD;
 begin
  Result:=0;
  {$IF DEFINED (WINDOWS)}
  Result:=windows.GetPriorityClass(hProcess);
  {$ELSEIF DEFINED (UNIX)}
  Result:=0;
  {$ENDIF }
 end;
 function SetPriorityClass(hProcess:THandle; aClass:DWORD):Boolean;
 begin
  Result:=false;
  {$IF DEFINED (WINDOWS)}
  Result:=windows.SetPriorityClass(hProcess,aClass);
  {$ELSEIF DEFINED (UNIX)}
  Result:=false;
  {$ENDIF}
 end;
 function GetCurrentThread:THandle;
 begin
  Result:=0;
  {$IF DEFINED (WINDOWS)}
  Result:=windows.GetCurrentThread;
  {$ELSEIF DEFINED (UNIX)}
  Result:=GetCurrentThreadId;
  {$ENDIF}
 end;
 function SetThreadPriority(hThread:THandle; aPriority:LongInt):Boolean;
 begin
  Result:=false;
  {$IF DEFINED (WINDOWS)}
  Result:=windows.SetThreadPriority(hThread,aPriority);
  {$ELSEIF DEFINED (UNIX)}
  Result:=false;
  {$ENDIF}
 end;
{$ENDIF ~SKIP_DRAFT}
 //
 // Exception handler.
 //
 procedure BugReport(const E:Exception; const Location:LongString='');
 const ExeName:LongString='';
 begin
  if Length(ExeName)=0 then ExeName:=SysUtils.ExtractFileName(ParamStr(0));
  if Assigned(E) then
  if Length(Location)=0 
  then Echo(Format('@OnException=%s, %s (in %s)',[E.ClassName,E.Message,ExeName]))
  else Echo(Format('@OnException=%s, %s (in %s, %s)',[E.ClassName,E.Message,ExeName,Location]));
 end;
 //
 // Print usage info on --help option.
 //
 procedure PrintUsageInfo;
 begin
  StdOut.Put:='Usage: ';
  StdOut.Put:=' '+CmdArgs.ExeBaseName+' [obj [set [dns]]] [-opt]';
  StdOut.Put:='Where:';
  StdOut.Put:=' obj       - SMI object name as DOMAIN::OBJECT';
  StdOut.Put:=' set       - Set initial SMI object state';
  StdOut.Put:=' dns       - Value of DIM_DNS_NODE';
  StdOut.Put:=' -opt      - Options starts from -';
  StdOut.Put:='Options:';
  StdOut.Put:=' -obj=o    - Attach SMI object o=DOMAIN::OBJECT';
  StdOut.Put:=' -set=s    - Set initial state = s';
  StdOut.Put:=' -dns=n    - Set DIM_DNS_NODE = n';
  StdOut.Put:=' -volatile - Run as volatile';
  StdOut.Put:=' -vol      - Run as volatile';
  StdOut.Put:=' -verbose  - Print verbose';
  StdOut.Put:=' -verb     - Print verbose';
  StdOut.Put:='Notes:';
  StdOut.Put:=' 1) By default proxy is not volatile';
  StdOut.Put:=' 2) -dns=. is same as -dns=localhost';
  StdOut.Put:=' 3) Option --opt means same as -opt';
  StdOut.Put:='Example:';
  StdOut.Put:=' '+CmdArgs.ExeBaseName+' -obj=DEMO::LOGGER -set=NOT_LOGGING -dns=localhost -vol -verb';
  StdOut.Put:=' '+CmdArgs.ExeBaseName+' DEMO::LOGGER NOT_LOGGING localhost -volatile -verbose';
 end;
 //
 // Report on error.
 //
 procedure ReportError(const msg:LongString);
 begin
  StdOut.Put:=Format('@smi_report=ERROR: %s',[msg]);
  LockedInc(smi_err_count);
 end;
 //
 // SMI was attached?
 //
 function IsSmiAttached:Boolean;
 begin
  Result:=(smi_attach_res<>0);
 end;
 //
 // Report on SMI call if SMI was not attached.
 //
 procedure SmiWasNotAttachedWarning;
 begin
  ReportError('SMI was not attached.');
 end;
 //
 // Check if running in main thread.
 //
 function IsMainThread:Boolean;
 begin
  Result:=(MainThreadId<>0) and (GetCurrentThreadId=MainThreadId);
 end;
 //
 // SMI proxy command handler.
 //
 procedure smi_proxy_handler; cdecl;
 var n,i,n_params,typ,size,iv:Integer; fv:double; sv:LongString;
     state,param,action:array[0..MAX_NAME] of char; command:PChar;
 begin
  try
   state:='';
   param:='';
   action:='';
   n_params:=0;
   size:=0; typ:=0; sv:='';
   command:=Allocate(1024*64);
   try
    LockedInc(smi_cmd_count);
    StdOut.Put:=Format('@smi_proxy_handler=BEGIN,%d',[LockedGet(smi_cmd_count)]);
    if opt_verb then StdOut.Put:=Format('IsMainThread=%d',[Ord(IsMainThread)]);
    n:=smi_get_state(state,MAX_NAME);
    StdOut.Put:=Format('@smi_get_state=%d,%s',[n,state]);
    n:=smi_get_action(action,n_params);
    StdOut.Put:=Format('@smi_get_action=%d,%s,%d',[n,action,n_params]);
    n:=smi_get_command(command,size);
    StdOut.Put:=Format('@smi_get_command=%d,%s',[n,command]);
    i:=0;
    while (smi_get_next_par(param,typ,size)>0) do begin
     StdOut.Put:=Format('@smi_get_next_par=%d,%s,%d,%d',[i,param,typ,size]);
     if (size>0) then
     case typ of
      SMI_STRING: begin
       SetLength(sv,size);
       if (smi_get_par_value(param,PChar(sv))>0) then
       StdOut.Put:=Format('@smi_get_par_value=%d,%s,%d,%s',[i,param,typ,sv]);
       sv:='';
      end;
      SMI_INTEGER: begin
       ZeroMemory(@iv,sizeof(iv));
       if (size=sizeof(iv)) then begin
        if (smi_get_par_value(param,@iv)>0) then
        StdOut.Put:=Format('@smi_get_par_value=%d,%s,%d,%d',[i,param,typ,iv]);
       end else ReportError(Format('Invalid param %s size %d',[param,size]));
      end;
      SMI_FLOAT: begin
       ZeroMemory(@fv,sizeof(fv));
       if (size=sizeof(fv)) then begin
        if (smi_get_par_value(param,@fv)>0) then
        StdOut.Put:=Format('@smi_get_par_value=%d,%s,%d,%g',[i,param,typ,fv]);
       end else ReportError(Format('Invalid param %s size %d',[param,size]));
      end;
     end;
     inc(i);
    end;
   finally
    StdOut.Put:=Format('@smi_proxy_handler=END,%d',[LockedGet(smi_cmd_count)]);
    Deallocate(Pointer(command));
   end;
  except
   on E:Exception do BugReport(E,'smi_proxy_handler');
  end;
 end;
 //
 // Request: @help
 // Reply:   help info
 // Comment: Show help.
 //
 procedure DoHelp(const cmnd,args:LongString);
 begin
  StdOut.Put:=Format('%s',[cmnd]);
  StdOut.Put:='>> Command list:';
  StdOut.Put:='>> @help                     This help.';
  StdOut.Put:='>> @exit=n                   Exit program with code n.';
  StdOut.Put:='>> @errors                   Return error counter.';
  StdOut.Put:='>> @memory                   Return memory status.';
  StdOut.Put:='>> @processpriority=n        Set process priority:Idle/Low/Normal/High/RealTime.';
  StdOut.Put:='>> @threadpriority=n         Set thread priority:tpIdle/tpLow/tpNormal/tpHigh/tpTimeCritical.';
  StdOut.Put:='>> @onexception=m            Raise test exception with message m.';
  StdOut.Put:='>> @dim_dns_node=n           Set/get DIM_DNS_NODE environment variable n.';
  StdOut.Put:='>> @smi_attach=n             Attach to SMI DOMAIN::OBJECT n.';
  StdOut.Put:='>> @smi_volatile             Make SMO proxy volatile.';
  StdOut.Put:='>> @smi_set_par=n,t,v        Set SMI param name n type t value v.';
  StdOut.Put:='>> @smi_set_state=n          Set SMI initial state n.';
  StdOut.Put:='>> @smi_get_state            Get SMI state.';
  StdOut.Put:='>> @smi_terminate_action=n   Terminate action and set state n.';
  StdOut.Put:='>> @smi_errors               Get/set SMI errors counter.';
  StdOut.Put:='<< Outcome message list:';
  StdOut.Put:='<< @smi_proxy_handler=c,n    SMI proxy command handler c=BEGIN/END,n=Count';
  StdOut.Put:='<< @smi_get_action=a,n       SMI action=a to run with n params';
  StdOut.Put:='<< @smi_get_command=c        SMI command=c to run with params';
  StdOut.Put:='<< @smi_get_next_par=n,t,s   SMI parameter with name n, type t, size s';
  StdOut.Put:='<< @smi_get_par_value=n,t,v  SMI parameter with name n, type t, value v';
  StdOut.Put:='<< @report=m                 Report on error with message m.';
 end;
 //
 // Request: @exit
 // Request: @exit=n
 // Reply:   @exit=n
 // Comment: Terminate program with exit code n.
 //
 procedure DoExit(const cmnd,args:LongString);
 begin
  Terminated:=true;
  System.ExitCode:=StrToIntDef(args,0);
  StdOut.Put:=Format('%s=%d',[cmnd,System.ExitCode]);
 end;
 //
 // Request: @errors
 // Request: @errors=n
 // Reply:   @errors=n
 // Comment: Return value n of error counter.
 //
 procedure DoErrors(const cmnd,args:LongString);
 var
  n : LongInt;
 begin
  if Str2Long(args,n)
  then n:=LockedExchange(StdIoErrorCount,n)+LockedExchange(smi_err_count,0)
  else n:=LockedGet(StdIoErrorCount)+LockedGet(smi_err_count);
  StdOut.Put:=Format('%s=%d',[cmnd,n]);
 end;
 //
 // Request: @memory
 // Request: @memory=n
 // Comment: Return AllocMemSize.
 //
 procedure DoMemory(const cmnd,args:LongString);
 begin
  StdOut.Put:=Format('%s=%d',[cmnd,GetAllocMemSize]);
 end;
 //
 // Request: @ProcessPriority
 // Request: @ProcessPriority=n
 // Reply:   @ProcessPriority=n
 // Comment: Set process priority class.
 //
 procedure DoProcessPriority(const cmnd,args:LongString);
 var p:DWORD;
 begin
  if not IsEmptyStr(args) then begin
   p:=GetPriorityClassByName(args);
   if p>0 then SetPriorityClass(GetCurrentProcess,p);
  end;
  StdOut.Put:=Format('%s=%s',[cmnd,GetPriorityClassName(GetPriorityClass(GetCurrentProcess))]);
 end;
 //
 // Request: @ThreadPriority
 // Request: @ThreadPriority=n
 // Reply:   @ThreadPriority=n
 // Comment: Set thread priority class.
 //
 procedure DoThreadPriority(const cmnd,args:LongString);
 var p:TThreadPriority;
 begin
  if not IsEmptyStr(args) then begin
   p:=GetPriorityByName(args);
   StdIn.Priority:=p;
   StdOut.Priority:=p;
   case p of
    tpIdle         : SetThreadPriority(GetCurrentThread,THREAD_PRIORITY_IDLE);
    tpLowest       : SetThreadPriority(GetCurrentThread,THREAD_PRIORITY_LOWEST);
    tpLower        : SetThreadPriority(GetCurrentThread,THREAD_PRIORITY_BELOW_NORMAL);
    tpNormal       : SetThreadPriority(GetCurrentThread,THREAD_PRIORITY_NORMAL);
    tpHigher       : SetThreadPriority(GetCurrentThread,THREAD_PRIORITY_ABOVE_NORMAL);
    tpHighest      : SetThreadPriority(GetCurrentThread,THREAD_PRIORITY_HIGHEST);
    tpTimeCritical : SetThreadPriority(GetCurrentThread,THREAD_PRIORITY_TIME_CRITICAL);
   end;
  end;
  StdOut.Put:=Format('%s=%s',[cmnd,GetPriorityName(StdIn.Priority)]);
 end;
 //
 // Request: @OnException=Message
 // Reply:   @OnException=...Message...
 // Comment: Raise test exception.
 //
 procedure DoOnException(const cmnd,args:LongString);
 begin
  try
   Raise ESoftException.Create(args);
  except
   on E:Exception do BugReport(E,'DoOnException');
  end;
 end;
 //
 // Request: @dim_dns_node
 // Request: @dim_dns_node=n
 // Reply:   @dim_dns_node=n
 // Comment: Set/get DIM_DNS_NODE environment variable as n.
 //
 procedure DoDimDnsNode(const cmnd,args:LongString);
 var node:LongString; buff:array[0..MAX_PATH] of char;
 begin
  node:=ExtractWord(1,args,ScanSpaces);
  if SameText(node,'.') then node:='localhost';
  if (node<>'') then SetEnv('DIM_DNS_NODE',node);
  if (node<>'') then dim_set_dns_node(PChar(node));
  if (dim_get_dns_node(buff)>0) then node:=buff else node:=GetEnv('DIM_DNS_NODE');
  StdOut.Put:=Format('%s=%s',[cmnd,node]);
 end;
 //
 // Request: @smi_attach
 // Request: @smi_attach=n
 // Reply:   @smi_attach=status,n
 // Comment: Attach to SMI object name n.
 //
 procedure DoSmiAttach(const cmnd,args:LongString);
 begin
  if not IsSmiAttached then begin
   smi_attach_obj:=UpperCase(Trim(args));
   if (smi_attach_obj<>'') then
   smi_attach_res:=smi_attach(PChar(smi_attach_obj),smi_proxy_handler);
  end;
  StdOut.Put:=Format('%s=%d,%s',[cmnd,smi_attach_res,smi_attach_obj]);
 end;
 //
 // Request: @smi_volatile
 // Reply:   @smi_volatile=n (status 0/1)
 // Comment: Make SMI volatile.
 //
 procedure DoSmiVolatile(const cmnd,args:LongString);
 var res:Integer;
 begin
  res:=0;
  if IsSmiAttached then begin
   smi_volatile;
   res:=1;
  end;
  StdOut.Put:=Format('%s=%d',[cmnd,res]);
 end;
 //
 // Request: @smi_set_par=name,type,value
 // Reply:   @smi_set_par=status,name,type,value
 // Comment: Set SMI param value.
 //
 procedure DoSmiSetPar(const cmnd,args:LongString);
 var res,typ,iv:Integer; sname,stype,xtype,value:LongString; fv:Double;
 const STYPES=';0;S;(S);STR;STRING;SMI_STRING;C;(C);CHAR;SMI_CHAR;';
 const ITYPES=';1;I;(I);INT;INTEGER;SMI_INTEGER;L;(L);LONG;SMI_LONG;';
 const FTYPES=';2;F;(F);FLOAT;SMI_FLOAT;R;(R);REAL;SMI_REAL;D;(D);DOUBLE;SMI_DOUBLE;';
 begin
  res:=0; typ:=0;
  sname:=''; value:='';
  if IsSmiAttached then begin
   if (WordCount(args,ScanSpaces)>=3) then begin
    sname:=UpperCase(ExtractWord(1,args,ScanSpaces));
    stype:=UpperCase(ExtractWord(2,args,ScanSpaces));
    value:=SkipWords(2,args,ScanSpaces);
    xtype:=';'+stype+';';
    if (Pos(xtype,STYPES)>0) then typ:=SMI_STRING  else
    if (Pos(xtype,ITYPES)>0) then typ:=SMI_INTEGER else
    if (Pos(xtype,FTYPES)>0) then typ:=SMI_FLOAT   else typ:=-1;
    case typ of
     SMI_STRING:  res:=smi_set_par(PChar(sname),PChar(value),typ);
     SMI_INTEGER: if Str2Int(value,iv)
                  then res:=smi_set_par(PChar(sname),@iv,typ)
                  else ReportError('Bad number in @smi_set_par='+args);
     SMI_FLOAT:   if Str2Real(value,fv)
                  then res:=smi_set_par(PChar(sname),@fv,typ)
                  else ReportError('Bad number in @smi_set_par='+args);
     else         ReportError('Invalid type in @smi_set_par='+args);
    end;
   end else ReportError('Missed params in @smi_set_par='+args);
  end else SmiWasNotAttachedWarning;
  StdOut.Put:=Format('%s=%d,%s,%d,%s',[cmnd,res,sname,typ,value]);
 end;
 //
 // Request: @smi_set_state=state
 // Reply:   @smi_set_state=status,state
 // Comment: Set SMI initial state.
 //          State may be a single word like READY or command
 //          like READY/NUMBER(I)=1/VOLTAGE(F)=1.23/NAME(S)=ABC
 //          C-like escaping uses to pass strings.
 //
 procedure DoSmiSetState(const cmnd,args:LongString);
 var res:Integer; state:LongString;
 begin
  res:=0; state:=UpperCase(Trim(args));
  if IsSmiAttached then begin
   if (state<>'') then
   res:=smi_set_state(PChar(state));
  end else SmiWasNotAttachedWarning;
  StdOut.Put:=Format('%s=%d,%s',[cmnd,res,state]);
 end;
 //
 // Request: @smi_get_state
 // Reply:   @smi_get_state=state
 // Comment: Get SMI current state.
 //
 procedure DoSmiGetState(const cmnd,args:LongString);
 var res:Integer; state:LongString; buff:array[0..MAX_NAME] of char;
 begin
  state:='DEAD'; buff:='';
  if IsSmiAttached then begin
   res:=smi_get_state(buff,MAX_NAME);
   if (res<>0) then state:=buff;
  end else SmiWasNotAttachedWarning;
  StdOut.Put:=Format('%s=%s',[cmnd,state]);
 end;
 //
 // Request: @smi_terminate_action=state
 // Reply:   @smi_terminate_action=status,state
 // Comment: Tertminate action, set SMI current state.
 //          State may be a single word like READY or command
 //          like READY/NUMBER(I)=1/VOLTAGE(F)=1.23/NAME(S)=ABC
 //          C-like escaping uses to pass strings.
 //
 procedure DoSmiTerminateAction(const cmnd,args:LongString);
 var res:Integer; state:LongString;
 begin
  res:=0; state:=UpperCase(Trim(args));
  if IsSmiAttached then begin
   if (state<>'') then
   res:=smi_terminate_action(PChar(state));
  end else SmiWasNotAttachedWarning;
  StdOut.Put:=Format('%s=%d,%s',[cmnd,res,state]);
 end;
 //
 // Request: @smi_errors
 // Request: @smi_errors=n
 // Reply:   @smi_errors=n
 // Comment: Return value n of error counter.
 //
 procedure DoSmiErrors(const cmnd,args:LongString);
 var n:LongInt;
 begin
  if Str2Long(args,n)
  then n:=LockedExchange(smi_err_count,n)
  else n:=LockedGet(smi_err_count);
  StdOut.Put:=Format('%s=%d',[cmnd,n]);
 end;
 //
 // Translate DEMO/PAR1=ABC/PAR2(S)=DEF/PAR3(I)=123/PAR4(F)=1.2
 // to commands
 // @smi_set_par=PAR1,S,ABC
 // @smi_set_par=PAR2,S,DEF
 // @smi_set_par=PAR3,I,123
 // @smi_set_par=PAR4,F,1.2
 // @smi_set_state=DEMO
 //
 function TranslateStateCommand(WantedCommand,s:LongString):LongString;
 var i:Integer; t:TStringList;
 begin
  s:=Trim(s);
  WantedCommand:=Trim(WantedCommand);
  if (s<>'') and (WantedCommand<>'') then
  if (Pos('/',s)=0) then s:=WantedCommand+s else begin
   s:=Trim(StringReplace(s,'/',LineEnding,[rfReplaceAll]));
   if (s<>'') then begin
    t:=TStringList.Create;
    try
     t.Text:=s;
     if t.Count=0 then s:='' else begin
      for i:=1 to t.Count-1 do begin
       s:=Trim(t[i]);
       if (Pos('=',s)>0) then begin
        if (Pos('(',s)>0) and (Pos(')=',s)>0) then begin
         s:=StringReplace(s,')=',',',[]);
         s:=StringReplace(s,'(',',',[]);
         s:='@smi_set_par='+s;
        end else begin
         s:=StringReplace(s,'=',',S,',[]);
         s:='@smi_set_par='+s;
        end;
        t[i]:=s;
       end;
      end;
      t.Add(WantedCommand+Trim(t[0]));
      t.Delete(0);
      s:=t.Text;
     end;
    finally
     t.Free;
    end;
   end;
  end;
  Result:=s;
 end;
 //
 // Put multiline text to StdIn.
 //
 procedure PutTextToStdIn(const S:LongString);
 var t:TStringList; i:Integer;
 begin
  if S='' then Exit;
  t:=TStringList.Create;
  try
   t.Text:=Trim(S);
   for i:=0 to t.Count-1 do StdIn.Put:=t[i];
  finally
   t.Free;
  end;
 end;
 //
 // This callback handles unrecognized commands.
 //
 procedure DoSpecificCommands(const args:LongString);
 begin
  if Length(args)>0 then
  ReportError(Format('Could not recognize "%s"',[args]));
 end;
 //
 // Get EXE file version info.
 //
 function GetVersionInfo(const Name:LongString):LongString;
 begin
  Result:=CookieScan(GetFileVersionInfoAsText(ProgName),Name);
 end;
 function DotEnding(const S:LongString):LongString;
 const dot='.';
 begin
  Result:=S;
  if (Result<>'') then if (StrFetch(Result,Length(Result))<>dot) then Result:=Result+dot;
 end;
 procedure PrintVersionInfo(const Fallback:LongString);
 begin
  if not IsEmptyStr(GetVersionInfo('ProductName')) then begin
   StdOut.Put:=DotEnding(GetVersionInfo('ProductName')+' version '+GetVersionInfo('ProductVersion'));
   StdOut.Put:=DotEnding(GetVersionInfo('FileDescription'));
   StdOut.Put:=DotEnding(GetVersionInfo('LegalCopyright'));
  end else begin
   StdOut.Put:=Fallback;
  end;
 end;
 //
 // Application specific initialization.
 //
 procedure SpecificInitialization;
 begin
  //
  // Version. Copyright. Greetings.
  //
  UseRunCommandEx(True);
  SystemEchoProcedure:=StdOutEcho;
  PrintVersionInfo('smiProxy server for CRW-DAQ,');
  StdOut.Put:='Call smiProxy --help to get help on cmdline params.';
  StdOut.Put:=Format('PID %d started.',[GetCurrentProcessId]);
  LockedInit(smi_cmd_count);
  LockedInit(smi_err_count);
  //
  // Check SmiRtlLibrary
  //
  if not LoadSmiRtlLibrary then begin
   StdOut.Put:='Could not load '+SmiRtlLibraryName+' library.';
   StdOut.Put:=SmiRtlLibrary.Report; Sleep(1000);
   raise ESmiRtlLibrary.Create('Could not load '+SmiRtlLibraryName);
  end;
  // 
  // Register user commands coming from StdIn.
  //
  SystemEchoProcedure:=StdOutEcho;
  StdIn.SpecHandler:=DoSpecificCommands;
  StdIn.AddCommand('@Help',                 DoHelp);
  StdIn.AddCommand('@Exit',                 DoExit);
  StdIn.AddCommand('@Errors',               DoErrors);
  StdIn.AddCommand('@Memory',               DoMemory);
  StdIn.AddCommand('@ProcessPriority',      DoProcessPriority);
  StdIn.AddCommand('@ThreadPriority',       DoThreadPriority);
  StdIn.AddCommand('@OnException',          DoOnException);
  StdIn.AddCommand('@dim_dns_node',         DoDimDnsNode);
  StdIn.AddCommand('@smi_attach',           DoSmiAttach);
  StdIn.AddCommand('@smi_volatile',         DoSmiVolatile);
  StdIn.AddCommand('@smi_set_par',          DoSmiSetPar);
  StdIn.AddCommand('@smi_set_state',        DoSmiSetState);
  StdIn.AddCommand('@smi_get_state',        DoSmiGetState);
  StdIn.AddCommand('@smi_terminate_action', DoSmiTerminateAction);
  StdIn.AddCommand('@smi_errors',           DoSmiErrors);
  //
  // Parse command line
  //
  if CmdArgs.HasOption('-h')
  or CmdArgs.HasOption('/?')
  or CmdArgs.HasOption('--help') then begin
   PrintUsageInfo;
   Exit;
  end;
  CmdArgs.ListOptVal:='--obj;-obj;--set;-set;--dns;-dns';
  if CmdArgs.HasParam(1) then opt_obj:=CmdArgs.GetParam(1);
  if CmdArgs.HasOption('--obj') then opt_obj:=CmdArgs.GetOptionValue('--obj');
  if CmdArgs.HasOption('-obj') then opt_obj:=CmdArgs.GetOptionValue('-obj');
  if CmdArgs.HasParam(2) then opt_set:=CmdArgs.GetParam(2);
  if CmdArgs.HasOption('--set') then opt_set:=CmdArgs.GetOptionValue('--set');
  if CmdArgs.HasOption('-set') then opt_set:=CmdArgs.GetOptionValue('-set');
  if CmdArgs.HasParam(3) then opt_dns:=CmdArgs.GetParam(3);
  if CmdArgs.HasOption('--dns') then opt_dns:=CmdArgs.GetOptionValue('--dns');
  if CmdArgs.HasOption('-dns') then opt_dns:=CmdArgs.GetOptionValue('-dns');
  if CmdArgs.HasOption('--volatile') then opt_vol:=true;
  if CmdArgs.HasOption('-volatile') then opt_vol:=true;
  if CmdArgs.HasOption('--vol') then opt_vol:=true;
  if CmdArgs.HasOption('-vol') then opt_vol:=true;
  if CmdArgs.HasOption('--verbose') then opt_verb:=true;
  if CmdArgs.HasOption('-verbose') then opt_verb:=true;
  if CmdArgs.HasOption('--verb') then opt_verb:=true;
  if CmdArgs.HasOption('-verb') then opt_verb:=true;
  if opt_verb then Echo(ReportCmdArgs);
  if (opt_dns<>'') then StdIn.Put:='@dim_dns_node='+opt_dns;
  if (opt_obj<>'') then StdIn.Put:='@smi_attach='+opt_obj;
  if (opt_obj<>'') then if opt_vol then StdIn.Put:='@smi_volatile';
  if (opt_set<>'') then PutTextToStdIn(TranslateStateCommand('@smi_set_state=',opt_set));
 end;
 //
 // Application specific finalization.
 //
 procedure SpecificFinalization;
 begin
  LockedFree(smi_cmd_count);
  LockedFree(smi_err_count);
 end;
 //
 // Application specific polling.
 //
 procedure SpecificPolling;
 const LastTicks:QWord=0;
 var CurrTicks:QWord;
 begin
  CurrTicks:=GetTickCount64;
  if (CurrTicks>LastTicks+1000) then begin
   TTask.PollDetachedPids;
   LastTicks:=CurrTicks;
  end;
  if BecameZombie(FILE_TYPE_PIPE,1000) then StdIn.Put:='@Exit';
 end;
 //
 // Main program
 //
begin
 try
  try
   SpecificInitialization;
   while not Terminated do begin
    while (StdIn.Count>0) do StdIn.Process(StdIn.Get);
    SpecificPolling;
    Sleep(TPolling.DefPollPeriod);
   end;
  finally
   SpecificFinalization;
  end;
 except
  on E:Exception do BugReport(E,'main');
 end;
 Sleep(100);
 if BecameZombie(FILE_TYPE_PIPE) then ExitCode:=1;
end.

//////////////
// END OF FILE
//////////////

