Programming Forums

Programming Forums (http://www.programmingforums.org/forumindex.php)
-   Delphi (http://www.programmingforums.org/forum41.html)
-   -   Componets in a dll (http://www.programmingforums.org/showthread.php?t=12708)

sggaunt Mar 2nd, 2007 4:05 AM

Componets in a dll
 
The following code is an attempt to put the Delphi7 demo application Delphi7/demos/Internet/netchat/netchat.dpr
into a dll

I have used the dll wizard to set up the library code and then added a data unit to it.
The data unit includes the
TTcpServer and TTcpClient components from the Internet tab.
any attempt to access the Client or Server componets via the dll just causes a hang.

I also tried it with fastmem4 removed and tried starting a timer on the datamodule all with the same result.


As you can see I have attempted to include fastmm4 (correctly?)
and the string list which is meant to replace the memo in the demo seems to work OK (i.e I can read and write to it via

the dll).



CODE
library Ethernet;
{ To avoid using sharemempass string information using PChar or ShortString parameters. }
uses
FastMM4 in 'FastMM4.pas',
SysUtils,
Classes,
dataUnit in 'dataUnit.pas' {DataModule1: TDataModule};

{$R *.res}

function Initalise: integer; stdcall;
begin
result := 0;
MemRecv := TStringList.Create; // create a string list to hold recived messages
memrecv.Add('Test line');
end;

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

function ActivateServer(LocalP: shortstring;
remoteH: shortstring;
RemoteP: shortstring): integer; stdcall;
begin
result := 0;
with DataModule1 do
try
// any one of these calls cause the app to hang!!
Client.RemoteHost := RemoteH;
Client.RemotePort := RemoteP;
Server.LocalPort := LocalP;
Server.Active := True;
except
result := 1;
end;
end;

// read the first line out of the stringlist and delete it!!
// this works as expeceted.

function ReadLine: shortstring; stdcall;
begin
if MemRecv.Count > 0 then
begin
result := MemRecv.Strings[0];
Memrecv.Delete(0);
end
else
result := 'NO DATA';

end;



This is the data unit code (slightly) amended from the demo code.


CODE
unit dataUnit;

interface

uses
fastmm4, SysUtils, Sockets, ExtCtrls, Classes;

type
TDataModule1 = class(TDataModule)
Server: TTcpServer;
Client: TTcpClient;
procedure ServerAccept(Sender: TObject;
ClientSocket: TCustomIpClient);
private
{ Private declarations }
public
{ Public declarations }
end;

TClientDataThread = class(TThread)
private
public
ListBuffer :TStringList;
TargetList :TStrings;
procedure synchAddDataToControl;
constructor Create(CreateSuspended: Boolean);
procedure Execute; override;
procedure Terminate;
end;

var
DataModule1: TDataModule1;
Memrecv: Tstringlist;
implementation

{$R *.dfm}

//------------- TClientDataThread impl -----------------------------------------
constructor TClientDataThread.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
FreeOnTerminate := true;
ListBuffer := TStringList.Create;
end;

procedure TClientDataThread.Terminate;
begin
ListBuffer.Free;
inherited;
end;

procedure TClientDataThread.Execute;
begin
Synchronize(synchAddDataToControl);
end;

procedure TClientDataThread.synchAddDataToControl;
begin
TargetList.AddStrings(ListBuffer);
end;

//------------- end TClientDataThread impl -------------------------------------


procedure TDataModule1.ServerAccept(Sender: TObject;
ClientSocket: TCustomIpClient);
var
s: string;
DataThread: TClientDataThread;
begin
// create thread
DataThread:= TClientDataThread.Create(true);
// set the TagetList to the output string list.
DataThread.TargetList := memRecv;

// Load the Threads ListBuffer
DataThread.ListBuffer.Add('*** Connection Accepted ***');
DataThread.ListBuffer.Add('Remote Host: ' + ClientSocket.LookupHostName(ClientSocket.RemoteHost) +
' (' + ClientSocket.RemoteHost + ')');
DataThread.ListBuffer.Add('===== Begin message =====');
s := ClientSocket.Receiveln;
while s <> '' do
begin
DataThread.ListBuffer.Add(s);
s := ClientSocket.Receiveln;
end;
DataThread.ListBuffer.Add('===== End of message =====');

// Call Resume which will execute and synch the
// ListBuffer with the TargetList
DataThread.Resume;
end;

end.

DaWei Mar 2nd, 2007 8:15 AM

As a new member, it would be polite and sociable for you to read the rules/FAQ, particularly with regard to the use of code tags.

sggaunt Mar 3rd, 2007 5:00 AM

Sorry about that: The original location of this post did indeed have [code[/code] tags, but they didnt come across in the paste.
Shortage of time to completion of this project meant I needed help with this as quickly as possible. Hence the lack of time to Read the [rules] as I should and now have. Once again sorry.

BTW the code doesnt explicitly create the datamodule in the DLL which has now been sorted out but there are still problems with the operation

Steve

Ooble Mar 3rd, 2007 8:47 AM

Could you please repost the code with the [code] tags? It's very difficult to read without proper formatting.

sggaunt Mar 5th, 2007 3:47 AM

Here is the code in tags,
Please notice the ammended part (lack of implict creation of the data module)which was causing the hang.
However there are still problems in that although my fire wall detectes activity, the dll still dosent seem to be able to transmitt or recieve messages.


:

library Ethernet;
{ To avoid using sharemempass string information using PChar or ShortString parameters. }
uses
FastMM4 in 'FastMM4.pas',
SysUtils,
Classes,
dataUnit in 'dataUnit.pas' {DataModule1: TDataModule};

{$R *.res}

function Initalise: integer; stdcall;
begin
result := 0;
// The following line was missing from the original code and this caused most of the
//problem I belive it moust also be implictly freed.

DataModule1 := TDataModule1.Create(nil); // creates the data module
 
MemRecv := TStringList.Create; // create a string list to hold recived messages
memrecv.Add('Test line');
end;

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

function ActivateServer(LocalP: shortstring;
remoteH: shortstring;
RemoteP: shortstring): integer; stdcall;
begin
result := 0;
with DataModule1 do
try
// any one of these calls cause the app to hang!!
Client.RemoteHost := RemoteH;
Client.RemotePort := RemoteP;
Server.LocalPort := LocalP;
Server.Active := True;
except
result := 1;
end;
end;

// read the first line out of the stringlist and delete it!!
// this works as expeceted.

function ReadLine: shortstring; stdcall;
begin
if MemRecv.Count > 0 then
begin
result := MemRecv.Strings[0];
Memrecv.Delete(0);
end
else
result := 'NO DATA';

end;

// new fuction retives the values from the components this works ok.
function RetrieveSettings(Code: integer): shortstring; stdcall;
begin
  with datamodule1 do
  case code of
  0: result := Server.LocalHost;
  1: result := Server.LocalPort;
  2: result := Client.RemotePort;
  3: result := Client.RemoteHost;
  else result := 'Illegal Code';
  end;
end;


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

begin
end;



This is the data unit code (slightly) amended from the demo code.


:

unit dataUnit;

interface

uses
fastmm4, SysUtils, Sockets, ExtCtrls, Classes;

type
TDataModule1 = class(TDataModule)
Server: TTcpServer;
Client: TTcpClient;
procedure ServerAccept(Sender: TObject;
ClientSocket: TCustomIpClient);
private
{ Private declarations }
public
{ Public declarations }
end;

TClientDataThread = class(TThread)
private
public
ListBuffer :TStringList;
TargetList :TStrings;
procedure synchAddDataToControl;
constructor Create(CreateSuspended: Boolean);
procedure Execute; override;
procedure Terminate;
end;

var
DataModule1: TDataModule1;
Memrecv: Tstringlist;
implementation

{$R *.dfm}

//------------- TClientDataThread impl -----------------------------------------
constructor TClientDataThread.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
FreeOnTerminate := true;
ListBuffer := TStringList.Create;
end;

procedure TClientDataThread.Terminate;
begin
ListBuffer.Free;
inherited;
end;

procedure TClientDataThread.Execute;
begin
Synchronize(synchAddDataToControl);
end;

procedure TClientDataThread.synchAddDataToControl;
begin
TargetList.AddStrings(ListBuffer);
end;

//------------- end TClientDataThread impl -------------------------------------


procedure TDataModule1.ServerAccept(Sender: TObject;
ClientSocket: TCustomIpClient);
var
s: string;
DataThread: TClientDataThread;
begin
// create thread
DataThread:= TClientDataThread.Create(true);
// set the TagetList to the output string list.
DataThread.TargetList := memRecv;

// Load the Threads ListBuffer
DataThread.ListBuffer.Add('*** Connection Accepted ***');
DataThread.ListBuffer.Add('Remote Host: ' + ClientSocket.LookupHostName(ClientSocket.RemoteHos t) +
' (' + ClientSocket.RemoteHost + ')');
DataThread.ListBuffer.Add('===== Begin message =====');
s := ClientSocket.Receiveln;
while s <> '' do
begin
DataThread.ListBuffer.Add(s);
s := ClientSocket.Receiveln;
end;
DataThread.ListBuffer.Add('===== End of message =====');

// Call Resume which will execute and synch the
// ListBuffer with the TargetList
DataThread.Resume;
end;

end.


Jimmy Bazooka Mar 8th, 2007 8:58 AM

I cant see any Client or Server var declarations?! Well even if they are there, then you make it all wrong. You need to supply your dll host app with pointer to class instance variable. for example

some code in DLL:

:


...
 var
  SomeForm: TForm;

function CreateSomeForm: Pointer;
begin
 Result := SomeForm.Create(nil);
 ...
 // some other code
end;

procedure DestroySomeForm;
begin
 If Assigned(SomeForm) then SomeForm.Free;
 ...
 // some other code
end;


sggaunt Mar 8th, 2007 11:11 AM

Thanks for your reply, not sure I fully understand, heres are some of the calling functions maybe you can put it more in context?

:

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

procedure TForm1.Button2Click(Sender: TObject);
begin
  try
      MessageBox.Text := inttostr(Maximum(10,20));
  except
      showmessage('Error in Dll');
  end;
end;

procedure TForm1.SetupBtnClick(Sender: TObject);
begin
  if ActivateServer(strtoint(LocalPort.Text), Address.Text, strtoint(RemotePort.Text)) = 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.SendBtnClick(Sender: TObject);
begin
  if send(OutputBox.Text, trim(address.Text), strtoint(trim(remotePort.Text))) = 0 then
      messagebox.Text := 'Client connected';
  BSent.Caption := inttostr(ClientBytesSent);
end;


Jimmy Bazooka Mar 12th, 2007 2:15 PM

:

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


firstly, i dont see the reason why you use try except block?:confused:
you have no crash unsafe calls there so you can easly remove it.

secondly, could you supply more info what components you want to implement in your DLL?:)

and what is MessageBox in your case? Is it the component created by DLL?

sggaunt Mar 13th, 2007 6:14 AM

Thanks for the Reply Jimmy

Yes not done (much/anything) with DLL's untill now so I wasnt sure if errors would propergate out of the DLL or how they should be handled!

We were going to put the Client server demo from the Delphi7 Intenet tab into the DLL , but now this dosnt seems to be the correct thing to do.
So what I now propose to use is the Indy IDTCPServer component, and the functionality will be based around the Demo code for this component.
in Indy/demos/IIdTCPDemo/Server.

We are starting to make some progress getting the application version of this talk to to our hardware (PIC Micro) server.
But our costomer wants a DLL so he can do something with VB.

Steve

Jimmy Bazooka Mar 14th, 2007 1:05 PM

One more thing to know...
 
OK, Steve. Also I'd like to aware you of problem of calling conventions missmatch, that is, you need to specify the StdCall convention to make sure that your exported function will work in VB. By default, Delphi uses Register calling convention, so the parameters are passed from left to right, and if possible, through CPU registers instead of creating new stack frame. But compilers (intepreters) from Microsoft and many other developers use StdCall convention by default (as all functions of WinAPI). Under StdCall calling convention parameters are passed from right to left and always on the stack.
;)


All times are GMT -5. The time now is 12:27 PM.

Powered by vBulletin® Version 3.7.0, Copyright ©2000 - 2008, Jelsoft Enterprises Ltd.
Copyright ©2007 DaniWeb® LLC