Why doesn't WINWORD.EXE quit after Closing the document from Delphi? - delphi

I managed to distill one of the underlying issues rooted in my question How to trace _AddRef / _Release calls for OLE Automation objects in the unit below.
I'll answer this answer too, just in case anyone else bumps into this.
The question: with the below code, why doesn't WINWORD.EXE always quit (sometimes it does quit).
The unit can probably be trimmed down even more.
unit Unit2;
interface
uses
Winapi.Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls,
WordXP;
type
TForm2 = class(TForm)
WordXPFailsToQuitButton: TButton;
procedure WordXPFailsToQuitButtonClick(Sender: TObject);
private
FWordApplication: TWordApplication;
strict protected
function GetWordApplication: TWordApplication; virtual;
function GetWordApplication_Documents: Documents; virtual;
procedure WordApplication_DocumentBeforeClose(ASender: TObject; const Doc: _Document; var Cancel: WordBool); virtual;
procedure WordApplication_Quit(Sender: TObject); virtual;
property WordApplication: TWordApplication read GetWordApplication;
property WordApplication_Documents: Documents read GetWordApplication_Documents;
end;
var
Form2: TForm2;
implementation
uses
Vcl.OleServer;
{$R *.dfm}
function TForm2.GetWordApplication: TWordApplication;
begin
if not Assigned(FWordApplication) then
begin
FWordApplication := TWordApplication.Create(nil);
FWordApplication.AutoConnect := False;
FWordApplication.AutoQuit := False;
FWordApplication.ConnectKind := ckNewInstance;
FWordApplication.OnDocumentBeforeClose := WordApplication_DocumentBeforeClose;
FWordApplication.OnQuit := WordApplication_Quit;
FWordApplication.Connect;
end;
Result := FWordApplication;
end;
function TForm2.GetWordApplication_Documents: Documents;
begin
Result := WordApplication.Documents;
if not Assigned(Result) then
raise EAccessViolation.Create('WordApplication.Documents');
end;
procedure TForm2.WordXPFailsToQuitButtonClick(Sender: TObject);
begin
try
WordApplication_Documents.Add(EmptyParam, EmptyParam, EmptyParam, EmptyParam);
WordApplication.Visible := True;
WordApplication.ActiveDocument.Close(False, EmptyParam, EmptyParam);
finally
WordApplication.OnQuit := nil;
WordApplication.OnDocumentBeforeClose := nil;
WordApplication.AutoQuit := True;
WordApplication.Disconnect;
WordApplication.Free;
FWordApplication := nil;
end;
end;
procedure TForm2.WordApplication_DocumentBeforeClose(ASender: TObject; const Doc: _Document; var Cancel: WordBool);
begin
FWordApplication.Disconnect;
end;
procedure TForm2.WordApplication_Quit(Sender: TObject);
begin
FWordApplication.Disconnect;
end;
end.

Answer part 1:
Comment out the disconnect in the below event:
procedure TForm2.WordApplication_DocumentBeforeClose(ASender: TObject; const Doc: _Document; var Cancel: WordBool);
begin
// FWordApplication.Disconnect;
end;
The event will be called during the DocumentClose(...) method, then disconnect and delete the OLE interface from the FWordApplication instance.
I have not yet figured out which reference is dangling, but this effectively keeps WINWORD.EXE alive most of the times.
Answer part 2:
Sometimes WINWORD.EXE does quit because toe WordApplication_DocumentBeforeClose event is not called. The reason is that the code runs so fast that Word is not fully initialized yet to perform the event.

Related

Unable to a print in Windows 10 when Delphi / QuickReport is within a DLL

