Using Dspack grab a single image - dspack

How to use DSpack to grab a single image from a webcam??
Is it possible to use SampleGrabber?
I am coding in delphi, I tryed every thing but unsuccessfully thanks for help

Link to project
unit frmCaptureU;
interface
uses
Windows, Messages, SysUtils, Classes, Controls, Forms,
Dialogs, StdCtrls, DSPack, DSUtil, DirectShow9, ExtCtrls,Jpeg,Registry;
type
TfrmCapture = class(TForm)
SampleGrabber: TSampleGrabber;
CaptureGraph: TFilterGraph;
VideoSourceFilter: TFilter;
VideoWindow: TVideoWindow;
pnlBottom: TPanel;
lblVideoSource: TLabel;
lblVideoFormat: TLabel;
btnStart: TButton;
btnStopCamera: TButton;
btnGrabImage: TButton;
pnlVideo: TPanel;
cbKeepAspectRatio: TCheckBox;
cbxVideoFormats: TComboBox;
cbxVideoSources: TComboBox;
btnSaveSettings: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure cbxVideoSourcesClick(Sender: TObject);
procedure btnGrabImageClick(Sender: TObject);
procedure btnStartClick(Sender: TObject);
procedure btnStopCameraClick(Sender: TObject);
procedure FormResize(Sender: TObject);
procedure cbKeepAspectRatioClick(Sender: TObject);
procedure CaptureGraphGraphVideoSizeChanged(sender: TObject; Width, height: Word);
procedure cbxVideoFormatsClick(Sender: TObject);
procedure btnSaveSettingsClick(Sender: TObject);
private
CapEnum: TSysDevEnum;
VideoMediaTypes : TEnumMediaType;
theratio : Real;
procedure LoadIni;
procedure SaveIni;
procedure StopCaptureGraph;
procedure SetAspectRatio;
procedure EnumerateVideoFormats;
procedure StartCaptureGraph;
{ Private declarations }
public
procedure GrabAs(aFileName : String);
{ Public declarations }
end;
const
IniFileName = 'capture.ini';
var
frmCapture: TfrmCapture;
implementation
{$R *.dfm}
{ TMainForm }
procedure TfrmCapture.CaptureGraphGraphVideoSizeChanged(sender: TObject; Width, height: Word);
//Just made this if you need to get the actual video size from the running graph.
begin
Caption:=format('Image Grabber (H:%u W:%u)',[Height,Width]);
theratio := width/height;
end;
procedure TfrmCapture.FormCreate(Sender: TObject);
var
i: integer;
begin
CapEnum:=TSysDevEnum.Create(CLSID_VideoInputDeviceCategory);
for i:=0 to CapEnum.CountFilters-1 do
cbxVideoSources.Items.Add(CapEnum.Filters[i].FriendlyName);
VideoMediaTypes:=TEnumMediaType.Create;
LoadIni;
end;
procedure TfrmCapture.FormDestroy(Sender: TObject);
begin
CapEnum.Free;
VideoMediaTypes.Free;
end;
procedure TfrmCapture.FormResize(Sender: TObject);
begin
if cbKeepAspectRatio.Checked then
SetAspectRatio;
end;
procedure TfrmCapture.LoadIni;
Var
Inifile : TRegIniFile;
begin
Inifile:=TRegIniFile.Create(IniFileName);
Try
cbxVideoSources.ItemIndex:=IniFile.ReadInteger('Grabbing','VideoSource',-1);
If cbxVideoSources.ItemIndex<>-1 Then
Begin
EnumerateVideoFormats;
cbxVideoFormats.ItemIndex:=IniFile.ReadInteger('Grabbing','VideoFormat',-1);
if cbxVideoFormats.ItemIndex<>-1 then
StartCaptureGraph;
End;
Finally
IniFile.Free;
End;
end;
procedure TfrmCapture.cbxVideoSourcesClick(Sender: TObject);
begin
if cbxVideoSources.ItemIndex<>-1 then
EnumerateVideoFormats;
end;
procedure TfrmCapture.cbxVideoFormatsClick(Sender: TObject);
begin
if (cbxVideoSources.ItemIndex<>-1) And (cbxVideoFormats.ItemIndex<>-1) then
Begin
btnStart.Enabled:=True;
btnGrabImage.Enabled:=True;
End;
end;
procedure TfrmCapture.btnGrabImageClick(Sender: TObject);
begin
//GrabAs('C:\Test.jpg');
Grabas('c:\database\test.jpg');
end;
procedure TfrmCapture.btnSaveSettingsClick(Sender: TObject);
begin
SaveIni;
end;
procedure TfrmCapture.btnStartClick(Sender: TObject);
begin
StartCaptureGraph;
end;
procedure TfrmCapture.btnStopCameraClick(Sender: TObject);
begin
StopCaptureGraph;
end;
procedure TfrmCapture.cbKeepAspectRatioClick(Sender: TObject);
begin
if cbKeepAspectRatio.Checked then
Begin
VideoWindow.Align:=alNone;
SetAspectRatio;
End
Else
VideoWindow.Align:=alClient;
end;
procedure TfrmCapture.SetAspectRatio;
// Hold Aspect ratio 4:3 og centrér video display
// Den fil der bliver gemt vil altid have det korrekte aspect ratio (størrelsen på det faktiske video-format)
begin
if (pnlVideo.Width>pnlVideo.Height * theratio) then
begin
VideoWindow.Height:=pnlvideo.Height;
VideoWindow.Width:=Round(pnlvideo.Height * theratio);
VideoWindow.Left:=pnlVideo.Width div 2 - VideoWindow.Width div 2;
end
else
begin
VideoWindow.Height:=Round(pnlVideo.Width / theratio);
VideoWindow.Width:=pnlVideo.Width;
VideoWindow.Top:=pnlVideo.Height div 2 - VideoWindow.Height div 2;
end;
end;
procedure TfrmCapture.GrabAs(aFileName : String);
// Antager et validt filnavn, og at Capturegraph kører.
var
aJpg: TJPEGImage;
aImage: TImage;
begin
aImage:=TImage.Create(self);
try
SampleGrabber.GetBitmap(aImage.Picture.Bitmap);
aJpg:=TJpegImage.Create;
try
aJpg.Assign(aImage.Picture.Bitmap);
aJpg.SaveToFile(ChangeFileExt(aFileName,'.jpg'));
finally
aJpg.Free;
end;
finally
aImage.Free;
end;
end;
procedure TfrmCapture.StartCaptureGraph;
var
VideoFormatIndex: Integer;
PinList: TPinList;
begin
try
// Aktivér Capturegraph før vi kan ændre noget
CaptureGraph.Active:=true;
// Konfigurér video formatet
if VideoSourceFilter.FilterGraph<>nil then
begin
PinList := TPinList.Create(VideoSourceFilter as IBaseFilter);
try
if cbxVideoFormats.ItemIndex<>-1 then
begin
VideoFormatIndex:=Integer(cbxVideoFormats.Items.Objects[cbxVideoFormats.ItemIndex]);
(PinList.First as IAMStreamConfig).SetFormat(VideoMediaTypes.Items[VideoFormatIndex].AMMediaType^);
end;
finally
PinList.Free;
end;
end;
// Set Renderingssvejene
// - Der er een for videosource.preview til videovinduet
// - og een for videosource.capture til samplegrabberen som vi får bitmappen fra.
with CaptureGraph as IcaptureGraphBuilder2 do
begin
// Forbind Video preview (VideoSource Preview -> VideoWindow)
if VideoSourceFilter.BaseFilter.DataLength>0 then
RenderStream(#PIN_CATEGORY_PREVIEW, nil, VideoSourceFilter as IBaseFilter, nil, VideoWindow as IBaseFilter);
// Forbind Video capture streams (VideoSource Capture -> SampleGrabber)
if VideoSourceFilter.FilterGraph<>nil then
RenderStream(#PIN_CATEGORY_CAPTURE, nil, VideoSourceFilter as IBaseFilter, nil, SampleGrabber as IBaseFilter);
end;
//Start Capturegraph (renderer live video stream)
CaptureGraph.Play;
btnStart.Enabled:=False;
btnStopCamera.Enabled:=True;
btnGrabImage.Enabled:=True;
cbxVideoFormats.Enabled:=False;
cbxVideoSources.Enabled:=False;
except
MessageDlg('Could not initialize camera.' +#13#10+ 'Please make sure that it is connected and the proper drivers are installed.', mtError, [mbOK], 0);
end;
end;
procedure TfrmCapture.EnumerateVideoFormats;
var
PinList: TPinList;
i: Integer;
begin
cbxVideoFormats.Clear;
CapEnum.SelectGUIDCategory(CLSID_VideoInputDeviceCategory);
VideoSourceFilter.BaseFilter.Moniker:=CapEnum.GetMoniker(cbxVideoSources.ItemIndex);
VideoSourceFilter.FilterGraph:=CaptureGraph;
CaptureGraph.Active:=true;
PinList:=TPinList.Create(VideoSourceFilter as IBaseFilter);
try
VideoMediaTypes.Assign(PinList.First);
for i:=0 to VideoMediaTypes.Count-1 do
begin
// Brug kun VideoInfo headers, da vi ikke bruger Overlay Mixeren.
if Pos('VideoInfo2', VideoMediaTypes.MediaDescription[i])=0 then
cbxVideoFormats.Items.AddObject(VideoMediaTypes.MediaDescription[i], TObject(I));
end;
CaptureGraph.Active:=false;
finally
PinList.Free;
end;
end;
procedure TfrmCapture.SaveIni;
Var
Inifile : TRegIniFile;
begin
Inifile:=TRegIniFile.Create(IniFileName);
Try
IniFile.WriteInteger('Grabbing','VideoSource',cbxVideoSources.ItemIndex);
IniFile.WriteInteger('Grabbing','VideoFormat',cbxVideoFormats.ItemIndex);
Finally
IniFile.Free;
End;
end;
procedure TfrmCapture.StopCaptureGraph;
begin
btnStart.Enabled:=True;
btnStopCamera.Enabled:=False;
btnGrabImage.Enabled:=False;
cbxVideoFormats.Enabled:=True;
cbxVideoSources.Enabled:=True;
CaptureGraph.Stop;
CaptureGraph.Active:=False;
end;
end. here

Related

Prevent TIdTcpServer Stuck Connections

how are you?
I come here ask for a solution, how prevent TIdTcpServer stuck connections?
Version of indy 10.6.2.5341 and Rad Studio 10.1 Berlin
On both images show the number of connections on TIdTcpServer, these numbers are retrieved from this function:
var
NumClients: Integer;
begin
with Form1.IdTCPServer1.Contexts.LockList do
try
NumClients := Count;
finally
Form1.IdTCPServer1.Contexts.UnlockList;
end;
Result := NumClients;
What happen is, in almost cases this numbers only increase and not decrease. so i believe connections are being stucked on TIdTcpServer.
I use a IdSchedulerOfThreadDefault1 on Scheduler, i don't know if that change something or no but i added.
For manage connections i use ContextClass:
IdTCPServer1.ContextClass := TClientContext;
Who definition is:
type
TCommand = (
cmdConnect,
cmdDisconnect,
cmdHWID,
cmdScreenShotData,
cmdMensagem);
type
TClient = record
HWID : String[40];
Tempo : TDateTime;
Msg : String[100];
end;
const
szClient = SizeOf(TClient);
type
TProtocol = record
Command: TCommand;
Sender: TClient;
DataSize: Integer;
end;
const
szProtocol = SizeOf(TProtocol);
type
TClientContext = class(TIdServerContext)
private
FCriticalSection : TCriticalSection;
FClient : TClient;
public
constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); override;
destructor Destroy; override;
public
procedure Lock;
procedure Unlock;
public
property Client: TClient read FClient write FClient;
end;
Others functions who are used:
procedure InitProtocol(var AProtocol: TProtocol);
begin
FillChar(AProtocol, szProtocol, 0);
end;
function ProtocolToBytes(const AProtocol: TProtocol): TBytes;
begin
SetLength(Result, szProtocol);
Move(AProtocol, Result[0], szProtocol);
end;
constructor TClientContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil);
begin
inherited Create(AConnection, AYarn, AList);
FCriticalSection := TCriticalSection.Create;
end;
destructor TClientContext.Destroy;
begin
FreeAndNil(FCriticalSection);
inherited;
end;
procedure TClientContext.Lock;
begin
FCriticalSection.Enter;
end;
procedure TClientContext.Unlock;
begin
FCriticalSection.Leave;
end;
function BytesToProtocol(const ABytes: TBytes): TProtocol;
begin
Move(ABytes[0], Result, szProtocol);
end;
procedure ClearBuffer(var ABuffer: TBytes);
begin
SetLength(ABuffer, 0);
end;
procedure ClearBufferId(var ABuffer: TIdBytes);
begin
SetLength(ABuffer, 0);
end;
All events (connect/disconnect) i manage on IdTCPServer1Execute
like this example above:
type
PTBytes = ^TBytes;
PTIdBytes = ^TIdBytes;
var
LBuffer : TIdBytes;
LProtocol : TProtocol;
FTempBuffer : TIdBytes;
Enviar : TBytes;
Protocolo : TProtocol;
Conexao : TClientContext;
//
Queue: TStringList;
List: TStringList;
x : Integer;
//
procedure AddToMemo(const AStr: string);
begin
TThread.Synchronize(nil,
procedure
begin
Memo1.Lines.Add(AStr);
Form1.StatusBar1.Panels[0].Text := Format('Connections [%d]', [RetornaOn]);
end
);
end;
begin
Conexao := TClientContext(AContext);
// QUEUE
List := nil;
try
Queue := Conexao.Queue.Lock;
try
if Queue.Count > 0 then
begin
List := TStringList.Create;
List.Assign(Queue);
Queue.Clear;
end;
finally
Conexao.Queue.Unlock;
end;
if List <> nil then
begin
for x := 0 to List.Count-1 do
begin
InitProtocol(Protocolo);
Protocolo.Command := cmdMensagem;
Protocolo.Sender.Msg := Edit2.Text;
Enviar := ProtocolToBytes(Protocolo);
Conexao.Connection.IOHandler.Write(PTIdBytes(#Enviar)^);
ClearBuffer(Enviar);
end;
// Delete Queue
for x := 0 to List.Count-1 do
begin
List.Delete(x);
end;
end;
finally
List.Free;
end;
// QUEUE
if AContext.Connection.IOHandler.InputBufferIsEmpty then
begin
//AddToMemo(Format('[%s] Running 1 ...', [TimeToStr(Now)]));
AContext.Connection.IOHandler.CheckForDataOnSource(100);
AContext.Connection.IOHandler.CheckForDisconnect;
if AContext.Connection.IOHandler.InputBufferIsEmpty then
begin
{AddToMemo(Format('[%s] Running 2 ...', [TimeToStr(Now)]));
if GetTickDiff(Conexao.Client.Tick, Ticks) >= 10000 then
begin
AddToMemo(Format('[%s] Running 3 [%d] ...', [TimeToStr(Now), Conexao.Client.Tick]));
AContext.Connection.Disconnect;
Exit;
end;}
Exit;
end;
end;
AContext.Connection.IOHandler.ReadBytes(LBuffer, szProtocol, False);
LProtocol := BytesToProtocol(PTBytes(#LBuffer)^);
case LProtocol.Command of
cmdConnect: begin
Conexao.Client := LProtocol.Sender;
Conexao.FClient.Tick := Ticks;
AddToMemo(Format('[%s] : [%s][%s]', ['Connect', AContext.Connection.Socket.Binding.PeerIP, Protocolo.Sender.HWID]));
end;
cmdMensagem: begin
AddToMemo(Format('[%s] : [%s][%s][%s]', ['Msg', AContext.Connection.Socket.Binding.PeerIP, Conexao.Client.HWID, LProtocol.Sender.Msg]));
end;
cmdDisconnect: begin
AddToMemo(Format('[%s] : [%s][%s]', ['Disconnect', AContext.Connection.Socket.Binding.PeerIP, Conexao.Client.HWID]));
end;
end;
In next code i show how client side connect to TIdTcpServer:
type
PTIdBytes = ^TIdBytes;
var
LBuffer : TBytes;
LProtocol : TProtocol;
begin
ClientThread := TClientThread.Create(False);
InitProtocol(LProtocol);
LProtocol.Command := cmdConnect;
LProtocol.Sender.HWID := Edit1.Text;
LProtocol.Sender.Tempo := Now;
LBuffer := ProtocolToBytes(LProtocol);
IdTCPClient1.IOHandler.Write(PTIdBytes(#LBuffer)^);
ClearBuffer(LBuffer);
AddToMemo('IdTCPClient1 connected to server');
ClientThread on client:
procedure TClientThread.Execute;
type
PTBytes = ^TBytes;
PTIdBytes = ^TIdBytes;
var
LBuffer : TIdBytes;
LDataSize : Integer;
LProtocol : TProtocol;
procedure AddToMemo(const AStr: string);
begin
TThread.Synchronize(nil,
procedure
begin
Form1.Memo1.Lines.Add('Received From Server: ' + AStr);
end
);
end;
begin
inherited;
while NOT Terminated and Form1.IdTCPClient1.Connected do begin
//LDataSize := Form1.IdTCPClient1.IOHandler.InputBuffer.Size;
//if LDataSize >= szProtocol then begin
try
Form1.IdTCPClient1.IOHandler.ReadBytes(LBuffer, szProtocol);
LProtocol := BytesToProtocol(PTBytes(#LBuffer)^);
case LProtocol.Command of
cmdHWID:
begin
HWID := LProtocol.Sender.HWID;
AddToMemo('HWID > ' + LProtocol.Sender.HWID);
end;
cmdDisconnect:
begin
AddToMemo('DC > ' + LProtocol.Sender.HWID);
end;
cmdMensagem:
begin
AddToMemo('MSG > ' + LProtocol.Sender.Msg);
end;
end;
finally
ClearBufferId(LBuffer);
end;
//end;
Sleep(50);
end;
end;
Anybody know why these connections are being stucked on TIdTcpServer?
Maybe if i loop all conenctions and try send a single text will disconnect they if don't are really connected to IdTcpServer no ?
Thanks.

How to know which Form is opened and how to close it?

I have a TAction.OnExecute event handler triggered from my main form,
FormPrincipal, which opens other Forms.
procedure TFormPrincipal.AbreFormBancoExecute(Sender: TObject);
begin
Formbanco := Tformbanco.Create(self);
Formbanco.Parent := PanelCorpo;
Formbanco.Align := alclient;
Formbanco.BorderIcons := [];
Formbanco.Show;
Formbanco.BorderStyle := bsNone;
Formbanco.SetFocus;
end;
Once I'll have several forms, how to know which one is opened and how to close it, before triggering OnExecute to open another Form?
=========== Finally it is Working as I expected =======
The main form is form1 from which I call form2 and form3. In form1 I have a panel1 which is parent of form2 and form3. See form1 code :
...
var
Form1: TForm1;
implementation
{$R *.dfm}
uses unit2, unit3;
procedure Tform1.CloseActiveForm (Formname : string);
// Free memory allocated to the current form , set it to nil
// I'll have to find a better way to perform FreeanNil without
// use many IFs command
begin
if Formname = 'form2' then FreeAndnil(Form2) else
if Formname = 'form3' then FreeandNil(Form3);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
CloseActiveForm(Edit1.Text); //Edit1 has the current active form name
if form2 = nil then
begin
Application.CreateForm(Tform2,Form2);
Form2.Parent := Panel1;
Form2.Align := alclient;
Form2.Show;
Form2.BorderStyle := bsnone;
Form2.SetFocus;
Form2.OnActivate(Sender); //Method Show blocks Activate event
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
CloseActiveForm(Edit1.Text); //Edit1 has the current active form name
if form3 = nil then
begin
Application.CreateForm(Tform3,Form3);
Form3.Parent := Panel1;
Form3.Align := alclient;
Form3.Show;
Form3.BorderStyle := bsnone;
Form3.SetFocus;
Form3.OnActivate(Sender); //Method Show blocks Activate event
end;
end;
procedure TForm1.FormActivate(Sender: TObject);
begin
Edit1.Text := Screen.ActiveForm.Name;
end;
end.
2) Code in form2 and form3 (consider form3 is identical)
...
var
Form2: TForm2;
implementation
{$R *.dfm}
uses unit1;
procedure TForm2.Button1Click(Sender: TObject);
begin
Edit2.Text := Screen.ActiveForm.Name;
end;
procedure TForm2.FormActivate(Sender: TObject);
begin
setfocus;
Edit1.Text := Form2.Name;
Form1.Edit1.Text := Form2.Name; //set form name
// the property Screen.ActiveForm.Name if used here, will always presents
// form1 name (main form) because this form2 is set to a parent panel
// in form1
end;
end.
Destroy the form if it exist and create a new instance of it.
procedure TFormPrincipal.AbreFormBancoExecute(Sender: TObject);
procedure CreateFormbanco;
begin
Formbanco := TFormbanco.Create(self);
Formbanco.Parent := PanelCorpo;
Formbanco.Align := alclient;
Formbanco.BorderIcons := [];
Formbanco.BorderStyle := bsNone;
Formbanco.Show;
Formbanco.SetFocus;
Formbanco.OnDestroy := FormDestroyEvent;
end;
begin
if not Assigned(Formbanco) then
begin
CreateFormbanco;
end
else
begin
Formbanco.Destroy;
CreateFormbanco;
end;
procedure TFormPrincipal.FormDestroyEvent(Sender: TObject);
begin
Formbanco := nil;
end;
This code will check if Formbanco existed, if so it will destroy it and create a new instance of it otherwise it will create a new one.
Edit: create different forms and use the code above, just change Formbanco and TFormbanco to their respected new form name.

Client.Socket.SendText from of a Form that is displayed above of other Form that stays always on top

I'm with difficulties for send text from a Client.Socket. The Form that sends the text, is displayed always above of Form that stays always on top, when FormOnTop not exists (not is showing), works fine, already when FormOnTop exists and Form that will sends the text is displayed above of FormOntop, the send fail, and text (message) not even exit of Client.exe of my software.
There are some solution for this?
For better understand, I will leave the code that I using:
Form that contains Client Socket component
unit Unit1;
interface
uses
FormSender;
type
......
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.CS1Read(Sender: TObject; Socket: TCustomWinSocket);
var
StrCommand: String;
begin
StrCommand := Socket.ReceiveText;
if Pos('<|Command_From_Server|>', StrCommand) > 0 then
begin
FormSender.PopupMode:= pmExplicit;
FormSender.PopupParent:= FormOnTop;
FormSender.Show;
end;
end;
end.
Form that sends text (FormSender) and appear above of Form that stays always On Top:
unit FormSender;
interface
uses
Unit1;
type
......
end;
var
FormSender: TFormSender;
implementation
{$R *.dfm}
procedure TFormSender.Button1Click(Sender: TObject);
begin
Form1.CS1.Socket.SendText('<|Hello_Server!|>' + Edit1.Text + '<<|);
end;
end.
Form that stays always on top:
FormStyle property is: fsStayOnTop.
unit FormOnTop;
interface
uses
.......
type
......
end;
var
FormOnTop: TFormOnTop;
implementation
{$R *.dfm}
procedure TFormOnTop.FormCreate(Sender: TObject);
begin
{ Position form }
Top := 0 ;
Left := 0 ;
{ Go full screen }
BorderStyle := bsNone ;
WindowState := wsmaximized;
ClientWidth := Screen.Width ;
ClientHeight := Screen.Height;
Refresh;
SetForegroundWindow(Handle) ;
SetActiveWindow(Application.Handle);
end;
procedure TFormOnTop.CreateParams(var Params: TCreateParams);
begin
inherited;
if (FormStyle = fsStayOnTop) then begin
Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
Params.WndParent := GetDesktopWindow;
end;
end;
procedure TFormOnTop.FormShow(Sender: TObject);
begin
SetWindowPos(FormOnTop.handle, HWND_TOPMOST, FormOnTop.Left, FormOnTop.Top, FormOnTop.Width, FormOnTop.Height, 0);
end;
end.

IdTcpServer cannot free its context thread after context.connection.disconnect() under CentOS+Lazarus

The result of Memo1 is still "ConnCnt:1" after I click btDisconn button, though I wait for several minutes.
But under windows xp it works fine, how can I make idtcpserver remove the invalid context thread?
Here's my code:
Client side (Windows7 + DelphiXE2 + Indy10.5.8):
procedure TForm1.FormShow(Sender: TObject);
begin
TcpClient.Host:=192.168.1.103;
TcpClient.Port:=10000;
TcpClient.Connect;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
try
TcpClient.Disconnect;
except
end;
end;
Server side (Vmware + CentOS + Lararus1.0.12 + Indy10.5.8)
procedure TForm1.FormShow(Sender: TObject);
var Bind:TIdSocketHandle;
begin
TCPServer.Bindings.Clear;
Bind:=TCPServer.Bindings.Add;
Bind.IPVersion:=Id_IPv4;
Bind.Port:=10000;
TcpServer.OnExecute:=#TcpServerExecute;
TcpServer.DefaultPort:=10000;
TcpServer.Active:=true;
Timer1.Interval:=5000;
Timer1.Enabled:=true;
end;
procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
Timer1.Enabled:=false;
TcpServer.Active:=false;
end;
procedure TForm1.TcpServerExecute(AContext: TIdContext);
var b:Byte;
begin
try
b:=AContext.Connection.IOHandler.ReadByte();
except
on E:Exception do memo1.Lines.Add('Error:'+E.Message)
end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var lst:TList;
begin
try
lst:=TcpServer.Contexts.LockList();
Memo1.Lines.Add('ConnCnt:'+inttostr(lst.Count));//the result is still ConnCnt:1 after i click btDisconn
finally
TcpServer.Contexts.UnlockList();
end;
end;
procedure TForm1.btDisconnClick(Sender: TObject);
var i:Integer;lst:TList;itm:TIdContext;
begin
try
lst:=TcpServer.Contexts.LockList();
for i:=0 to lst.Count-1 do begin
itm:=TIdContext(lst.Items[i]);
if Assigned(itm) then begin
itm.Connection.Disconnect();
itm.Connection.IOHandler.DiscardAll;
end;
end;
finally
TcpServer.Contexts.UnlockList();
end;
end;
There are two problems with your server code that prevent it from shutting down correctly:
your OnExecute code is catching and discarding all exceptions and not allowing TIdTCPServer to process any of them. When TIdTCPServer is being deactivated, it closes all active sockets, which in turn causes current/subsequent socket operations to fail and raise exceptions. By discarding the exceptions, TIdTCPServer has no clue that the connections have been closed, and happily keeps calling the OnExecute event. If you must catch exceptions (such as to log them), you need to re-raise any Indy-specific exceptions when you are done with them so TIdTCPServer can then process them.
you are accessing the TMemo in a thread-unsafe manner, which can (amongst other things) cause deadlocks.
Try this instead:
uses
..., IdSync;
type
TMemoNotify = class(TIdNotify)
protected
FMsg: String;
procedure DoNotify; override;
public
class procedure AddToMemo(const AMsg: string);
end;
procedure TMemoNotify.DoNotify;
begin
Form1.Memo1.Lines.Add(FMsg);
end;
class procedure TMemoNotify.AddToMemo(const AMsg: string);
begin
with Create do
begin
FMsg := AMsg;
Notify;
end;
end;
uses
..., EIdException;
procedure TForm1.FormShow(Sender: TObject);
var
Bind: TIdSocketHandle;
begin
TCPServer.Bindings.Clear;
Bind := TCPServer.Bindings.Add;
Bind.IPVersion := Id_IPv4;
Bind.Port := 10000;
TcpServer.OnExecute := TcpServerExecute;
TcpServer.Active := True;
Timer1.Interval := 5000;
Timer1.Enabled := True;
end;
procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
Timer1.Enabled := False;
TcpServer.Active := False;
end;
procedure TForm1.TcpServerExecute(AContext: TIdContext);
var
b: Byte;
begin
try
b := AContext.Connection.IOHandler.ReadByte;
except
on E: Exception do
begin
TMemoNotify.AddToMemo('Error:'+E.Message);
if E is EIdException then raise;
end;
end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
lst: TList;
begin
lst := TcpServer.Contexts.LockList;
try
Memo1.Lines.Add('ConnCnt:'+IntToStr(lst.Count));
finally
TcpServer.Contexts.UnlockList;
end;
end;
procedure TForm1.btDisconnClick(Sender: TObject);
var
i: Integer;
lst: TList;
begin
lst := TcpServer.Contexts.LockList;
try
for i := 0 to lst.Count-1 do
begin
try
TIdContext(lst.Items[i]).Connection.Disconnect;
except
end;
end;
finally
TcpServer.Contexts.UnlockList;
end;
end;
Alternatively to re-raising Indy exceptions, you could just get rid of the exception handling in the OnExecute event altogether and use the TIdTCPServer.OnException event instead:
procedure TForm1.TcpServerExecute(AContext: TIdContext);
var
b: Byte;
begin
b := AContext.Connection.IOHandler.ReadByte;
...
end;
procedure TForm1.TcpServerException(AContext: TIdContext; AException: Exception);
begin
TMemoNotify.AddToMemo('Error:'+AException.Message);
end;

Thread open forms in Delphi

I want to create new instances of form(and show them) from a Thread. But it seems that it freeze my application and my thread(my thread becomes an non syncrhonization thread, and it freeze my aplication).
Like this(but it doesn't make what i am looking for)
procedure a.Execute;
var frForm:TForm;
B:TCriticalSection;
begin
b:=TCriticalSection.Create;
while 1=1 do
begin
b.Enter;
frForm:=TForm.Create(Application);
frForm.Show;
b.Leave;
sleep(500); //this sleep with sleep my entire application and not only the thread.
//sleep(1000);
end;
end;
I don't want to use Classes.TThread.Synchronize method
TThread.Synchronize() is the simplest solution:
procedure a.Execute;
begin
while not Terminated do
begin
Synchronize(CreateAndShowForm);
Sleep(500);
end;
end;
procedure a.CreateAndShowForm;
var
frForm:TForm;
begin
frForm:=TForm.Create(Application);
frForm.Show;
end;
If you are using a modern version of Delphi and don't need to wait for the TForm creation to complete before letting the thread move on, you could use TThread.Queue() instead:
procedure a.Execute;
begin
while not Terminated do
begin
Queue(CreateAndShowForm);
Sleep(500);
end;
end;
Update: If you want to use PostMessage(), the safest option is to post your messages to either the TApplication window or a dedicated window created via AllocateHWnd(), eg:
const
WM_CREATE_SHOW_FORM = WM_USER + 1;
procedure TMainForm.FormCreate(Sender: TObject);
begin
Application.OnMessage := AppMessage;
end;
procedure TMainForm.AppMessage(var Msg: TMsg; var Handled: Boolean);
var
frForm:TForm;
begin
if Msg.message = WM_CREATE_SHOW_FORM then
begin
Handled := True;
frForm := TForm.Create(Application);
frForm.Show;
end;
end;
procedure a.Execute;
begin
while not Terminated do
begin
PostMessage(Application.Handle, WM_CREATE_SHOW_FORM, 0, 0);
Sleep(500);
end;
end;
.
const
WM_CREATE_SHOW_FORM = WM_USER + 1;
var
ThreadWnd: HWND = 0;
procedure TMainForm.FormCreate(Sender: TObject);
begin
ThreadWnd := AllocateHWnd(ThreadWndProc);
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
DeallocateHwnd(ThreadWnd);
ThreadWnd := 0;
end;
procedure TMainForm.ThreadWndProc(var Message: TMessage);
var
frForm:TForm;
begin
if Message.Msg = WM_CREATE_SHOW_FORM then
begin
frForm := TForm.Create(Application);
frForm.Show;
end else
Message.Result := DefWindowProc(ThreadWnd, Message.Msg, Message.WParam, Message.LParam);
end;
procedure a.Execute;
begin
while not Terminated do
begin
PostMessage(ThreadWnd, WM_CREATE_SHOW_FORM, 0, 0);
Sleep(500);
end;
end;
You cannot create a notoriously thread-unsafe VCL form in this way, (note - it's not just Delphi - all GUI development I have seen has this restriction). Either use TThread.Synchronize to signal the main thread to create the form, or use some other signaling mechanism like the PostMessage() API.
Overall, it's best to try an keep GUI stuff out of secondary threads, as far as you can. Secondary threads are better used for non-GUI I/O and/or CPU-intensive operations, (especially if they can be split up and be performed in parallel).
PostMessage example, (the form has just one speedbutton on it):
unit mainForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Buttons;
const
CM_OBJECTRX=$8FF0;
type
EmainThreadCommand=(EmcMakeBlueForm,EmcMakeGreenForm,EmcMakeRedForm);
TformMakerThread = class(TThread)
protected
procedure execute; override;
public
constructor create;
end;
TForm1 = class(TForm)
SpeedButton1: TSpeedButton;
procedure SpeedButton1Click(Sender: TObject);
private
myThread:TformMakerThread;
protected
procedure CMOBJECTRX(var message:Tmessage); message CM_OBJECTRX;
end;
var
Form1: TForm1;
ThreadPostWindow:Thandle;
implementation
{$R *.dfm}
{ TForm1 }
procedure TForm1.CMOBJECTRX(var message: Tmessage);
var thisCommand:EmainThreadCommand;
procedure makeForm(formColor:integer);
var newForm:TForm1;
begin
newForm:=TForm1.Create(self);
newForm.Color:=formColor;
newForm.Show;
end;
begin
thisCommand:=EmainThreadCommand(message.lparam);
case thisCommand of
EmcMakeBlueForm:makeForm(clBlue);
EmcMakeGreenForm:makeForm(clGreen);
EmcMakeRedForm:makeForm(clRed);
end;
end;
function postThreadWndProc(Window: HWND; Mess, wParam, lParam: Longint): Longint; stdcall;
begin
result:=0;
if (Mess=CM_OBJECTRX) then
begin
try
TControl(wparam).Perform(CM_OBJECTRX,0,lParam);
result:=-1;
except
on e:exception do application.messageBox(PChar(e.message),PChar('PostToMainThread perform error'),MB_OK);
end;
end
else
Result := DefWindowProc(Window, Mess, wParam, lParam);
end;
var
ThreadPostWindowClass: TWndClass = (
style: 0;
lpfnWndProc: #postThreadWndProc;
cbClsExtra: 0;
cbWndExtra: 0;
hInstance: 0;
hIcon: 0;
hCursor: 0;
hbrBackground: 0;
lpszMenuName: nil;
lpszClassName: 'TpostThreadWindow');
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
TformMakerThread.create;
end;
{ TformMakerThread }
constructor TformMakerThread.create;
begin
inherited create(true);
freeOnTerminate:=true;
resume;
end;
procedure TformMakerThread.execute;
begin
while(true) do
begin
postMessage(ThreadPostWindow,CM_OBJECTRX,integer(Form1),integer(EmcMakeBlueForm));
sleep(1000);
postMessage(ThreadPostWindow,CM_OBJECTRX,integer(Form1),integer(EmcMakeGreenForm));
sleep(1000);
postMessage(ThreadPostWindow,CM_OBJECTRX,integer(Form1),integer(EmcMakeRedForm));
sleep(1000);
end;
end;
initialization
Windows.RegisterClass(ThreadPostWindowClass);
ThreadPostWindow:=CreateWindow(ThreadPostWindowClass.lpszClassName, '', 0,
0, 0, 0, 0, 0, 0, HInstance, nil);
finalization
DestroyWindow(ThreadPostWindow);
end.
Just use the "TThread.Synchronize" static method, as it is static and public it can be used even outside the thread
TThread.Synchronize(MyThread, procedure begin Myform.Show(); end);
at least in this event, in the others if "MyForm.DoubleBuffered: = true;" you will have no sync problems, but anything can call the "Application.ProcessMessages ();" method in sync.