Recent

Author Topic: Pass class by type  (Read 4864 times)

SymbolicFrank

  • Hero Member
  • *****
  • Posts: 1313
Pass class by type
« on: July 19, 2017, 03:57:58 pm »
This is probably a stupid question, but anyway.

My application is distributed, so instead of calling a function, I create a thread. And all communication is through sockets.

Like this:

Code: Pascal  [Select][+][-]
  1.   TNode = class(TThread)
  2.   protected
  3.     MyWorker: TWorker;  // This is what is going to execute the action!
  4.   public
  5.     procedure Execute; override;
  6.   end;
  7.  
  8.   THandler = class(TThread)
  9.   public
  10.     procedure Execute; override;
  11.     function ProcessRequest(Request, URI: string): integer;
  12.   end;
  13.  
  14.   TWorker = class(TThread)
  15.   public
  16.     constructor Create(Command: string; Data: string);
  17.     procedure Execute; override;
  18.   end;
  19.  
  20. procedure TNode.Execute;
  21. var
  22.   ClientSock:TSocket;
  23. begin
  24.   with MySocket do
  25.     begin
  26.       CreateSocket;
  27.       SetLinger(True, 1000);
  28.       Bind('0.0.0.0', MyPort);
  29.       Listen;
  30.       repeat
  31.         if Terminated then Break;
  32.         if CanRead(1000) then
  33.           begin
  34.             ClientSock := Accept;
  35.             if LastError=0 then THandler.Create(ClientSock);
  36.           end;
  37.       until False;
  38.     end;
  39. end;
  40.  
  41. procedure THandler.Execute;
  42. begin
  43. ...
  44.     ResultCode := ProcessRequest(Method, URI);
  45.     MySocket.SendString(Protocol + ' ' + IntTostr(ResultCode) + CRLF);
  46. ...
  47. end;
  48.  
  49. function THandler.ProcessRequest(Request, URI: string): integer;
  50. begin
  51. ...
  52.     MyWorker.Create(URI, Data);
  53. ...
  54. end;
  55.  
  56.  

I left out all the irrelevant details.

Anyway, in ProcessRequest I have to create the right worker. So, if I created a new one, like so:

Code: Pascal  [Select][+][-]
  1. type
  2.   TReaderWorker = class(TWorker);

how can I get that one created without making a new TNode and THandler as well?


I tried storing the type instead:

Code: Pascal  [Select][+][-]
  1. TWorkerType = class of TWorker;

and pass that to TNode and THandler, but that crashed on the first line of the constructor (and the wrong one at that, from TWorker).

And I cannot pass them to TNode, because a new one is needed for each request.

