I'm working in a project where want receive continuous frames of a live webcam and i found this code example that in my tests worked fine. Now want know how can make this receiving inside a TThread (Socket NonBlocking) similar to approach of Server multiclient/multithread? I tried this, but the server not received none frame from client. I hope that you can help me.
Server:
uses
System.Win.ScktComp, Winapi.WinSock, Vcl.Imaging.jpeg, System.Math;
type
TMyThread = class(TThread)
private
Socket: TCustomWinSocket;
protected
procedure Execute; override;
public
constructor Create(aSocket: TCustomWinSocket);
end;
type
TForm1 = class(TForm)
Button1: TButton;
Image1: TImage;
ServerSocket1: TServerSocket;
procedure ServerSocket1Accept(Sender: TObject; Socket: TCustomWinSocket);
procedure ServerSocket1ClientConnect(Sender: TObject; Socket: TCustomWinSocket);
procedure ServerSocket1ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket);
procedure ServerSocket1ClientError(Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer);
procedure Button1Click(Sender: TObject);
procedure ServerSocket1Listen(Sender: TObject; Socket: TCustomWinSocket);
private
{ Private declarations }
MyThread: TMyThread;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
constructor TMyThread.Create(aSocket: TCustomWinSocket);
begin
inherited Create(True);
Socket := aSocket;
FreeOnTerminate := True;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
ServerSocket1.Port := 1234;
ServerSocket1.Active := true;
end;
procedure TForm1.ServerSocket1Accept(Sender: TObject; Socket: TCustomWinSocket);
begin
MyThread := TMyThread.Create(Socket);
MyThread.Start;
end;
procedure TForm1.ServerSocket1ClientConnect(Sender: TObject; Socket: TCustomWinSocket);
begin
Socket.Data := nil;
end;
procedure TForm1.ServerSocket1ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket);
begin
if Socket.Data <> nil then
TMemoryStream(Socket.Data).Free;
end;
procedure TForm1.ServerSocket1ClientError(Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer);
begin
ErrorCode := 0;
end;
procedure TForm1.ServerSocket1Listen(Sender: TObject; Socket: TCustomWinSocket);
begin
ShowMessage('Server listen on port: ' + IntToStr(Socket.LocalPort));
end;
procedure TMyThread.Execute;
var
Stream: TMemoryStream;
BytesReceived: Integer;
StreamSize, TempSize: Int32;
BytesRemaining: Int64;
P: PByte;
ChunkSize: Integer;
jpg: TJpegImage;
const
MaxChunkSize: Int64 = 8192;
begin
while Socket.Connected do
begin
Stream := TMemoryStream(Socket.Data);
if Stream = nil then
begin
if Socket.ReceiveLength < SizeOf(TempSize) then
Exit;
BytesReceived := Socket.ReceiveBuf(TempSize, SizeOf(TempSize));
if BytesReceived <= 0 then
Exit;
StreamSize := ntohl(TempSize);
Stream := TMemoryStream.Create;
Socket.Data := Stream;
Stream.Size := StreamSize;
BytesRemaining := StreamSize;
end
else
BytesRemaining := Stream.Size - Stream.Position;
if BytesRemaining > 0 then
begin
P := PByte(Stream.Memory);
if Stream.Position > 0 then
Inc(P, Stream.Position);
repeat
ChunkSize := Integer(Min(BytesRemaining, MaxChunkSize));
BytesReceived := Socket.ReceiveBuf(P^, ChunkSize);
if BytesReceived <= 0 then
Exit;
Inc(P, BytesReceived);
Dec(BytesRemaining, BytesReceived);
Stream.Seek(BytesReceived, soCurrent);
until BytesRemaining = 0;
end;
try
jpg := TJpegImage.Create;
try
Stream.Position := 0;
jpg.LoadFromStream(Stream);
Synchronize(
procedure
begin
Form1.Image1.Picture.Assign(jpg);
end);
finally
jpg.Free;
end;
finally
Socket.Data := nil;
Stream.Free;
end;
end;
end;
end.
You need to use the TServerSocket in thread-blocking mode in order to effectively use worker threads with its accepted clients. Non-blocking mode and worker threads don't mix well together.
Non-blocking mode was invented to be able to use TClientSocket and TServerSocket in the main UI thread without blocking it. But when using sockets outside of the main UI thread, there is very little use for non-blocking mode (just some corner cases that don't apply to your situation). Internally, TCustomWinSocket allocates an HWND to detect socket activity when used in non-blocking, and that HWND requires a message loop. But since each accepted client socket is created outside of your worker threads, their HWNDs will not be able to be serviced by any message loop you run in your threads. So all the more reason why you need to use thread-blocking mode anyway.
Also, using thread-blocking mode will greatly simplify your socket I/O code anyway.
Try something more like this:
unit Unit1;
interface
uses
..., System.Win.ScktComp;
type
TForm1 = class(TForm)
Button1: TButton;
Image1: TImage;
ServerSocket1: TServerSocket;
procedure Button1Click(Sender: TObject);
procedure ServerSocket1ClientError(Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer);
procedure ServerSocket1GetThread(Sender: TObject; ClientSocket: TServerClientWinSocket; var SocketThread: TServerClientThread);
procedure ServerSocket1Listen(Sender: TObject; Socket: TCustomWinSocket);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses
Winapi.WinSock, Vcl.Imaging.jpeg, System.Math;
{$R *.dfm}
type
TMyThread = class(TServerClientThread)
protected
procedure ClientExecute; override;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
// this can be set at design-time, if desired...
ServerSocket1.ServerType := TServerType.stThreadBlocking;
// so can this...
ServerSocket1.Port := 1234;
ServerSocket1.Active := True;
end;
procedure TForm1.ServerSocket1ClientError(Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer);
begin
ErrorCode := 0;
end;
procedure TForm1.ServerSocket1GetThread(Sender: TObject; ClientSocket: TServerClientWinSocket; var SocketThread: TServerClientThread);
begin
SocketThread := TMyThread.Create(False, ClientSocket);
end;
procedure TForm1.ServerSocket1Listen(Sender: TObject; Socket: TCustomWinSocket);
begin
ShowMessage('Server listen on port: ' + IntToStr(Socket.LocalPort));
end;
procedure TMyThread.ClientExecute;
var
Stream: TMemoryStream;
StreamSize: Int32;
jpg: TJpegImage;
function DoRead(Buffer: Pointer; BufSize: Int64): Boolean;
const
MaxChunkSize: Int64 = 8192;
var
P: PByte;
BytesReceived: Integer;
ChunkSize: Integer;
begin
Result := False;
P := PByte(Buffer);
while BufSize > 0 do
begin
ChunkSize := Integer(Min(BufSize, MaxChunkSize));
BytesReceived := ClientSocket.ReceiveBuf(P^, ChunkSize);
if BytesReceived <= 0 then
Exit;
Inc(P, BytesReceived);
Dec(BufSize, BytesReceived);
end;
Result := True;
end;
begin
while (not Terminated) and ClientSocket.Connected do
begin
if not DoRead(#StreamSize, SizeOf(StreamSize)) then Exit;
StreamSize := ntohl(StreamSize);
if StreamSize <= 0 then Continue;
jpg := TJpegImage.Create;
try
Stream := TMemoryStream.Create;
try
Stream.Size := StreamSize;
if not DoRead(Stream.Memory, StreamSize) then Exit;
Stream.Position := 0;
jpg.LoadFromStream(Stream);
finally
Stream.Free;
end;
Synchronize(
procedure
begin
Form1.Image1.Picture.Assign(jpg);
end
);
finally
jpg.Free;
end;
end;
end;
end.
That being said, I strongly suggest you stop using these outdated and deprecated socket components from Borland's legacy. For instance, Indy 10 ships pre-installed in the IDE, and has a TIdTCPServer component that will greatly simplify the above threading logic even further (TIdTCPServer is a multi-threaded component and will manage per-client threads for you), eg:
unit Unit1;
interface
uses
..., IdContext, IdTCPServer;
type
TForm1 = class(TForm)
Button1: TButton;
Image1: TImage;
IdTCPServer1: TIdTCPServer;
procedure Button1Click(Sender: TObject);
procedure IdTCPServer1Connect(AContext: TIdContext);
procedure IdTCPServer1Execute(AContext: TIdContext);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses
Vcl.Imaging.jpeg, System.Math;
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
IdTCPServer1.DefaultPort := 1234;
IdTCPServer1.Active := True;
ShowMessage('Server listen on port: ' + IntToStr(IdTCPServer1.DefaultPort));
end;
procedure TForm1.IdTCPServer1Connect(AContext: TIdContext);
begin
// tell ReadStream() to read the stream size as an Int32 and not as an Int64...
AContext.Connection.IOHandler.LargeStream := False;
end;
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
Stream: TMemoryStream;
jpg: TJpegImage;
begin
// OnExecute is a looped event, it is called in a continuous
// loop for the lifetime of the TCP connection...
jpg := TJpegImage.Create;
try
Stream := TMemoryStream.Create;
try
// ReadStream() can read the stream size first, then read the stream data...
AContext.Connection.IOHandler.ReadStream(Stream, -1, False);
Stream.Position := 0;
jpg.LoadFromStream(Stream);
finally
Stream.Free;
end;
TThread.Synchronize(nil,
procedure
begin
Form1.Image1.Picture.Assign(jpg);
end
);
finally
jpg.Free;
end;
end;
end.
Related
Happy new year to all StackOverFlow member and readers !
I come to you today for a question regarding threads in Delphi (I browsed most of what was already posted on the subject but could not find a clue).
I have a very simple test application with one Form (frmIMGDown) and a thread unit.
On the form are found
a Tbutton
a TImage
a TprogressBar
When clicked, the button starts a thread that downloads an image from the web, updates the progressbar during the process and displays a downloaded image in the Timage.
This works fine as long as the calling Form (frmIMGDown) is the main application form, OR if it is called from another form but all forms are created on application start.
Now, if I dynamically create frmIMGDown from a button click on the Main Form with :
procedure TForm1.Button2Click(Sender: TObject);
var
frmIMGDown : TfrmIMGDown;
begin
try
frmIMGDown := TfrmIMGDown.Create(nil);
frmIMGDown.ShowModal;
finally
frmIMGDown.Free;
end;
end;
I get an Access Violation at address... error
If I change
frmIMGDown := TfrmIMGDown.Create(nil);
to
frmIMGDown := TfrmIMGDown.Create(Form1);
the result is the same with the same error.
I suspect this has to do with the thread I implemented and maybe the variables used and that I try to send back to frmIMGDown, but I can't find the solution.
Here is the thread unit :
unit unit_MyThread;
interface
uses
Classes, IdHTTP, VCL.Forms, SyStem.UITypes, SysUtils, VCL.Dialogs, Graphics, IdTCPClient, IdTCPConnection, IdComponent,IdBaseComponent;
type
TIdHTTPThread = class(TThread)
private
FURL : String;
idHTTP: TIdHTTP;
B : TBitMap;
W : TWICImage;
//MS : TMemoryStream;
public
Constructor Create(CreateSuspended: Boolean);
Destructor Destroy; override;
Property URL : String read FURL WRITE FURL;
procedure OnWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
procedure OnWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Int64);
procedure OnWorkEnd(ASender: TObject; AWorkMode: TWorkMode);
protected
procedure Execute; override;
end;
implementation
uses
unit_IMG_Down;
Constructor TiDHTTPThread.Create(CreateSuspended: Boolean);
begin
inherited Create(Suspended);
IdHTTP := TIdHTTP.Create;
Screen.Cursor := crHourGlass;
IdHTTP.onWork := OnWork;
IdHTTP.OnWorkbegin := OnWorkBegin;
IdHTTP.OnWorkEnd := OnWorkEnd;
B := TBitmap.Create;
W := TWICImage.Create;
end;
Destructor TIdHTTPThread.Destroy;
begin
idHTTP.Free;
B.Free;
W.Free;
Screen.Cursor := crDefault;
inherited Destroy;
end;
procedure TIdHTTPThread.Execute;
var
MS : TMemoryStream;
begin
Screen.Cursor := crHourGlass;
try
MS := TMemoryStream.Create;
try
IdHTTP.Request.UserAgent := 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0';
IdHTTP.Get(URL,MS);
MS.Position := 0;
W.LoadFromStream(MS);
B.Assign(W);
frmIMGDown.Image3.Picture.Assign(B);
except
On E: Exception do ShowMessage(E.Message);
end;
finally
MS.Free;
end;
end;
procedure TIdHTTPThread.OnWork(ASender: TObject; AWorkMode: TWorkMode;
AWorkCount: Int64);
var
Http: TIdHTTP;
ContentLength: Int64;
Percent: Integer;
begin
Http := TIdHTTP(ASender);
ContentLength := Http.Response.ContentLength;
if (Pos('chunked', LowerCase(Http.Response.TransferEncoding)) = 0) and
(ContentLength > 0) then
begin
Percent := 100*AWorkCount div ContentLength;
frmIMGDown.ProgressBar3.Position := AWorkCount +2;
frmIMGDown.ProgressBar3.Position := AWorkCount -1;
end;
end;
procedure TIdHTTPThread.OnWorkBegin(ASender: TObject; AWorkMode: TWorkMode;
AWorkCountMax: Int64);
begin
frmIMGDown.ProgressBar3.Visible := True;
frmIMGDown.ProgressBar3.Position := 0;
end;
procedure TIdHTTPThread.OnWorkEnd(ASender: TObject; AWorkMode: TWorkMode);
begin
frmIMGDown.ProgressBar3.Visible := false;
end;
end.
And the call to thread from the button
procedure TfrmIMGDown.Button3Click(Sender: TObject);
var
HTTPThread : TIdHTTPThread;
begin
HTTPThread := TIdHTTPThread.Create(False);
HTTPThread.URL := 'https://bw-1651cf0d2f737d7adeab84d339dbabd3-bcs.s3.amazonaws.com/products/product_119522/Full119522_283b3acc91f119ab4b2939b1beb67211.jpg';
HTTPThread.FreeOnTerminate := True;
end;
SIDE NOTE : I used TWICImage to download the image (LoadFromStream) because I don't known which format the image will be (here the URl is hard-coded for the test) and assign it to a TBitmap after that.
Thanks in advance and, again, a happy new year to all.
Math
Your thread is accessing the Form's global pointer variable. When you get the Access Violation error, it is because you are not assigning the new Form object to that global variable, you are assigning it to a local variable of the same name instead. So the global pointer is invalid when the thread tries to access it.
The solution is to have the Form object pass its Self pointer to the thread, and then store it in a member of the thread. Don't rely on the global pointer at all.
A better solution is to not let the thread know anything about the UI at all. I would suggest defining events in the thread class, and have the thread fire those events when needed (image downloaded, progress updates, errors, etc). Then the Form can assign handlers to those events to update the UI as needed.
Also, your thread is not synchronizing with the main thread when accessing the Form's UI controls. The VCL is not thread-safe, so you MUST synchronize access to the UI. Even TBitmap is not thread-safe (not sure about TWICImage), you must Lock its Canvas when working with it in a thread, and Unlock when done.
Also, you have a race condition, as you are allowing the thread to (potentially) start running before you have assigned its URL and FreeOnTerminated values. You need to create the thread in a suspended state and not start it running until after you finish initializing it. The best way to do that is to create the thread with CreateSuspended=False and handle all of the initializations in the thread's constructor itself. The thread will not start running until its constructor exits. Otherwise, create the thread with CreateSuspended=True and explicitly resume it when ready.
With all of that said, try something more like this:
unit unit_MyThread;
interface
uses
Classes, IdComponent, IdBaseComponent;
type
THTTPStage = (HTTPInit, HTTPDownloading, HTTPDone);
THTTPStatusEvent = procedure(Sender: TObject; Progress, Total: Int64; Stage: THTTPStage) of object;
THTTPImageEvent = procedure(Sender: TObject; Data: TStream) of object;
THTTPThread = class(TThread)
private
FURL : String;
FStream : TMemoryStream;
FProgress, FTotal : Int64;
FStage : THTTPStage;
FOnStatus : THTTPStatusEvent;
FOnImage : THTTPImageEvent;
procedure DoOnStatus;
procedure DoOnImage;
procedure HTTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
procedure HTTPWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Int64);
procedure HTTPWorkEnd(ASender: TObject; AWorkMode: TWorkMode);
protected
procedure Execute; override;
public
constructor Create(const AURL: string);
property OnStatus: THTTPStatusEvent read FOnStatus write FOnStatus;
property OnImage: THTTPImageEvent read FOnImage write FOnImage;
end;
implementation
uses
IdTCPClient, IdTCPConnection, IdHTTP;
constructor THTTPThread.Create(const AURL: string);
begin
inherited Create(True);
FreeOnTerminate := True;
FURL := AURL;
end;
procedure THTTPThread.Execute;
var
IdHTTP: TIdHTTP;
begin
IdHTTP := TIdHTTP.Create;
try
IdHTTP.OnWork := HTTPWork;
IdHTTP.OnWorkBegin := HTTPWorkBegin;
IdHTTP.OnWorkEnd := HTTPWorkEnd;
IdHTTP.Request.UserAgent := 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0';
FStream := TMemoryStream.Create;
try
IdHTTP.Get(FURL, FStream);
FStream.Position := 0;
if Assigned(FOnImage) then
Synchronize(DoOnImage);
finally
FStream.Free;
end;
finally
IdHTTP.Free;
end;
end;
procedure THTTPThread.DoOnStatus;
begin
if Assigned(FOnStatus) then
FOnStatus(Self, FProgress, FTotal, FStage);
end;
procedure THTTPThread.DoOnImage;
begin
if Assigned(FOnImage) then
FOnImage(Self, FStream);
end;
procedure THTTPThread.HTTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
begin
if AWorkMode = wmRead then
begin
FProgress := AWorkCount;
FStage := HTTPDownloading;
if Assigned(FOnStatus) then
Synchronize(DoOnStatus);
end;
end;
procedure THTTPThread.HTTPWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Int64);
begin
if AWorkMode = wmRead then
begin
FProgress := 0;
FTotal := AWorkCountMax;
FStage := HTTPInit;
if Assigned(FOnStatus) then
Synchronize(DoOnStatus);
end;
end;
procedure THTTPThread.HTTPWorkEnd(ASender: TObject; AWorkMode: TWorkMode);
begin
if AWorkMode = wmRead then
begin
FProgress := FTotal;
FStage := HTTPDone;
if Assigned(FOnStatus) then
Synchronize(DoOnStatus);
end;
end;
end.
procedure TfrmIMGDown.Button3Click(Sender: TObject);
var
HTTPThread : THTTPThread;
begin
HTTPThread := THTTPThread.Create('https://bw-1651cf0d2f737d7adeab84d339dbabd3-bcs.s3.amazonaws.com/products/product_119522/Full119522_283b3acc91f119ab4b2939b1beb67211.jpg');
HTTPThread.OnStatus := HTTPStatus;
HTTPThread.OnImage := HTTPImage;
HTTPThread.OnTerminate := HTTPTerminated;
HTTPThread.Resume;
end;
procedure TfrmIMGDown.HTTPStatus(Sender: TObject; Progress, Total: Int64; Stage: THTTPStage);
begin
case Stage of
HTTPInit: begin
ProgressBar3.Visible := True;
ProgressBar3.Position := 0;
ProgressBar3.Max := 100;
Screen.Cursor := crHourGlass;
end;
HTTPDownloading: begin
if Total <> 0 then
ProgressBar3.Position := 100*Progress div Total;
end;
HTTPDone: begin
ProgressBar3.Visible := false;
Screen.Cursor := crDefault;
end;
end;
procedure TfrmIMGDown.HTTPImage(Sender: TObject; Data: TStream);
var
J: TJPEGImage;
begin
J := TJPEGImage.Create;
try
J.LoadFromStream(Data);
Image3.Picture.Assign(J);
finally
J.Free;
end;
end;
procedure TfrmIMGDown.HTTPTerminated(Sender: TObject);
begin
if TThread(Sender).FatalException <> nil then
ShowMessage(Exception(TThread(Sender).FatalException).Message);
end;
i have this TThread that i use inside my dll to update some visual control its working fine but i face issue when i try to close my dll and reopen it again its raised this exception
checksynchronize called from thread which is not the main thread
what iam doing wrong ? i need to call checksynchronize within timer because i will update some vcl with Threading while app running .
Here is my Thread unit
unit Thread;
interface
uses Messages, Windows, SysUtils, dialogs, Classes, Menus, forms, ComOBJ,
ShlObj;
{ Thread client }
type
TThreadCallbackProc = procedure(Sender: TObject; Updatestring : string) of object;
TAPPTHREAD = class(TThread)
private
Fstatus : String;
FOnCallbackProc: TThreadCallbackProc;
procedure dosomework;
procedure DoCallbackProc;
//
protected
procedure Execute; override;
Public
constructor Create(CreateSuspended: Boolean; aThreadCallbackProc: TThreadCallbackProc);
destructor Destroy; override;
end;
var
APPTHREAD : TAPPTHREAD;
implementation
constructor TAPPTHREAD.Create(CreateSuspended: Boolean;
aThreadCallbackProc: TThreadCallbackProc);
begin
inherited Create(CreateSuspended);
FreeOnTerminate := True;
FOnCallbackProc := aThreadCallbackProc;
end;
destructor TAPPTHREAD.Destroy;
begin
//
end;
procedure TAPPTHREAD.DoCallbackProc;
begin
if Assigned(FOnCallbackProc) then
FOnCallbackProc(self, Fstatus);
end;
procedure TAPPTHREAD.Execute;
begin
while not Terminated do
begin
Fstatus := 'Synched';
if Fstatus <> '' then
dosomework;
end;
end;
procedure TAPPTHREAD.dosomework;
begin
if Assigned(FOnCallbackProc) then
begin
Synchronize(DoCallbackProc);
end;
end;
end.
Main Form
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TForm1 = class(TForm)
Label1: TLabel;
Timer1: TTimer;
Timer2: TTimer;
procedure FormShow(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Timer2Timer(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
public
{ Public declarations }
procedure callbackproc(Sender: TObject; Updatestring : String);
end;
var
Form1: TForm1;
implementation
uses Thread;
{$R *.dfm}
procedure TForm1.callbackproc(Sender: TObject; Updatestring: String);
begin
label1.Caption := updatestring;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := Cafree;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
try
if Assigned(APPTHREAD) then
AppThread.Terminate;
except end;
try
Timer2.Enabled := False;
except end;
end;
procedure TForm1.FormShow(Sender: TObject);
begin
Timer1.Enabled := True;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := False;
APPTHREAD := TAPPTHREAD.Create(false, CallbackProc);
Timer2.Enabled := True;
end;
procedure TForm1.Timer2Timer(Sender: TObject);
begin
Checksynchronize;
end;
end.
DFM
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 242
ClientWidth = 472
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnClose = FormClose
OnDestroy = FormDestroy
OnShow = FormShow
PixelsPerInch = 96
TextHeight = 13
object Label1: TLabel
Left = 0
Top = 0
Width = 472
Height = 13
Align = alTop
Caption = 'Label1'
ExplicitLeft = 232
ExplicitTop = 136
ExplicitWidth = 31
end
object Timer1: TTimer
Enabled = False
OnTimer = Timer1Timer
Left = 232
Top = 128
end
object Timer2: TTimer
Enabled = False
Interval = 1
OnTimer = Timer2Timer
Left = 320
Top = 168
end
end
dll code
library dllapp;
uses
System.SysUtils,
Themes,
Windows,
Forms,
dialogs,
Graphics,
Vcl.ExtCtrls,
Unit1 in 'Unit1.pas' {Unit1},
DThreadsend in 'Thread.pas';
var
mHandle: THandle;
DLLHandle: Longint = 0;
function createApp(Width: Integer; Height: Integer; hw: HWnd;
app: TApplication): boolean; stdcall;
begin
mHandle := CreateMutex(nil, True, 'APPNAMETLOAD');
if GetLastError = ERROR_ALREADY_EXISTS then
begin
Halt;
end;
try
form1 := Tform1.CreateParented(hw); // **
form1.Width := Width;
form1.Height := Height;
Result := True
except
on e: exception do
begin
Result := False;
end;
end;
end;
procedure closeApp; stdcall;
begin
ApplicationClosed := True;
try
if mHandle <> 0 then
CloseHandle(mHandle);
except
end;
if Assigned(form1) then
try
FreeAndNil(form1);
except
end;
try
OptimizeRamUsage;
except
end;
end;
procedure showapp; stdcall;
begin
try
form1.Visible := True;
except
end;
form1.Show;
end;
procedure DLLEntryProc(EntryCode: Integer);
begin
case EntryCode of
DLL_PROCESS_DETACH:
begin
StyleServices.Free;
end;
DLL_PROCESS_ATTACH:
begin
end;
DLL_THREAD_ATTACH:
begin
end;
DLL_THREAD_DETACH:
begin
end;
end;
end;
exports
closeApp,
createApp,
showapp;
begin
DllProc := #DLLEntryProc;
end.
Host Application and how i create Dll
loadapp Unit
unit loadapp;
interface
uses windows, forms, System.SysUtils , dialogs;
procedure loadmainapp;
type
TcreaFunc = function (Width: Integer; Height: Integer; hw:HWnd; app: TApplication): boolean; stdcall;
TshowFunc = procedure stdcall;
TCloseAppFunc = procedure stdcall;
var
dllHandle : THandle = 0;
creaFunc : TcreaFunc;
showFunc : TshowFunc;
CloseAppFunc: TCloseAppFunc;
implementation
uses Mainapp;
procedure loadmainapp;
var
S: widestring;
PW: PWideChar;
begin
S := 'dllapp.dll';
pw:=pwidechar(widestring(s));
dllHandle := LoadLibrary(pw);
if dllHandle <> 0 then
begin
#creaFunc := GetProcAddress(dllHandle, 'createApp');
#showFunc := GetProcAddress(dllHandle, 'showapp');
if Assigned (creaFunc) then
begin
creaFunc(mainfrm.panel1.Width, mainfrm.panel1.Height, mainfrm.panel1.Handle, Application);
DisFunc;
end
else
ShowMessage('ERROR');
end
else
begin
ShowMessage('ERROR');
end;
end;
end.
Active Form
unit activeform;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ActiveX, AxCtrls, Frmldr_TLB, StdVcl, Vcl.ExtCtrls, ShlObj, Vcl.StdCtrls, SHDocVw, MSHTML;
type
TActiveFrmldr = class(TActiveForm, IActiveFrmldr)
mpanl: TPanel;
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
end;
implementation
uses ComObj, ComServ, Mainapp, libacload;
{$R *.DFM}
{ TActiveFrmldr }
procedure TActiveFrmldr.FormDestroy(Sender: TObject);
begin
if dllHandle <> 0 then
begin
#CloseAppFunc := GetProcAddress(dllHandle, 'closeApp');
CloseAppFunc;
FreeLibrary(dllHandle); //release dll
end;
if Assigned(mainfrm) then
try
FreeAndNil(mainfrm);
except
end;
end;
procedure TActiveFrmldr.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := False;
mainfrm.Parent := mpanl;
mainfrm.Left := 0;
mainfrm.Top := 0;
mainfrm.Width := self.Width;
mainfrm.Height := self.Height;
mainfrm.Align := alClient;
mainfrm.Show;
end;
procedure TActiveFrmldr.FormCreate(Sender: TObject);
begin
Application.CreateForm(Tmainfrm, mainfrm);
Timer1.Enabled := True;
end;
initialization
TActiveFormFactory.Create(
ComServer,
TActiveFormControl,
TActiveFrmldr,
Class_ActiveFrmldr,
0,
'',
OLEMISC_SIMPLEFRAME or OLEMISC_ACTSLIKELABEL,
tmApartment);
finalization
end.
Main app Form that call load library function
unit Mainapp;
interface
uses
Windows, Messages, System.SysUtils, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, System.Classes, libacload,
Vcl.Controls, Vcl.StdCtrls;
type
Tmainfrm = class(TForm)
Panel1: TPanel;
Timer1: TTimer;
Timer2: TTimer;
procedure FormShow(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure Timer2Timer(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
mainfrm: Tmainfrm;
implementation
Uses loadapp;
{$R *.dfm}
procedure Tmainfrm.FormShow(Sender: TObject);
begin
Timer1.Enabled := True;
end;
procedure Tmainfrm.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := False;
loadmainapp;
end;
procedure Tmainfrm.Timer2Timer(Sender: TObject);
begin
checksynchronize; // i do this to check some thread in activex it self
end;
end.
The error means that CheckSynchronize() is being called in a thread whose ThreadID does not match the RTL's global System.MainThreadID variable.
A DLL does not have a main thread of its own. MainThreadID gets initialized to whatever thread is initializing the DLL. So, if your DLL is creating its GUI in a different thread than the one that is initializing your DLL, CheckSynchronize() (and TThread.Synchronize(), and TThread.Queue()) will not work unless you manually update the MainThreadID variable to the ThreadID that is running your GUI. Do that before creating your worker thread, eg:
if IsLibrary then
MainThreadID := GetCurrentThreadID;
Form1 := TForm1.Create(nil);
Or:
procedure TForm1.FormCreate(Sender: TObject);
begin
if IsLibrary then
MainThreadID := GetCurrentThreadID;
end;
Or:
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := False;
if IsLibrary then
MainThreadID := GetCurrentThreadID;
APPTHREAD := TAPPTHREAD.Create(false, CallbackProc);
Timer2.Enabled := True;
end;
My initial question was misleading, I'll try to improve it:
I was able to write a small delphi programme, which is able to do a threaded download by using indy's idHTTP. It consists of 2 files:
the form:
interface
type
TForm1 = class(TForm)
Button1: TButton;
Label1: TLabel;
Button2: TButton;
IdSSLIOHandlerSocketOpenSSL1: TIdSSLIOHandlerSocketOpenSSL;
IdAntiFreeze1: TIdAntiFreeze;
IdHTTP: TIdHTTP;
ProgressBar1: TProgressBar;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
procedure UpdateLabel(BytesDone: Integer);
procedure UpdateProgressBar(AWorkCount: Int64);
procedure InitProgressBar(AWorkCountMax: Int64);
procedure ResetProgressBar;
end;
var
Form1: TForm1;
uStopDownloading: Boolean;
implementation
{$R *.dfm}
uses Unit2;
procedure TForm1.Button1Click(Sender: TObject);
var
HTTPThread: TIdHTTPThread;
begin
HTTPThread := TIdHTTPThread.Create(True);
HTTPThread.Url := 'https://www.bot-factory.de/tmp/lorem7.txt';
HTTPThread.EncodedStr := '';
HTTPThread.Filename := 'C:\test.txt';
HTTPThread.FreeOnTerminate := True;
HTTPThread.Resume;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
idhttp.Disconnect;
end;
procedure TForm1.UpdateLabel(BytesDone: Integer);
begin
Label1.caption := format('%.0n',[extended(BytesDone+0.0)]) +' bytes loaded.';
end;
procedure TForm1.UpdateProgressBar(AWorkCount: Int64);
begin
ProgressBar1.Position := AWorkCount;
end;
procedure TForm1.InitProgressBar(AWorkCountMax: Int64);
begin
Screen.Cursor := crHourGlass;
ProgressBar1.Max := AWorkCountMax;
ProgressBar1.Position := 0;
end;
procedure TForm1.ResetProgressBar;
begin
Screen.Cursor := crDefault;
showmessage('Job is done');
end;
END.
And the Thread-Unit:
interface
uses
Classes, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP, sysutils;
type
TIdHTTPThread = class(TThread)
private
FURL: AnsiString;
FencodedStr: string;
FFilename: AnsiString;
FBytesDone,FProgress,FWorkCountMax: Int64;
IdHTTP: TIdHTTP;
procedure OnWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
procedure OnWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Int64);
procedure OnWorkEnd(ASender: TObject; AWorkMode: TWorkMode);
procedure Updatelabel;
procedure UpdateProgressBar;
procedure InitProgressBar;
procedure ResetProgressBar;
procedure Disconnect;
public
Constructor Create(CreateSuspended: Boolean);
Destructor Destroy; override;
property Url: AnsiString read FURL write FUrl;
property encodedstr: String read FencodedStr write FencodedStr;
property Filename: AnsiString read FFilename write FFilename;
protected
procedure Execute; override;
end;
implementation
uses
Unit1; // Formular Unit
constructor TIdHTTPThread.Create(CreateSuspended: Boolean);
begin
inherited Create(Suspended);
IdHTTP := TIdHTTP.Create;
IdHTTP.OnWork := OnWork;
IdHTTP.OnWorkBegin := OnWorkBegin;
IdHTTP.OnWorkEnd := OnWorkEnd;
//IdHTTP.Disconnect := Disconnect;
end;
destructor TIdHTTPThread.Destroy;
begin
IdHTTP.Free;
inherited;
end;
procedure TIdHTTPThread.Execute;
var
DestStream: TFileStream;
begin
DestStream := TFileStream.Create(Filename, fmCreate);
try
IdHTTP.Get(Url, DestStream);
finally
DestStream.Free;
end;
end;
procedure TIdHTTPThread.OnWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
begin
FBytesDone := AWorkCount;
FProgress := AWorkCount;
Synchronize(Updatelabel);
Synchronize(UpdateProgressBar);
end;
procedure TIdHTTPThread.OnWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Int64);
begin
FWorkCountMax := AWorkCountMax;
Synchronize(InitProgressBar);
end;
procedure TIdHTTPThread.Disconnect;
begin
idhttp.Disconnect;
end;
procedure TIdHTTPThread.OnWorkEnd(ASender: TObject; AWorkMode: TWorkMode);
begin
Synchronize(ResetProgressBar);
end;
procedure TIdHTTPThread.Updatelabel;
begin
Form1.UpdateLabel(FBytesDone);
end;
procedure TIdHTTPThread.UpdateProgressBar;
begin
Form1.UpdateProgressBar(FProgress);
end;
procedure TIdHTTPThread.InitProgressBar;
begin
Form1.initProgressBar(FWorkCountMax);
end;
procedure TIdHTTPThread.resetProgressBar;
begin
Form1.resetProgressBar;
end;
END.
I have in fact 2 questions:
How can I interrupt the download of a (large) file ? I know that
idhttp.disconnect should do the trick, but I do not know how to use it properly in my thread.
And furthermore: how can I use POST instead of GET?
I need to run this POST in a thread:
{++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//submit_post
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
Procedure submit_post(url_string,EncodedStr,filename:string);
var
aStream: TMemoryStream;
Params: TStringStream;
begin
astream := TMemoryStream.create;
Params := TStringStream.create('');
Form1.IdHTTP.Request.Clear;
Form1.IdHTTP.HandleRedirects := TRUE;
try
with Form1.IdHTTP do
begin
Params.WriteString(EncodedStr);
Request.ContentType := 'application/x-www-form-urlencoded';
Request.Charset := 'utf-8';
try
Response.KeepAlive := False;
Post(url_string, params, astream);
except
on E: Exception do
begin
exit;
end;
end;
end;
astream.WriteBuffer(#0' ', 1);
astream.Position := 0;
astream.SaveToFile(filename);
finally
astream.Free;
Params.Free;
end;
end;
I'm trying (in D7) to set up a thread with a message pump, which eventually I want to transplant into a DLL.
Here's the relevant/non-trivial parts of my code:
const
WM_Action1 = WM_User + 1;
scThreadClassName = 'MyThreadClass';
type
TThreadCreatorForm = class;
TWndThread = class(TThread)
private
FTitle: String;
FWnd: HWND;
FWndClass: WNDCLASS;
FCreator : TForm;
procedure HandleAction1;
protected
procedure Execute; override;
public
constructor Create(ACreator: TForm; const Title: String);
end;
TThreadCreatorForm = class(TForm)
btnCreate: TButton;
btnAction1: TButton;
Label1: TLabel;
btnQuit: TButton;
btnSend: TButton;
edSend: TEdit;
procedure FormShow(Sender: TObject);
procedure btnCreateClick(Sender: TObject);
procedure btnAction1Click(Sender: TObject);
procedure btnQuitClick(Sender: TObject);
procedure btnSendClick(Sender: TObject);
procedure WMAction1(var Msg : TMsg); message WM_Action1;
procedure FormCreate(Sender: TObject);
public
{ Public declarations }
WndThread : TWndThread;
ThreadID : Integer;
ThreadHWnd : HWnd;
end;
var
ThreadCreatorForm: TThreadCreatorForm;
implementation
{$R *.DFM}
procedure SendStringViaWMCopyData(HSource, HDest : THandle; const AString : String);
var
Cds : TCopyDataStruct;
Res : Integer;
begin
FillChar(Cds, SizeOf(Cds), 0);
GetMem(Cds.lpData, Length(Astring) + 1);
try
StrCopy(Cds.lpData, PChar(AString));
Res := SendMessage(HDest, WM_COPYDATA, HSource, Cardinal(#Cds));
ShowMessage(IntToStr(Res));
finally
FreeMem(Cds.lpData);
end;
end;
procedure TThreadCreatorForm.FormShow(Sender: TObject);
begin
ThreadID := GetWindowThreadProcessId(Self.Handle, Nil);
Assert(ThreadID = MainThreadID);
end;
procedure TWndThread.HandleAction1;
begin
//
end;
constructor TWndThread.Create(ACreator: TForm; const Title:String);
begin
inherited Create(True);
FTitle := Title;
FCreator := ACreator;
FillChar(FWndClass, SizeOf(FWndClass), 0);
FWndClass.lpfnWndProc := #DefWindowProc;
FWndClass.hInstance := HInstance;
FWndClass.lpszClassName := scThreadClassName;
end;
procedure TWndThread.Execute;
var
Msg: TMsg;
Done : Boolean;
S : String;
begin
if Windows.RegisterClass(FWndClass) = 0 then Exit;
FWnd := CreateWindow(FWndClass.lpszClassName, PChar(FTitle), WS_DLGFRAME, 0, 0, 0, 0, 0, 0, HInstance, nil);
if FWnd = 0 then Exit;
Done := False;
while GetMessage(Msg, 0, 0, 0) and not done do begin
case Msg.message of
WM_Action1 : begin
HandleAction1;
end;
WM_COPYDATA : begin
Assert(True);
end;
WM_Quit : Done := True;
else begin
TranslateMessage(msg);
DispatchMessage(msg)
end;
end; { case }
end;
if FWnd <> 0 then
DestroyWindow(FWnd);
Windows.UnregisterClass(FWndClass.lpszClassName, FWndClass.hInstance);
end;
Once I've created the thread, I find its window handle using FindWindow and that works fine.
If I PostMessage it my user-defined WM_Action1 message, it's received by the GetMessage(), and caught by the case statement in the thread's Execute, and that works fine.
If I send myself (i.e. my host form) a WM_CopyData message using the SendStringViaWMCopyData() routine that works fine.
However: If I send my thread the WM_CopyData message, the GetMessage and case statement in Execute never see it and the SendMessage in SendStringViaWMCopyData returns 0.
So, my question is, why does the WM_CopyData message not get received by the GetMessage in .Execute? I have an uncomfortable feeling I'm missing something ...
WM_COPYDATA is not a posted message, it is a sent message, so it does not go through the message queue and thus a message loop will never see it. You need to assign a window procedure to your window class and process WM_COPYDATA in that procedure instead. Don't use DefWindowProc() as your window procedure.
Also, when sending WM_COPYDATA, the lpData field is expressed in bytes not in characters, so you need to take that in to account. And you are not filling in the COPYDATASTRUCT correctly. You need to provide values for the dwData and cbData fields. And you don't need to allocate memory for the lpData field, you can point it to your String's existing memory instead.
Try this:
const
WM_Action1 = WM_User + 1;
scThreadClassName = 'MyThreadClass';
type
TThreadCreatorForm = class;
TWndThread = class(TThread)
private
FTitle: String;
FWnd: HWND;
FWndClass: WNDCLASS;
FCreator : TForm;
procedure WndProc(var Message: TMessage);
procedure HandleAction1;
procedure HandleCopyData(const Cds: TCopyDataStruct);
protected
procedure Execute; override;
procedure DoTerminate; override;
public
constructor Create(ACreator: TForm; const Title: String);
end;
TThreadCreatorForm = class(TForm)
btnCreate: TButton;
btnAction1: TButton;
Label1: TLabel;
btnQuit: TButton;
btnSend: TButton;
edSend: TEdit;
procedure FormShow(Sender: TObject);
procedure btnCreateClick(Sender: TObject);
procedure btnAction1Click(Sender: TObject);
procedure btnQuitClick(Sender: TObject);
procedure btnSendClick(Sender: TObject);
procedure WMAction1(var Msg : TMsg); message WM_Action1;
procedure FormCreate(Sender: TObject);
public
{ Public declarations }
WndThread : TWndThread;
ThreadID : Integer;
ThreadHWnd : HWnd;
end;
var
ThreadCreatorForm: TThreadCreatorForm;
implementation
{$R *.DFM}
var
MY_CDS_VALUE: UINT = 0;
procedure SendStringViaWMCopyData(HSource, HDest : HWND; const AString : String);
var
Cds : TCopyDataStruct;
Res : Integer;
begin
ZeroMemory(#Cds, SizeOf(Cds));
Cds.dwData := MY_CDS_VALUE;
Cds.cbData := Length(AString) * SizeOf(Char);
Cds.lpData := PChar(AString);
Res := SendMessage(HDest, WM_COPYDATA, HSource, LPARAM(#Cds));
ShowMessage(IntToStr(Res));
end;
procedure TThreadCreatorForm.FormShow(Sender: TObject);
begin
ThreadID := GetWindowThreadProcessId(Self.Handle, Nil);
Assert(ThreadID = MainThreadID);
end;
function TWndThreadWindowProc(hWnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
var
pSelf: TWndThread;
Message: TMessage;
begin
pSelf := TWndThread(GetWindowLongPtr(hWnd, GWL_USERDATA));
if pSelf <> nil then
begin
Message.Msg := uMsg;
Message.WParam := wParam;
Message.LParam := lParam;
Message.Result := 0;
pSelf.WndProc(Message);
Result := Message.Result;
end else
Result := DefWindowProc(hWnd, uMsg, wParam, lParam);
end;
constructor TWndThread.Create(ACreator: TForm; const Title:String);
begin
inherited Create(True);
FTitle := Title;
FCreator := ACreator;
FillChar(FWndClass, SizeOf(FWndClass), 0);
FWndClass.lpfnWndProc := #TWndThreadWindowProc;
FWndClass.hInstance := HInstance;
FWndClass.lpszClassName := scThreadClassName;
end;
procedure TWndThread.Execute;
var
Msg: TMsg;
begin
if Windows.RegisterClass(FWndClass) = 0 then Exit;
FWnd := CreateWindow(FWndClass.lpszClassName, PChar(FTitle), WS_DLGFRAME, 0, 0, 0, 0, 0, 0, HInstance, nil);
if FWnd = 0 then Exit;
SetWindowLongPtr(FWnd, GWL_USERDATA, ULONG_PTR(Self));
while GetMessage(Msg, 0, 0, 0) and (not Terminated) do
begin
TranslateMessage(msg);
DispatchMessage(msg);
end;
end;
procedure TWndThread.DoTerminate;
begin
if FWnd <> 0 then
DestroyWindow(FWnd);
Windows.UnregisterClass(FWndClass.lpszClassName, FWndClass.hInstance);
inherited;
end;
procedure TWndThread.WndProc(var Message: TMessage);
begin
case Message.Msg of
WM_Action1 : begin
HandleAction1;
Exit;
end;
WM_COPYDATA : begin
if PCopyDataStruct(lParam).dwData = MY_CDS_VALUE then
begin
HandleCopyData(PCopyDataStruct(lParam)^);
Exit;
end;
end;
end;
Message.Result := DefWindowProc(FWnd, Message.Msg, Message.WParam, Message.LParam);
end;
procedure TWndThread.HandleAction1;
begin
//
end;
procedure TWndThread.HandleCopyData(const Cds: TCopyDataStruct);
var
S: String;
begin
if Cds.cbData > 0 then
begin
SetLength(S, Cds.cbData div SizeOf(Char));
CopyMemory(Pointer(S), Cds.lpData, Length(S) * SizeOf(Char));
end;
// use S as needed...
end;
initialization
MY_CDS_VALUE := RegisterWindowMessage('MY_CDS_VALUE');
end.
The copy data message is sent synchronously. Which means that it won't be returned by GetMessage. So you'll need to supply a window procedure to process the message because sent messages are dispatched directly to the window procedure of their windows, being synchronous rather than asynchronous.
Beyond that the other problem is that you don't specify the length of the data in the copy data struct, cbData. That's needed when sending the message cross-thread so that the system can marshal your data.
You should set dwData so that the recipient can check that they are handling the intended message.
You don't need to use GetMem at all here, you can use the string buffer directly. A window handle is an HWND and not a THandle. A message only window would be most appropriate here.
'MyThread' does not run. I do not know whether the problem happens on 'DataTransferServiceStart' procedure. I guess the 'DataTransferServiceStart' procedure does not execute. IDE is Delphi XE. Please help me, thank you very much.
Thread's Unit:
unit Unit_MyThread;
interface
uses
Classes, SysUtils;
type
TMyThread = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
implementation
procedure TMyThread.Execute;
var
log: TextFile;
logPath: String;
i: Integer;
begin
logPath := 'd:\test.log';
AssignFile(log, logPath);
Append(log);
i := 0;
while not self.Terminated do
begin
Sleep(1);
Writeln(log, IntToStr(i));
if i=10 then
Terminate;
i := i + 1;
end;
CloseFile(log);
end;
end.
Main Service Unit:
unit Unit_main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs,
ExtCtrls, DB, ADODB, Unit_MyThread;
type
TDataTransferService = class(TService)
DBSrc: TADOConnection;
procedure DataTransferServiceStart(Sender: TService; var Started: Boolean);
procedure DataTransferServiceContinue(Sender: TService; var Continued: Boolean);
procedure DataTransferServicePause(Sender: TService; var Paused: Boolean);
procedure DataTransferServiceStop(Sender: TService; var Stopped: Boolean);
public
function GetServiceController: TServiceController; override;
end;
var
DataTransferService: TDataTransferService;
MyThread: TMyThread;
implementation
{$R *.DFM}
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
DataTransferService.Controller(CtrlCode);
end;
function TDataTransferService.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
procedure TDataTransferService.DataTransferServiceStart(Sender: TService;
var Started: Boolean);
begin
MyThread := TMyThread.Create(False);
Started := True;
end;
procedure TDataTransferService.DataTransferServiceContinue(Sender: TService;
var Continued: Boolean);
begin
MyThread.Start;
Continued := True;
end;
procedure TDataTransferService.DataTransferServicePause(Sender: TService;
var Paused: Boolean);
begin
MyThread.Suspended := true;
Paused := True;
end;
procedure TDataTransferService.DataTransferServiceStop(Sender: TService;
var Stopped: Boolean);
begin
MyThread.Terminate;
Stopped := True;
end;
end.
Your service is most likely failing to start because you have a TADOConnection component dropped into your service. You cannot do this in services. Since ADO is COM, you must initialize each thread with CoInitialize(nil) and CoUninitialize, and only create/use your database components within this.
uses
ActiveX;
procedure TDataTransferService.DataTransferServiceStart(Sender: TService;
var Started: Boolean);
begin
CoInitialize(nil);
DBSrc:= TADOConnection.Create(nil);
//Initialize and Connect DBSrc
MyThread := TMyThread.Create(False);
Started := True;
end;
procedure TDataTransferService.DataTransferServiceStop(Sender: TService;
var Stopped: Boolean);
begin
MyThread.Terminate;
//Disconnect DBSrc
DBSrc.Free;
CoUninitialize;
Stopped := True;
end;
Read here: Ok to use TADOConnection in threads