Receive partial data with timeout using TIdTcpClient - delphi

How to receive a 100-byte-string with following conditions using TIdTcpClient ?:
If nothing comes in, the Read call shall be blocking and thread will wait eternally
If 100 bytes were received the Read call should return the byte string
If more than 0 bytes, but less than 100 were received, Read call should return after some timeout (say 1 second) in order to return at least something in a reasonable time, without producing timeout exception, because exception handling in Delphi IDE's debug mode hasn't been made convenient.
My not optimal code for now is as follows:
unit Unit2;
interface
uses
System.Classes, IdTCPClient;
type
TTcpReceiver = class(TThread)
private
_tcpc: TIdTCPClient;
_onReceive: TGetStrProc;
_buffer: AnsiString;
procedure _receiveLoop();
procedure _postBuffer;
protected
procedure Execute(); override;
public
constructor Create(); reintroduce;
destructor Destroy(); override;
property OnReceive: TGetStrProc read _onReceive write _onReceive;
end;
implementation
uses
System.SysUtils, Vcl.Dialogs, IdGlobal, IdExceptionCore;
constructor TTcpReceiver.Create();
begin
inherited Create(True);
_buffer := '';
_tcpc := TIdTCPClient.Create(nil);
//_tcpc.Host := '192.168.52.175';
_tcpc.Host := '127.0.0.1';
_tcpc.Port := 1;
_tcpc.ReadTimeout := 1000;
_tcpc.Connect();
Suspended := False;
end;
destructor TTcpReceiver.Destroy();
begin
_tcpc.Disconnect();
FreeAndNil(_tcpc);
inherited;
end;
procedure TTcpReceiver.Execute;
begin
_receiveLoop();
end;
procedure TTcpReceiver._postBuffer();
var buf: string;
begin
if _buffer = '' then Exit;
buf := _buffer;
_buffer := '';
if Assigned(_onReceive) then begin
Synchronize(
procedure()
begin
_onReceive(buf);
end
);
end;
end;
procedure TTcpReceiver._receiveLoop();
var
c: AnsiChar;
begin
while not Terminated do begin
try
c := AnsiChar(_tcpc.IOHandler.ReadByte());
_buffer := _buffer + c;
if Length(_buffer) > 100 then
_postBuffer();
except
//Here I have to ignore EIdReadTimeout in Delphi IDE everywhere, but I want just to ignore them here
on ex: EIdReadTimeout do _postBuffer();
end;
end;
end;
end.

