Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations MikeeOK on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

TIdTCPServer in a DLL

Status
Not open for further replies.

sggaunt

Programmer
Jul 4, 2001
8,620
GB
This would seem to do what we want provided That the altered Indy demo server is run our PIC board can now communicate with the server demo.

But this has to be in a DLL. and that refuses to work.

All that happen is that our hardware 'client' can see a connection being established. but nothing at all is returned from the Delphi application.
Though the calling application can retrieve the settings [bound ports/ machine name etc] from the server component


Here's the complete code
DLL Main file
Code:
library Ethernet2;

uses
  SysUtils,
  Classes,
  DataUnit2 in 'DataUnit2.pas' {DataModule2: TDataModule};

{$R *.res}
function Initalise: integer;  stdcall;
begin
   result := 0;
   try
     DataModule2 := TDataModule2.Create(nil); // creates the data module
     MemRecv := TStringList.Create;  // create a string list to hold recived messages
     memrecv.Add('Test line');
   except
     result := 1;
   end;
end;


function StopAll: integer; stdcall;
begin
  result := 0;
  try
    Datamodule2.Server.Active := false;
    Freeandnil(Memrecv);
  except
    result := 1;
  end;
end;

function ActivateServer: integer; stdcall;
begin
  result := 0;
  with DataModule2 do
    try
       Server.Active := True;
    except
       result := 1;
    end;
end;

procedure ChangeSettings(BoundIP: shortstring;
                        BoundP: integer;
                        RemoteP: integer); stdcall;
begin
   with DataModule2 do
      begin
        Server.DefaultPort := RemoteP;
        Server.Bindings.Items[0].Port := BoundP;
        Server.Bindings.Items[0].IP := BoundIp;
      end;

end;



// read the first line out of the stringlist and delete it!!
function ReadLine: shortstring; stdcall;
begin
   if MemRecv.Count > 0 then
      begin
         result := MemRecv.Strings[0];
         Memrecv.Delete(0);
      end
   else
      result := 'NO DATA';
end;

// read the number of lines stored
function ReadCount: integer; stdcall;
begin
  result := MemRecv.count;
end;

function RetrieveSettings(Code: integer): shortstring; stdcall;
begin
   with datamodule2 do
   case code of
   0: result := Server.LocalName;
   1: result := inttostr(Server.DefaultPort);
   2: result := Server.Version;
   else result := 'Illegal Code';
   end;
end;


function Minimum(X, Y: Integer): Integer; stdcall;
begin
 if X < Y then Minimum := X else Minimum := Y;
end;

function Maximum(X, Y: Integer): Integer; stdcall;
begin
 if X > Y then Maximum := X else Maximum := Y;
end;

exports
 Initalise, StopAll, ActivateServer, ReadLine,
 Minimum, Maximum, RetrieveSettings, ReadCount,
 ChangeSettings;

begin
end.

DLL datamodule containg TldTCPServer and IdThreadMGRDefault exactly as on the Demo form.
The only differences are that the data structure definition has been moved into the datamodule code.
A string list has replaced the output memo.

Code:
unit DataUnit2;

interface

uses
  SysUtils, Classes, IdTCPServer, IdBaseComponent, IdComponent,
  IdTCPConnection, IdTCPClient, IdThreadMgr, IdThreadMgrDefault;

type
  TCommBlock = record   // the Communication Block used in both parts (Server+Client)
                 Command,
                 MyUserName,                 // the sender of the message
                 Msg,                        // the message itself
                 ReceiverName: string[25];  // name of receiver
               end;

  PClient   = ^TClient;
  TClient   = record  // Object holding data of client (see events)
                DNS         : String[20];            { Hostname }
                Connected,                           { Time of connect }
                LastAction  : TDateTime;             { Time of last transaction }
                Thread      : Pointer;               { Pointer to thread }
              end;


  TDataModule2 = class(TDataModule)
    Server: TIdTCPServer;
    IdThreadMgrDefault1: TIdThreadMgrDefault;
    procedure ServerConnect(AThread: TIdPeerThread);
    procedure ServerDisconnect(AThread: TIdPeerThread);
    procedure ServerExecute(AThread: TIdPeerThread);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  DataModule2: TDataModule2;
  Memrecv: Tstringlist;
  Clients: TThreadList;     // Holds the data of all clients
implementation

{$R *.dfm}

