Has anyone ever tried to attach delphi to his own windows service(32 bit app.) process under Windows Server 2008 64 bit?
When I try to do this I get the error:
Unable to create the process. The parameter is incorrect.
if anyone of you know how to do this, that help would be really appreciated.
Thanks!
Whilst you can debug a Delphi service there are a number of hoops that you need to jump through to make it work. I never bother and simply ensure that my services can run either as a service or as a standard app. When I want to debug I run as a standard app and so sidestep all the headaches.
I've hacked out all the code into a single file for the purpose of this answer, but you'd want to structure it a bit differently.
program MyService;
uses
SysUtils, Classes, Windows, Forms, SvcMgr;
type
TMyService = class(TService)
private
procedure ServiceStart(Sender: TService; var Started: Boolean);
procedure ServiceStop(Sender: TService; var Stopped: Boolean);
procedure ServicePause(Sender: TService; var Paused: Boolean);
procedure ServiceExecute(Sender: TService);
procedure ServiceContinue(Sender: TService; var Continued: Boolean);
protected
FDescription: string;
FEventLogSourceName: string;
procedure Initialise; virtual; abstract;
class function CreateRunner: TObject; virtual; abstract;
public
constructor Create(AOwner: TComponent); override;
function GetServiceController: TServiceController; override;
end;
TMyServiceClass = class of TMyService;
{ TMyService }
constructor TMyService.Create(AOwner: TComponent);
begin
inherited;
Initialise;
OnStart := ServiceStart;
OnStop := ServiceStop;
OnPause := ServicePause;
OnExecute := ServiceExecute;
OnContinue := ServiceContinue;
end;
procedure TMyService.ServiceStart(Sender: TService; var Started: Boolean);
begin
Started := True;
end;
procedure TMyService.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
Stopped := True;
end;
procedure TMyService.ServiceContinue(Sender: TService; var Continued: Boolean);
begin
ServiceStart(Sender, Continued);
end;
procedure TMyService.ServicePause(Sender: TService; var Paused: Boolean);
begin
ServiceStop(Sender, Paused);
end;
procedure TMyService.ServiceExecute(Sender: TService);
var
Runner: TObject;
begin
Runner := CreateRunner;
Try
while not Terminated do begin
ServiceThread.ProcessRequests(True);
end;
Finally
FreeAndNil(Runner);
End;
end;
var
Service: TMyService;
procedure ServiceController(CtrlCode: DWORD); stdcall;
begin
Service.Controller(CtrlCode);
end;
function TMyService.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
procedure RunAsService(ServiceClass: TMyServiceClass; var Service);
var
Application: TServiceApplication;
begin
Application := SvcMgr.Application;
Application.Initialize;
Application.CreateForm(ServiceClass, Service);
Application.Run;
end;
procedure RunAsStandardExecutable(ServiceClass: TMyServiceClass);
var
Application: TApplication;
Runner: TObject;
begin
Application := Forms.Application;
Application.Initialize;
Runner := ServiceClass.CreateRunner;
Try
while True do begin
Try
Application.HandleMessage;
Except
Application.HandleException(Application);
End;
end;
Finally
FreeAndNil(Runner);
End;
end;
procedure ServiceMain(ServiceClass: TMyServiceClass);
begin
if FindCmdLineSwitch('RunAsApp', ['-', '/'], True) then begin
RunAsStandardExecutable(ServiceClass);
end else begin
RunAsService(ServiceClass, Service);
end;
end;
begin
ServiceMain(TMyService);
end.
To use this you need to create a new class, inherited from TMyService, and implement Initialise and CreateRunner. CreateRunner is the key. In my services this creates an object which in turn opens a listening socket ready for clients to communicate over.
The standard app code is pretty basic. It doesn't even have a mechanism to terminate—it runs inside a while True loop. That doesn't matter for my debugging needs.
Did you try running the IDE as Administrator?
I've already done such process attach under Win64, but I had to run the IDE with Administrator rights, as far as I remember.
Related
The code below, via...
procedure TTimetellServiceServerMonitor.ServiceDebugLog(const AMsg: String);
const cDebugLogFile = 'd:\temp\service.log';
... outputs this debug info showing that we go through the OnCreate several times (I added the - - descriptions):
- testsvcserverMonitor /install -
S 1802 servicecreate
S 1802 AfterInstall
- start from services app -
S 1741 servicecreate
S 1741 servicestart
S 1741 MonitorThread.Start
- stop from services app -
S 1741 servicestop
- testsvcserverMonitor /uninstall -
S 1336 servicecreate
S 1336 beforeuninstall
I assign a random tag value to the service in its OnCreate and you can see that these are different.
Why does this happen, is there a bug, should I prevent it and how?
(Windows 32 bit, Delphi 10.4.2. Sydney)
.pas code:
unit USvcServerMonitor;
interface
uses
WinApi.Windows, WinApi.Messages, System.SysUtils, System.Classes, Vcl.SvcMgr, WinApi.WinSvc;
type
TMonitorServiceThread = class(TThread) // Worker thread
private
FCheckLiveEvery,
FLastLiveCheck : TDateTime;
public
procedure Execute; override;
end;
type
TApplicationMonitor = class(TService)
procedure ServiceBeforeUninstall(Sender: TService);
procedure ServiceCreate(Sender: TObject);
procedure ServiceStop(Sender: TService; var Stopped: Boolean);
// procedure ServiceExecute(Sender: TService); Not necessary, WorkerThread does the work
procedure ServiceStart(Sender: TService; var Started: Boolean);
procedure ServiceAfterInstall(Sender: TService);
private
procedure ServiceDebugLog(const AMsg: String);
public
function GetServiceController: TServiceController; override;
end;
var
MonitorThread : TMonitorServiceThread;
ApplicationMonitor: TApplicationMonitor;
implementation
{$R *.DFM}
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
ApplicationMonitor.Controller(CtrlCode);
end;
function TApplicationMonitor.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
procedure TApplicationMonitor.ServiceAfterInstall(Sender: TService);
begin
ServiceDebugLog('AfterInstall');
// StartType is stAuto, but start manually after install
end;
procedure TApplicationMonitor.ServiceBeforeUninstall(Sender: TService);
begin
ServiceDebugLog('beforeuninstall');
end;
procedure TApplicationMonitor.ServiceCreate(Sender: TObject);
begin
Self.Tag := 1000 + Random(1000); // For debugging
ServiceDebugLog('servicecreate');
end;
procedure TApplicationMonitor.ServiceStart(Sender: TService; var Started: Boolean);
begin
ServiceDebugLog('servicestart');
MonitorThread := TMonitorServiceThread.Create(true); // Suspended
ServiceDebugLog('MonitorThread.Start');
MonitorThread.Start;
Started := true;
end;
procedure TApplicationMonitor.ServiceDebugLog(const AMsg: String);
// Quick-n-dirty debugging routine
const cDebugLogFile = 'd:\temp\service.log';
var t: textfile;
begin
if not fileexists(cDebugLogFile) then
begin
assignfile(t,cDebugLogFile);
Rewrite(t);
end
else
begin
assignfile(t,cDebugLogFile);
Append(T);
end;
writeln(T,'S ' + Inttostr(self.Tag) + ' ' + AMsg);
closefile(t);
end;
procedure TApplicationMonitor.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
ServiceDebugLog('servicestop');
MonitorThread.Terminate;
Sleep(100);
MonitorThread.Free;
Sleep(100);
Stopped := True;
end;
{ TMonitorServiceThread }
procedure TMonitorServiceThread.Execute;
begin
inherited;
FLastLiveCheck := Now;
FCheckLiveEvery := 1;
while not Terminated do
begin
try
if (FCheckLiveEvery > 0) and (Now-FLastLiveCheck > FCheckLiveEvery/1440) then
begin
// Do some checks
FLastLiveCheck := Now;
end;
Sleep(500);
finally
end;
end;
end;
end.
.dfm file:
object ApplicationMonitor: TApplicationMonitor
Tag = 123
OldCreateOrder = False
OnCreate = ServiceCreate
AllowPause = False
DisplayName = 'Test Application Monitor Service'
AfterInstall = ServiceAfterInstall
BeforeUninstall = ServiceBeforeUninstall
OnStart = ServiceStart
OnStop = ServiceStop
Height = 250
Width = 400
end
TService is derived from TDataModule, so OnCreate will be called when the TService instance is created. That obviously happens when the service is going to be started, but also when it is installed and uninstalled.
So, no, it is not a bug and you also should not prevent it.
Perhaps it is just that your expectations are wrong?
I don't know why but my windows service application only receive the information from my TcpServer one time (At Windows Service Startup), thread still running but always stuck at Service1.Cliente.IOHandler.ReadBytes(FData, szProtocol, False);
Tested on a normal windows application and works fine, but when move to windows service only receive one time and stop.
PS: The thread still running.
constructor TReadingThread.Create(AClient: TIdTCPClient);
begin
inherited Create(True);
FClient := AClient;
end;
procedure TReadingThread.Execute;
begin
FreeOnTerminate := False;
while not Terminated do
begin
if Service1.Cliente.Connected then
begin
if not Service1.Cliente.IOHandler.InputBufferIsEmpty then
begin
Service1.Cliente.IOHandler.ReadBytes(FData, szProtocol, False);
if (FData <> nil) and Assigned(FOnData) then Synchronize(DataReceived);
CriaLog('Received something');
end;
end;
Sleep(1);
end;
end;
procedure TReadingThread.DataReceived;
begin
if Assigned(FOnData) then FOnData(FData);
end;
The same code at normal application works fine, but when the application it's a windows service this problem happen.
Answer for Remy, Here is how szProtocol are defined and what more that use:
type
TCommand = (
cmdConnect,
cmdDisconnect,
cmdLibera);
type
TClient = record
HWID : String[40];
Msg : String[200];
end;
const
szClient = SizeOf(TClient);
type
TProtocol = record
Command: TCommand;
Sender: TClient;
DataSize: Integer;
end;
const
szProtocol = SizeOf(TProtocol);
My TThread Structure who i use to receive informations was defined as:
type
TDataEvent = procedure(const LBuffer: TIdBytes) of object;
TReadingThread = class(TThread)
private
FClient : TIdTCPClient;
FData : TIdBytes;
FOnData : TDataEvent;
procedure DataReceived;
protected
procedure Execute; override;
public
constructor Create(AClient: TIdTCPClient); reintroduce;
property OnData: TDataEvent read FOnData write FOnData;
end;
This procedure is who show me what are received from server and i do some actions.
procedure TService1.DataReceived(const LBuffer: TIdBytes);
type
PTBytes = ^TBytes;
PTIdBytes = ^TIdBytes;
var
LDataSize : Integer;
LProtocol : TProtocol;
begin
LProtocol := BytesToProtocol(PTBytes(#LBuffer)^);
case LProtocol.Command of
cmdLibera:
begin
// action
end;
end;
end;
and the others functions from TTHread structure:
constructor TReadingThread.Create(AClient: TIdTCPClient);
begin
inherited Create(True);
FClient := AClient;
end;
procedure TReadingThread.DataReceived;
begin
if Assigned(FOnData) then FOnData(FData);
end;
I know the code works because as i said i use it on a normal application (who isn't a service) and all works perfectly, but at service it don't work.
The answer is, just add a "packed" and solved the problem, Thanks Remy.
Source: Delphi XE10; Os: W10; Database server: FB 2_5_1;
I created client application to connect more than one database. My application create TSource->Create TThread -> create DataModule with FDConnection.
Every TSource have own TThread which have own DataModule;
To tests I have two definitions: HostLocal and HostRemote;
My Test:
a)Run: Connection.Open for HostRemote;
b)Next Run: Connection.Open for HostLocal;
When HostRemote is unavailable than HostLocal wait for I dont know for what.
Why unavailable connection blocked another correct connection?
EDIT:
Database Server was updated to 3.0.0
Code:
begin
Application.Initialize;
Application.CreateForm(TFormMain, FormMain);
Application.Run;
end.
unit UnitFormMain;
procedure TFormMain.FormCreate(Sender: TObject);
begin
Source0 := TFormSource.Create(Self);
Source1 := TFormSource.Create(Self);
end;
procedure TFormMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FreeAndNil( Source0 );
FreeAndNil( Source1 );
end;
procedure TFormMain.Button1Click(Sender: TObject);
begin
Source0.Show;
Source1.Show;
Source0.ThStart( 0 );
Source1.ThStart( 1 );
end;
unit UnitFormSource;
TFormSource = class(TForm)
Memo: TMemo;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
th: TMyThread;
public
procedure ThStart( value: Integer );
procedure MyTerminate( Sender: TObject );
end;
...
procedure TFormSource.FormCreate(Sender: TObject);
begin
th := TMyThread.Create( TRUE );
th.OnTerminate := MyTerminate;
th.Priority := TThreadPriority.tpLower;
th.FreeOnTerminate := TRUE;
end;
procedure TFormSource.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FreeAndNil( th );
Action := TCloseAction.caFree;
end;
procedure TFormSource.ThStart(value: Integer);
begin
th.SetSwitch( value );
th.Resume;
Memo.Lines.Add( TimeToStr(Now) + ' Start' );
end;
procedure TFormSource.MyTerminate(Sender: TObject);
begin
Memo.Lines.Add( TimeToStr(Now) + ' Terminate' );
end;
unit UnitThread;
TMyThread = class(TThread)
private
Switch: Integer;
dm: TDMFireBird;
protected
procedure Execute; override;
public
constructor Create(CreateSuspended: Boolean);
destructor Destroy; override;
procedure SetSwitch(value: Integer);
end;
...
constructor TMyThread.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
dm := TDMFireBird.Create(nil);
Switch := -1;
end;
destructor TMyThread.Destroy;
begin
FreeAndNil(dm);
inherited;
end;
procedure TMyThread.Execute;
begin
with dm.FDConnection.Params do
begin
Clear;
Add('DriverID=FB');
end;
case Switch of
0:
with dm.FDConnection.Params do
Add('Protocol=Local');
1:
with dm.FDConnection.Params do
begin
Add('Protocol=TCPIP');
Add('Server=10.0.0.1'); // unavailable connection - server not exist
Add('Port=3050');
end;
end;
with dm.FDConnection.Params do
begin
Add('Database=D:\temp\test.fdb');
Add('User_Name=SYSDBA');
Add('Password=masterkey');
end;
try
dm.FDConnection.Open;
except
// to-do: update error message
end;
end;
procedure TMyThread.SetSwitch(value: Integer);
begin
Switch := value;
end;
unit UnitDMFireBird;
TDMFireBird = class(TDataModule)
FDConnection: TFDConnection;
FDTransaction: TFDTransaction;
private
{ Private declarations }
public
{ Public declarations }
end;
Test no.1
Code:
Source0.ThStart( 0 );
// Source1.ThStart( 1 );
Result for Source0:
14:46:20 Start
14:46:20 Terminate
Result for Source1: no result
Comment: At the same time first connection was opened;
Test no.2
Code:
Source0.ThStart( 0 );
Source1.ThStart( 1 );
Result for Source0:
14:48:16 Start
14:48:40 Terminate
Result for Source1:
14:48:16 Start
14:48:40 Terminate
Comment: Terminate for both at the same time.
My question. Why one blocked another ? Why (for test no.2) first correct connection wait for second unavailable connection? In test no.2 first connection should to open like in test no.1 -> fast, without wait.
I am struggling with service application in Delphi but no major success so far. I tried to recreate this project, but it doesn't seem to work properly. File is created, but date and time aren't added to file every 10 seconds. I also don't see a message popping up from my ShowMessage. I successfully install and start service application.
Here is my code:
unit TMS;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.SvcMgr, Vcl.Dialogs,
Vcl.ExtCtrls;
type
TWorkflow = class(TService)
Timer1: TTimer;
procedure ServiceExecute(Sender: TService);
procedure Timer1Timer(Sender: TObject);
procedure ServiceBeforeInstall(Sender: TService);
private
{ Private declarations }
public
function GetServiceController: TServiceController; override;
{ Public declarations }
end;
var
Workflow: TWorkflow;
implementation
{$R *.dfm}
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
Workflow.Controller(CtrlCode);
end;
function TWorkflow.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
procedure TWorkflow.ServiceBeforeInstall(Sender: TService);
begin
Interactive := True;
end;
procedure TWorkflow.ServiceExecute(Sender: TService);
begin
while not Terminated do
begin
ServiceThread.ProcessRequests(True);
end;
end;
procedure TWorkflow.Timer1Timer(Sender: TObject);
const
FileName = 'D:\Projekti\EMBRACADERO\TMSWorkflow\Win32\Debug\Log.txt';
var
F : TextFile;
begin
AssignFile(F, FileName);
if FileExists(FileName) then
Append(F)
else
Rewrite(F);
Writeln(F, DateTimeToStr(Now), ' ', DiskFree(0));
ShowMessage(DateTimeToStr(Now));
CloseFile(F);
end;
end.
Could somebody give me an example of a service application with threads maybe or service with visual components included?
UPDATE1:
It is working with following code for inserting some data in database every 3 seconds.
private
thread : TThread;
procedure TWorkflow.InsertInDatabase;
begin
FDTransaction1.StartTransaction;
try
FDQuery1.Execute;
FDTransaction1.Commit;
except
FDTransaction1.Rollback;
end;
end;
procedure TWorkflow.ServiceExecute(Sender: TService);
begin
while not Terminated do
begin
ServiceThread.ProcessRequests(False);
InsertInDatabase();
thread.sleep(3000);
end;
end;
procedure TWorkflow.ServiceStart(Sender: TService; var Started: Boolean);
begin
thread := TThread.Create;
end;
procedure TWorkflow.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
FreeAndNil(thread);
end;
The TTimer code you showed is fine (though your OnExecute event is redundant can should be completely removed), except for the call to ShowMessage(), which you cannot use in a service at all (the TService.Interactive property has no effect on Windows Vista+). If you must display a popup message box from a service (which you should strive not to), you must use the Win32 API MessageBox() with the MB_SERVICE_NOTIFICATION flag specified, or use WTSSendMessage() instead. Otherwise, you have to delegate any UI to a separate non-service process that the service spawns and/or communicates with as needed.
Your TThread code, on the other hand, is completely wrong. It should be more like this instead:
unit TMS;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, Vcl.SvcMgr;
type
TWorkflowThread = class(TThread)
protected
procedure Execute; override;
end;
TWorkflow = class(TService)
FDTransaction1: TFDTransaction;
FDQuery1: TFDQuery;
procedure ServiceStart(Sender: TService; var Started: Boolean);
procedure ServiceStop(Sender: TService; var Stopped: Boolean);
procedure ServiceShutdown(Sender: TService);
private
{ Private declarations }
thread: TWorkflowThread;
procedure InsertInFile;
procedure InsertInDatabase;
public
function GetServiceController: TServiceController; override;
{ Public declarations }
end;
var
Workflow: TWorkflow;
implementation
{$R *.dfm}
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
Workflow.Controller(CtrlCode);
end;
function TWorkflow.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
procedure TWorkflow.InsertInFile;
const
FileName = 'D:\Projekti\EMBRACADERO\TMSWorkflow\Win32\Debug\Log.txt';
var
F : TextFile;
begin
try
AssignFile(F, FileName);
try
if FileExists(FileName) then
Append(F)
else
Rewrite(F);
Writeln(F, DateTimeToStr(Now), ' ', DiskFree(0));
//ShowMessage(DateTimeToStr(Now));
finallly
CloseFile(F);
end;
except
end;
end;
procedure TWorkflow.InsertInDatabase;
begin
try
FDTransaction1.StartTransaction;
try
FDQuery1.Execute;
FDTransaction1.Commit;
except
FDTransaction1.Rollback;
end;
except
end;
end;
procedure TWorkflow.ServiceStart(Sender: TService; var Started: Boolean);
begin
thread := TWorkflowThread.Create(False);
Started := True;
end;
procedure TWorkflow.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
ServiceShutdown(Sender);
Stopped := True;
end;
procedure TWorkflow.ServiceShutdown(Sender: TService);
begin
if Assigned(thread) then
begin
thread.Terminate;
while WaitForSingleObject(thread.Handle, WaitHint-100) = WAIT_TIMEOUT do
ReportStatus;
FreeAndNil(thread);
end;
end;
procedure TWorkflowThread.Execute;
begin
while not Terminated do
begin
Workflow.InsertInFile;
Workflow.InsertInDatabase;
TThread.Sleep(3000);
end;
end;
end.
Your timer code will not execute because timers rely on a window handle and message pump that TService does not provide. Furthermore, TTimer is not thread-safe because in uses the VCL's AllocateHwnd() function which is not thread-safe and should not be used outside the context of the main thread. Generally, when writing service applications you would spawn a worker thread to perform the main logic.
If you need a thread-safe timer, I would suggest you use a different timer mechanism, such as WaitForSingleObject()
Assitionally, services should not contain any visual controls as they should not interact with the desktop at all.
Could somebody give me an example of service application with threads.
If your code is doing all its work in a thread, you are almost done.
Just start your thread in the service start event. For debugging, run the thread in a small (console) program.
Instead of a timer, let your main thread sleep for a while.
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.