Can someone can explain me why i receive sometime an Exception "Argument out of range" under the ios simulator when i execute the code below? on android i never get any error. I use Delphi berlin.
the functions where the error appear :
{**********************************************************************}
procedure Twin_WorkerThreadPool.Enqueue(const Value: Twin_WorkerThread);
begin
Tmonitor.Enter(fPool);
try
fPool.Add(Value);
fSignal.SetEvent;
finally
Tmonitor.Exit(fPool);
end;
end;
{********************************************************}
function Twin_WorkerThreadPool.Dequeue: Twin_WorkerThread;
begin
Tmonitor.Enter(self); // << only one thread can execute the code below
try
Tmonitor.Enter(fPool);
try
if Fpool.Count > 0 then begin
result := fPool[Fpool.Count - 1];
fPool.Delete(Fpool.Count - 1);
exit;
end;
fSignal.ResetEvent;
finally
Tmonitor.Exit(fPool);
end;
fSignal.WaitFor(Infinite);
Tmonitor.Enter(fPool);
try
result := fPool[Fpool.Count - 1]; // << exception argument out of range ? but how it's possible ?
fPool.Delete(Fpool.Count - 1);
finally
Tmonitor.Exit(fPool);
end;
finally
Tmonitor.exit(self);
end;
end;
Below the full source code :
{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
Twin_WorkerThreadPool = class(TObject)
private
fPool: TObjectList<Twin_WorkerThread>;
fSignal: Tevent;
public
procedure Enqueue(const Value: Twin_WorkerThread);
function Dequeue: Twin_WorkerThread;
end;
{***********************************}
constructor Twin_WorkerThread.Create;
begin
FProc := nil;
FProcReadySignal := TEvent.Create(nil, false{ManualReset}, false, '');
FProcFinishedSignal := TEvent.Create(nil, false{ManualReset}, false, '');
inherited Create(False); // see http://www.gerixsoft.com/blog/delphi/fixing-symbol-resume-deprecated-warning-delphi-2010
end;
{***********************************}
destructor Twin_WorkerThread.Destroy;
begin
Terminate;
FProcReadySignal.setevent;
WaitFor;
FProcReadySignal.Free;
FProcFinishedSignal.Free;
inherited;
end;
{**********************************}
procedure Twin_WorkerThread.Execute;
begin
while True do begin
try
//wait the signal
FProcReadySignal.WaitFor(INFINITE);
//if terminated then exit
if Terminated then Break;
//execute fProc
if assigned(FProc) then FProc();
//signal the proc is finished
FProcFinishedSignal.SetEvent;
except
//hide the exception
end;
end;
end;
{**********************************************************}
procedure Twin_WorkerThread.ExecuteProc(const AProc: TProc);
begin
fProc := AProc;
FProcFinishedSignal.ResetEvent;
FProcReadySignal.SetEvent;
end;
{*****************************************************************}
procedure Twin_WorkerThread.ExecuteAndWaitProc(const AProc: TProc);
begin
fProc := AProc;
FProcFinishedSignal.ResetEvent;
FProcReadySignal.SetEvent;
FProcFinishedSignal.WaitFor(INFINITE);
end;
{********************************************************************}
constructor Twin_WorkerThreadPool.Create(const aThreadCount: integer);
var i: integer;
begin
fPool := TObjectList<Twin_WorkerThread>.create(false{aOwnObjects});
fSignal := TEvent.Create(nil, false{ManualReset}, false, '');
for I := 0 to aThreadCount - 1 do
fPool.Add(Twin_WorkerThread.Create)
end;
{***************************************}
destructor Twin_WorkerThreadPool.Destroy;
var i: integer;
begin
for I := 0 to fPool.Count - 1 do begin
fPool[i].disposeOf;
fPool[i] := nil;
end;
fPool.Free;
fSignal.Free;
inherited Destroy;
end;
{*********************************************************************}
procedure Twin_WorkerThreadPool.ExecuteAndWaitProc(const AProc: TProc);
var aThread: Twin_WorkerThread;
begin
aThread := Dequeue;
try
aThread.ExecuteAndWaitProc(aProc);
finally
Enqueue(aThread);
end;
end;
NOTE:
Just to explain a little better, remember it's only on ios it's not work, and if i add a sleep(1000) after the fSignal.resetEvent then it's work :
Tmonitor.Enter(fPool);
try
if Fpool.Count > 0 then begin
result := fPool[Fpool.Count - 1];
fPool.Delete(Fpool.Count - 1);
exit;
end;
fSignal.ResetEvent;
sleep(1000);
finally
Tmonitor.Exit(fPool);
end;
fSignal.WaitFor(Infinite);
so it's look like that the signal is not set to OFF just after doing fSignal.ResetEvent;
i m affraid it's a bug in TEvent or Tmonitor :(
Your pool is using an auto-reset event when it should be using a manual-reset event instead. You don't want each wait operation in Dequeue() to reset the event while there are still threads in the pool. It should be signaled while any item is in the pool, and unsignaled while the pool is empty. And your constructor is not signaling the event after adding your initial threads to the pool so they can be dequeued layer.
As for Dequeue() itself, is a bit more complicated than it should be. It can be simplified to something more like the following instead:
procedure Twin_WorkerThreadPool.Enqueue(const Value: Twin_WorkerThread);
begin
TMonitor.Enter(fPool);
try
fPool.Add(Value);
if fPool.Count = 1 then
fSignal.SetEvent;
finally
TMonitor.Exit(fPool);
end;
end;
function Twin_WorkerThreadPool.Dequeue: Twin_WorkerThread;
begin
repeat
TMonitor.Enter(fPool);
try
if fPool.Count > 0 then begin
Result := fPool[fPool.Count - 1];
fPool.Delete(fPool.Count - 1);
if fPool.Count = 0 then
fSignal.ResetEvent;
Exit;
end;
finally
TMonitor.Exit(fPool);
end;
fSignal.WaitFor(Infinite);
until False;
end;
Something else I notice is that fPool is a TObjectList<T> with its OwnsObjects property set to false, which kind of defeats the purpose of using TObjectList. You may as well just use TList<T> instead. And actually, the way you are using fPool, you should use TStack<T> instead. It would clean up your code a little more and make it that much easier to read and understand.
Try this instead:
type
Twin_WorkerThreadPool = class(TObject)
private
fPool: TStack<Twin_WorkerThread>;
fSignal: Tevent;
public
constructor Create(const aThreadCount: integer);
destructor Destroy; override;
procedure Enqueue(const Value: Twin_WorkerThread);
function Dequeue: Twin_WorkerThread;
end;
constructor Twin_WorkerThreadPool.Create(const aThreadCount: integer);
var
i: integer;
begin
inherited Create;
fPool := TStack<Twin_WorkerThread>.Create;
fSignal := TEvent.Create(nil, True{ManualReset}, False, '');
for I := 0 to aThreadCount - 1 do
fPool.Add(Twin_WorkerThread.Create);
if fPool.Count > 0 then
fPool.SetEvent;
end;
destructor Twin_WorkerThreadPool.Destroy;
var
i: integer;
begin
for I := fPool.Count - 1 downto 0 do
fPool.Pop.DisposeOf;
fPool.Free;
fSignal.Free;
inherited Destroy;
end;
procedure Twin_WorkerThreadPool.ExecuteAndWaitProc(const AProc: TProc);
var
aThread: Twin_WorkerThread;
begin
aThread := Dequeue;
try
aThread.ExecuteAndWaitProc(aProc);
finally
Enqueue(aThread);
end;
end;
procedure Twin_WorkerThreadPool.Enqueue(const Value: Twin_WorkerThread);
begin
TMonitor.Enter(fPool);
try
fPool.Push(Value);
if fPool.Count = 1 then
fSignal.SetEvent;
finally
TMonitor.Exit(fPool);
end;
end;
function Twin_WorkerThreadPool.Dequeue: Twin_WorkerThread;
begin
repeat
TMonitor.Enter(fPool);
try
if fPool.Count > 0 then begin
Result := fPool.Pop;
if fPool.Count = 0 then
fSignal.ResetEvent;
Exit;
end;
finally
TMonitor.Exit(fPool);
end;
fSignal.WaitFor(Infinite);
until False;
end;
Related
Following the tips from this Stack Overflow answer I created a simple application for Windows that can get a snapshot from the webcam, using DirectX library.
Now I am trying to get the same result using thread. Here is what I got so far:
TGetWebcam = class(TThread)
private
FWCVideo: TVideoImage;
FJpgShot: TJPEGImage;
procedure OnNewVideoFrame(Sender: TObject;
Width, Height: Integer; DataPtr: Pointer);
protected
procedure Execute; override;
public
constructor Create;
destructor Destroy; override;
end;
constructor TGetWebcam.Create;
begin
FreeOnTerminate := True;
FJpgShot := TJPEGImage.Create;
FWCVideo := TVideoImage.Create;
FWCVideo.OnNewVideoFrame := OnNewVideoFrame;
inherited Create(False);
end;
destructor TGetWebcam.Destroy;
begin
FWCVideo.Free;
FJpgShot.Free;
inherited;
end;
procedure TGetWebcam.Execute;
var
TmpLst: TStringList;
JpgImg: TJpegImage;
begin
TmpLst := TStringList.Create;
try
FWCVideo.GetListOfDevices(TmpLst);
if TmpLst.Count <= 0 then Exit;
if FWCVideo.VideoStart(TmpLst[0]) = 0 then
begin
TmpLst.Clear;
FWCVideo.GetListOfSupportedVideoSizes(TmpLst);
if TmpLst.Count <= 0 then Exit;
FWCVideo.SetResolutionByIndex(TmpLst.Count - 1);
JpgImg := TJPEGImage.Create;
try
JpgImg.Assign(FJpgShot);
JpgImg.CompressionQuality := 50;
JpgImg.SaveToFile('c:\test.jpg');
finally
JpgImg.Free;
end;
FWCVideo.VideoStop;
end;
finally
TmpLst.Free;
end;
end;
procedure TGetWebcam.OnNewVideoFrame(Sender: TObject; Width, Height: Integer;
DataPtr: Pointer);
begin
FWCVideo.GetJPG(FJpgShot); // I added this procedure "GetJPG" to VFrames.pas
end;
Problem is, GetListOfDevices always return empty when using inside thread.
Please, what am I doing wrong? Thanks!
EDIT:
After many tests and debugging following Remy Lebeau great tips, my conclusion is that OnNewVideoFrame is never fired when using TVideoImage inside thread. So my next test was trying to get the webcam shot inside the same execute method that creates TVideoImage, after waiting for some seconds, and it worked in the first time, but next time it always get blank white images, I need to close the application and open again for it to work one more time. Here is a abstract of the code I am using:
procedure TGetWebcam.Execute;
var
WCVideo: TVideoImage;
TmpList: TStringList;
JpgShot: TJPEGImage;
begin
CoInitialize(nil);
try
WCVideo := TVideoImage.Create;
try
TmpList := TStringList.Create;
try
WCVideo.GetListOfDevices(TmpList);
if TmpList.Count = 0 then Exit;
if WCVideo.VideoStart(TmpList[0]) <> 0 then Exit;
TmpList.Clear;
WCVideo.GetListOfSupportedVideoSizes(TmpList);
if TmpList.Count = 0 then Exit;
WCVideo.SetResolutionByIndex(ScnResId);
Sleep(5000);
JpgShot := TJPEGImage.Create;
try
WCVideo.GetJPG(JpgShot);
JpgShot.SaveToFile('c:\test.jpg');
finally
JpgShot.Free;
end;
finally
WCVideo.VideoStop;
end;
finally
TmpList.Free;
end;
finally
WCVideo.Free;
end;
finally
CoUninitialize;
end;
end;
Please, why this code works in the first time it runs but in next times always get blank white images? Thanks!
DirectX uses ActiveX/COM interfaces. As such, your thread's Execute() method needs to initialize the COM library for itself via CoInitialize/Ex() before accessing any COM objects.
But more importantly, you are creating and using the TVideoImage object across thread boundaries. Most COM objects are not designed to be used across thread boundaries, they would have to be marshaled in order to do that. So don't use TVideoImage that way. Create, use, and destroy it all within the same thread (ie, inside your Execute() method).
Try this instead:
type
TGetWebcam = class(TThread)
private
FWCVideo: TVideoImage;
FJpgShot: TJPEGImage;
procedure OnNewVideoFrame(Sender: TObject; Width, Height: Integer; DataPtr: Pointer);
protected
procedure Execute; override;
public
constructor Create; reintroduce;
destructor Destroy; override;
end;
...
uses
Winapi.ActiveX;
constructor TGetWebcam.Create;
begin
inherited Create(False);
FreeOnTerminate := True;
FJpgShot := TJPEGImage.Create;
end;
destructor TGetWebcam.Destroy;
begin
FJpgShot.Free;
inherited;
end;
procedure TGetWebcam.Execute;
var
TmpLst: TStringList;
JpgImg: TJpegImage;
begin
CoInitialize(nil);
try
FWCVideo := TVideoImage.Create;
try
FWCVideo.OnNewVideoFrame := OnNewVideoFrame;
TmpLst := TStringList.Create;
try
FWCVideo.GetListOfDevices(TmpLst);
if TmpLst.Count <= 0 then Exit;
if FWCVideo.VideoStart(TmpLst[0]) <> 0 then Exit;
try
TmpLst.Clear;
FWCVideo.GetListOfSupportedVideoSizes(TmpLst);
if TmpLst.Count <= 0 then Exit;
FWCVideo.SetResolutionByIndex(TmpLst.Count - 1);
JpgImg := TJPEGImage.Create;
try
JpgImg.Assign(FJpgShot);
JpgImg.CompressionQuality := 50;
JpgImg.SaveToFile('c:\test.jpg');
finally
JpgImg.Free;
end;
finally
FWCVideo.VideoStop;
end;
finally
TmpLst.Free;
end;
finally
FWCVideo.Free;
end;
finally
CoUninitialize;
end;
end;
procedure TGetWebcam.OnNewVideoFrame(Sender: TObject; Width, Height: Integer; DataPtr: Pointer);
begin
FWCVideo.GetJPG(FJpgShot);
end;
That being said, I would suggest a slightly tweaked approach - assuming the OnNewVideoFrame event is fired asynchronously, the thread should actually wait for the event to fire and not just assume it does, and also it should stop the video capture before using the captured JPG, eg:
uses
..., System.SyncObjs;
type
TGetWebcam = class(TThread)
private
FJpgShot: TJPEGImage;
FJpgShotReady: TEvent;
procedure OnNewVideoFrame(Sender: TObject; Width, Height: Integer; DataPtr: Pointer);
function GetJpgShot: Boolean;
protected
procedure Execute; override;
public
constructor Create; reintroduce;
destructor Destroy; override;
end;
...
uses
Winapi.ActiveX;
constructor TGetWebcam.Create;
begin
inherited Create(False);
FreeOnTerminate := True;
FJpgShot := TJPEGImage.Create;
FJpgShotReady := TEvent.Create;
end;
destructor TGetWebcam.Destroy;
begin
FJpgShot.Free;
FJpgShotReady.Free;
inherited;
end;
procedure TGetWebcam.Execute;
var
JpgImg: TJpegImage;
begin
CoInitialize(nil);
try
if not GetJpgShot() then Exit;
JpgImg := TJPEGImage.Create;
try
JpgImg.Assign(FJpgShot);
JpgImg.CompressionQuality := 50;
JpgImg.SaveToFile('c:\test.jpg');
finally
JpgImg.Free;
end;
finally
CoUninitialize;
end;
end;
function TGetWebcam.GetJpgShot: Boolean;
var
TmpLst: TStringList;
WCVideo: TVideoImage;
begin
Result := False;
WCVideo := TVideoImage.Create;
try
WCVideo.OnNewVideoFrame := OnNewVideoFrame;
TmpLst := TStringList.Create;
try
WCVideo.GetListOfDevices(TmpLst);
if TmpLst.Count < 1 then Exit;
if WCVideo.VideoStart(TmpLst[0]) <> 0 then Exit;
try
TmpLst.Clear;
WCVideo.GetListOfSupportedVideoSizes(TmpLst);
if TmpLst.Count < 1 then Exit;
WCVideo.SetResolutionByIndex(TmpLst.Count - 1);
Result := FJpgShotReady.WaitFor(5000) = wrSignaled;
finally
WCVideo.VideoStop;
end;
finally
TmpLst.Free;
end;
finally
WCVideo.Free;
end;
end;
procedure TGetWebcam.OnNewVideoFrame(Sender: TObject; Width, Height: Integer; DataPtr: Pointer);
begin
TVideoImage(Sender).GetJPG(FJpgShot);
FJpgShotReady.SetEvent;
end;
UPDATE: you might need to add a message loop to your thread in order for the OnNewVideoFrame event to fire correctly, eg:
uses
..., Winapi.Windows;
type
TGetWebcam = class(TThread)
private
FJpgShot: TJPEGImage;
FJpgShotReady: Boolean;
procedure OnNewVideoFrame(Sender: TObject; Width, Height: Integer; DataPtr: Pointer);
function GetJpgShot: Boolean;
protected
procedure Execute; override;
public
constructor Create; reintroduce;
destructor Destroy; override;
end;
...
uses
Winapi.ActiveX;
constructor TGetWebcam.Create;
begin
inherited Create(False);
FreeOnTerminate := True;
FJpgShot := TJPEGImage.Create;
end;
destructor TGetWebcam.Destroy;
begin
FJpgShot.Free;
inherited;
end;
procedure TGetWebcam.Execute;
var
JpgImg: TJpegImage;
begin
CoInitialize(nil);
try
if not GetJpgShot() then Exit;
JpgImg := TJPEGImage.Create;
try
JpgImg.Assign(FJpgShot);
JpgImg.CompressionQuality := 50;
JpgImg.SaveToFile('c:\test.jpg');
finally
JpgImg.Free;
end;
finally
CoUninitialize;
end;
end;
function TGetWebcam.GetJpgShot: Boolean;
var
TmpLst: TStringList;
WCVideo: TVideoImage;
Msg: TMSG;
begin
Result := False;
WCVideo := TVideoImage.Create;
try
WCVideo.OnNewVideoFrame := OnNewVideoFrame;
TmpLst := TStringList.Create;
try
WCVideo.GetListOfDevices(TmpLst);
if TmpLst.Count < 1 then Exit;
if WCVideo.VideoStart(TmpLst[0]) <> 0 then Exit;
try
TmpLst.Clear;
WCVideo.GetListOfSupportedVideoSizes(TmpLst);
if TmpLst.Count < 1 then Exit;
WCVideo.SetResolutionByIndex(TmpLst.Count - 1);
FJpgShotReady := False;
while (not FJpgShotReady) and GetMessage(Msg, 0, 0, 0) do
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
Result := FJpgShotReady;
finally
WCVideo.VideoStop;
end;
finally
TmpLst.Free;
end;
finally
WCVideo.Free;
end;
end;
procedure TGetWebcam.OnNewVideoFrame(Sender: TObject; Width, Height: Integer; DataPtr: Pointer);
begin
TVideoImage(Sender).GetJPG(FJpgShot);
FJpgShotReady := True;
end;
After some time my client thread stops receiving/sending commands from/to TIdTcpServer.
Here is the client side thread I copied from an example from Remy:
Tested locally and it doesn't happen, only on a running environment the error occurs...
type
TDataEvent = procedure(const LBuffer: TIdBytes) of object;
TReadingThread = class(TThread)
private
FClient : TIdTCPClient;
FData : TIdBytes;
FOnData : TDataEvent;
procedure DataReceived;
protected
procedure Execute; override;
public
constructor Create(AClient: TIdTCPClient); reintroduce;
property OnData: TDataEvent read FOnData write FOnData;
end;
constructor TReadingThread.Create(AClient: TIdTCPClient);
begin
inherited Create(True);
FClient := AClient;
end;
procedure TReadingThread.Execute;
begin
while not Terminated do
begin
Form1.Cliente.IOHandler.ReadBytes(FData, szProtocol, False);
if (FData <> nil) and Assigned(FOnData) then Synchronize(DataReceived);
end;
end;
procedure TReadingThread.DataReceived;
begin
if Assigned(FOnData) then FOnData(FData);
end;
procedure TForm1.DataReceived(const LBuffer: TIdBytes);
type
PTBytes = ^TBytes;
PTIdBytes = ^TIdBytes;
var
LDataSize : Integer;
LProtocol : TProtocol;
LBuffer2 : TBytes;
LProtocol2 : TProtocol;
begin
LProtocol := BytesToProtocol(PTBytes(#LBuffer)^);
case LProtocol.Command of
cmdHWID:
begin
HWID := LProtocol.Sender.HWID;
end;
cmdPing:
begin
InitProtocol(LProtocol2);
LProtocol2.Command := cmdPing;
LProtocol2.Sender.PBack := GetTickCount;
LBuffer2 := ProtocolToBytes(LProtocol2);
Form1.Cliente.IOHandler.Write(PTIdBytes(#LBuffer2)^);
ClearBuffer(LBuffer2);
end;
end;
end;
For a while all works perfectly, but after some time, the client side stops receiving/sending. The connection to the server is seems to be still open.
function to find connection by ip:
list := IdTCPServer1.Contexts.LockList;
try
for i := 0 to list.Count - 1 do
begin
ctx := TIdContext(list[i]);
if ctx.Binding.PeerIP = Edit9.Text then
begin
TLog.AddMsg('IP FOUND');
Achou := True;
Cliente := TClientContext(ctx);
SerialCn := Cliente.Client.HWID;
IpCn := Cliente.Client.IP;
break;
end;
end;
finally
IdTCPServer1.Contexts.UnlockList;
end;
Before I explain my problem, I'm sorry for my bad english.
Okay, here my problem. when my Indy server sends bitmap frame to client, always appeared warning like this :
"EAccessViolation at address 004DD42A..."
And error syntax blue highlighted on this :
Athread.Connection.WriteInteger(MemoryStream.Size);
here my source code :
SERVER
procedure TFormHome.TCPServerConnect(AThread: TIdPeerThread);
var
NewClient: PClient;
begin
GetMem(NewClient, SizeOf(TClient));
NewClient.PeerIP := AThread.Connection.Socket.Binding.PeerIP;
NewClient.HostName := GStack.WSGetHostByAddr(NewClient.PeerIP);
NewClient.Connected := Now;
NewClient.LastAction := NewClient.Connected;
NewClient.Thread := AThread;
AThread.Data := TObject(NewClient);
try
Clients.LockList.Add(NewClient);
finally
Clients.UnlockList;
end;
procedure TFormHome.TCPServerExecute(AThread: TIdPeerThread);
var
pesan:string;
begin
pesan:=Athread.Connection.ReadLn;
if pesan = 'video' then
begin
Athread.Connection.WriteLn('send');
Timer1.Enabled:=true;
FormStream.Show;
Athread.Connection.WriteInteger(MemoryStream.Size);
Athread.Connection.OpenWriteBuffer;
Athread.Connection.WriteStream(MemoryStream);
AThread.Connection.CloseWriteBuffer;
FreeAndNil(MemoryStream);
FormStream.Image1.Picture.Bitmap.Free;
end;
procedure TFormHome.Timer1Timer(Sender: TObject);
begin
pic := TBitmap.Create;
MemoryStream:=TMemoryStream.Create;
VideoGrabber.GetBitmap(FormStream.image1.Picture.Bitmap);
pic := FormStream.Image1.Picture.Bitmap;
pic.SaveToStream(MemoryStream);
//Pic.Free;
//FreeAndNil(Pic);
end;
CLIENT
procedure TFormClient.TCPClientConnected(Sender: TObject);
var
pesan : string;
begin
IncomingMessages.Lines.Insert(0,'Connected to Server');
TCPClient.WriteLn('video');
pesan := TCPClient.ReadLn;
if pesan = 'send' then Timer1.Enabled:=true;
end;
procedure TFormClient.Timer1Timer(Sender: TObject);
var
Size : integer;
ReadStream : TMemoryStream;
begin
ReadStream := TMemoryStream.Create;
Size := TCPClient.ReadInteger;
TCPClient.ReadStream(ReadStream,Size,True);
Image1.Picture.Bitmap.LoadFromStream(ReadStream);
Image1.Picture.Bitmap.Free;
FreeAndNil(ReadStream);
end;
what's wrong witha my code? i need your help.
Thank you before.. ^^
You are trying to send the TMemoryStream before it has even been created. You can't use TTimer or TForm in a worker thread (which OnExecute is called in). Even if you could, when TTimer is enabled, its OnTimer event is not triggered immediately, but your code is expecting it to be.
You need to re-write your code to delegate all UI work to the main thread, where it belongs. Try something more like this:
Server:
Uses
..., IdSync;
type
TVideoStartNotify = class(TIdNotify)
protected
procedure DoNotify; override;
public
Thread: TIdPeerThread;
end;
procedure TFormHome.TCPServerDisconnect(AThread: TIdPeerThread);
begin
TIdNotify.NotifyMethod(VideoStop);
end;
procedure TFormHome.TCPServerExecute(AThread: TIdPeerThread);
var
pesan: string;
begin
pesan := AThread.Connection.ReadLn;
if pesan = 'videostart' then
begin
AThread.Connection.WriteLn('send');
with TVideoStartNotify.Create do
begin
Thread := AThread;
Notify;
end;
end
else if pesan = 'videostop' then
begin
AThread.Connection.WriteLn('stop');
TIdNotify.NotifyMethod(VideoStop);
end;
end;
procedure TVideoStartNotify.DoNotify;
begin
FormHome.VideoStart(Thread);
end;
procedure TFormHome.VideoStart(AThread: TIdPeerThread);
begin
ThreadToSendTo := AThread;
Timer1.Enabled := true;
FormStream.Show;
end;
procedure TFormHome.VideoStop;
begin
ThreadToSendTo := nil;
Timer1.Enabled := false;
FormStream.Hide;
end;
procedure TFormHome.Timer1Timer(Sender: TObject);
var
pic: TBitmap;
MemoryStream: TMemoryStream;
begin
if ThreadToSendTo = nil then
begin
Timer1.Enabled := False;
Exit;
end;
pic := FormStream.Image1.Picture.Bitmap;
try
MemoryStream := TMemoryStream.Create;
try
VideoGrabber.GetBitmap(pic);
pic.SaveToStream(MemoryStream);
try
ThreadToSendTo.Connection.WriteStream(MemoryStream, True, True);
except
ThreadToSendTo := nil;
Timer1.Enabled := False;
end;
finally
MemoryStream.Free;
end;
finally
FormStream.Image1.Picture := nil;
end;
end;
Client:
Uses
..., IdSync;
type
TLogNotify = class(TIdNotify)
protected
procedure DoNotify; override;
public
Msg: String;
end;
procedure TLogNotify.DoNotify;
begin
FormClient.LogMsg(Msg);
end;
procedure TFormClient.Button1Click(Sender: TObject);
begin
TCPClient.Connect;
end;
procedure TFormClient.Button2Click(Sender: TObject);
begin
try
TCPClient.WriteLn('videostop');
finally
TCPClient.Disconnect;
end;
end;
procedure TFormClient.TCPClientConnected(Sender: TObject);
var
pesan : string;
begin
with TLogNotify.Create do
begin
Msg := 'Connected to Server';
Notify;
end;
TCPClient.WriteLn('videostart');
pesan := TCPClient.ReadLn;
if pesan = 'send' then
TIdNotify.NotifyMethod(VideoStart);
end;
procedure TFormClient.TCPClientDisconnected(Sender: TObject);
begin
with TLogNotify.Create do
begin
Msg := 'Disconnected from Server';
Notify;
end;
TIdNotify.NotifyMethod(VideoStop);
end;
procedure TFormClient.LogMsg(const AMsg: string);
begin
IncomingMessages.Lines.Insert(0, AMsg);
end;
procedure TFormClient.VideoStart;
begin
Timer1.Enabled := true;
end;
procedure TFormClient.VideoStop;
begin
Timer1.Enabled := false;
Image1.Picture := nil;
end;
procedure TFormClient.Timer1Timer(Sender: TObject);
var
ReadStream : TMemoryStream;
begin
ReadStream := TMemoryStream.Create;
try
TCPClient.ReadStream(ReadStream, -1, False);
ReadStream.Position := 0;
Image1.Picture.Bitmap.LoadFromStream(ReadStream);
finally
ReadStream.Free;
end;
end;
in this article delphi.net(prism) support async file io.
Delphi(Native/VCL) has Async File IO Class too?
Have you seen this code? http://pastebin.com/A2EERtyW
It is a good start for ansynchronous file I/O, but personally I would write a wrapper around the standard TStream class to maintain compatibility with VCL/RTL.
EDIT 2: This one looks good, too. http://www.torry.net/vcl/filedrv/other/dstreams.zip
I am posting it here just in case it disappears from Pastebin:
unit xfile;
{$I cubix.inc}
interface
uses
Windows,
Messages,
WinSock,
SysUtils,
Classes;
const
MAX_BUFFER = 1024 * 32;
type
TFileReadEvent = procedure(Sender: TObject; const Buffer; Count: Integer) of object;
TAsyncFile = class
private
FHandle: THandle;
FPosition: Cardinal;
FReadPending: Boolean;
FOverlapped: TOverlapped;
FBuffer: Pointer;
FBufferSize: Integer;
FOnRead: TFileReadEvent;
FEof: Boolean;
FSize: Integer;
function ProcessIo: Boolean;
procedure DoOnRead(Count: Integer);
function GetOpen: Boolean;
public
constructor Create(Filename: string; BufferSize: Integer = MAX_BUFFER);
destructor Destroy; override;
procedure BeginRead;
procedure Seek(Position: Integer);
procedure Close;
property OnRead: TFileReadEvent read FOnRead write FOnRead;
property Eof: Boolean read FEof;
property IsOpen: Boolean read GetOpen;
property Size: Integer read FSize;
end;
function ProcessFiles: Boolean;
implementation
var
Files: TList;
function ProcessFiles: Boolean;
var
i: Integer;
AsyncFile: TAsyncFile;
begin
Result := False;
for i := Files.Count - 1 downto 0 do
begin
AsyncFile := TAsyncFile(Files[i]);
Result := AsyncFile.ProcessIo or Result;
end;
end;
procedure Cleanup;
var
i: Integer;
AsyncFile: TAsyncFile;
begin
for i := Files.Count - 1 downto 0 do
begin
AsyncFile := TAsyncFile(Files[i]);
AsyncFile.Free;
end;
Files.Free;
end;
{ TAsyncFile }
constructor TAsyncFile.Create(Filename: string; BufferSize: Integer);
begin
Files.Add(Self);
FReadPending := False;
FBufferSize := BufferSize;
GetMem(FBuffer, FBufferSize);
FillMemory(#FOverlapped, SizeOf(FOverlapped), 0);
Cardinal(FHandle) := CreateFile(
PChar(Filename), // file to open
GENERIC_READ, // open for reading
0, // do not share
nil, // default security
OPEN_EXISTING, // open existing
FILE_ATTRIBUTE_NORMAL, //or // normal file
//FILE_FLAG_OVERLAPPED, // asynchronous I/O
0); // no attr. template
FSize := FileSeek(FHandle, 0, soFromEnd);
FileSeek(FHandle, 0, soFromBeginning);
FPosition := 0;
end;
destructor TAsyncFile.Destroy;
begin
Files.Remove(Self);
CloseHandle(FHandle);
FreeMem(FBuffer);
inherited;
end;
function TAsyncFile.ProcessIo: Boolean;
var
ReadCount: Cardinal;
begin
Result := False; Exit;
if not FReadPending then
begin
Exit;
end;
if GetOverlappedResult(FHandle, FOverlapped, ReadCount, False) then
begin
FReadPending := False;
DoOnRead(ReadCount);
end
else
begin
case GetLastError() of
ERROR_HANDLE_EOF:
begin
FReadPending := False;
FEof := True;
end;
ERROR_IO_PENDING:
begin
FReadPending := True;
end;
0:
begin
Result := True;
end;
end;
end;
end;
procedure TAsyncFile.BeginRead;
var
ReadResult: Boolean;
ReadCount: Cardinal;
begin
ReadCount := 0;
Seek(FPosition);
ReadResult := ReadFile(FHandle, FBuffer^, FBufferSize, ReadCount, nil);//#FOverlapped);
if ReadResult then
begin
FEof := False;
FReadPending := False;
FPosition := FPosition + ReadCount;
DoOnRead(ReadCount);
end
else
begin
case GetLastError() of
ERROR_HANDLE_EOF:
begin
FReadPending := False;
FEof := True;
end;
ERROR_IO_PENDING:
begin
FReadPending := True;
end;
end;
end;
end;
procedure TAsyncFile.DoOnRead(Count: Integer);
begin
if Assigned(FOnRead) then
begin
FOnRead(Self, FBuffer^, Count);
end;
end;
function TAsyncFile.GetOpen: Boolean;
begin
Result := Integer(FHandle) >= 0;
end;
procedure TAsyncFile.Close;
begin
FileClose(FHandle);
end;
procedure TAsyncFile.Seek(Position: Integer);
begin
FPosition := Position;
FileSeek(FHandle, Position, soFromBeginning);
end;
initialization
Files := Tlist.Create;
finalization
Cleanup;
end.
There is nothing built in to the RTL/VCL that offers asynchronous I/O for files. Incidentally the support in Delphi Prism is down to the .net framework rather than being language based.
You can either code directly against the Win32 API (that's not much fun) or hunt around for a Delphi wrapper to that API. Off the top of my head, I don't know any Delphi wrappers of asynchronous file I/O but they must exist.
I use this code with TCheckListbox (lbServices) and it works fine. But with TcxCheckListBox from Devexpress it raise exception.
procedure TMaintenanceForm.AfterConstruction;
var
i: Integer;
ActionObj: TAction;
begin
inherited;
for i := 0 to ServiceActionList.ActionCount-1 do
begin
ActionObj := ServiceActionList.Actions[i] as TAction;
lbServices.Items.AddObject(ActionObj.Caption, ActionObj);
end;
end;
procedure TMaintenanceForm.btnStopClick(Sender: TObject);
begin
fContinue := False;
end;
procedure TMaintenanceForm.cmdExecuteSelectedClick(Sender: TObject);
var
i: Integer;
begin
Screen.Cursor := crHourGlass;
try
for i := 0 to lbServices.Count -1 do
if lbServices.Selected[i] then
(lbServices.Items.Objects[i] as TAction).Execute; // Exception here!!!!
finally
Screen.Cursor := crDefault;
end;
end;
If I debug the code lbServices.Count = 12.
lbServices.Items.Objects[i] is nil for all items in the list. What is wrong here ?
Use the following code instead:
var
AItem: TcxCheckListBoxItem;
begin
AItem := cxCheckListBox1.Items.Add;
AItem.ItemObject := Action1;
AItem.Text := Action1.Caption;
end;
...
var
I: Integer;
begin
for I := 0 to cxCheckListBox1.Items.Count - 1 do
if cxCheckListBox1.Items[I].Checked then
(cxCheckListBox1.Items[I].ItemObject as TACtion).Execute;
end;
There is no Objects property of TcxCheckListBox.Items