Search code examples
delphiindydelphi-10.3-rio

How to setup an Indy custom TIdIOHandler?


I want to use a custom TIdIOHandler for my Indy TCP client and I don't know how to set it up. I create a new IOHandler class, I registered with TIdIOHandler.RegisterIOHandler, and then I use TIdTCPClient.CreateIOHandler with my new handler reference. Now, when I try to write to the TIdTCPClient.Socket I get "Abstract error" exception... Do I need to configure other things besides my example ?

  TIdEnhancedIOHandler = class(TIdIOHandlerSocket)
  public
    function Acknowledge(Command: Cardinal = 0): Boolean;
  end

{this is a client in a thread}
constructor TMyTCPClient.Create(const AHost: String; APort: Word);
begin
 inherited Create;
 FreeOnTerminate:= True;
 TIdEnhancedIOHandler.RegisterIOHandler;
 TCPClient:= TIdTCPClient.Create;
 TCPClient.CreateIOHandler(TIdEnhancedIOHandler);
 TCPClient.ConnectTimeout:= 1000;
 TCPClient.ReadTimeout:= -1;
 TCPClient.Host:= AHost;
 TCPClient.Port:= APort;
 RetSuccess:= False;
 RetMessage:= 'Unknown error.';
end;

procedure TMainClient.Execute;
var CMD: Cardinal;
begin
 TCPClient.Connect;
 TCPClient.Socket.Write(CMD);
end;

Solution

  • From the Indy Help I understand that ...

    The help is old, and has outdated information.

    I must use CreateIOHandler and specify my custom IOHandler class.

    You can do that, but you don't actually need to. You can simply assign an instance of your custom class directly to the TIdTCPClient.IOHandler property, before calling TIdTCPClient.Connect(), eg:

    TCPClient := TIdTCPClient.Create;
    TCPClient.IOHandler :- TIdEnhancedIOHandler.Create(TCPClient);
    ...
    

    I do that and I get an exception which says that this class is not installed.

    That means Indy's internal GIOHandlerClassList does not contain any class types that derive from the class type you specify to CreateIOHandler().

    How cand I install it ? The variable GIOHandlerClassList that holds the list is private...

    You would need to call the public RegisterIOHandler() or SetDefaultClass() class method on your custom class type at runtime, such as in your unit's initialization section, eg:

    initialization
      TIdEnhancedIOHandler.RegisterIOHandler();
    

    However, the only thing in Indy that actually uses CreateIOHandler() is TIdSimpleServer, so there is no need to "register" your custom class in this situation.

    On the other hand, TIdTCPClient.Connect() does call TIdIOHandler.MakeDefaultIOHandler() if no IOHandler is assigned, and that uses GIOHandlerClassDefault, which is set by TIdIOHandler.SetDefaultClass() (which also calls RegisterIOHandler()). So, if you want TIdTCPClient to create an object of your custom class type for you, you would need to call SetDefaultClass() at least, eg:

    initialization
      TIdEnhancedIOHandler.SetDefaultClass();
    

    Either way, RegisterIOHandler()/SetDefaultClass() should be called only one time, such as at program startup.


    UPDATE: you are getting an abstract error because you are deriving your class from TIdIOHandlerSocket, which is an abstract class, as it does not implement the ReadDataFromSource() and WriteDataToTarget() methods that are declared as abstract in TIdIOHandler. You need to derive your class from TIdIOHandlerStack instead, which derives from TIdIOHandlerSocket and implements those methods.