(They start, do their thing and send a message to the specified client by themselves, if requested. And then they terminate. I don't even keep the pointers.)

How should I do that?

taazz

  • Hero Member
  • *****
  • Posts: 5368
Re: Pass class by type
« Reply #1 on: July 19, 2017, 05:11:20 pm »
take a look on TCollection, the ItemClass parameter on the constructor for a how to pass example and the add method for a how to create example.
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

SymbolicFrank

  • Hero Member
  • *****
  • Posts: 1313
Re: Pass class by type
« Reply #2 on: July 20, 2017, 08:21:48 am »
Thanks, taazz, but that is what I tried and it crashes on the first 'begin' of the wrong constructor.

taazz

  • Hero Member
  • *****
  • Posts: 5368
Re: Pass class by type
« Reply #3 on: July 20, 2017, 08:33:10 am »
it crashes on the first 'begin' of the wrong constructor.
wrong constructor? Create a small sample that demonstrates the problem and post it, As far as I can tell it has nothing to do with the class passing mechanism you do not have proper constructor chain ee one of your classes does not have virtual constructor.
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

Thaddy

  • Hero Member
  • *****
  • Posts: 14157
  • Probably until I exterminate Putin.
Re: Pass class by type
« Reply #4 on: July 20, 2017, 09:05:23 am »
That's my guess too. A missing or wrongly declared constructor. (inherited? override?, these two)

But there is a lot more wrong with that code. Passing strings w/o const modifiers is not a particularly smart idea in most cases, and certainly not with threads.
function ProcessRequest(Request, URI: string): integer; should be function ProcessRequest(const Request, URI: string): integer; everywhere...

usually:
- you don't want local copies, you want the content.
- you don't want to mess up things with excessive reference counts.

So use the const modifier for strings wherever possible.
Apart from possibly introducing the dreaded deadlock issues it is also much faster and safer.
« Last Edit: July 20, 2017, 09:16:38 am by Thaddy »
Specialize a type, not a var.

SymbolicFrank

  • Hero Member
  • *****
  • Posts: 1313
Re: Pass class by type
« Reply #5 on: July 20, 2017, 09:14:10 am »
So, I have to do this to fill in the right type?

Code: Pascal  [Select][+][-]
  1. type
  2.   TNewWorker = class(TWorker);
  3.   TNewWorkerType = class of TNewWorker;
  4.  
  5. constructor TNewNode.Create(ThisPort: string);
  6. var
  7.   w: TNewWorkerType;
  8. begin
  9.   inherited;
  10.   MyWorkerType := w;  // This looks totally wrong, MyWorkerType gets passed along
  11. end;
  12.  
  13. function THandler.ProcessRequest(Request, URI: string): integer;
  14. var
  15.   Worker: TWorker;
  16. begin
  17.   Worker := MyWorkerType.Create(URI, Data);
  18. ...
  19.  

That is the only way it compiles, but that looks totally wrong. And it doesn't work, obviously:

Code: Pascal  [Select][+][-]
  1. constructor TWorker.Create(Command: string; Data: string);
  2. begin                        // Here it crashes
  3.   Create(TMessage.Create(Command, Data));
  4. end;
  5.  

Thaddy

  • Hero Member
  • *****
  • Posts: 14157
  • Probably until I exterminate Putin.
Re: Pass class by type
« Reply #6 on: July 20, 2017, 09:19:10 am »
It is wrong. Even if you use a class "reference" that class still needs to be created. And plz use the const modifier for strings. See above.

Add a simple compilable project and I feed you back with something that would be acceptable.
« Last Edit: July 20, 2017, 09:21:24 am by Thaddy »
Specialize a type, not a var.

taazz

  • Hero Member
  • *****
  • Posts: 5368
Re: Pass class by type
« Reply #7 on: July 20, 2017, 09:26:40 am »
So, I have to do this to fill in the right type?

Code: Pascal  [Select][+][-]
  1. type
  2.   TNewWorker = class(TWorker);
  3.   TNewWorkerType = class of TNewWorker;
  4.  
  5. constructor TNewNode.Create(ThisPort: string);
  6. var
  7.   w: TNewWorkerType;
  8. begin
  9.   inherited;
  10.   MyWorkerType := w;  // This looks totally wrong, MyWorkerType gets passed along
  11. end;
  12.  
  13. function THandler.ProcessRequest(Request, URI: string): integer;
  14. var
  15.   Worker: TWorker;
  16. begin
  17.   Worker := MyWorkerType.Create(URI, Data);
  18. ...
  19.  

That is the only way it compiles, but that looks totally wrong. And it doesn't work, obviously:

Code: Pascal  [Select][+][-]
  1. constructor TWorker.Create(Command: string; Data: string);
  2. begin                        // Here it crashes
  3.   Create(TMessage.Create(Command, Data));
  4. end;
  5.  
The view you present is to narrow. I can't make heads or tails of what is going on. I need a complete declaration of the TWorker, TNode and TNewWorker(constructor/destructor and dummy use methods, avoid all the other methods). Again once properly declared you should be able to do something along the lines of
Code: Pascal  [Select][+][-]
  1. MyWorkerType := TNewWorkerType;
and of course a call to inherited is not optional. eg
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

Thaddy

  • Hero Member
  • *****
  • Posts: 14157
  • Probably until I exterminate Putin.
Re: Pass class by type
« Reply #8 on: July 20, 2017, 10:06:44 am »
Well all the information is already in the answers...
Code: Pascal  [Select][+][-]
  1. constructor TWorker.Create(Command: string; Data: string);
  2. begin                        // Here it crashes   <--- yes of course it crashes....
  3.   Create(TMessage.Create(Command, Data)); <--- just one param here?
  4. end;

Now, where is your inherited call to the base constructor of TThread
Why do you call create on itself?
And why do you do that with the wrong number of parameters.... (and in the wrong order if you mean TMethod)

There is a lot more wrong but fix that first.
Specialize a type, not a var.

SymbolicFrank

  • Hero Member
  • *****
  • Posts: 1313
Re: Pass class by type
« Reply #9 on: July 20, 2017, 03:54:13 pm »
Ok, I made all the strings constant and a demo project.

Edit: fixed a bug.
« Last Edit: July 20, 2017, 03:58:31 pm by SymbolicFrank »

Thaddy

  • Hero Member
  • *****
  • Posts: 14157
  • Probably until I exterminate Putin.
Re: Pass class by type
« Reply #10 on: July 20, 2017, 03:56:52 pm »
Ok, now I something to work with  ::)
Specialize a type, not a var.

SymbolicFrank

  • Hero Member
  • *****
  • Posts: 1313