TCP is stream oriented, not message oriented like UDP is. Reading arbitrary bytes without any structure to them is bad design, and will easily corrupt your communications if you stop reading prematurely and then the bytes you wanted to read arrive after you have stopped reading. The bytes are not removed from the socket until they are read, so the next read may have more/different bytes than expected.
If you are expecting 100 bytes, then just read 100 bytes and be done with it. If the sender only sends 50 bytes, it needs to tell you that ahead of time so you can stop reading after 50 bytes are received. If the sender is not doing that, then this is a very poorly designed protocol. Using a timeout to detect end-of-transmission in general is bad design. Network lag could easily cause false detections.
TCP messages should be adequately framed so that the receiver knows exactly where one message ends and the next message begins. There are three ways to do that in TCP:
use fixed-length messages. The receiver can keep reading until the expected number of bytes has arrived.
send a message's length before sending the message itself. The receiver can read the length first and then keep reading until the specified number of bytes has arrived.
terminate a message with a unique delimiter that does not appear in the message data. The receiver can keep reading bytes until that delimiter has arrived.
That being said, what you are asking for can be done in TCP (but shouldn't be done in TCP!). And it can be done without using a manual buffer at all, use Indy's built-in buffer instead. For example:
unit Unit2;
interface
uses
System.Classes, IdTCPClient;
type
TTcpReceiver = class(TThread)
private
_tcpc: TIdTCPClient;
_onReceive: TGetStrProc;
procedure _receiveLoop;
procedure _postBuffer;
protected
procedure Execute; override;
public
constructor Create; reintroduce;
destructor Destroy; override;
property OnReceive: TGetStrProc read _onReceive write _onReceive;
end;
implementation
uses
System.SysUtils, Vcl.Dialogs, IdGlobal;
constructor TTcpReceiver.Create;
begin
inherited Create(False);
_tcpc := TIdTCPClient.Create(nil);
//_tcpc.Host := '192.168.52.175';
_tcpc.Host := '127.0.0.1';
_tcpc.Port := 1;
end;
destructor TTcpReceiver.Destroy;
begin
_tcpc.Free;
inherited;
end;
procedure TTcpReceiver.Execute;
begin
_tcpc.Connect;
try
_receiveLoop;
finally
_tcpc.Disconnect;
end;
end;
procedure TTcpReceiver._postBuffer;
var
buf: string;
begin
with _tcpc.IOHandler do
buf := ReadString(IndyMin(InputBuffer.Size, 100));
{ alternatively:
with _tcpc.IOHandler.InputBuffer do
buf := ExtractToString(IndyMin(Size, 100));
}
if buf = '' then Exit;
if Assigned(_onReceive) then
begin
Synchronize(
procedure
begin
if Assigned(_onReceive) then
_onReceive(buf);
end
);
end;
end;
procedure TTcpReceiver._receiveLoop;
var
LBytesRecvd: Boolean;
begin
while not Terminated do
begin
while _tcpc.IOHandler.InputBufferIsEmpty do
begin
_tcpc.IOHandler.CheckForDataOnSource(IdTimeoutInfinite);
_tcpc.IOHandler.CheckForDisconnect;
end;
while _tcpc.IOHandler.InputBuffer.Size < 100 do
begin
// 1 sec is a very short timeout to use for TCP.
// Consider using a larger timeout...
LBytesRecvd := _tcpc.IOHandler.CheckForDataOnSource(1000);
_tcpc.IOHandler.CheckForDisconnect;
if not LBytesRecvd then Break;
end;
_postBuffer;
end;
end;
end.
On a side note, your statement that "exception handling in Delphi IDE's debug mode hasn't been made convenient" is simply ridiculous. Indy's IOHandler has properties and method parameters for controlling exception behavior, and also if you don't like the way the debugger handles exceptions then simply configure it to ignore them. You can configure the debugger to ignore specific exception types, or you can use breakpoints to tell the debugger to skip handling exceptions in specific blocks of code.

Related

Take screenshot in webcam using VFrames in Console Application

I'm doing a program to take a picture of the webcam using Delphi XE2 and VFrames to achieve this, the problem is that I have it all figured out, in a graphic application everything works fine, but when I use the unit in a console application, it returns me error saying
First chance exception at $76B6B727. Exception class EAccessViolation with message 'Access violation at address 004A271B in module 'console.exe'. Read of address 00000260'. Process console.exe (3676)
My Unit :
unit Webcam;
interface
uses SysUtils, Windows, Vcl.Imaging.Jpeg, Vcl.Graphics, VSample,
VFrames, Classes;
type
TWebcam = class
private
procedure NewVideoFrameEvent(Sender: TObject; Width, Height: integer;
DataPtr: pointer);
public
constructor Create;
destructor Destroy; override;
procedure capture_webcam(take_name: string);
end;
var
web_image: TVideoImage;
name_screen: string;
implementation
constructor TWebcam.Create;
begin
inherited Create;
end;
destructor TWebcam.Destroy;
begin
inherited Destroy;
end;
Procedure TWebcam.NewVideoFrameEvent(Sender: TObject; Width, Height: integer;
DataPtr: pointer);
var
bitmap: TBitmap;
name: string;
begin
name := name_screen;
if (FileExists(name)) then
begin
DeleteFile(Pchar(name));
end;
bitmap := TBitmap.Create;
bitmap.PixelFormat := pf24bit;
web_image.GetBitmap(bitmap);
bitmap.SaveToFile(name);
bitmap.Free;
web_image.VideoStop;
web_image.Free;
end;
procedure TWebcam.capture_webcam(take_name: string);
var
list_cams: TStringList;
begin
web_image := TVideoImage.Create();
list_cams := TStringList.Create;
web_image.GetListOfDevices(list_cams);
if not(list_cams.count = 0) then
begin
name_screen := take_name;
web_image.VideoStart(list_cams[0]);
end;
list_cams.Free;
web_image.OnNewVideoFrame := NewVideoFrameEvent;
end;
end.
Console :
program console;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
Webcam;
var
webcamz: TWebcam;
begin
try
webcamz := TWebcam.Create();
webcamz.capture_webcam('test.jpg');
webcamz.Free();
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
What should I do ?
The relevent source code for the VFrames unit is available on Github:
https://github.com/heise/GRBLize/blob/edge/VFrames.pas
https://github.com/heise/GRBLize/blob/edge/VSample.pas
The TVideoImage.VideoStart() method has a dependancy on Application.MainForm.Handle. A console application does not have a MainForm by default, so that alone will crash the code in a console application unless you create a MainForm (which defeats the purpose of making a console app).
Aside from that, TVideoImage also has a dependancy on a message loop, as it creates a hidden window to receive video notifications used to fire the OnNewVideoFrame event. Your console application does not have a message loop. And even if it did, you are freeing the TVideoImage object before the event would fire anyway, as your capture_webcam() code is not waiting for the event to fire before exiting.
Also, TVideoSample class (which TVideoImage uses internally) uses the DirectShow API to capture images from the webcam's video stream. DirectShow is a COM-based API. Your console application is not initializing COM before using TVideoImage. That alone would cause GetListOfDevices() to fail and return a blank list. And if you attempted to ignore that and provide a device name anyway, VideoStart() would still crash when it tries to access a COM object that TVideoSample was not able to create during construction.

TCPClient : Custom timeout time

I need to set custom timeout for TTcpClient. I think the default timeout time is about 20-25 seconds but I need to change it to 500ms. Is it possible And How?
procedure TForm1.Button1Click(Sender: TObject);
begin
TcpClient2.RemoteHost := '192.168.1.1';
TcpClient2.RemotePort := '23';
TcpClient2.Connect;
tcpclient2.Receiveln();
tcpclient2.Sendln('admin');
tcpclient2.Receiveln;
end;
I tried non-blocking option but the software returns an error after I click on button And I have to do it again 4-5 times. Any help?
Thanks :)
Winsock has no connect timeout, but this can be overcomed.
You have several options:
Without threads:
Using non-blocking mode: call Connect, then wait using Winsock select function (encapsulated in TBaseSocket Select method inherited by TTcpClient).
Using blocking mode: changing temporarily to non-blocking mode and proceeding as in the previous case.
With threads: see Remy Lebeau's answer to How to control the connect timeout with the Winsock API?.
Use Indy.
Blocking vs non-blocking
Using blocking or non-blocking mode is a very important design decision that will affect many of your code and which you can't easily change afterward.
For example, in non-blocking mode, receive functions (as Receiveln), will not wait until there is enough input available and could return with an empty string. This can be an advantage if is this what you need, but you need to implement some strategy, such as waiting using TcpClient.WaitForData before calling the receive function (in your example, the Receiveln-Sendln-Receiveln will not work as is).
For simple tasks, blocking mode is easier to deal with.
Non-blocking mode
The following function will wait until the connection is successful or the timeout elapses:
function WaitUntilConnected(TcpClient: TTcpClient; Timeout: Integer): Boolean;
var
writeReady, exceptFlag: Boolean;
begin
// Select waits until connected or timeout
TcpClient.Select(nil, #writeReady, #exceptFlag, Timeout);
Result := writeReady and not exceptFlag;
end;
How to use:
// TcpClient.BlockMode must be bmNonBlocking
TcpClient.Connect; // will return immediately
if WaitUntilConnected(TcpClient, 500) then begin // wait up to 500ms
... your code here ...
end;
Also be aware of the following drawbacks/flaws in TTcpClient's non-blocking mode design:
Several functions will call OnError with SocketError set to WSAEWOULDBLOCK (10035).
Connected property will be false because is assigned in Connect.
Blocking mode
Connection timeout can be achieved by changing to non-blocking mode after socket is created but before calling Connect, and reverting back to blocking mode after calling it.
This is a bit more complicated because TTcpClient closes the connection and the socket if we change BlockMode, and also there is not direct way of creating the socket separately from connecting it.
To solve this, we need to hook after socket creation but before connection. This can be done using either the DoCreateHandle protected method or the OnCreateHandle event.
The best way is to derive a class from TTcpClient and use DoCreateHandle, but if for any reason you need to use TTcpClient directly without the derived class, the code can be easily rewriten using OnCreateHandle.
type
TExtendedTcpClient = class(TTcpClient)
private
FIsConnected: boolean;
FNonBlockingModeRequested, FNonBlockingModeSuccess: boolean;
protected
procedure Open; override;
procedure Close; override;
procedure DoCreateHandle; override;
function SetBlockModeWithoutClosing(Block: Boolean): Boolean;
function WaitUntilConnected(Timeout: Integer): Boolean;
public
function ConnectWithTimeout(Timeout: Integer): Boolean;
property IsConnected: boolean read FIsConnected;
end;
procedure TExtendedTcpClient.Open;
begin
try
inherited;
finally
FNonBlockingModeRequested := false;
end;
end;
procedure TExtendedTcpClient.DoCreateHandle;
begin
inherited;
// DoCreateHandle is called after WinSock.socket and before WinSock.connect
if FNonBlockingModeRequested then
FNonBlockingModeSuccess := SetBlockModeWithoutClosing(false);
end;
procedure TExtendedTcpClient.Close;
begin
FIsConnected := false;
inherited;
end;
function TExtendedTcpClient.SetBlockModeWithoutClosing(Block: Boolean): Boolean;
var
nonBlock: Integer;
begin
// TTcpClient.SetBlockMode closes the connection and the socket
nonBlock := Ord(not Block);
Result := ErrorCheck(ioctlsocket(Handle, FIONBIO, nonBlock)) <> SOCKET_ERROR;
end;
function TExtendedTcpClient.WaitUntilConnected(Timeout: Integer): Boolean;
var
writeReady, exceptFlag: Boolean;
begin
// Select waits until connected or timeout
Select(nil, #writeReady, #exceptFlag, Timeout);
Result := writeReady and not exceptFlag;
end;
function TExtendedTcpClient.ConnectWithTimeout(Timeout: Integer): Boolean;
begin
if Connected or FIsConnected then
Result := true
else begin
if BlockMode = bmNonBlocking then begin
if Connect then // will return immediately, tipically with false
Result := true
else
Result := WaitUntilConnected(Timeout);
end
else begin // blocking mode
// switch to non-blocking before trying to do the real connection
FNonBlockingModeRequested := true;
FNonBlockingModeSuccess := false;
try
if Connect then // will return immediately, tipically with false
Result := true
else begin
if not FNonBlockingModeSuccess then
Result := false
else
Result := WaitUntilConnected(Timeout);
end;
finally
if FNonBlockingModeSuccess then begin
// revert back to blocking
if not SetBlockModeWithoutClosing(true) then begin
// undesirable state => abort connection
Close;
Result := false;
end;
end;
end;
end;
end;
FIsConnected := Result;
end;
How to use:
TcpClient := TExtendedTcpClient.Create(nil);
try
TcpClient.BlockMode := bmBlocking; // can also be bmNonBlocking
TcpClient.RemoteHost := 'www.google.com';
TcpClient.RemotePort := '80';
if TcpClient.ConnectWithTimeout(500) then begin // wait up to 500ms
... your code here ...
end;
finally
TcpClient.Free;
end;
As noted before, Connected doesn't work well with non-blocking sockets, so I added a new IsConnected property to overcome this (only works when connecting with ConnectWithTimeout).
Both ConnectWithTimeout and IsConnected will work with both blocking and non-blocking sockets.

TCPserver without OnExecute event

I want to make a TCPserver and send/receive message to clients as needed, not OnExecute event of the TCPserver.
Send/receive message is not a problem; I do like that:
procedure TFormMain.SendMessage(IP, Msg: string);
var
I: Integer;
begin
with TCPServer.Contexts.LockList do
try
for I := 0 to Count-1 do
if TIdContext(Items[I]).Connection.Socket.Binding.PeerIP = IP then
begin
TIdContext(Items[I]).Connection.IOHandler.WriteBuffer(Msg[1], Length(Msg));
// and/or Read
Break;
end;
finally
TCPServer.Contexts.UnlockList;
end;
end;
Note 1: If I don't use OnExecute, the program raise an exception when a client connects.
Note 2: If I use OnExecute without doing anything, the CPU usage goes to %100
Note 3: I don't have a chance to change the TCP clients.
So what should I do?
TIdTCPServer requires an OnExecute event handler assigned by default. To get around that, you would have to derive a new class from TIdTCPServer and override its virtual CheckOkToBeActive() method, and should also override the virtual DoExecute() to call Sleep(). Otherwise, just assign an event handler and have it call Sleep().
This is not an effective use of TIdTCPServer, though. A better design is to not write your outbound data to clients from inside of your SendMessage() method directly. Not only is that error-prone (you are not catching exceptions from WriteBuffer()) and blocks SendMessage() during writing, but it also serializes your communications (client 2 cannot receive data until client 1 does first). A much more effective design is to give each client its own thread-safe outbound queue, and then have SendMessage() put the data into each client's queue as needed. You can then use the OnExecute event to check each client's queue and do the actual writing. This way, SendMessage() does not get blocked anymore, is less error-prone, and clients can be written to in parallel (like they should be).
Try something like this:
uses
..., IdThreadSafe;
type
TMyContext = class(TIdServerContext)
private
FQueue: TIdThreadSafeStringList;
FEvent: TEvent;
public
constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil); override;
destructor Destroy; override;
procedure AddMsgToQueue(const Msg: String);
function GetQueuedMsgs: TStrings;
end;
constructor TMyContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil);
begin
inherited;
FQueue := TIdThreadSafeStringList.Create;
FEvent := TEvent.Create(nil, True, False, '');
end;
destructor TMyContext.Destroy;
begin
FQueue.Free;
FEvent.Free;
inherited;
end;
procedure TMyContext.AddMsgToQueue(const Msg: String);
begin
with FQueue.Lock do
try
Add(Msg);
FEvent.SetEvent;
finally
FQueue.Unlock;
end;
end;
function TMyContext.GetQueuedMsgs: TStrings;
var
List: TStringList;
begin
Result := nil;
if FEvent.WaitFor(1000) <> wrSignaled then Exit;
List := FQueue.Lock;
try
if List.Count > 0 then
begin
Result := TStringList.Create;
try
Result.Assign(List);
List.Clear;
except
Result.Free;
raise;
end;
end;
FEvent.ResetEvent;
finally
FQueue.Unlock;
end;
end;
procedure TFormMain.FormCreate(Sender: TObject);
begin
TCPServer.ContextClass := TMyContext;
end;
procedure TFormMain.TCPServerExecute(AContext: TIdContext);
var
List: TStrings;
I: Integer;
begin
List := TMyContext(AContext).GetQueuedMsgs;
if List = nil then Exit;
try
for I := 0 to List.Count-1 do
AContext.Connection.IOHandler.Write(List[I]);
finally
List.Free;
end;
end;
procedure TFormMain.SendMessage(const IP, Msg: string);
var
I: Integer;
begin
with TCPServer.Contexts.LockList do
try
for I := 0 to Count-1 do
begin
with TMyContext(Items[I]) do
begin
if Binding.PeerIP = IP then
begin
AddMsgToQueue(Msg);
Break;
end;
end;
end;
finally
TCPServer.Contexts.UnlockList;
end;
end;
Use OnExecute and if you have nothing to do, Sleep() for a period of time, say 10 milliseconds. Each connection has its own OnExecute handler so this will only affect each individual connection.
In the OnExecute handler, you can use thread communication methods like TEvent and TMonitor to wait until there is data for the client.
TMonitor is available since Delphi 2009 and provides methods (Wait, Pulse and PulseAll) to send / receive notifications with mininmal CPU usage.
The Indy component set is designed to emulate blocking operation on a network connection. You're supposed to encapsulate all your code in the OnExecute event handler. That's supposed to be easier, because most protocols are blocking any way (send command, wait for response, etc).
You apparently don't like it's mode of operation, you'd like something that works without blocking. You should consider using a component suite that's designed for the way you intend to use it: give the ICS suite a try! ICS doesn't use threads, all the work is done in event handlers.
I had similar situation taking 100% CPU and it solved by adding IdThreadComponent and:
void __fastcall TForm3::IdThreadComponent1Run(TIdThreadComponent *Sender)
{
Sleep(10);
}
Is it right? I am not sure.