Delphi 7 / QuickReport 5.02.2
We've used similar code for several years but have run into an issue recently now that we're migrating workstations to Windows 10. Previously, we were using Windows 7 and all was fine. Maybe there's something I'm missing or doing wrong?
Here's a simple test project I put together to test this. When the report is within a DLL every call to Printer.GetPrinter fails in Windows 10. Though, if the report is on a form within the main application it works fine.
Below is the code, and a zipped up folder for anyone that's interested. There is the dependency on QuickReport though, which can't be helped. Thanks for looking.
https://1drv.ms/u/s!AsbtokV75aocsXM6MQZcrvwpHKcg
DLL Project.
library test_dll;
uses
SysUtils,
Classes,
Forms,
report in 'report.pas' {report_test};
{$R *.res}
function Report_Print(PrinterName: Widestring): Integer; export;
var
Receipt: Treport_test;
begin
try
Receipt := Treport_test.Create(nil);
try
Receipt.Print(PrinterName);
Receipt.Close;
finally
Receipt.Free;
end;
except
Application.HandleException(Application.Mainform);
end;
Result := 1;
end;
exports
Report_Print;
begin
end.
Report Unit
unit report;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, QRCtrls, QuickRpt, ExtCtrls, Printers, QRpCtrls, QRPrntr;
type
Treport_test = class(TForm)
QuickRep1: TQuickRep;
DetailBand1: TQRBand;
TitleBand1: TQRBand;
QRLabel1: TQRLabel;
SummaryBand1: TQRBand;
QRLabel2: TQRLabel;
QRLabel3: TQRLabel;
private
{ Private declarations }
public
{ Public declarations }
procedure Print(const PrinterName: string);
end;
var
report_test: Treport_test;
procedure SetupPrinter(QuickRep: TQuickRep; const PrinterName: string);
function SelectPrinter(QuickRep: TQuickRep; const PrinterName: string): boolean;
implementation
var
DLL_QRPrinter: TQRPrinter;
{$R *.dfm}
function SelectPrinter(QuickRep: TQuickRep; const PrinterName: string): boolean;
var
i: integer;
compareLength: integer;
windowsPrinterName: string;
selectedPrinter: Integer;
defaultPrinterAvailable: Boolean;
begin
defaultPrinterAvailable := True;
try // an exception will occur if there is no default printer
i := Printer.printerIndex;
if i > 0 then ; // this line is here so Delphi does not generate a hint
except
defaultPrinterAvailable := False;
end;
compareLength := Length(PrinterName);
if (not Assigned(QuickRep.QRPrinter)) then
begin
QuickRep.QRPrinter := DLL_QRPrinter;
end;
// Look for the printer.
selectedPrinter := -1;
// Attempt #1: first try to find an exact match
for i := 0 to QuickRep.QRPrinter.Printers.Count - 1 do
begin
windowsPrinterName := Copy(QuickRep.QRPrinter.Printers.Strings[i], 1, compareLength);
if (UpperCase(windowsPrinterName) = UpperCase(PrinterName)) then
begin
selectedPrinter := i;
Break;
end;
end;
// Attempt #2: if no exact matches, look for the closest
if (selectedPrinter < 0) then
for i := 0 to QuickRep.QRPrinter.Printers.Count - 1 do
begin
windowsPrinterName := Copy(QuickRep.QRPrinter.Printers.Strings[i], 1, compareLength);
if (Pos(UpperCase(PrinterName), UpperCase(QuickRep.QRPrinter.Printers.Strings[i])) > 0) then
begin
selectedPrinter := i;
Break;
end;
end;
// Attempt #3: if no exact matches, and nothing close, use default printer
if (selectedPrinter < 0) and (defaultPrinterAvailable) then
selectedPrinter := QuickRep.Printer.printerIndex;
Result := False;
if (selectedPrinter > -1) then
begin
QuickRep.PrinterSettings.PrinterIndex := selectedPrinter;
Result := True;
end;
end;
procedure SetupPrinter(QuickRep: TQuickRep; const PrinterName: string);
begin
//check if we have the default printer instead of the selected printer
SelectPrinter(QuickRep, PrinterName);
QuickRep.Page.Units := Inches;
QuickRep.Page.Length := 11;
end;
procedure Treport_test.Print(const PrinterName: string);
begin
SetupPrinter(QuickRep1, PrinterName);
QuickRep1.Print;
end;
initialization
DLL_QRPrinter := TQRPrinter.Create(nil);
finalization
DLL_QRPrinter.Free;
DLL_QRPrinter := nil;
end.
Test Application
program Project1;
uses
Forms,
Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
Main Form
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, QRPrntr,
Dialogs, StdCtrls, QuickRpt, QRCtrls, ExtCtrls, Printers, QRPCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
type
TPrintReport = function(PrinterName: Widestring): Integer;
var
Form1: TForm1;
procedure PrintReport(const PrinterName: string);
implementation
var
DLLHandle: THandle = 0;
POS: TPrintReport = nil;
{$R *.dfm}
procedure PrintReport(const PrinterName: string);
begin
try
POS(PrinterName);
except on e: Exception do
ShowMessage(e.Message);
end;
end;
procedure LoadDLL;
var
DLLName: string;
DLLRoutine: PChar;
begin
DLLName := 'test_dll.dll';
DLLRoutine := 'Report_Print';
if not (FileExists(DLLName)) then
raise Exception.CreateFmt('The DLL "%s" is missing. Build the DLL project and try again.', [DLLName]);
Application.ProcessMessages;
DLLHandle := LoadLibrary(PChar(DLLName));
Application.ProcessMessages;
if (DLLHandle = 0) then
raise Exception.CreateFmt('Error: %s, while attempting to load DLL %s.', [IntToStr(GetLastError), DLLName]);
POS := GetProcAddress(DLLHandle, DLLRoutine);
if (#POS = nil) then
raise Exception.CreateFmt('Error: %s, while attempting get address to %s in DLL %s.', [IntToStr(GetLastError), DLLRoutine, DLLName]);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
LoadDLL;
ShowMessage('dll loaded');
PrintReport('MyPrinter');
FreeLibrary(DLLHandle);
end;
end.
Snippet from QuickReport
procedure TPrinterSettings.ApplySettings;
var
Cancel : boolean;
begin
FPrinter.GetPrinter(FDevice, FDriver, FPort, DeviceMode);
DevMode := GlobalLock(DeviceMode);
begin
SetField(dm_paperlength);
...
DeviceMode is 0, so SetField throws an access violation. See below.
Access violation at address 036BFBA7 in module 'test_dll.dll'. Write of address 00000028.
Try comment out those 2 lines for GetPrinter and for DevMode
procedure TPrinterSettings.ApplySettings;
var
Cancel : boolean;
begin
// FPrinter.GetPrinter(FDevice, FDriver, FPort, DeviceMode);
// DevMode := GlobalLock(DeviceMode);
begin
SetField(dm_paperlength);
...
end
uses ComObj, ActiveX, StdVcl;
if Printer.Printers.Count>0 then
begin
FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
FWMIService := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
FWbemObject := FWMIService.Get(Format('Win32_Printer.DeviceID="%s"',[Printer.Printers.Strings[0]]));
if not VarIsClear(FWbemObject) then
FWbemObject.SetDefaultPrinter();
end;
new solution
Windows 10 have not default printer with this code u can set the default printer

Delphi 2007: save only the breakpoint options in the DSK file?

Is it possible in Delphi to just save the breakpointss in the .DSK file for a project and no other Desktop settings?
Most of the .DSK gets in the way, but not being able to save debug breakpoints is a real pain (especially when they are conditionally or actions are attached).
I've never come across an IDE facility to save only the breakpoint-related settings in the .Dsk file.
For amusement, I thought I'd try and implement something via an IDE add-in using OTA notifications. The code below runs fine installed into a package installed in D7, and the IDE seems quite happy to re-open a project whose .Dsk file has been processed by it (and the breakpoints get set!).
As you can see, it catches an OTA notifier's FileNotification event when called with a NotifyCode of ofnProjectDesktopSave, which happens just after the IDE has saved the .Dsk file (initially with the extension '.$$$', which I faile to notice when first writing this). It then reads the saved file file, and and prepares an updated version from which all except a specified list of sections are removed. The user then has the option to save the thinned-out file back to disk. I've used a TMemIniFile to do most of the processing simply to minimize the amount of code needed.
I had zero experience of writing an OTA notifier when I read your q, but the GE Experts FAQ referenced below was immensely helpful, esp the example notifier code.
Normally, deleting a project's .Dsk file is harmless, but use this code with caution as it has not been stress-tested.
Update: I noticed that the filename received by TIdeNotifier.FileNotification event actually has an extension of '.$$$'. I'm not quite sure why that should be, but seemingly the event is called before the file is renamed to xxx.Dsk. I thought that would require a change to how
to save the thinned-out version, but evidently not.
Update#2: Having used a folder-monitoring utility to see what actually happens, it turns out that the desktop-save notification the code receives is only the first of a number of operations related to the .Dsk file. These include renaming any existing version of the .Dsk file as a .~Dsk file and finally saving the .$$$ file as the new .Dsk file.
unit DskFilesu;
interface
{$define ForDPK} // undefine to test in regular app
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Buttons, StdCtrls, IniFiles, TypInfo
{$ifdef ForDPK}
, ToolsApi
{$endif}
;
{$ifdef ForDPK}
{
Code for OTA TIdeNotifier adapted from, and courtesy of, the link on http://www.gexperts.org/open-tools-api-faq/#idenotifier
}
type
TIdeNotifier = class(TNotifierObject, IOTANotifier, IOTAIDENotifier)
protected
procedure AfterCompile(Succeeded: Boolean);
procedure BeforeCompile(const Project: IOTAProject; var Cancel: Boolean);
procedure FileNotification(NotifyCode: TOTAFileNotification;
const FileName: string; var Cancel: Boolean);
end;
{$endif}
type
TDskForm = class(TForm)
edDskFileName: TEdit;
SpeedButton1: TSpeedButton;
OpenDialog1: TOpenDialog;
lbSectionsToKeep: TListBox;
lbDskSections: TListBox;
moDskFile: TMemo;
btnSave: TButton;
procedure btnSaveClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
private
procedure GetSectionsToKeep;
function GetDskFileName: String;
procedure SetDskFileName(const Value: String);
function GetDskFile: Boolean;
protected
public
DskIni : TMemIniFile;
property DskFileName : String read GetDskFileName write SetDskFileName;
end;
var
NotifierIndex: Integer;
DskForm: TDskForm;
{$ifdef ForDPK}
procedure Register;
{$endif}
implementation
{$R *.DFM}
{$ifdef ForDPK}
procedure Register;
var
Services: IOTAServices;
begin
Services := BorlandIDEServices as IOTAServices;
Assert(Assigned(Services), 'IOTAServices not available');
NotifierIndex := Services.AddNotifier(TIdeNotifier.Create);
end;
{$endif}
procedure DskPopUp(FileName : String);
var
F : TDskForm;
begin
F := TDskForm.Create(Application);
try
F.DskFileName := FileName;
F.ShowModal;
finally
F.Free;
end;
end;
function TDskForm.GetDskFileName: String;
begin
Result := edDskFileName.Text;
end;
procedure TDskForm.SetDskFileName(const Value: String);
begin
edDskFileName.Text := Value;
if Assigned(DskIni) then
FreeAndNil(DskIni);
btnSave.Enabled := False;
DskIni := TMemIniFile.Create(DskFileName);
DskIni.ReadSections(lbDskSections.Items);
GetSectionsToKeep;
end;
procedure TDskForm.btnSaveClick(Sender: TObject);
begin
DskIni.UpdateFile;
end;
procedure TDskForm.FormCreate(Sender: TObject);
begin
lbSectionsToKeep.Items.Add('watches');
lbSectionsToKeep.Items.Add('breakpoints');
lbSectionsToKeep.Items.Add('addressbreakpoints');
if not IsLibrary then
DskFileName := ChangeFileExt(Application.ExeName, '.Dsk');
end;
procedure TDskForm.GetSectionsToKeep;
var
i,
Index : Integer;
SectionName : String;
begin
moDskFile.Lines.Clear;
for i := lbDskSections.Items.Count - 1 downto 0 do begin
SectionName := lbDskSections.Items[i];
Index := lbSectionsToKeep.Items.IndexOf(SectionName);
if Index < 0 then
DskIni.EraseSection(SectionName);
end;
DskIni.GetStrings(moDskFile.Lines);
btnSave.Enabled := True;
end;
function TDskForm.GetDskFile: Boolean;
begin
OpenDialog1.FileName := DskFileName;
Result := OpenDialog1.Execute;
if Result then
DskFileName := OpenDialog1.FileName;
end;
procedure TDskForm.SpeedButton1Click(Sender: TObject);
begin
GetDskFile;
end;
{$ifdef ForDPK}
procedure RemoveNotifier;
var
Services: IOTAServices;
begin
if NotifierIndex <> -1 then
begin
Services := BorlandIDEServices as IOTAServices;
Assert(Assigned(Services), 'IOTAServices not available');
Services.RemoveNotifier(NotifierIndex);
end;
end;
function MsgServices: IOTAMessageServices;
begin
Result := (BorlandIDEServices as IOTAMessageServices);
Assert(Result <> nil, 'IOTAMessageServices not available');
end;
procedure TIdeNotifier.AfterCompile(Succeeded: Boolean);
begin
end;
procedure TIdeNotifier.BeforeCompile(const Project: IOTAProject; var Cancel: Boolean);
begin
Cancel := False;
end;
procedure TIdeNotifier.FileNotification(NotifyCode: TOTAFileNotification;
const FileName: string; var Cancel: Boolean);
begin
Cancel := False;
// Note: The FileName passed below has an extension of '.$$$'
if NotifyCode = ofnProjectDesktopSave then
DskPopup(FileName);
end;
initialization
finalization
RemoveNotifier;
{$endif}
end.

Accessing IXMLDOMDocument2 via TXMLDocument?

I have some working code using Delphi's TXMLDocument class, and using the TransformNode method to perform XSLT translation.
But, I need to enable XSLT Javascript functions (<msxml:script> tags) and - after much googling - this means I need to set the AllowXsltScript property of the IXMLDOMDocument2 to true.
http://msdn.microsoft.com/en-us/library/windows/desktop/ms760290(v=vs.85).aspx
I've achieved this successfully - but only by modifying the source of the Delphi Library function CreateDOMDocument in msxmldom.pas.
function CreateDOMDocument: IXMLDOMDocument;
var doc :IXMLDOMDocument2;
begin
doc := TryObjectCreate([CLASS_DOMDocument60, CLASS_DOMDocument40, CLASS_DOMDocument30,
CLASS_DOMDocument26, msxml.CLASS_DOMDocument]) as IXMLDOMDocument2;
if not Assigned(doc) then
raise DOMException.Create(SMSDOMNotInstalled);
doc.setProperty('AllowXsltScript', true); // Allow XSLT scripts!!
Result := doc;
end;
Obviously this is far from satisfactory - so how can I access IXMLDOMDocument2 objects without modifying library code??
You can override the create function via the MSXMLDOMDocumentCreate variable:
unit Unit27;
interface
uses
xmldoc, xmlintf, msxml, msxmldom, Forms, SysUtils,
ActiveX, ComObj, XmlDom, XmlConst,
Windows, Messages, Classes, Controls, StdCtrls;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
function TryObjectCreate(const GuidList: array of TGuid): IUnknown;
var
I: Integer;
Status: HResult;
begin
Status := S_OK;
for I := Low(GuidList) to High(GuidList) do
begin
Status := CoCreateInstance(GuidList[I], nil, CLSCTX_INPROC_SERVER or
CLSCTX_LOCAL_SERVER, IDispatch, Result);
if Status = S_OK then Exit;
end;
OleCheck(Status);
end;
function CreateDOMDocument2: IXMLDOMDocument;
var
Doc2 : IXMLDOMDocument2;
begin
Doc2 := TryObjectCreate([CLASS_DOMDocument60, CLASS_DOMDocument40, CLASS_DOMDocument30,
CLASS_DOMDocument26, msxml.CLASS_DOMDocument]) as IXMLDOMDocument2;
if not Assigned(Doc2) then
raise DOMException.Create(SMSDOMNotInstalled);
Doc2.setProperty('AllowXsltScript', true);
Result := Doc2;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
Doc : IXMLDocument;
begin
Doc := TXMLDocument.Create(nil);
Doc.LoadFromFile('c:\temp\test.xml');
end;
initialization
MSXMLDOMDocumentCreate := CreateDOMDocument2;
end.
Note that in XE3 and above, MSXMLDOMDocumentCreate is deprecated in favor of subclassing TMSXMLDOMDocumentFactory and overriding it's CreateDOMDocument function. For future reference, here's an example for XE3 and XE4:
interface
type
TMSXMLDOMDocument2Factory = class(TMSXMLDOMDocumentFactory)
public
class function CreateDOMDocument: IXMLDOMDocument; override;
end;
implementation
{ TMSXMLDOMDocument2Factory }
class function TMSXMLDOMDocument2Factory.CreateDOMDocument: IXMLDOMDocument;
begin
Result := inherited;
if not Assigned(Result) then
raise DOMException.Create(SMSDOMNotInstalled);
AddDOMProperty('AllowXsltScript', True);
SetDOMProperties(Result as IXMLDOMDocument2);
end;
initialization
MSXMLDOMDocumentFactory := TMSXMLDOMDocument2Factory;
end.

Building HTTP Server Application

I have a project which does financial reports and I want to let user to be able to get this reports through the internet
I tried using TIdHTTPServer which is an Indy component to make my application to work as an HTTP Server and to let it to be able
receive request -> process the request -> send back the result of the request process
using a special port.
now my problem is that I'm getting a lot of Access Violation errors and random exceptions
it looks like about threads problem or I don't know because if I process the same request without using the TIdHTTPServer I don't get any problem
i'm using the OnCommandGet Event to process the request and send the result back to user inside the context stream.
what I need is a demonstration on how to use it with TADODataSet and TADOConnection
for example I need the user to be able to send a request and the TIdHTTPServer takes the request (for example call a stored procedure using to ADODataSet and take the result as XML file and send it back to the user)
please help....thank you.
one possibility how a Server could work ...
unit Unit3;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs,IDContext, IdBaseComponent, IdComponent, IdCustomTCPServer, IdTCPServer, StdCtrls, DB, ADODB;
type
TForm3 = class(TForm)
IdTCPServer1: TIdTCPServer;
Memo1: TMemo;
Button1: TButton;
DummyConnection: TADOConnection;
procedure Button1Click(Sender: TObject);
procedure IdTCPServer1Execute(AContext: TIdContext);
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
var
Form3: TForm3;
implementation
uses ComObj,AdoInt,ActiveX;
{$R *.dfm}
function SendStream(AContext: TIdContext; AStream: TStream): Boolean;
begin
Result := False;
try
AContext.Connection.IOHandler.Write(AStream.Size); // sending length of Stream first
AContext.Connection.IOHandler.WriteBufferOpen;
AContext.Connection.IOHandler.Write(AStream, AStream.Size);
AContext.Connection.IOHandler.WriteBufferFlush;
finally
AContext.Connection.IOHandler.WriteBufferClose;
end;
Result := True;
end;
procedure TForm3.Button1Click(Sender: TObject);
begin
IdTCPServer1.Active := true;
end;
{ Clientside function
Function RecordsetFromXMLStream(Stream:TStream): _Recordset;
var
RS: Variant;
begin
RS := CreateOleObject('ADODB.Recordset');
RS.Open(TStreamAdapter.Create(Stream) as IUnknown);
Result := IUnknown(RS) as _Recordset;
end;
}
Procedure RecordsetToXMLStream(const Recordset: _Recordset;Stream:TStream);
var
RS: Variant;
begin
if Recordset = nil then Exit;
RS := CreateOleObject('ADODB.Recordset');
RS := Recordset;
RS.Save(TStreamAdapter.Create(stream) as IUnknown, adPersistXML);
Stream.Position := 0;
end;
Procedure GetQueryStream(Const s,ConStr:String;ms:TMemoryStream);
var
AC:TAdoConnection;
ads:TAdodataset;
begin
AC:=TAdoConnection.Create(nil);
try
ads:=TAdodataset.Create(nil);
try
ads.Connection := AC;
AC.ConnectionString := ConStr;
ads.CommandText := s;
ads.Open;
RecordsetToXMLStream(ads.Recordset,ms);
finally
ads.Free
end;
finally
AC.Free
end;
end;
procedure TForm3.IdTCPServer1Execute(AContext: TIdContext);
var
cmd:String;
ms:TMemoryStream;
begin
CoInitialize(nil);
AContext.Connection.IOHandler.Readln(cmd);
ms:=TMemoryStream.Create;
try
GetQueryStream('Select * from Adressen',DummyConnection.ConnectionString,ms);
ms.Position := 0;
SendStream(AContext,ms);
AContext.Connection.Socket.CloseGracefully;
finally
ms.Free;
CoUninitialize;
end;
end;
end.

Timer process affect the user interface in delphi

i have developed application for read information from card reader. Here i have used timer for get the information each five second, so every five second the user interface getting slow
because it's get the information from reader. how to run the timer in background with out affecting user interface
unit frmVistorreg;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms;
type
thread1=class(TThread)
private
FEvent: THandle;
protected
procedure Execute; override;
public
procedure MyTerminate;
end;
TForm3 = class(TForm)
txt_name: TEdit;
txt_cardno.Text TEdit;
private
public
end;
var
Form3: TForm3;
implementation
{$R *.dfm}
procedure thread1.Execute;
var
idcard_info :array[0..1024*5] of byte;
flag :Integer;
portflag :Integer;
st :TStrings;
str :string;
begin
FEvent:= CreateEvent(nil, False, false, nil);
try
while not Terminated do begin
if MainForm.PortFlag=0 then
begin
Form3.Label11.Caption:='port has been successfully opened';
Form3.Label11.Font.Color :=32768;
flag := GetIdCardInfo(#idcard_info[0],1024*5,5);
str := byteArray2Str(#idcard_info[0],1024*5);
if(flag=0) then
begin
st := TStringList.Create;
try
SplitStr('^_^',str,st);
Form3.txt_name.Text := st.Strings[0];
Form3.txt_cardno.Text := st.Strings[5];
finally
st.Free;
end;
end;
end
else
begin
Form3.Label11.Caption:='Please open the port';
Form3.Label11.Font.Color:=clRed;
end;
if WaitForSingleObject(FEvent, 500) <> WAIT_TIMEOUT // 5 seconds timeout
then Terminate;
end;
finally
CloseHandle(FEvent);
end;
end;
procedure thread1.MyTerminate;
begin
SetEvent(FEvent);
end;
procedure TForm3.FormClose(Sender: TObject; var Action: TCloseAction);
var
Objthread1:thread1;
begin
Objthread1.MyTerminate;
Action := caFree;
end;
procedure TForm3.FormCreate(Sender: TObject);
var
Objthread1:thread1;
begin
Objthread1:=thread1.Create(false);
end;
end.
when i close the form have error like
Project MDIAPP.exe raised exception class EAccessViolation with message 'Access violation at address 0051B9F1 in module 'MDIAPP.exe'. Read of address 00000198'.
how can i solve this.
You need not a timer component for that, you need a background thread. A simplest solution is to use Sleep function in the thread:
unit Unit2;
interface
uses
Classes;
type
TMyThread = class(TThread)
protected
procedure Execute; override;
end;
implementation
procedure TMyThread.Execute;
begin
while not Terminated do begin
// do your processing here
Sleep(5000); // wait 5 seconds
end;
end;
end.
A better approach is to use WaitForSingleObject and an event instead of Sleep to be able to terminate your background thread immediately without 5 seconds delay:
unit Unit2;
interface
uses
Windows, Classes;
type
TMyThread = class(TThread)
private
FEvent: THandle;
protected
procedure Execute; override;
public
procedure MyTerminate;
end;
implementation
procedure TMyThread.Execute;
begin
FEvent:= CreateEvent(nil, False, False, nil);
try
while not Terminated do begin
// do your processing here
// ..
if WaitForSingleObject(FEvent, 5000) <> WAIT_TIMEOUT // 5 seconds timeout
then Terminate;
end;
finally
CloseHandle(FEvent);
end;
end;
procedure TMyThread.MyTerminate;
begin
SetEvent(FEvent);
end;
end.
To terminate TMyThread instance on closing a form call MyTerminate method from OnClose event handler of a form.
And yes, it is interesting to know what error message you receive, not just 'showing error'.

Resources