i used a httpd to request some data from internet
function requestToServer(lParamList: TStringList) : string;
var
userDataString : string;
lHTTP: TIdHTTP;
serverResponce : string;
aobj: ISuperObject;
begin
application.ProcessMessages;
TThread.CreateAnonymousThread(
procedure
begin
lHTTP := TIdHTTP.Create(nil);
try
serverResponce := lHTTP.Post('http://domain.com/mjson.php', lParamList);
application.ProcessMessages;
aobj:= SO(serverResponce);
try
X := aobj['dta'].AsArray;
Except
form2.Memo1.Lines.Add('errr');
end;
if aobj['result'].AsString = 'lr_102' then
begin
form2.Label3.Text:='Saved token expired.';
form2.Rectangle2.Visible:=true;
end
else if aobj['result'].AsString = 'lr_103' then
begin
form2.Label3.Text:='Auto login.';
//load device data
form2.allDeviceListData := X;
form2.Hide;
form1.show;
end;
// globalReachedServer:=true;
finally
lHTTP.Free;
lParamList.Free;
end;
TThread.Synchronize(nil,
procedure
begin
end);
end
).Start();
end;
but after reach this function
the application show a black page and dont do anything until manually close
how can i do a web request at the background and with out hanging on fire-monkey !?
what a bout using REST is it better to access web service's?
Your code is not thread-safe. Your thread is directly accessing UI controls without synchronizing with the main UI thread. That alone can cause problems.
Also, all of the variables declared in the var section of requestToServer() should be moved into the var section of the anonymous procedure instead, since requestToServer() does not use them, so they can be completely local to the thread instead. The only thing the anonymous procedure should be capturing is the lParamList content.
Try something more like this:
function requestToServer(lParamList: TStringList) : string;
var
Params: TStringList;
Thread: TThread;
begin
Params := TStringList.Create;
try
Params.Assign(lParamList);
except
Params.Free;
raise;
end;
TThread.CreateAnonymousThread(
procedure
var
lHTTP: TIdHTTP;
serverResponce : string;
aObj: ISuperObject;
begin
try
try
lHTTP := TIdHTTP.Create(nil);
try
serverResponce := lHTTP.Post('http://domain.com/mjson.php', lParamList);
aObj := SO(serverResponce);
if aObj['result'].AsString = 'lr_102' then
begin
TThread.Queue(nil,
procedure
begin
form2.Label3.Text := 'Saved token expired.';
form2.Rectangle2.Visible := true;
end
);
end
else if aObj['result'].AsString = 'lr_103' then
begin
X := aObj['dta'].AsArray;
TThread.Queue(nil,
procedure
begin
form2.Label3.Text := 'Auto login.';
//load device data
form2.allDeviceListData := X;
form2.Hide;
form1.show;
end
);
end;
// globalReachedServer := true;
finally
lHTTP.Free;
end;
finally
Params.Free;
end;
except
TThread.Queue(nil,
procedure
begin
form2.Memo1.Lines.Add('errr');
end
);
end;
end
).Start;
end;
Related
I do write an app, that uses Indy 10 TCP/IP Client and TThread. The app connects to the server on Form.OnCreate event and disconnects from it on Form.OnClose event. Connection to the server is realized in TThread.
When I do start the app while ethernet cable is disconnected and try to close app until connection time out, then I do get these two exeptions:
Socket.Error #10038Socket operation on non-socket.
Thread Error: The handle is invalid(6).
If I try to close app while it is connected to client, then I get only this exeption:
Thread Error: The handle is invalid(6).
If I close the app while thread executes sleep, then no exeptions I do get.
What am I doing wrong, or it is normal behavior?
TThread class code:
type
connThread = class (TThread)
protected
procedure Execute ; override;
private
procedure Sinchronizuot(zinute : string; spalva : TColor; tmrNormalReconn : Boolean);
end;
Form.OnCreate code:
procedure TForm1.FormCreate(Sender: TObject);
begin
fellnerConn := connThread.Create(True);
fellnerConn.FreeOnTerminate := True;
fellnerConn.Start;
end;
Form.OnClose code:
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if fellnerConn <> nil then
fellnerConn.Terminate;
if idCl.Connected then
begin
try
idCl.Disconnect;
idCl.IOHandler.Free;
finally
if fellnerConn <> nil then
begin
fellnerConn.WaitFor;
fellnerConn := nil;
end;
end;
end;
end;
Thread execute code:
procedure connThread.Execute;
var
zinute : string;
spalva : TColor;
begin
inherited;
while not Form1.fellnerConn.Terminated do
begin
zinute := 'Jungiamasi prie Moxa serverio ' + Form1.idCl.Host;
spalva := clYellow;
Synchronize(procedure
begin
Sinchronizuot(zinute, spalva, False);
end
);
try
Form1.idCl.Connect;
except
on E: Exception do
begin
zinute := e.Message + ' Nepavyko prisijungti.';
spalva := clWebRed;
Synchronize(procedure
begin
Sinchronizuot(zinute, spalva, False);
end);
Sleep(1000);
end;
end;
end;
end;
The socket error is to be expected. The main thread is closing the socket while the worker thread is still using it.
But, you cannot use TThread.WaitFor() with FreeOnTerminate=True, that is why you keep getting "the handle is invalid" errors. The thread object is being destroyed, closing its handle, while WaitFor is still using it.
You should not be using FreeOnTerminate like this. It should only be used for start-and-forget type of threads. As soon as you need to keep a reference to a thread object, you should not use its FreeOnTerminate property anymore.
Either way, you should be using the thread's OnTerminate event so you can nil your reference to the thread as soon as the thread has terminated.
Try something more like this:
type
connThread = class (TThread)
protected
FClient: TIdTCPClient;
procedure Execute; override;
private
procedure Sinchronizuot(zinute : string; spalva : TColor; tmrNormalReconn : Boolean);
public
constructor Create(Client: TIdTCPClient); reintroduce;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
fellnerConn := connThread.Create(IdCl);
fellnerConn.OnTerminate := ThreadTerminated;
fellnerConn.Start;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if fellnerConn <> nil then
fellnerConn.Terminate;
try
idCl.Disconnect;
finally
if fellnerConn <> nil then
begin
fellnerConn.OnTerminate := nil;
fellnerConn.WaitFor;
FreeAndNil(fellnerConn);
end;
end;
end;
procedure TForm1.ThreadTerminated(Sender: TObject);
begin
fellnerConn := nil;
TThread.ForceQueue(nil, Sender.Free);
end;
constructor connThread.Create(Client: TIdTCPClient);
begin
inherited Create(True);
FClient := Client;
end;
procedure connThread.Execute;
var
zinute : string;
spalva : TColor;
begin
while not Terminated do
begin
zinute := 'Jungiamasi prie Moxa serverio ' + FClient.Host;
spalva := clYellow;
Synchronize(procedure
begin
Sinchronizuot(zinute, spalva, False);
end
);
try
FClient.Connect;
except
on E: Exception do
begin
zinute := e.Message + ' Nepavyko prisijungti.';
spalva := clWebRed;
Synchronize(procedure
begin
Sinchronizuot(zinute, spalva, False);
end
);
if Terminated then Exit;
Sleep(1000);
Continue;
end;
end;
try
// use FClient as needed...
finally
FClient.Disconnect;
end;
end;
end;
i am using indy TidTcpserver inside my server application its working good but some times after 10 clients connected my server application got a deadlock and stop from response here is my server execute and broadcast protocol codes
Tcp server execute
procedure TfMain.TCPServerExecute(AContext: TIdContext);
var
Connection: TConnection;
Command: String;
Params: array[1..10] of String;
ParamsCount, P: Integer;
PackedParams: TPackedParams;
IdBytes: TIdBytes;
MS: TMemoryStream;
ReceiveParams, ReceiveStream: Boolean;
Size: Int64;
begin
Connection := Pointer(AContext.Data);
MS := TMemoryStream.Create;
ReceiveParams := False;
ReceiveStream := False;
Command := AContext.Connection.Socket.ReadLn; //read command
if Command[1] = '1' then //command with params
begin
Command := Copy(Command, 2, Length(Command));
ReceiveParams := True;
end
else if Command[1] = '2' then //command + memorystream
begin
Command := Copy(Command, 2, Length(Command));
ReceiveStream := True;
MS.Position := 0;
end
else if Command[1] = '3' then //command with params + memorystream
begin
Command := Copy(Command, 2, Length(Command));
ReceiveParams := True;
ReceiveStream := True;
end;
if ReceiveParams then //params is incomming
begin
AContext.Connection.Socket.ReadBytes(IdBytes, SizeOf(PackedParams), False);
BytesToRaw(IdBytes, PackedParams, SizeOf(PackedParams));
ParamsCount := 0;
repeat
Inc(ParamsCount);
p := Pos(Sep, String(PackedParams.Params));
Params[ParamsCount] := Copy(String(PackedParams.Params), 1, P - 1);
Delete(PackedParams.Params, 1, P + 4);
until PackedParams.Params = '';
end;
if ReceiveStream then //stream is incomming
begin
Size := AContext.Connection.Socket.ReadInt64;
AContext.Connection.Socket.ReadStream(MS, Size, False);
MS.Position := 0;
end;
if Command = 'LOGIN' then
begin
usrnm := Params[1];
passwd := params[2];
if not userexists(usrnm, passwd) then
AContext.Connection.Socket.WriteLn('INVALIDPASSWORD')
else
begin
userslq.Close;
userslq.SQL.Clear;
userslq.SQL.Add('SELECT * FROM `users` WHERE `username` = "'+ trim(usrnm) +'" AND `password` = "' + trim(passwd) + '"');
userslq.Open;
if NOT userslq.IsEmpty then
begin
SendCommandWithParams(Connection, 'SENDYOURINFO', IntToStr(Connection.UniqueID) + Sep);
userslq.Close;
end;
userslq.Close;
userslq.SQL.Clear;
userslq.SQL.Add('UPDATE `users` SET `lastlogin` = :Date, `timeslogin`=(`timeslogin`+1) WHERE `users`.`username` = :uname;');
userslq.ParamByName('uname').AsString := trim(usrnm);
userslq.ParamByName('Date').AsDate := Now;
userslq.ExecSQL;
userslq.Close;
end;
end;
if Command = 'TAKEMYINFO' then //login ok, add to listview
begin
Connection.Name := Params[1];
Connections.Add(Connection);
AddConnectionToListView(Connection);
end;
if Command = 'TEXTMESSAGE' then
begin
BroadCastTextMessage(Params[1], Connection.UniqueID, Connection.Name, Connection.IP);
end;
if Command = 'DISCONNECTED' then
begin
DeleteConnectionFromList(Connection.UniqueID);
DeleteConnectionFromListView(Connection.UniqueID);
end;
MS.Free;
end;
broadcast Protocol and used procedures
procedure TfMain.AddConnectionToListView(Connection: TConnection);
begin
with lwConnections.Items.Add do
begin
Caption := Connection.Name;
SubItems.Add(Connection.IP);
SubItems.Add(FormatDateTime('hh:mm:ss', Connection.Connected));
SubItems.Add(IntToStr(Connection.UniqueID));
end;
end;
procedure TfMain.DeleteConnectionFromListView(UniqueID: DWord);
var
I: Integer;
begin
for I := 0 to lwConnections.Items.Count - 1 do
begin
if lwConnections.Items.Item[I].SubItems.Strings[2] = IntToStr(UniqueID) then
begin
lwConnections.Items.Delete(I);
Break;
end;
end;
end;
procedure TfMain.DeleteConnectionFromList(UniqueID: DWord);
var
I, Pos: Integer;
begin
Pos := -1;
for I := 0 to Connections.Count - 1 do
begin
if TConnection(Connections.Items[I]).UniqueID = UniqueID then
begin
Pos := I;
Break;
end;
end;
if Pos <> -1 then
Connections.Delete(Pos);
end;
procedure TfMain.BroadCastTextMessage(const TextMessage: String; const FromUniqueID: DWord;
const FromName: string; const dip: string);
var
I: Integer;
Connection: TConnection;
begin
for I := 0 to Connections.Count - 1 do
begin
Connection := Connections.Items[I];
if Connection.UniqueID <> FromUniqueID then
SendCommandWithParams(Connection, 'TEXTMESSAGE', FromName + Sep + TextMessage + Sep + dip + Sep);
end;
end;
procedure TfMain.SendCommandWithParams(Connection: TConnection; Command, Params:String);
var
PackedParams: TPackedParams;
begin
if not TIdContext(Connection.Thread).Connection.Socket.Connected then
Exit;
TCPServer.Contexts.LockList;
try
PackedParams.Params := ShortString(Params);
with TIdContext(Connection.Thread).Connection.Socket do
begin
WriteLn('1' + Command);
Write(RawToBytes(PackedParams, SizeOf(PackedParams)));
end;
finally
TCPServer.Contexts.UnlockList;
end;
end;
on connect server event
procedure Tfmain.TcpServerConnect(AContext: TIdContext);
var
Connection : TConnection;
begin
Connection := TConnection.Create;
Connection.IP := AContext.Connection.Socket.Binding.PeerIP;
Connection.Connected := Now;
Connection.UniqueID := GetTickCount;
if Connection.UniqueID = LastUniqueID then
Connection.UniqueID := GetTickCount + 1000;
LastUniqueID := Connection.UniqueID;
Connection.Thread := AContext;
AContext.Data := Connection;
end;
Updated
by following remy answer and his great details i started to do synchronize but in remy answer i am confused about TCriticalSection also i will have to rewrite the client code to be able to do same as his code doing , so i had to go with thread synchronize first here is example of what i did by following remy code i did some manage and removed database temporarily to avoid confusing here is the code of trying synchronization UI inside server execute
if Command = 'LOGIN' then
begin
if Password <> Params[1] then
AContext.Connection.Socket.WriteLn('INVALIDPASSWORD')
else
SendCommandWithParams(Connection, 'SENDYOURINFO', IntToStr(Connection.UniqueID) + Sep);
end;
if Command = 'TAKEMYINFO' then //login ok, add to listview
begin
Connection.Name := Params[1];
Connections.Add(Connection);
AddConnectionToListView(Connection);// this is not safe i know and thats what makes me confused so in this procedure call i do same as remy doing
end;
procedure TfMain.AddConnectionToListView(Connection: TConnection);
begin
TThread.Queue(nil,
procedure
var
Item: TListItem;
begin
Item := lwConnections.Items.Add;
try
Item.Caption := Connection.Name;
Item.SubItems.Add(Connection.IP);
Item.SubItems.Add(FormatDateTime('hh:mm:ss', Connection.Connected));
Item.SubItems.Add(IntToStr(Connection.UniqueID));
except
Item.Delete;
raise;
end;
end
);
end;
is this correct to synchronize ? whats makes me confused is this thread synchronize by itself ? i mean there is no thread class to execute and synchronize is this correct way ?
Updates about synchronize
Remy answer helps me i thanks him too much , but iam trying to understand thus synchronize part i found some ways on google as example include
idsync in my uses
and call it like this as example
uses
idsync;
// and in server execute i call TiDNotify To synchronize what ever i want ?
procedure TfMain.DeleteConnectionFromListView;
var
I: Integer;
begin
for I := 0 to lwConnections.Items.Count - 1 do
begin
if lwConnections.Items.Item[I].SubItems.Strings[2] = IntToStr(linetToID) then
begin
DeleteConnectionFromList(linetToID);
lwConnections.Items.Delete(I);
Break;
end;
end;
end;
procedure TfMain.TCPServerExecute(AContext: TIdContext);
var
Connection: TConnection;
Command: String;
Params: array[1..10] of String;
ParamsCount, P: Integer;
PackedParams: TPackedParams;
IdBytes: TIdBytes;
MS: TMemoryStream;
ReceiveParams, ReceiveStream: Boolean;
Size: Int64;
begin
Connection := Pointer(AContext.Data);
MS := TMemoryStream.Create;
ReceiveParams := False;
ReceiveStream := False;
Command := AContext.Connection.Socket.ReadLn; //read command
if Command[1] = '1' then //command with params
begin
Command := Copy(Command, 2, Length(Command));
ReceiveParams := True;
end
else if Command[1] = '2' then //command + memorystream
begin
Command := Copy(Command, 2, Length(Command));
ReceiveStream := True;
MS.Position := 0;
end
else if Command[1] = '3' then //command with params + memorystream
begin
Command := Copy(Command, 2, Length(Command));
ReceiveParams := True;
ReceiveStream := True;
end;
if ReceiveParams then //params is incomming
begin
AContext.Connection.Socket.ReadBytes(IdBytes, SizeOf(PackedParams), False);
BytesToRaw(IdBytes, PackedParams, SizeOf(PackedParams));
ParamsCount := 0;
repeat
Inc(ParamsCount);
p := Pos(Sep, String(PackedParams.Params));
Params[ParamsCount] := Copy(String(PackedParams.Params), 1, P - 1);
Delete(PackedParams.Params, 1, P + 4);
until PackedParams.Params = '';
end;
if ReceiveStream then //stream is incomming
begin
Size := AContext.Connection.Socket.ReadInt64;
AContext.Connection.Socket.ReadStream(MS, Size, False);
MS.Position := 0;
end;
if Command = 'LOGIN' then
begin
if Password <> Params[1] then
AContext.Connection.Socket.WriteLn('INVALIDPASSWORD')
else
SendCommandWithParams(Connection, 'SENDYOURINFO', IntToStr(Connection.UniqueID) + Sep);
end;
if Command = 'TAKEMYINFO' then //login ok, add to listview
begin
Connection.Name := Params[1];
Connections.Add(Connection);
TIdNotify.NotifyMethod(Connection.AddToListView);
end;
if Command = 'TEXTMESSAGE' then
begin
BroadCastTextMessage(Params[1], Connection.UniqueID);
end;
if Command = 'GETLIST' then
begin
SendClientsListTo(Connection.UniqueID);
end;
if Command = 'DISCONNECTED' then
begin
linetToID := Connection.UniqueID;// fmain private string variable
TIdNotify.NotifyMethod(DeleteConnectionFromListView);
end;
MS.Free;
end;
TIdTCPServer is a multi-threaded component. Its OnExecute event is triggered in the context of a worker thread. But your TAKEMYINFO and DISCONNECTED command handlers are directly accessing UI controls without synchronizing with the main UI thread. That can easily cause deadlocks (amongst other problems, including crashes, killing the UI, etc). You MUST sync!
Also, is userexists() thread-safe? Is userslq? Your use of the Connections list is definitely not thread-safe.
Why is SendCommandWithParams() locking the server's Contexts list, especially when called by OnExecute? You don't need to do that. You should be locking it in BroadCastTextMessage() instead.
Try something more like this:
type
TConnnection = class(TIdServerContext)
private
WriteLock: TCriticalSection;
public
Name: String;
IP: String;
Connected: TDateTime;
UniqueID: Dword;
constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); override;
destructor Destroy; override;
procedure AddToListView;
procedure DeleteFromListView;
procedure BroadcastTextMessage(const TextMessage: String);
procedure SendCommandWithParams(const Command, Params: String);
procedure SendLn(const S: String);
function UserExists(const User, Passwd: string): Boolean;
procedure UpdateLastLogin(const User: String);
end;
constructor TConnection.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil);
begin
inherited;
WriteLock := TCriticalSection.Create;
end;
destructor TConnection.Destroy;
begin
WriteLock.Free;
inherited;
end;
procedure TConnection.AddToListView;
var
LName: string;
LIP: string;
LConnected: TDateTime;
LUniqueID: Dword;
begin
// in case the client disconnects and destroys this object before
// TThread.Queue() can update the ListView, capture the values so
// this object's fields are not accessed directly...
//
LName := Self.Name;
LIP := Self.IP;
LConnected := Self.Connected;
LUniqueID := Self.UniqueID;
TThread.Queue(nil,
procedure
var
Item: TListItem;
begin
if (fMain = nil) or (fMai.lwConnections = nil) then Exit;
Item := fMain.lwConnections.Items.Add;
try
Item.Data := Self;
Item.Caption := LName;
Item.SubItems.Add(LIP);
Item.SubItems.Add(FormatDateTime('hh:mm:ss', LConnected));
Item.SubItems.Add(IntToStr(LUniqueID));
except
Item.Delete;
raise;
end;
end
);
end;
procedure TConnection.DeleteFromListView;
begin
TThread.Queue(nil,
procedure
var
Item: TListItem;
begin
if (fMain = nil) or (fMai.lwConnections = nil) then Exit;
Item := fMain.lwConnections.FindData(0, Self, True, False);
if Item <> nil then Item.Delete;
end
);
end;
procedue TConnection.BroadCastTextMessage(const TextMessage: String);
var
List: TList; // or TIdContextList if using a modern Indy version
I: Integer;
Connection: TConnection;
begin
List := Server.Contexts.LockList;
try
for I := 0 to List.Count - 1 do
begin
Connection := TConnection(List.Items[I]);
if Connection <> Self then
begin
try
Connection.SendCommandWithParams('TEXTMESSAGE', FromName + Sep + TextMessage + Sep + dip + Sep);
except
end;
end;
finally
Server.Contexts.UnlockList;
end;
end;
procedure TConnection.SendCommandWithParams(const Command, Params: String);
var
PackedParams: TPackedParams;
begin
PackedParams.Params := ShortString(Params);
WriteLock.Enter;
try
with Connection.Socket do
begin
WriteLn('1' + Command);
Write(RawToBytes(PackedParams, SizeOf(PackedParams)));
end;
finally
WriteLock.Leave;
end;
end;
procedure TConnection.SendLn(const S: String);
begin
WriteLock.Enter;
try
Connection.Socket.WriteLn(S);
finally
WriteLock.Leave;
end;
end;
function TConnection.UserExists(const User, Passwd: string): Boolean;
var
Exists: Boolean;
begin
// if you give each client its own DB connection, or use
// a thread-safe DB pool, you don't have to sync this ...
//
TThread.Synchronize(nil,
procedure
if (fMain = nil) or (fMai.userslq = nil) then Exit;
with fMain.userslq do
begin
Close;
SQL.Text := 'SELECT * FROM `users` WHERE `username` = :uname AND `password` = :passwd;';
ParamByName('uname').AsString := Trim(User);
ParamByName('passwd').AsString := Trim(Passwd);
Open;
try
Exists := not IsEmpty;
finally
Close;
end;
end;
end
);
Result := Exists;
end;
procedure TConnection.UpdateLastLogin(const User: String);
begin
// if you give each client its own DB connection, or use
// a thread-safe DB pool, you don't have to sync this ...
//
TThread.Synchronize(nil,
procedure
if (fMain = nil) or (fMai.userslq = nil) then Exit;
with fMain.userslq do
begin
Close;
SQL.Text := 'UPDATE `users` SET `lastlogin` = :Date, `timeslogin`=(`timeslogin`+1) WHERE `users`.`username` = :uname;';
ParamByName('uname').AsString := Trim(User);
ParamByName('Date').AsDate := Now;
ExecSQL;
Close;
end;
end
);
end;
procedure TfMain.FormCreate(Sender: TObject);
begin
// set this before activating the server
TCPServer.ContextClass := TConnection;
end;
procedure TfMain.TCPServerConnect(AContext: TIdContext);
var
Connection: TConnection;
begin
Connection := AContext as TConnection;
Connection.Name := '';
Connection.IP := AContext.Binding.PeerIP;
Connection.Connected := Now;
Connection.UniqueID := ...;
end;
procedure TfMain.TCPServerDisconnect(AContext: TIdContext);
var
Connection: TConnection;
begin
Connection := AContext as TConnection;
Connection.DeleteFromListView;
end;
procedure TfMain.TCPServerExecute(AContext: TIdContext);
var
Connection: TConnection;
Command: String;
Params: array[1..10] of String;
ParamsCount, P: Integer;
PackedParams: TPackedParams;
IdBytes: TIdBytes;
MS: TMemoryStream;
ReceiveParams, ReceiveStream: Boolean;
S: String;
begin
Connection := AContext as TConnection;
Command := AContext.Connection.Socket.ReadLn; //read command
if Command = '' then Exit;
ReceiveParams := False;
ReceiveStream := False;
if Command[1] = '1' then //command with params
begin
Command := Copy(Command, 2, MaxInt);
ReceiveParams := True;
end
else if Command[1] = '2' then //command + memorystream
begin
Command := Copy(Command, 2, MaxInt);
ReceiveStream := True;
end
else if Command[1] = '3' then //command with params + memorystream
begin
Command := Copy(Command, 2, MaxInt);
ReceiveParams := True;
ReceiveStream := True;
end;
if ReceiveParams then //params is incomming
begin
AContext.Connection.Socket.ReadBytes(IdBytes, SizeOf(PackedParams), False);
BytesToRaw(IdBytes, PackedParams, SizeOf(PackedParams));
S := String(PackedParams.Params);
ParamsCount := 0;
while (S <> '') and (ParamsCount < 10) do
begin
Inc(ParamsCount);
p := Pos(Sep, S);
if p = 0 then
Params[ParamsCount] := S
else
begin
Params[ParamsCount] := Copy(S, 1, P - 1);
Delete(S, 1, P + 4);
end;
end;
end;
MS := nil;
try
if ReceiveStream then //stream is incomming
begin
MS := TMemoryStream.Create;
AContext.Connection.Socket.LargeStream := True;
AContext.Connection.Socket.ReadStream(MS, -1, False);
MS.Position := 0;
end;
if Command = 'LOGIN' then
begin
if ParamsCount <> 2 then
begin
Connection.SendLn('INVALIDPARAMS');
Exit;
end;
if not Connection.UserExists(Params[1], Params[2]) then
begin
Connection.SendLn('INVALIDPASSWORD');
Exit;
end;
Connection.UpdateLastLogin(Params[1]);
Connection.SendCommandWithParams('SENDYOURINFO', IntToStr(Connection.UniqueID) + Sep);
end
else if Command = 'TAKEMYINFO' then //login ok, add to listview
begin
if ParamsCount <> 1 then
begin
Connection.SendLn('INVALIDPARAMS');
Exit;
end;
Connection.Name := Params[1];
Connection.AddToListView;
end
else if Command = 'TEXTMESSAGE' then
begin
if ParamsCount <> 1 then
begin
Connection.SendLn('INVALIDPARAMS');
Exit;
end;
Connection.BroadCastTextMessage(Params[1]);
end
else if Command = 'DISCONNECTED' then
begin
AContext.Connection.Disconnect;
Exit;
end;
finally
MS.Free;
end;
end;
This does not display the image:
Button to send a picture:
procedure TForm1.Button3Click(Sender: TObject);
var
ms :TMemoryStream;
begin
try
ms := TMemoryStream.Create;
IdTCPClient2.Host:=Edit1.Text;
IdTCPClient2.Connect;
Image1.Bitmap.SaveToStream(ms);
ms.Position := 0;
IdTCPClient2.IOHandler.LargeStream := true;
IdTCPClient2.IOHandler.Write(ms,0,True);
finally
IdTCPClient2.Disconnect;
end;
ms.Free;
end;
and the server executes
procedure TForm1.IdTCPServer2Execute(AContext: TIdContext);
var
ms:TMemoryStream;
size : Integer;
begin
ms := TFileStream.Create;
try
ms.Position:= 0;
AContext.Connection.IOHandler.LargeStream:=true;
AContext.Connection.IOHandler.ReadStream(ms);
ms.Position:=0;
Image2.Bitmap.LoadFromStream(ms);
finally
ms.Free;
end;
end;
What is wrong?
OnExecute is called in the context of a worker thread. You have to synchronize with the main thread in order to update UI controls. For example:
procedure TForm1.IdTCPServer2Execute(AContext: TIdContext);
var
ms:TMemoryStream;
begin
ms := TMemoryStream.Create;
try
AContext.Connection.IOHandler.LargeStream:=true;
AContext.Connection.IOHandler.ReadStream(ms);
ms.Position := 0;
TThread.Synchronize(nil,
procedure
begin
Image2.Bitmap.LoadFromStream(ms);
end
);
finally
ms.Free;
end;
end;
I'm using Delphi 5 + BDE + Oracle. I have the following function:
class function TClientDataSetFactory.GetClientDataSet(
const qryGen: TDataSet): TClientDataSet;
var
dspDados: TDataSetProvider;
begin
Result := nil;
try
try
Result := TClientDataSet.Create(nil);
dspDados := TDataSetProvider.Create(Result);
dspDados.DataSet := qryGen;
qryGen.Active := True;
qryGen.First;
Result.Data := dspDados.Data;
Result.First;
except
on E: Exception do
begin
raise;
end;
end;
finally
end;
end;
so, when a run this:
var
qryGen: TQuery;
cdsGen: TClientDataSet;
begin
qryGen := nil;
try
try
qryGen := CriaQuery();
qryGen.SQL.Text :=
'SELECT SUM(TOTAL) AS TOTAL FROM MYTABLE';
cdsGen := TClientDataSetFactory.GetClientDataSet(qryGen);
ShowMessageFmt('Total: %f', [cdsGen.FieldByName('TOTAL').AsFloat]);
except
on E: Exception do
begin
raise;
end;
end;
finally
if Assigned(qryGen) then FreeAndNil(qryGen);
end;
end;
i got "159,00" but, if i run this:
ShowMessageFmt('Total: %f', [qryGen.FieldByName('TOTAL').AsFloat]);
i got "159,25".
can someone help me?
I solved the problem with another solution.
type
TInternalQuery = class(TQuery)
protected
procedure InternalInitFieldDefs; override;
public
constructor Create(AOwner: TComponent; const qryGen: TQuery); reintroduce;
end;
constructor TInternalQuery.Create(AOwner: TComponent; const qryGen: TQuery);
var
intCont: Integer;
begin
inherited Create(AOwner);
Self.DatabaseName := qryGen.DatabaseName;
Self.UpdateObject := qryGen.UpdateObject;
Self.SQL.Text := qryGen.SQL.Text;
for intCont := 0 to Self.ParamCount - 1 do
begin
Self.Params[intCont].Value := qryGen.Params[intCont].Value;
end;
end;
procedure TInternalQuery.InternalInitFieldDefs;
var
intCont: Integer;
begin
inherited InternalInitFieldDefs;
for intCont := 0 to FieldDefs.Count - 1 do
begin
if (FieldDefs[intCont].Size = 0) and (FieldDefs[intCont].DataType = ftBCD) then
begin
FieldDefs[intCont].Precision := 64;
FieldDefs[intCont].Size := 32;
end;
end;
end;
the problem is ((FieldDefs[intCont].Size = 0) and (FieldDefs[intCont].DataType = ftBCD)). when ClientDataSet is created, the field is truncated, because when oracle has a function like "SUM(TOTAL)" the result field is created with size 0, so the clientdataset handle the field as Integer field.
Try with
ShowMessageFmt('Total: %n', [cdsGen.FieldByName('TOTAL').AsFloat])
or this
cdsGen := TClientDataSetFactory.GetClientDataSet(qryGen);
**(cdsGen.FieldByName('Total') as TFloatField).DisplayFormat := '0.00';**
ShowMessageFmt('Total: %f', [cdsGen.FieldByName('TOTAL').AsFloat])
I create a Custom Tpanel and inside I put various Custom Components ...
procedure Panel_Comp(Location: TWinControl; NumOfComp: Integer;Left,Top,Height,width:Integer);
begin
MyPanel := TsPanel.Create(Conf);
MyPanel.Name := 'MyPanel' + IntToStr(NumOfComp);
MyPanel.Parent := Location;
MyPanel.Left := Left;
MyPanel.Top := Top;
MyPanel.Height := Height;
MyPanel.Width := width;
MyPanel.Caption := '';
end;
and i call it like this
Panel_Comp(Conf.ScrollBox1,1,8,10,70,322);
in the same logic i put inside the new panel other custom components including a tBitbtn the have a onclick event..
procedure BitBtn_Comp(Location: TWinControl; NumOfComp: Integer; Left,Top,Height,Width,ImageNum: Integer);
begin
MyBitBtn := TBitBtn.Create(Conf);
......
MyBitBtn.tag := NumOfComp;
MyBitBtn.OnClick:= Conf.CloseCurrentPanel;
end;
In the main Forn The TConf.CloseCurrentPanel;
procedure TConf.CloseCurrentPanel(Sender: TObject);
var
panelComp: TComponent;
begin
panelComp := FindComponentEx('Conf.MyPanel'+ IntToStr(TBitBtn(Sender).tag);
TPanel(panelComp).Free;
Application.ProcessMessages;
end;
When I call that I get access violation...
I think is something that I must free all the components inside the panel before free the panel but how I free the BitBtn before the panel and continue the action of the click event?
Here is the FindComponetEx function instead you need it...
function FindComponentEx(const Name: string): TComponent;
var
FormName: string;
CompName: string;
P: Integer;
Found: Boolean;
Form: TForm;
I: Integer;
begin
// Split up in a valid form and a valid component name
P := Pos('.', Name);
if P = 0 then
begin
raise Exception.Create('No valid form name given');
end;
FormName := Copy(Name, 1, P - 1);
CompName := Copy(Name, P + 1, High(Integer));
Found := False;
// find the form
for I := 0 to Screen.FormCount - 1 do
begin
Form := Screen.Forms[I];
// case insensitive comparing
if AnsiSameText(Form.Name, FormName) then
begin
Found := True;
Break;
end;
end;
if Found then
begin
for I := 0 to Form.ComponentCount - 1 do
begin
Result := Form.Components[I];
if AnsiSameText(Result.Name, CompName) then Exit;
end;
end;
Result := nil;
end;
The AV occurs because you are destroying a component (MyBitBtn) while it is still handling Windows messages. The solution is to postpone the destruction until later via PostMessage, similar to this:
unit Unit1;
interface
uses
Windows,
Messages,
SysUtils,
Variants,
Classes,
Graphics,
Controls,
Forms,
Dialogs,
ExtCtrls,
StdCtrls;
const
UM_DESTROYPANEL = WM_APP + 623; // some "unique" number; UM = user message
type
TConf = class(TForm)
Panel1: TPanel;
Button1: TButton;
procedure Button1Click(Sender: TObject);
strict private
procedure UMDestroyPanel(var Message: TMessage); message UM_DESTROYPANEL;
public
{ Public-Deklarationen }
end;
var
Conf: TConf;
implementation
{$R *.dfm}
procedure TConf.Button1Click(Sender: TObject);
begin
PostMessage(Handle, UM_DESTROYPANEL, 0, 0);
end;
procedure TConf.UMDestroyPanel(var Message: TMessage);
begin
Panel1.Free;
end;
end.
If needed you can use wParam and lParam to pass through parameters like so:
procedure TConf.Button1Click(Sender: TObject);
begin
PostMessage(Handle, UM_DESTROYPANEL, WPARAM(Panel1), 0);
end;
procedure TConf.UMDestroyPanel(var Message: TMessage);
begin
TObject(Message.WParam).Free;
end;
EDIT:
In your situation I'd probably rewrite TConf.CloseCurrentPanel like this:
procedure TConf.CloseCurrentPanel(Sender: TObject);
var
panelComp: TComponent;
begin
panelComp := FindComponentEx('Conf.MyPanel'+ IntToStr(TBitBtn(Sender).Tag);
PostMessage(Handle, UM_DESTROYPANEL, WPARAM(panelComp), 0);
end;
Alternatively you can pass through the Tag (might be the better solution because there's less casting involved):
procedure TConf.CloseCurrentPanel(Sender: TObject);
begin
PostMessage(Handle, UM_DESTROYPANEL, TBitBtn(Sender).Tag, 0);
end;
procedure TConf.UMDestroyPanel(var Message: TMessage);
var
panelComp: TComponent;
begin
panelComp := FindComponentEx('Conf.MyPanel'+ IntToStr(Message.WParam));
panelComp.Free;
end;
AFAICT the Application.ProcessMessages isn't needed.
procedure TConf.CloseCurrentPanel(Sender: TObject);
var
panelComp: TComponent;
begin
panelComp := FindComponentEx('Conf.MyPanel'+ IntToStr(TBitBtn(Sender).tag);
//Where you need to determine 'PanelComp' if there are.
if Assigned(panelComp) and (PanelComp is TPanel) then
TPanel(panelComp).Free;
Application.ProcessMessages;
end;