Window receives infinite amount of messages when mouse is hooked

I am writing an application which should draw a circle in place where user clicks a mouse. To achieve that i am hooking the mouse globally using SetWindowHookEx(WH_MOUSE,...)
The hooking, and the procedure that processes mouse action is in DLL. The procedure posts a registered message when it finds that mouse button was clicked using PostMessage(FindWindow('TMyWindow',nil), MyMessage, 0,0);
My application with TMyWindow form processes the messages in WndProc procedure. I check whether the message that came is the same as my registered one and only then draw the circle. After drawing the circle i create a timer, which should free the image after 500ms.
So everything seems to work just fine until i actually click on any part of my application form (for example click on still existing circle that was drawn not long ago). When i do that, form starts receiving my registered messages infinitely ans of course circle drawing procedure gets called every time.
I dont understand why is it doing so. Why is it working fine when i click somewhere off my application form but hangs when i click inside my form?
Let me know if you need more details.
Thanks
EDIT 1:
Main unit. $202 message is WM_LBUTTONUP.
unit main;
interface
uses
HookCommon,
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, StdCtrls, Menus, AppEvnts;
type
TTimer2 = class(TTimer)
private
FShape: TShape;
public
destructor Destroy; override;
property Shape: TShape read FShape write FShape;
end;
type
TShowMouseClick = class(TForm)
timerCountTimer: TTimer;
tray: TTrayIcon;
popMenu: TPopupMenu;
mnuExit: TMenuItem;
mnuActive: TMenuItem;
N1: TMenuItem;
mnuSettings: TMenuItem;
timersStx: TStaticText;
procedure timerCountTimerTimer(Sender: TObject);
procedure mnuExitClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormActivate(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
timerList: TList;
procedure shape();
procedure freeInactive(var Msg: TMessage); message WM_USER + 1545;
public
shapeColor: Tcolor;
procedure TimerExecute(Sender: TObject);
protected
procedure WndProc(var Message: TMessage); override;
{ Public declarations }
end;
var
ShowMouseClick: TShowMouseClick;
implementation
{$R *.dfm}
uses settings;
{$REGION 'Hide from TaskBar'}
procedure TShowMouseClick.FormActivate(Sender: TObject);
begin
ShowWindow(Application.Handle, SW_HIDE);
end;
procedure TShowMouseClick.FormShow(Sender: TObject);
begin
ShowWindow(Application.Handle, SW_HIDE);
end;
{$ENDREGION}
procedure TShowMouseClick.WndProc(var Message: TMessage);
begin
inherited WndProc(Message);
if (Message.Msg = HookCommon.MouseHookMessage) and
(Message.WParam = $202) then
shape;
end;
procedure TShowMouseClick.FormCreate(Sender: TObject);
begin
BorderStyle := bsNone;
FormStyle := fsStayOnTop;
WindowState := wsMaximized;
mnuActive.Checked := true;
HookCommon.HookMouse;
timerList := TList.Create;
timerList.Clear;
shapeColor := clGreen;
end;
procedure TShowMouseClick.FormDestroy(Sender: TObject);
begin
HookCommon.UnHookMouse;
end;
procedure TShowMouseClick.mnuExitClick(Sender: TObject);
begin
Close;
end;
procedure TShowMouseClick.timerCountTimerTimer(Sender: TObject);
begin
timersStx.Caption := 'Active timers: ' + IntToStr(timerList.Count);
end;
procedure TShowMouseClick.shape;
var
tm: TTimer2;
begin
tm := TTimer2.Create(nil);
tm.Tag := 0 ;
tm.Interval := 1;
tm.OnTimer := TimerExecute;
tm.Shape := nil;
timerList.Add(tm);
timersStx.Caption := 'Active timers: ' + IntToStr(timerList.Count);
tm.Enabled := true;
end;
procedure TShowMouseClick.TimerExecute(Sender: TObject);
var
img: TShape;
snd: TTimer2;
begin
snd := nil;
if Sender is TTimer2 then
snd := TTimer2(Sender);
if snd = nil then Exit;
if snd.Tag = 0 then
begin
snd.Interval := 500;
img := TShape.Create(nil);
img.Parent := ShowMouseClick;
img.Brush.Color := clGreen;
img.Shape := stCircle;
img.Width := 9;
img.Height := 9;
img.Left := Mouse.CursorPos.X-4;
img.Top := Mouse.CursorPos.Y-3;
snd.Tag := 1;
snd.Shape := img;
end else begin
snd.Enabled := false;
PostMessage(ShowMouseClick.Handle,WM_USER + 1545 , 0,0);
Application.ProcessMessages;
end;
end;
procedure TShowMouseClick.freeInactive(var Msg: TMessage);
var
i: integer;
begin
for i := timerList.Count - 1 downto 0 do
if TTimer2(timerList[i]).Enabled = false then
begin
TTimer2(timerList[i]).Free;
timerList.Delete(i);
end;
end;
destructor TTimer2.Destroy;
begin
FreeAndNil(FShape);
inherited;
end;
end.
Common unit.
unit HookCommon;
interface
uses Windows;
var
MouseHookMessage: Cardinal;
procedure HookMouse;
procedure UnHookMouse;
implementation
procedure HookMouse; external 'MouseHook.DLL';
procedure UnHookMouse; external 'MouseHook.DLL';
initialization
MouseHookMessage := RegisterWindowMessage('MouseHookMessage');
end.
DLL code.
library MouseHook;
uses
Forms,
Windows,
Messages,
HookCommon in 'HookCommon.pas';
{$J+}
const
Hook: HHook = 0;
{$J-}
{$R *.res}
function HookProc(nCode: Integer; MsgID: WParam; Data: LParam): LResult; stdcall;
var
notifyTestForm : boolean;
begin
notifyTestForm := false;
if msgID = $202 then
notifyTestForm := true;
if notifyTestForm then
begin
PostMessage(FindWindow('TShowMouseClick', nil), MouseHookMessage, MsgID, 0);
end;
Result := CallNextHookEx(Hook,nCode,MsgID,Data);
end;
procedure HookMouse; stdcall;
begin
if Hook = 0 then Hook:=SetWindowsHookEx(WH_MOUSE,#HookProc,HInstance,0);
end;
procedure UnHookMouse; stdcall;
begin
UnhookWindowsHookEx(Hook);
Hook:=0;
end;
exports
HookMouse, UnHookMouse;
begin
end.
The source of the mouse hook stuff is this
Why is it working fine when i click somewhere off my application form
but hangs when i click inside my form?
You're not posting the message to other windows when you click on them. First you should ask yourself, "what happens if I posted a message in my hook callback to all windows which are posted a WM_LBUTTONUP?".
Replace this line
PostMessage(FindWindow('TShowMouseClick', nil), MouseHookMessage, MsgID, 0);
in your dll code, with this:
PostMessage(PMouseHookStruct(Data).hwnd, MouseHookMessage, MsgID, 0);
It doesn't matter if the other applications would know or not what MouseHookMessage is, they will ignore the message. Launch your application and click the mouse wildly to other windows. Generally nothing will happen. Unless you click in the client area of any Delphi application. You'll instantly freeze it.
The answer to this question lies in both how a VCL message loop runs and how a WH_MOUSE hook works. A quote from MouseProc callback function's documentation.
[..] The system calls this function whenever an application calls the
GetMessage or PeekMessage function and there is a mouse message to be
processed.
Suppose you launch your application and the mouse is hooked, then you hover the mouse on your form and wait till your application calls 'WaitMessage', that it is idle. Now click in the client area to generate mouse messages. What happens is that the OS places messages to your application's main thread's message queue. And what your application does is that to remove and dispatch these messages with PeekMessage. This is where applications differ. The VCL first calls 'PeekMessage' with 'PM_NOREMOVE' passed in 'wRemoveMsg' parameter, while most other applications either removes the message with a call to 'PeekMessage' or do the same by using 'GetMessage'.
Now suppose it is 'WM_LBUTTONUP's turn. Refer to the quote above. As soon as PeekMessage is called, the OS calls the MouseProc callback. The call happens from 'user32.dll', that is, when your hook callback is called the statement following the 'PeekMessage' is not executed yet. Also, remember the VCL loop, the message is still in the queue, it has not been removed. Now, your callback function posts a message to the same message queue and returns. Execution returns to the VCL message loop and VCL again calls 'PeekMessage', this time to remove and dispatch the message, but instead of removing the 'WM_LBUTTONUP', it removes the custom message that you posted. 'WM_LBUTTONUP' remains in the queue. After the custom message is dispatched, since 'WM_LBUTTONUP' is still in the queue, 'PeekMessage' is again called, and again the OS calls the callback so that the callback can post another custom message to be removed instead of the mouse message. This loop effectively freezes the application.
To resolve, either post your message to a different thread that has its own message loop which would in some way synchronize with the main thread, or, I would not especially advice it but, instead of posting the message, send it. As an alternative you can remove the 'WM_LBUTTONUP' message yourself from the queue if one exists:
procedure TShowMouseClick.WndProc(var Message: TMessage);
begin
inherited WndProc(Message);
if (Message.Msg = HookCommon.MouseHookMessage) and
(Message.WParam = $202) then begin
if PeekMessage(Msg, Handle, WM_LBUTTONUP, WM_LBUTTONUP, PM_REMOVE) then
DispatchMessage(Msg); // or eat if you don't need it.
..
end;
The disadvantage to this approach is that, the PeekMessage itself, as mentioned above, will cause another custom message to be posted, so you'll be receiving those in pairs.
Either your Mouse click or your MyMessage messages are not removed from the Message Queue (unlikely) or they are somehow echoed back, or your code loops in a recursion.
I would try to remove any code from your TMyWindow.WndProc and replace it with some innocuous code (like an OutputDebugString to see it called in the message area of the IDE) to see if it is still looping or not.
Something like:
with Message do
case Msg of
WM_MyMessage: OutputDebugString('MyMessage received. Drawing a circle');
else
inherited WndProc(Message);
If it's only writing once per click, then the recursion is in your handling of the message (or in the timer handler) to draw/erase the circle.
If it's looping, then your click generates multiple messages or 1 that is spinning forever...
Update:
After giving a look at your code, I'd change the way you deal with the timers.
- Don't create the timer with an interval of 1 for the purpose of creating the shape. You'll be flooding your app with Timer events.
- As soon as you enter the Execute, disable the timer
- Avoid calling Application.ProcessMessages.
- You may have some reasons, but I find this very convoluted when it seems to me that a simple OnMouse event on your form could achieve this easily.
This happens because FindWindow actually sends messages on its own that also wind up in your hook. Specifically, it sends a WM_GETTEXT to get the window's title.
To avoid that, do the FindWindow up front (outside the hook's callback).

How to handle received data in the TCPClient ? (Delphi - Indy)

When i send a message from TCPClient to a TCPServer it will be handled using OnExecute event in the server . Now i want to handle the received messages in the Client but TCPClient doesn't have any event for this. So i have to make a thread to handle them manually. how can i do it ?
As others said in response to your question, TCP is not a message oriented protocol, but a stream one. I'll show you how to write and read to a very simple echo server (this is a slightly modified version of a server I did this week to answer other question):
The server OnExecute method looks like this:
procedure TForm2.IdTCPServer1Execute(AContext: TIdContext);
var
aByte: Byte;
begin
AContext.Connection.IOHandler.Writeln('Write anything, but A to exit');
repeat
aByte := AContext.Connection.IOHandler.ReadByte;
AContext.Connection.IOHandler.Write(aByte);
until aByte = 65;
AContext.Connection.IOHandler.Writeln('Good Bye');
AContext.Connection.Disconnect;
end;
This server starts with a welcome message, then just reads the connection byte per byte. The server replies the same byte, until the received byte is 65 (the disconnect command) 65 = 0x41 or $41. The server then end with a good bye message.
You can do this in a client:
procedure TForm3.Button1Click(Sender: TObject);
var
AByte: Byte;
begin
IdTCPClient1.Connect;
Memo1.Lines.Add(IdTCPClient1.IOHandler.ReadLn); //we know there must be a welcome message!
Memo1.Lines.Add('');// a new line to write in!
AByte := 0;
while (IdTCPClient1.Connected) and (AByte <> 65) do
begin
AByte := NextByte;
IdTCPClient1.IOHandler.Write(AByte);
AByte := IdTCPClient1.IOHandler.ReadByte;
Memo1.Lines[Memo1.Lines.Count - 1] := Memo1.Lines[Memo1.Lines.Count - 1] + Chr(AByte);
end;
Memo1.Lines.Add(IdTCPClient1.IOHandler.ReadLn); //we know there must be a goodbye message!
IdTCPClient1.Disconnect;
end;
The next byte procedure can be anything you want to provide a byte. For example, to get input from the user, you can turn the KeyPreview of your form to true and write a OnKeyPress event handler and the NextByte function like this:
procedure TForm3.FormKeyPress(Sender: TObject; var Key: Char);
begin
FCharBuffer := FCharBuffer + Key;
end;
function TForm3.NextByte: Byte;
begin
Application.ProcessMessages;
while FCharBuffer = '' do //if there is no input pending, just waint until the user adds input
begin
Sleep(10);
//this will allow the user to write the next char and the application to notice that
Application.ProcessMessages;
end;
Result := Byte(AnsiString(FCharBuffer[1])[1]); //just a byte, no UnicodeChars support
Delete(FCharBuffer, 1, 1);
end;
Anything the user writes in the form will be sent to the server and then read from there and added to memo1. If the input focus is already in Memo1 you'll see each character twice, one from the keyboard and the other form the server.
So, in order to write a simple client that gets info from a server, you have to know what to expect from the server. Is it a string? multiple strings? Integer? array? a binary file? encoded file? Is there a mark for the end of the connection? This things are usually defined at the protocol or by you, if you're creating a custom server/client pair.
To write a generic TCP without prior known of what to get from the server is possible, but complex due to the fact that there's no generic message abstraction at this level in the protocol.
Don't get confused by the fact there's transport messages, but a single server response can be split into several transport messages, and then re-assembled client side, your application don't control this. From an application point of view, the socket is a flow (stream) of incoming bytes. The way you interpret this as a message, a command or any kind of response from the server is up to you. The same is applicable server side... for example the onExecute event is a white sheet where you don't have a message abstraction too.
Maybe you're mixing the messages abstraction with the command abstraction... on a command based protocol the client sends strings containing commands and the server replies with strings containing responses (then probably more data). Take a look at the TIdCmdTCPServer/Client components.
EDIT
In comments OP states s/he wants to make this work on a thread, I'm not sure about what's the problem s/he is having with this, but I'm adding a thread example. The server is the same as shown before, just the client part for this simple server:
First, the thread class I'm using:
type
TCommThread = class(TThread)
private
FText: string;
protected
procedure Execute; override;
//this will hold the result of the communication
property Text: string read FText;
end;
procedure TCommThread.Execute;
const
//this is the message to be sent. I removed the A because the server will close
//the connection on the first A sent. I'm adding a final A to close the channel.
Str: AnsiString = 'HELLO, THIS IS _ THRE_DED CLIENT!A';
var
AByte: Byte;
I: Integer;
Client: TIdTCPClient;
Txt: TStringList;
begin
try
Client := TIdTCPClient.Create(nil);
try
Client.Host := 'localhost';
Client.Port := 1025;
Client.Connect;
Txt := TStringList.Create;
try
Txt.Add(Client.IOHandler.ReadLn); //we know there must be a welcome message!
Txt.Add('');// a new line to write in!
AByte := 0;
I := 0;
while (Client.Connected) and (AByte <> 65) do
begin
Inc(I);
AByte := Ord(Str[I]);
Client.IOHandler.Write(AByte);
AByte := Client.IOHandler.ReadByte;
Txt[Txt.Count - 1] := Txt[Txt.Count - 1] + Chr(AByte);
end;
Txt.Add(Client.IOHandler.ReadLn); //we know there must be a goodbye message!
FText := Txt.Text;
finally
Txt.Free;
end;
Client.Disconnect;
finally
Client.Free;
end;
except
on E:Exception do
FText := 'Error! ' + E.ClassName + '||' + E.Message;
end;
end;
Then, I'm adding this two methods to the form:
//this will collect the result of the thread execution on the Memo1 component.
procedure TForm3.AThreadTerminate(Sender: TObject);
begin
Memo1.Lines.Text := (Sender as TCommThread).Text;
end;
//this will spawn a new thread on a Create and forget basis.
//The OnTerminate event will fire the result collect.
procedure TForm3.Button2Click(Sender: TObject);
var
AThread: TCommThread;
begin
AThread := TCommThread.Create(True);
AThread.FreeOnTerminate := True;
AThread.OnTerminate := AThreadTerminate;
AThread.Start;
end;
TCP doesn't operate with messages. That is stream-based interface. Consequently don't expect that you will get a "message" on the receiver. Instead you read incoming data stream from the socket and parse it according to your high-level protocol.
Here is my code to Read / Write with Delphi 7. Using the Tcp Event Read.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ScktComp;
type
TForm1 = class(TForm)
ClientSocket1: TClientSocket;
Button1: TButton;
ListBox1: TListBox;
Edit1: TEdit;
Edit2: TEdit;
procedure Button1Click(Sender: TObject);
procedure ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
procedure ClientSocket1Error(Sender: TObject; Socket: TCustomWinSocket;
ErrorEvent: TErrorEvent; var ErrorCode: Integer);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
UsePort: Integer;
UseHost: String;
begin
UseHost := Edit1.Text;
UsePort := STRTOINT(Edit2.Text);
ClientSocket1.Port := UsePort;
ClientSocket1.Host := UseHost;
ClientSocket1.Active := true;
end;
procedure TForm1.ClientSocket1Read(Sender: TObject;
Socket: TCustomWinSocket);
begin
ListBox1.Items.Add(ClientSocket1.Socket.ReceiveText);
end;
procedure TForm1.ClientSocket1Error(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
begin
ErrorCode:=0;
ClientSocket1.Active := False;
end;
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
ClientSocket1.Socket.SendText(Edit1.Text);
end;
end.
If you need the Indy client to handle incoming "messages" (definition of "message" depends on the protocol used), I recommend to take a look at the implementation of TIdTelnet in the protocols\IdTelnet unit.
This component uses a receiving thread, based on a TIdThread, which asynchronously receives messages from the Telnet server, and passes them to a message handler routine. If you have a similar protocol, this could be a good starting point.
Update: to be more specific, the procedure TIdTelnetReadThread.Run; in IdTelnet.pas is where the asynchronous client 'magic' happens, as you can see it uses Synchronize to run the data processing in the main thread - but of course your app could also do the data handling in the receiving thread, or pass it to a worker thread to keep the main thread untouched. The procedure does not use a loop, because looping / pausing / restarting is implemented in IdThread.
Add a TTimer.
Set its Interval to 1.
Write in OnTimer Event:
procedure TForm1.Timer1Timer(Sender: TObject);
var
s: string;
begin
if not IdTCPClient1.Connected then Exit;
if IdTCPClient1.IOHandler.InputBufferIsEmpty then Exit;
s := IdTCPClient1.IOHandler.InputBufferAsString;
Memo1.Lines.Add('Received: ' + s);
end;
Don't set Timer.Interval something else 1.
Because, the received data deletes after some milliseconds.

Resources