procedure TDataModule2.ServerConnect(AThread: TIdPeerThread);
var  NewClient: PClient;
begin
  GetMem(NewClient, SizeOf(TClient));

  NewClient.DNS        := AThread.Connection.LocalName;
  NewClient.Connected  := Now;
  NewClient.LastAction := NewClient.Connected;
  NewClient.Thread     := AThread;

  AThread.Data := TObject(NewClient);

  try
    Clients.LockList.Add(NewClient);
  finally
    Clients.UnlockList;
  end;

  MemRecv.Add(TimeToStr(Time)+' Connection from "' + NewClient.DNS+'"');
  AThread.Connection.WriteLn('Connection established');
end;

procedure TDataModule2.ServerDisconnect(AThread: TIdPeerThread);
var ActClient: PClient;
begin
 ActClient := PClient(AThread.Data);
  MemRecv.Add (TimeToStr(Time)+' Disconnect from "'+ActClient^.DNS+'"');
  try
    Clients.LockList.Remove(ActClient);
  finally
    Clients.UnlockList;
  end;
  FreeMem(ActClient);
  AThread.Data := nil;
end;

procedure TDataModule2.ServerExecute(AThread: TIdPeerThread);
var
  ActClient, RecClient: PClient;
  CommBlock, NewCommBlock: TCommBlock;
  RecThread: TIdPeerThread;
  i: Integer;
begin
   MemRecv.Add(TimeToStr(Time)+' Server execute called ');

  if not AThread.Terminated and AThread.Connection.Connected then
  begin
    AThread.Connection.ReadBuffer (CommBlock, SizeOf (CommBlock));
    ActClient := PClient(AThread.Data);
    ActClient.LastAction := Now;  // update the time of last action

    if (CommBlock.Command = 'MESSAGE') or (CommBlock.Command = 'DIALOG') then
        begin  // 'MESSAGE': A message was send - forward or broadcast it
           // 'DIALOG':  A dialog-window shall popup on the recipient's screen
           // it's the same code for both commands...

           if CommBlock.ReceiverName = '' then
               begin  // no recipient given - broadcast
                  MemRecv.Add (TimeToStr(Time)+' Broadcasting '+CommBlock.Command+': "'+CommBlock.Msg+'"');
                  NewCommBlock := CommBlock;  // nothing to change ;-))

                  with Clients.LockList do
                     try
                        for i := 0 to Count-1 do  // iterate through client-list
	                          begin
                               RecClient := Items[i];           // get client-object
                               RecThread := RecClient.Thread;     // get client-thread out of it
                               RecThread.Connection.WriteBuffer(NewCommBlock, SizeOf(NewCommBlock), True);  // send the stuff
                            end;
                     finally
                        Clients.UnlockList;
               end;
        end
    else
        begin  // receiver given - search him and send it to him
           NewCommBlock := CommBlock; // again: nothing to change ;-))
           MemRecv.Add(TimeToStr(Time)+' Sending '+CommBlock.Command+' to "'+CommBlock.ReceiverName+'": "'+CommBlock.Msg+'"');
           with Clients.LockList do
              try
                 for i := 0 to Count-1 do
                    begin
                       RecClient:=Items[i];
                       if RecClient.DNS=CommBlock.ReceiverName then  // we don't have a login function so we have to use the DNS (Hostname)
                          begin
                             RecThread:=RecClient.Thread;
                             RecThread.Connection.WriteBuffer(NewCommBlock, SizeOf(NewCommBlock), True);
                          end;
                    end;
              finally
                  Clients.UnlockList;
        end;
      end;
    end
    else
    begin  // unknown command given
      MemRecv.Add (TimeToStr(Time)+' Unknown command from "' + CommBlock.MyUserName+'": '+CommBlock.Command);
      NewCommBlock.Command := 'DIALOG';       // the message should popup on the client's screen
      NewCommBlock.MyUserName := '[Server]';  // the server's username
      NewCommBlock.Msg := 'I don''t understand your command: "' + CommBlock.Command + '"';  // the message to show
      NewCommBlock.ReceiverName := '[return-to-sender]'; // unnecessary

      AThread.Connection.WriteBuffer (NewCommBlock, SizeOf (NewCommBlock), true);  // and there it goes...
    end;
  end;
end;

end.


This is the calling application

Code:
unit test;

interface