Re: Pass class by type
« Reply #11 on: July 20, 2017, 03:59:56 pm »
Fixed a quick bug: in Unit1.TForm1 it should be: MyProcess: TNewProcess;

I reuploaded a fixed version.

SymbolicFrank

  • Hero Member
  • *****
  • Posts: 1313
Re: Pass class by type
« Reply #12 on: July 21, 2017, 10:59:44 am »
I tried using generics:

Code: Pascal  [Select][+][-]
  1.     MyServer: TNexusNode<T>;
  2.  

but (as expected) that gives the following error during compilation:

Error: Generics without specialization cannot be used as a type for a variable


So, I have no idea how to do this, except for making three new classes each time. Which I want to prevent, because the type of the worker is the only thing that changes, and it is used in only a single location.

Thaddy

  • Hero Member
  • *****
  • Posts: 14157
  • Probably until I exterminate Putin.
Re: Pass class by type
« Reply #13 on: July 21, 2017, 11:19:41 am »
I tried using generics:

Code: Pascal  [Select][+][-]
  1.     MyServer: TNexusNode<T>;
  2.  

but (as expected) that gives the following error during compilation:

Error: Generics without specialization cannot be used as a type for a variable


So, I have no idea how to do this, except for making three new classes each time. Which I want to prevent, because the type of the worker is the only thing that changes, and it is used in only a single location.
No that should not be a showstopper. I get back to what I promised with your new code. (Although I strongly advise to use Delphi mode for generics: any specialize issues disappear).
Specialize a type, not a var.

taazz

  • Hero Member
  • *****
  • Posts: 5368
Re: Pass class by type
« Reply #14 on: July 21, 2017, 11:26:50 am »
So, I have no idea how to do this, except for making three new classes each time. Which I want to prevent, because the type of the worker is the only thing that changes, and it is used in only a single location.
I'm guessing here that you want the TProcess to be able to use different workers based on external feedback.
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
  9.   logging, nexustypes, nexusnode, nexusprocess;
  10.  
  11. type
  12.  
  13.   { TNewWorker }
  14.  
  15.   TNewWorker = class(TWorker)
  16.   public
  17.     procedure Execute; override;
  18.   end;
  19.  
  20.   TNewWorkerType = class of TNewWorker;
  21.  
  22.   { TNewProcess }
  23.  
  24.   TNewProcess = class(TNexusProcess)
  25.   private
  26.     FWorker:TWorker;
  27.   public
  28.     constructor Create(const ThisPort: string; const aWorker:TMyWorkerType);
  29.   end;
  30.  
  31.   { TForm1 }
  32.  
  33.   TForm1 = class(TForm)
  34.     Button1: TButton;
  35.     procedure Button1Click(Sender: TObject);
  36.     procedure FormCreate(Sender: TObject);
  37.     procedure FormDestroy(Sender: TObject);
  38.   private
  39.     MyProcess: TNewProcess;
  40.   public
  41.     { public declarations }
  42.   end;
  43.  
  44. var
  45.   Form1: TForm1;
  46.  
  47. implementation
  48.  
  49. {$R *.lfm}
  50.  
  51. { TNewProcess }
  52.  
  53. constructor TNewProcess.Create(const ThisPort: string; const aWorker:TWorkerType);
  54. begin
  55.   inherited;
  56.   //MyWorkerType := aWorker;   // either this if you add the ability to change it during construction
  57.   MyWorkerType := TNewWorker; //or this if you make it static for each process.
  58. end;
  59.  
  60. { TNewWorker }
  61.  
  62. procedure TNewWorker.Execute;
  63. begin
  64.   Log('Success!');
  65. end;
  66.  
  67. { TForm1 }
  68.  
  69. procedure TForm1.FormCreate(Sender: TObject);
  70. begin
  71.   Log('Starting...');
  72.   MyProcess := TNewProcess.Create('44544',  TNewWorker);
  73. end;
  74.  
  75. procedure TForm1.Button1Click(Sender: TObject);
  76. var
  77.   Addr: TAddress;
  78. begin
  79.   Addr.Host := '127.0.0.1';
  80.   Addr.Port := '44544';
  81.   Log('Sending message to: ' + Addr.Host + ':' + Addr.Port);
  82.   SendMessage(Addr, TMessage.Create(Commands.Info, ''));
  83. end;
  84.  
  85. procedure TForm1.FormDestroy(Sender: TObject);
  86. begin
  87.   Log('Stop!');
  88.   MyProcess.Free;
  89. end;
  90.  
  91. end.
  92.  
  93.  
in short TWorkerType is a data on it self that holds a class instead of an object. You are passing the wrong data type see the comment on the code.
« Last Edit: July 21, 2017, 11:28:35 am by taazz »
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

 

TinyPortal © 2005-2018