I need my program to catch TimeOutException every time SerialPort Read Times out, but it fails to do that. In fact, the program breaks when it goes to read and throws this exceptions, "The I/O operation has been aborted because of either a thread exit or an application request."
Here is how SerialPort Instantiated:
dxComm = class(System.Windows.Forms.Form)
private
protected
public
constructor;
serialPort1:System.IO.Ports.SerialPort;
thr:Thread;
method mythread;
end;
constructor DXComm;
begin
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code after InitializeComponent call
//
SerialPort1 := new System.Io.Ports.SerialPort();
thr:=nil;
end;
Here is how thread is created:
thr:= new Thread(#mythread);
thr.Start;
Here is the SerialPort settings:
case TypeDXCard.SelectedIndex of
0:
begin
DXProtocol := TDXProtocol.tDxTwo;
msglen := 6;
rmsglen := 5;
end;
1:
begin
DXProtocol := TDXProtocol.tDxExpress;
msglen:=0;
rmsglen:=0;
end;
else
begin
DXProtocol := TDXProtocol.tDxTwo;
msglen := 6;
rmsglen := 5;
end;
end;
dx := ord(DXProtocol);
if (SerialPort1 <> nil) then
begin
case CommPort.SelectedIndex of
0: SerialPort1.PortName := 'COM1';
1: SerialPort1.PortName := 'COM2';
2: SerialPort1.portName := 'COM3';
3: SerialPort1.PortName := 'COM4';
end;
case BaudRate.SelectedIndex of
0: SerialPort1.BaudRate := 1200;
1: SerialPort1.BaudRate := 2400;
2: SerialPort1.BaudRate := 4800;
3: SerialPort1.BaudRate := 9600;
4: SerialPort1.BaudRate := 19200;
5: SerialPort1.BaudRate := 38400;
6: SerialPort1.BaudRate := 57600;
7: SerialPort1.BaudRate := 115200;
end;
if (EvenParity.Checked) then
SerialPort1.Parity := System.IO.Ports.Parity.Even
else
SerialPort1.Parity := System.IO.Ports.Parity.None;
end;
with SerialPort1 do
begin
SerialPort1.DataBits:=8;
SerialPort1.DtrEnable:=true;
SerialPort1.ReadBufferSize:= 4096;
SerialPort1.ReadTimeout:=TimeOutDelay*2;
SerialPort1.RtsEnable:=true;
SerialPort1.StopBits:=System.IO.Ports.StopBits.One;
SerialPort1.WriteTimeout:=1000;
SerialPort1.Handshake := HandShake.None;
SerialPort1.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(#MySerialData);
end;
Here is my Thread that handles the SerialPort.Write:
method DXcomm.mythread;
var x,y:Integer;
begin
while true do
begin
Thread.Sleep(ScanTime);
SerialPort1.RtsEnable:=true;
SerialPort1.DiscardOutBuffer;
SendMessage; <---------Assembles the bytes and sends it out
while SerialPort1.BytesToWrite>0 do;
thread.Sleep(4);
SerialPort1.DiscardInBuffer;
SerialPort1.RtsEnable:=false;
if (stopthread) then
break;
end;
end;
Here is the event for reading bytes from the serialport:
method DXComm.MySerialData(sender: System.Object; e:SerialDataReceivedEventArgs);
begin
if not SerialPort1.IsOpen then Exit;
try
SerialPort1.Read(RXMsg,0,5); <------Here is Where my program throws that exception when I check on TimeOutException down below.
if changeFlag then
begin
changeList.IncRxCnt;
FixUpChangeList;
end
else
ActiveUnit.Retreive;
except on ex: TimeOutException do <----This line of code fails.
//except on ex: Exception do <----This line of code works fine, but executes all the time instead of just only when there is an exception.
begin
//TimeOut Exception
ActiveUnit.Timeout;
SerialPort1.DiscardInBuffer;
SerialPort1.DiscardOutBuffer;
end;
end;
end;
What am I doing wrong? I need to catch SerialPort.Read TimeOuts and take appropriate action.
It seems you're using the Serial port as a component on a form but doing the reading / writing in a background thread?
Or, as I got it, you write in a background thread and then read on some other, random, thread (the one that is calling the Event you react on).
That is a problem, because the background thread then (internally) want's to update the Serial Port 'Control', which isn't allowed from Background threads. The problem could also be that the thread waiting to read is interrupted by the other thread that is writing in the infinite loop and thus causes the I/O exception. It's a bit of guessing involved here.
First shot:
You have to either create the Serial Port dynamically (i.e. not putting it on your form but instanciating and configuring it by code) to prevent that or (strongly discouraged though), set System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls to false.
Second shot:
On the other hand I would strongly suggest to make definetly sure that only one thread at all is working with the serial port. Not writing in one thread and reading from another. Do everything that is related to this serial I/O in one single thread. Read OR write, but do not try to do both at the same time from different threads.
Instead of:
SerialPort1.Read(RXMsg,0,5);
Does Delphi have a serial function that returns the number of characters received and waiting to be read?
For example(in probably poor pseudo code):
while (Not Timeout)
{
if (serialport1.characterswaiting)
{
SerialPort1.Read(RXMsg,0,5);
}
}
I believe the problem lies in the fact that I am writing to the serialport in my own thread or user-defined thread and reading from the serialport in another. The event datareceived is part of the main thread of the program, I think.
As pointed out by Sebastian, it makes sense that writing and reading from the same thread should solve my serial communication problem. Indeed, it seems have to solved my serial communication, although it is little less than 100%. That's a timing issue, since my program depends on fixed time delays.
The steps: Within my thread, I write to the serial port and wait for sometime to read the response from the serialport. This seems to have greatly improved the communication, but now I don't wait for the datareceived event to fire once it sees something in the input buffer.
Correct me if I am wrong in my thinking or reasoning.
Related
I am taking over a project with code from another person. I have a PLC that currently has inputs in from pressure sensors and thermocouples. It then scales that data to PSI and temperature in fahrenheit. The way the data is set up from each of those sensors is to be formatted into an array. So, once the data is scaled it is in an array that is also in the Network Variable List of the program. I am trying to take each of these values from the array, record the value every certain amount of time (say 1 recording per second for sake of clarity), and then export each piece of data to a CSV file for every second. Not sure where to even go with this. This is the code I was left with, but I feel as if it it unnecessarily complicated?
//This is the support class for File_Handler
FUNCTION_BLOCK fileWrite
VAR_INPUT
xWrite : BOOL;
sData : STRING(200);
uiLineLength : INT := 200;
sDirectory : STRING := 'C:\ProgramData\CODESYS\CODESYSHMIWinV3\D5050FE1\PlcLogic\data';
//sDirectory : STRING := '/home/cds-apps/PlcLogic/data/';
sFilename : STRING;
END_VAR
VAR_OUTPUT
BytesWritten : __XWORD;
BytesWrittenTotal: DWORD;
xDone: BOOL;
END_VAR
VAR
hFile_: sysfile.RTS_IEC_HANDLE := sysfile.RTS_INVALID_HANDLE;
FileWriteResult: sysfile.RTS_IEC_RESULT;
FileOpenResult: sysfile.RTS_IEC_RESULT;
state: INT;
sys_Us_start: SYSTIME;
sys_Us_end: SYSTIME;
WriteTimeMS: ULINT;
END_VAR
sFilename := CONCAT(sDirectory, sFilename);
hFile_ := SysFileOpen(szFile:= sFilename, am:= ACCESS_MODE.AM_APPEND_PLUS, pResult:= ADR(FileOpenResult));
SysTimeGetUs(pUsTime:=sys_Us_start );
BytesWritten := SysFileWrite(hFile:= hfile_, pbyBuffer:= ADR(sData), ulSize:= uiLineLength, pResult:= ADR(FileWriteResult));
BytesWrittenTotal := BytesWrittenTotal + BytesWritten;
SysTimeGetUs(pUsTime:=sys_Us_end );
WriteTimeMS := (sys_Us_end - sys_Us_start)/1000;
SysFileClose(hFile:= hFile_);
I am not sure where to go with this code. It does create a CSV file, but I was looking to be able to create a CSV file for a piece of data every second? If anyone has any thoughts or resources I could check out that would be great.
A basic example of how to call this routine every second could be the following:
1)
You create a FuncBlock that takes care of calling your logger block.
Let's say you call it LoggerTask.
FUNCTION_BLOCK LoggerTask
VAR_INPUT
sData : STRING(200);
sFilename : STRING;
xExecute : BOOL;
END_VAR
VAR
fbRepeatTask : TON;
fbFileWrite : FileWrite;
uiStep : UINT;
END_VAR
2)
After that create a simple step chain:
(You can obviously extend and customize it as you like, you should add error handling in the case when FileWrite fails to write to file or writes less than expected for example.)
Implementation part:
fbRepeatTask(PT:=T#1S);
fbFileWrite(sData := sData, sFileName := sFileName);
IF xExecute
AND uiStep = 0
THEN
uiStep := 10;
ELSIF NOT xExecute
THEN
uiStep := 0;
fbFileWrite.xWrite := FALSE;
fbRepeatTask.IN := FALSE;
END_IF
CASE uiStep OF
10:
fbFileWrite.xWrite := TRUE;
IF fbFileWrite.xDone
THEN
fbFileWrite.xWrite := FALSE;
uiStep := 20;
END_IF
20:
fbRepeatTask.IN := TRUE;
IF fbRepeatTask.Q
THEN
fbRepeatTask.IN := FALSE;
uiStep := 10;
END_IF
END_CASE
3)
As you can see this block gets executed as soon as xExecute is set to true.
In order to reset the step chain set xExecute to false.
Just run this block cyclically for example like this fbLoggerTask(xExecute := TRUE);
I don't think you posted all the code of your FileWrite block because xDone is not set and xWrite is not checked anywhere.
So make sure that xDone is set to true for one cycle after the String is written to the file (if it's not already been implemented).
"Arguments are of the wrong type, are out of acceptable range, or are in conflict with one another". This is the run-time error message I receive when I try to run the code below. I am using a class, clsReceipt, to formulate a receipt in the form of a string so I can output it in a rich edit for the user to view before proceeding to purchase the products (an overview of sorts). I cannot find any errors and thus I need help. Please bear in mind I am a high school student and have a somewhat limited knowledge. I am using Delphi XE3 on Windows.
Below is the code for btnPurchase:
procedure TfrmBuy.btnPurchaseClick(Sender: TObject);
var
i, n, itemNumber, quant : integer;
found: boolean;
begin
repeat
i := strtoint(inputbox('Purchase','Enter the number of items you wish to buy or enter 0 to cancel',''));
until i>=0;
if i <> 0 then
begin
for n := 1 to i do
begin
found := false;
repeat
itemnumber := strtoint(inputbox('Item selection','Enter the Item number of purchase no. ' + inttostr(n),''));
if dm.ADOtbl.Locate('Item number',itemnumber,[]) then
found := true
else
showmessage('The item number you enteres was not found. Please try again.');
until found = true;
repeat
quant := strtoint(inputbox('Quantity selection','Please enter the quantity of the item you wish to purchase','>0'));
until quant >0;
Myreciept := TReceipt.create(itemnumber,quant,n,i);
end;
richedit1.Lines.Clear;
richedit1.Lines.Add(myreciept.tostring);
btnCheckout.Visible := true;
showmessage('Below is the reciept of your purchase. If you are satisfied, proceed to checkoutby selecting "Confirm" or restart by selecting "Reset"');
end;
repeat
i := strtoint(inputbox('Purchase','Enter the number of items you wish to buy or enter 0 to cancel',''));
until i>=0;
if i <> 0 then
begin
for n := 1 to i do
begin
found := false;
repeat
itemnumber := strtoint(inputbox('Item selection','Enter the Item number of purchase no. ' + inttostr(n),''));
if dm.ADOtbl.Locate('Item number',itemnumber,[]) then
found := true
else
showmessage('The item number you enteres was not found. Please try again.');
until found = true;
repeat
quant := strtoint(inputbox('Quantity selection','Please enter the quantity of the item you wish to purchase','>0'));
until quant >0;
Myreciept := TReceipt.create(itemnumber,quant,n,i);
end;
richedit1.Lines.Clear;
richedit1.Lines.Add(myreciept.tostring);
btnCheckout.Visible := true;
showmessage('Below is the reciept of your purchase. If you are satisfied, proceed to checkoutby selecting "Confirm" or restart by selecting "Reset"');
end;
end;
Below is the code for the ToString function in the class:
function TReceipt.ToString: string;
var
k:integer;
begin
result := '';
result := 'Reciept' + #13 + '===============================================' + #13;
result := result + 'Order ID: ' + fOrderID + #13;
result := result + 'Item Name' + #9 + 'Quantity' + 'Cost' + #13;
for k := 1 to length(arrItemNo) do
begin
dm.ADOtbl.RecNo := arritemno[k];
result := result + dm.ADOtbl['Item Name'] + #9 + inttostr(arrQuantity[k]) + #9 + floattostrf((arrQuantity[k] * dm.ADOtbl['Price'] ),ffcurrency,5,2) + #13;
end;
result := result + #13 + #13 + 'Subtotal: ' + floattostrf(getsubtotal,ffcurrency,5,2) + #13;
result := result + 'VAT: ' + floattostrf(getVat,ffcurrency,5,2) + #13;
result := result + 'Grand Total: ' + floattostrf(ftotal,ffcurrency,5,2) + #13 + '===============================================';
end;
end.
If anyone could assist me with solving this problem that would be great.
(Other readers: Obviously this is a bit of a work in progress, because the OP
possibly needs more guidance than will fit in comments. Anyway ...)
In this case, as mentioned in earlier comments, the message is coming not from your app but from the MS ADO data access layer your app is calling into, by operations your code
is carrying out on the TADOxxx components in your project.
At the risk of stating the obvious, debugging + fixing a problem like this is usually
a multi step process of a) finding out where the error occurs, b) figuring out what is
causing it and c) fixing or working around it.
a) can be trickier, particularly for someone finding their feet, than it might sound
at first, but it does get easier with practice, and the debugger is very helpful in the way
it interacts with the IDE and the user to zero in on the error location.
First thing is get your project in best shape for debugging, for which your first stop is
Project | Options | Compiler. Turn Optimizations off, Stack Frames on, Use Debug DCUs on
and (if your code can run with it) Range Checking On. Go to Debugger Options in the IDE
(it has moved around since older versions like Delphi 7). In XE+ versions go to Tools | Options, scroll down to Debugger Options | Embarcadero Debuggers and check the box "Notify on language exceptions".
Next, do a full build of your project and then run it until the point where the error occurs. If the error manifests as an exception, that makes things easier - just run the app with F9 and the debugger will wrest control from it when the exception occurs. At this point, go to View | Debug Windows, Call Stack: where the exception occurred will be at the top of the window and is usually in the RTL or VCL source code, rather than your project's. Further down the list, you should see routines in your own code - the top one if those is the one you're after. Put a breakpoint at its entry point, dismiss the exception message(s) and go through the motions to
provoke the error again. This time, the debugger should stop on your breakpoint, and
single-stepping should take you to exactly where the error occurs.
Often, the cause of the problem is obvious, and you can fix it on the spot. If you can't,
that's the starting point for deciding which code should be in your SO question.
Before trying the above on your actual problem, have a quick practice by doing this. Add a button to your form and in its Click event, put "raise Exception.Create('I am an error');". Then compile + run the app and click the button.
For your real error, I'd start by placing a breakpoint on the first line below "begin" in your ToString function and just run the app until the b.point trips and single-step (F8) from there until you get to the line where the exception occurs. Then try again and this time trace into (F7) that line ...
"Arguments" in the sense the error message means are values being supplied for the parameter "place holders" that a routine, be it in your own code, or something it's calling into, is expecting to receive.
The arguments the error msg is referring to are data your app is trying to send through the ADO layer to the DB, usually as parameters or text originating from operations on your project's ADO components. So it's only likely to be statements where you do something with one of those objects that are the ones which could set the error off. Once you've found out where, we'll need some info to go into the q and probably most of the existing code can come out as not relevant.
I found a solution to extract the contents of a zip file by creating a DLL using Ole. I put my own touch on this function, but for some reason, the compiler complains that the function's result is never used...
library unzipper;
{
title : UnZip for InnoSetup
version : 1.0
author : Daniel P. Stasinski
email : daniel#genericinbox.com
begin : Fri Nov 22 17:31:33 MST 2013
license : None
}
uses
Windows,
SysUtils,
ComObj;
const
SHCONTCH_NOPROGRESSBOX = 4;
SHCONTCH_AUTORENAME = 8;
SHCONTCH_RESPONDYESTOALL = 16;
SHCONTF_INCLUDEHIDDEN = 128;
SHCONTF_FOLDERS = 32;
SHCONTF_NONFOLDERS = 64;
UNZIP_SUCCESS = 0;
UNZIP_FAIL = -1;
function UnzipFile(ZipFile, TargetFolder: WideString): Integer; stdcall;
var
shellobj: variant;
ZipFileV, SrcFile: variant;
TargetFolderV, DestFolder: variant;
shellfldritems: variant;
begin
Result:= UNZIP_FAIL;
try
shellobj := CreateOleObject('Shell.Application');
ZipFileV := string(ZipFile);
TargetFolderV := string(TargetFolder);
SrcFile := shellobj.NameSpace(ZipFileV);
DestFolder := shellobj.NameSpace(TargetFolderV);
shellfldritems := SrcFile.Items;
DestFolder.CopyHere(shellfldritems, SHCONTCH_NOPROGRESSBOX or SHCONTCH_RESPONDYESTOALL);
Result:= UNZIP_SUCCESS;
except
on e: exception do begin
Result:= GetLastError;
end;
end;
end;
exports
UnzipFile;
begin
end.
It gives me the message...
[DCC Hint] Unzipper.dpr(35): H2077 Value assigned to 'UnzipFile' never used
This is coming from the first line of code in the function, which I'm initializing to a constant of -1 - which is my own error code if the entire function fails. I don't believe the compiler should be complaining about this, but I could be wrong. I always exterminate all compiler hints and warnings, but in this case, the compiler is more of a complainer.
Is this a fluke in the compiler, or is something wrong in the code?
The compiler is correct, and there's something wrong in the code. :-)
The function will either return UNZIP_SUCCESS if it works, or the result of GetLastError if an exception is raised. Therefore, the first assignment to Result is unnecessary - there is no path of execution that would cause UNZIP_FAIL to be returned.
If you remove the first line result assignment, there is no execution path that leaves result unassigned. Therefore, UNZIP_FAIL value will never be returned.
I need to go through a HTML string and replace characters with 0 (zero), except tags, spaces and line breaks. I created this code bellow, but it is too slow. Please, can someone help me to make it faster (optimize)?
procedure TForm1.btn1Click(Sender: TObject);
var
Txt: String;
Idx: Integer;
Tag: Boolean;
begin
Tag := False;
Txt := mem1.Text;
For Idx := 0 to Length(Txt) - 1 Do
Begin
If (Txt[Idx] = '<') Then
Tag := True Else
If (Txt[Idx] = '>') Then
Begin
Tag := False;
Continue;
end;
If Tag Then Continue;
If (not (Txt[Idx] in [#10, #13, #32])) Then
Txt[Idx] := '0';
end;
mem2.Text := Txt;
end;
The HTML text will never have "<" or ">" outside tags (in the middle of text), so I do not need to worry about this.
Thank you!
That looks pretty straightforward. It's hard to be sure without profiling the code against the data you're using, (which is always a good idea; if you need to optimize Delphi code, try running it through Sampling Profiler first to get an idea where you're actually spending all your time,) but if I had to make an educated guess, I'd guess that your bottleneck is in this line:
Txt[Idx] := '0';
As part of the compiler's guarantee of safe copy-on-write semantics for the string type, every write to an individual element (character) of a string involves a hidden call to the UniqueString routine. This makes sure that you're not changing a string that something else, somewhere else, holds a reference to.
In this particular case, that's not necessary, because you got the string fresh in the start of this routine and you know it's unique. There's a way around it, if you're careful.
CLEAR AND UNAMBIGUOUS WARNING: Do not do what I'm about to explain without making sure you have a unique string first! The easiest way to accomplish this is to call UniqueString manually. Also, do not do anything during the loop that could assign this string to any other variable. While we're doing this, it's not being treated as a normal string. Failure to heed this warning can cause data corruption.
OK, now that that's been explained, you can use a pointer to access the characters of the string directly, and get around the compiler's safeguards, like so:
procedure TForm1.btn1Click(Sender: TObject);
var
Txt: String;
Idx: Integer;
Tag: Boolean;
current: PChar; //pointer to a character
begin
Tag := False;
Txt := mem1.Text;
UniqueString(txt); //very important
if length(txt) = 0 then
Exit; //If you don't check this, the next line will raise an AV on a blank string
current := #txt[1];
dec(current); //you need to start before element 1, but the compiler won't let you
//assign to element 0
For Idx := 0 to Length(Txt) - 1 Do
Begin
inc(current); //put this at the top of the loop, to handle Continue cases correctly
If (current^ = '<') Then
Tag := True Else
If (current^ = '>') Then
Begin
Tag := False;
Continue;
end;
If Tag Then Continue;
If (not (current^ in [#10, #13, #32])) Then
current^ := '0';
end;
mem2.Text := Txt;
end;
This changes the metaphor. Instead of indexing into the string as an array, we're treating it like a tape, with the pointer as the head, moving forward one character at a time, scanning from beginning to end, and changing the character under it when appropriate. No redundant calls to UniqueString, and no repeatedly calculating offsets, which means this can be a lot faster.
Be very careful when using pointers like this. The compiler's safety checks are there for a good reason, and using pointers steps outside of them. But sometimes, they can really help speed things up in your code. And again, profile before trying anything like this. Make sure that you know what's slowing things down, instead of just thinking you know. If it turns out to be something else that's running slow, don't do this; find a solution to the real problem instead.
Edit: Looks like I was wrong - UniqueString is not the problem. The actual bottleneck seems to be accessing the string by character. Given that my entire answer was irrelevent, I've completely replaced it.
If you use a PChar to avoid recalculating the string offset, while still updating the string via Txt[Idx], the method is much faster (5 seconds down to 0.5 seconds in my test of 1000 runs).
Here's my version:
procedure TForm1.btn1Click(Sender: TObject);
var
Idx: Integer;
Tag: Boolean;
p : PChar;
Txt : string;
begin
Tag := False;
Txt := Mem1.Text;
p := PChar(txt);
Dec(p);
For Idx := 0 to Length(Txt) - 1 Do
Begin
Inc(p);
If (not Tag and (p^ = '<')) Then begin
Tag := True;
Continue;
end
Else If (Tag and (p^ = '>')) Then
Begin
Tag := False;
Continue;
end;
If Tag Then Continue;
If (not (p^ in [#10, #13, #32])) Then begin
Txt[Idx] := '0';
end;
end;
mem2.Text := Txt;
end;
I did some profiling and came up with this solution.
A test for > #32 instead of [#10,#13,#32] gains some speed (thanks #DavidHeffernan).
A better logic in the loop also gives a bit extra speed.
Accessing the string exclusively with the help of a PChar is more effective.
procedure TransformHTML( var Txt : String);
var
IterCnt : Integer;
PTxt : PChar;
tag : Boolean;
begin
PTxt := PChar(Txt);
Dec(PTxt);
tag := false;
for IterCnt := 0 to Length(Txt)-1 do
begin
Inc(PTxt);
if (PTxt^ = '<') then
tag := true
else
if (PTxt^ = '>') then
tag := false
else
if (not tag) and (PTxt^ > #32) then
PTxt^ := '0';
end;
end;
This solution is about 30% more effective than Mason's solution and 2.5 times more effective than Blorgbeard's.
I have developed an Application using BDS 2006 which uses MySQL database(connected using DataModule and MyDAC components).
Now I want to start my application on the start-up of the system(Windows XP).So I included the application shortcut in the start up folder .
Now on the start-up, my application starts prior to the MySQL service getting started. Hence I get an error Cannot connect to MySQL.
So I inserted a blank from on start of my application and performed the check to see if MySQL is running or not.If not running then wait until it is running.
function ServiceGetStatus(sMachine, sService: PChar): DWORD;
{******************************************}
{*** Parameters: ***}
{*** sService: specifies the name of the service to open
{*** sMachine: specifies the name of the target computer
{*** ***}
{*** Return Values: ***}
{*** -1 = Error opening service ***}
{*** 1 = SERVICE_STOPPED ***}
{*** 2 = SERVICE_START_PENDING ***}
{*** 3 = SERVICE_STOP_PENDING ***}
{*** 4 = SERVICE_RUNNING ***}
{*** 5 = SERVICE_CONTINUE_PENDING ***}
{*** 6 = SERVICE_PAUSE_PENDING ***}
{*** 7 = SERVICE_PAUSED ***}
{******************************************}
var
SCManHandle, SvcHandle: SC_Handle;
SS: TServiceStatus;
dwStat: DWORD;
begin
dwStat := 0;
// Open service manager handle.
SCManHandle := OpenSCManager(sMachine, nil, SC_MANAGER_CONNECT);
if (SCManHandle > 0) then
begin
SvcHandle := OpenService(SCManHandle, sService, SERVICE_QUERY_STATUS);
// if Service installed
if (SvcHandle > 0) then
begin
// SS structure holds the service status (TServiceStatus);
if (QueryServiceStatus(SvcHandle, SS)) then
dwStat := ss.dwCurrentState;
CloseServiceHandle(SvcHandle);
end;
CloseServiceHandle(SCManHandle);
end;
Result := dwStat;
end;
code source
// if MySQL not running then sleep until its running
procedure TForm1.FormCreate(Sender: TObject);
begin
while(ServiceGetStatus(nil, 'MySQL5.5') <>4 ) do
begin
sleep (200);
end;
end;
I would like to know if my approach is correct? If not suggest the same.
Also can this be done without the programming by using windows?
Sleeping in the main thread is never a good idea.
It's better to do the waiting in a thread and post a message to the main thread when MySQL is running.
Answering the comment from #mghie:
Why would waiting on the event be any better (or any different) than calling Sleep()?
An event-driven GUI is considered good programming practice. There is no waiting involved.
When the event is fired, the GUI is informed about the status change of the database connection.
If you would be waiting in a Sleep() loop, the application appears non-responsive.
And calling Application.ProcessMessages to somewhat take care of that, is really not a good practice.
An example how to wait until MySQL is running in a thread:
const
WM_MySQL_READY = WM_USER + 1; // The unique message id
type
TForm1 = class(TForm)
...
private
procedure OnMySqlReady( var Msg: TMessage); message WM_MySQL_READY;
...
end;
In your thread:
Constructor TMyThread.Create( OwnerForm : TForm);
begin
Inherited Create( false);
FOwnerForm := OwnerForm; // Keep for later use
Self.FreeOnTerminate := true;
end;
procedure TMyThread.Execute;
var
SQL_started : boolean;
sleepEvent : TSimpleEvent;
begin
sleepEvent := TSimpleEvent.Create;
try
repeat
SQL_started := (ServiceGetStatus(nil, 'MySQL5.5') = 4);
sleepEvent.WaitFor(200); // Better than sleep();
until SQL_started or Terminated;
finally
sleepEvent.Free;
end;
// Inform main thread
PostMessage( FOwnerForm.Handle,WM_MySQL_READY,WPARAM(SQL_started),0);
end;
Ok, I misunderstood #mghie a little, his question was why the TSimpleEvent.WaitFor() is better than Sleep() inside the thread.
For a background see: thread-sleep-is-a-sign-of-a-poorly-designed-program.
In short, Sleep() puts the thread into a deep sleep and control is not given back at best periodic rate (if ever in some corner cases).
TSimpleEvent.WaitFor() on the other hand is much more responsive with regards to timing and waking up. (Remember that Windows is not a true real-time OS and timing is not guaranteed). Anyway rule of thumb, in threads, use TSimpleEvent.Waitfor() instead of Sleep().
Should need arise to halt the wait for connection to the MySQL server, some adjustment to the code can be made:
constructor TMyThread.Create(OwnerForm: TForm; cancelEvent : TSimpleEvent);
begin
inherited Create(false);
FOwnerForm := OwnerForm; // Make sure it's assigned
FCancelEvent := cancelEvent; // Make sure it's assigned
Self.FreeOnTerminate := true;
end;
procedure TMyThread.Execute;
var
SQL_started : boolean;
cancel : boolean;
begin
repeat
SQL_started := (ServiceGetStatus(nil, 'MySQL5.5') = 4);
cancel := (FCancelEvent.WaitFor(200) = wrSignaled);
until SQL_started or Terminated or cancel;
// Inform main thread
PostMessage( FOwnerForm.Handle,WM_MySQL_READY,WPARAM(SQL_started),0);
end;
To abort the thread prior to the connection is made, just call MyEvent.SetEvent.
You can even present a splash screen from a thread if you want to inform the user about what's going on during the wait.
See Peter Below's Threaded Splashscreen for Delphi for such an example. Note that this code does not make use of any VCL components or anything that involves synchronizing with the main thread.
You might also want to look at: Show a splash screen while a database connection (that might take a long time) runs.