uses
  {fastmm4,} Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, registry, ExtCtrls;

 type
  TForm1 = class(TForm)
    Button1: TButton;
    MessageBox: TEdit;
    Button2: TButton;
    Address: TEdit;
    LocalPort: TEdit;
    Remoteport: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    SetupBtn: TButton;
    Closebtn: TButton;
    ReadBtn: TButton;
    StopBtn: TButton;
    getSetsBtn: TButton;
    Label4: TLabel;
    OutputBox: TEdit;
    SelectSetting: TComboBox;
    Label5: TLabel;
    Label6: TLabel;
    RemCount: TLabel;
    IPCheck: TTimer;
    ApplyBtn: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure SetupBtnClick(Sender: TObject);
    procedure ClosebtnClick(Sender: TObject);
    procedure ReadBtnClick(Sender: TObject);
    procedure getSetsBtnClick(Sender: TObject);
    procedure StopBtnClick(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure IPCheckTimer(Sender: TObject);
    procedure ApplyBtnClick(Sender: TObject);
  private
      { Private declarations }
      procedure ReadRegistry;
      procedure WriteRegistry;

  public
      FIniFile: TReginiFile;
  end;


  // DLL Function calls
  function SayHello: shortstring; stdcall; external 'hellow';
  function Maximum(X, Y: Integer): Integer; stdcall; external 'Ethernet2';
  function Initalise: integer; stdcall; external 'Ethernet2';
  function ActivateServer: integer; stdcall; external 'Ethernet2';

  function ReadLine: shortstring; stdcall; external 'Ethernet2';
  procedure ChangeSettings(BoundIP: shortstring;
                           BoundP: integer;
                           RemoteP: integer);   stdcall; external 'Ethernet2';

  function RetrieveSettings(Code: integer): shortstring; stdcall; external 'Ethernet2';
  function StopAll: integer; stdcall; external 'Ethernet2';
  function ReadCount: integer; stdcall; external 'Ethernet2';    // read the number of lines of recived text stored

var
  Form1: TForm1;


const
 SECTION = 'Intrex\Lantest';

implementation
{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  if Initalise > 0 then
     ShowMessage('Initalisation Error')
  else
     MessageBox.Text := 'Init OK';
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
   MessageBox.Text := inttostr(Maximum(10,20));
end;

procedure TForm1.SetupBtnClick(Sender: TObject);
begin
  if ActivateServer() = 0 then
      MessageBox.Text := 'Server active';
  IpCheck.Enabled := true;
end;

procedure TForm1.ClosebtnClick(Sender: TObject);
begin
  ipcheck.Enabled := false;
  close
end;

procedure TForm1.ReadBtnClick(Sender: TObject);
begin
  MessageBox.Text := ReadLine;
  RemCount.Caption := inttostr(ReadCount);
end;

procedure TForm1.getSetsBtnClick(Sender: TObject);
begin
  MessageBox.Clear;
  MessageBox.Text := RetrieveSettings(SelectSetting.itemindex);
end;

procedure TForm1.StopBtnClick(Sender: TObject);
begin
   ipcheck.Enabled := false;
   if stopall = 0 then
     messagebox.Text := 'Server and Client Stopped'
   else
     messagebox.Text := 'Error Stopping Server and Client';
end;

procedure TForm1.FormActivate(Sender: TObject);
begin
  ReadRegistry;
end;

procedure TForm1.ReadRegistry;
begin
    FIniFile := TRegIniFile.Create('Software');
   with FIniFile do
     begin
        Address.Text := ReadString(SECTION, 'Last', '127.0.0.1');
        LocalPort.Text := ReadString(SECTION, 'Local Port', '4000');
       free;
     end;
end;

procedure TForm1.WriteRegistry;
begin
    FIniFile := TRegIniFile.Create('Software');
    with FIniFile do
       begin
         WriteString(SECTION, 'Remote', Address.Text);
         WriteString(SECTION, 'Local Port', LocalPort.Text);
         WriteString(SECTION, 'Remote Port', RemotePort.Text);
         free;
     end;

end;


procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  WriteRegistry;
end;

procedure TForm1.IPCheckTimer(Sender: TObject);
begin
   RemCount.Caption := Inttostr(ReadCount);
end;

procedure TForm1.ApplyBtnClick(Sender: TObject);
begin
  ChangeSettings(Address.Text, Strtoint(LocalPort.Text), strtoint(RemotePort.Text));
end;

end.

Steve [The sane]: Delphi a feersum engin indeed.
 
A-Ha
The demo Application does this
Clients := TThreadList.Create;
in 'formcreate'. Doh

The dll does now and now it works!!!
It also explains the symptoms. We saw a connection but no client replies, what I still don't understand is why this doesnt throw an exception?





Steve [The sane]: Delphi a feersum engin indeed.
 
Glad you sorted it out. about the exceptions, maybe you don't get them because you are in a dll??

in services and dll's I always add a debugger that logs to file.

so something like

Code:
procedure proc1
begin
 try
  ...
 except
  on E:Exception do
   debug.output(format('proc1 - exception: %s',[E.message]);
 end
end;

this already saved me tons of bughunting time...

Cheers,
Daddy

-----------------------------------------------------
What You See Is What You Get
Never underestimate tha powah of tha google!
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top