Delphi Writing Service Program summary

Source: Internet
Author: User
Tags case statement message queue

Delphi Writing Service Program summary

One, the difference between the service program and the desktop program

Windows 2000/XP/2003 supports a process called a "system service program," The difference between a system service and a desktop program is:
System services can be run without logging in, system services run under System Idle process/system/smss/winlogon/services, desktop programs run under Explorer, System services have higher privileges, System services have Sytem permissions, and desktop programs only Administrator permissions, in Delphi system service is the desktop program is once again encapsulated, both system services inherited from the desktop program. Therefore has the desktop program has the characteristic, the system service has made the improvement to the desktop program Dohandleexception, will automatically writes the exception information to the NT service log; The normal application starts with only one thread, and the service startup contains at least three threads. (Service contains three threads: Tservicestartthread service startup thread; Tservicethread service running thread; application main thread, responsible for Message loop);
Excerpt code:
Procedure Tserviceapplication.run;
Begin
.
.
.
Startthread: = Tservicestartthread.create (servicestarttable);
Try
While not Forms.Application.Terminated do
Forms.Application.HandleMessage;
Forms.Application.Terminate;
If Startthread.returnvalue <> 0 Then
Feventlogger.logmessage (Syserrormessage (Startthread.returnvalue));
Finally
Startthread.free;
End
.
.
.
End

Procedure Tservice.dostart;
Begin
Try
Status: = csstartpending;
Try
Fservicethread: = Tservicethread.create (self);
Fservicethread.resume;
Fservicethread.waitfor;
Freeandnil (Fservicethread);
Finally
Status: = csstopped;
End
Except
On E:exception do
LogMessage (Format (Sservicefailed,[sexecute, e.message]));
End
End
The TTimer can also be used in system services because the system service uses tapplication in the background to distribute the message;

Second, how to write a system service

Open the Delphi Editor and select file| in the menu new| Other ..., select the Service application item in new item, and Delphi will automatically create a new project for you based on Tserviceapplication. Tserviceapplication is a class that encapsulates the NT Service program, which contains a TService1 object and the handling, registration, and cancellation methods of the service program.
TService Property Description:
Allowpause: Is it permissible to suspend;
Allowstop: Is it permissible to stop;
Dependencies: Services dependent on the start of the service, if the dependent services do not exist can not start the service, and start the service will automatically start dependent services;
DisplayName: Service display name;
ErrorSeverity: The severity of the error;
Interactive: Whether to allow interaction with the desktop;
Loadgroup: Load group;
Name: service names;
Password: service password;
Servicestartname: Service startup name;
ServiceType: Service type;
StartType: Startup type;
Event Description:
Afterinstall: The method that is called after the service is installed;
Afteruninstall: The method that is called after the service is unloaded;
Beforeinstall: The method that is called before the service is installed;
Beforeuninstall: The method that was called before the service was unloaded;
OnContinue: The method by which the service pauses to continue calling;
OnExecute: Executes the method that the service begins to invoke;
OnPause: Method of suspending service invocation;
OnShutdown: Method to invoke when closing;
OnStart: Method of initiating service invocation;
OnStop: Method of stopping service invocation;

Iii. preparation of an amphibious service

An amphibious system service (two modes of system service and desktop program) can be implemented using the following method
Project Code:
Program Fleetreportsvr;

Uses
Svcmgr,
Forms,
Sysutils,
Windows
Svrmain in ' Svrmain.pas ' {fleetreportservice:tservice},
Appmain in ' Appmain.pas ' {fmfleetreport};

{$R *. RES}

Const
Csmutexname = ' Global\services_application_mutex ';
Var
Oneinstancemutex:thandle;
Secmem:security_attributes;
Asd:security_descriptor;
Begin
InitializeSecurityDescriptor (@aSD, security_descriptor_revision);
SetSecurityDescriptorDacl (@aSD, True, Nil, False);
Secmem.nlength: = SizeOf (security_attributes);
Secmem.lpsecuritydescriptor: = @aSD;
Secmem.binherithandle: = False;
Oneinstancemutex: = CreateMutex (@SecMem, False, csmutexname);
if (GetLastError = error_already_exists) Then
Begin
Dlgerror (' Error, program or service already running! ');
Exit;
End
If Findcmdlineswitch (' Svc ', True) or
Findcmdlineswitch (' Install ', True) or
Findcmdlineswitch (' uninstall ', True) then
Begin
SvcMgr.Application.Initialize;
SvcMgr.Application.CreateForm (Tsvsvrmain, Svsvrmain);
SvcMgr.Application.Run;
End
Else
Begin
Forms.Application.Initialize;
Forms.Application.CreateForm (Tfmfmmain, Fmmain);
Forms.Application.Run;
End
End.
Then register the service in Svrmain:
Unit Svrmain;

Interface

Uses
Windows, Messages, Sysutils, Classes, Graphics, Controls, Svcmgr, Dialogs, Msgcenter;

Type
Tsvsvrmain = Class (TService)
Procedure Servicestart (Sender:tservice; var started:boolean);
Procedure Servicestop (Sender:tservice; var stopped:boolean);
Procedure Servicebeforeinstall (Sender:tservice);
Procedure Serviceafterinstall (Sender:tservice);
Private
{Private declarations}
Public
function Getservicecontroller:tservicecontroller; Override
{Public declarations}
End

Var
Svsvrmain:tsvsvrmain;

Implementation

Const
Csregserviceurl = ' system\currentcontrolset\services\ ';
csregdescription = ' Description ';
Csregimagepath = ' ImagePath ';
csservicedescription = ' Services Sample. ';

{$R *. DFM}

Procedure ServiceController (Ctrlcode:dword); stdcall;
Begin
Svsvrmain.controller (Ctrlcode);
End

function TSvSvrMain.GetServiceController:TServiceController;
Begin
Result: = ServiceController;
End

Procedure Tsvsvrmain.servicestart (Sender:tservice;
var started:boolean);
Begin
Started: = Dmpublic.start;
End

Procedure Tsvsvrmain.servicestop (Sender:tservice;
var stopped:boolean);
Begin
Stopped: = Dmpublic.stop;
End

Procedure Tsvsvrmain.servicebeforeinstall (Sender:tservice);
Begin
Regvaluedelete (HKEY_LOCAL_MACHINE, Csregserviceurl + Name, csregdescription);
End

Procedure Tsvsvrmain.serviceafterinstall (Sender:tservice);
Begin
Regwritestring (HKEY_LOCAL_MACHINE, Csregserviceurl + Name, csregdescription,
Csservicedescription);
Regwritestring (HKEY_LOCAL_MACHINE, Csregserviceurl + Name, Csregimagepath,
PARAMSTR (0) + '-svc ');
End

End.
This way, double-clicking the program runs as a normal program and runs as a service if it is run with Service Manager.
For example, public modules:
Dmpublic, provides a start,stop method.

In the main form, call the Dmpublic.start,dmpublic.stop method.
Also in the service, call the Dmpublic.start,dmpublic.stop method.

First, how to restrict system services and desktop programs to run only one

How to restrict system services and desktop programs to run only one

In the project, add the following code to set up system services and desktop programs to run only one.
Program Fleetreportsvr;

Uses
Svcmgr,
Forms,
Sysutils,
Windows
Svrmain in ' Svrmain.pas ' {fleetreportservice:tservice},
Appmain in ' Appmain.pas ' {fmfleetreport};

{$R *. RES}

Const
Csmutexname = ' Global\services_application_mutex ';
Var
Oneinstancemutex:thandle;
Secmem:security_attributes;
Asd:security_descriptor;
Begin
InitializeSecurityDescriptor (@aSD, security_descriptor_revision);
SetSecurityDescriptorDacl (@aSD, True, Nil, False);
Secmem.nlength: = SizeOf (security_attributes);
Secmem.lpsecuritydescriptor: = @aSD;
Secmem.binherithandle: = False;
Oneinstancemutex: = CreateMutex (@SecMem, False, csmutexname);
if (GetLastError = error_already_exists) Then
Begin
Dlgerror (' Error, program or service already running! ');
Exit;
End
If Findcmdlineswitch (' Svc ', True) or
Findcmdlineswitch (' Install ', True) or
Findcmdlineswitch (' uninstall ', True) then
Begin
SvcMgr.Application.Initialize;
SvcMgr.Application.CreateForm (Tsvsvrmain, Svsvrmain);
SvcMgr.Application.Run;
End
Else
Begin
Forms.Application.Initialize;
Forms.Application.CreateForm (Tfmfmmain, Fmmain);
Forms.Application.Run;
End
End.

Ii. sharing memory between system services and desktop programs

The function used to create the kernel object almost always has a pointer to the SECURITY_ATTRIBUTES structure as its argument, and when using the CreateFileMapping function, it is usually just passing NULL for that parameter, so that a kernel object with default security can be created.
Default security means that any member of the object's management team and the creator of the object have full access to the object, and everyone else does not have access to the object. You can specify a ecurity_attributes structure, initialize it, and pass the address of the structure to that parameter.
It contains a security-related member that actually has only one, that is, Lpsecuritydescriptor. When you want to gain access to the corresponding kernel object (instead of creating a new object), you must set what you want to do with the object. If you want to access an existing file-mapped kernel object to read its data, call the OpenFileMapping function: by passing file_map_read as the first argument to OpenFileMapping, Indicates that you intend to read the file after gaining access to the file image, and before returning a valid handle value, first
Perform a security check. if (logged-in user) is allowed access to an existing file-mapped kernel object, a valid handle is returned. However, if access to the object is denied, NULL is returned.

System service-side core code:

Constructor Tpublicvars.create (Anew:boolean);
var
secmem:security_attributes;
Asd:security_descriptor;
Begin
inherited Create;
{Create a Kernel object access that any user can access}
InitializeSecurityDescriptor (@aSD, security_descriptor_revision);
SetSecurityDescriptorDacl (@aSD, True, Nil, False);
Secmem.nlength: = SizeOf (security_attributes);
Secmem.lpsecuritydescriptor: = @aSD;
Secmem.binherithandle: = False;
Fmapfile: = CreateFileMapping ($FFFFFFFF, @SecMem, Page_readwrite, 0, Csharedmemsize, csharedmemname);
Fmapfile: = openfilemapping (file_map_all_access, False, csharedmemname);
if (fmapfile = 0) then
Begin
Raise Exception.create (Syserrormessage (GetLastError));
OutputDebugString (PChar (Syserrormessage (GetLastError)));
End
Else
Begin//Success
Fsharemem: = MapViewOfFile (fmapfile, file_map_all_access, 0, 0, csharedmemsize);
OutputDebugString (PChar (Syserrormessage (GetLastError) + ', handle= ' + inttostr (Handle)));
End;
End;

destructor Tpublicvars.destroy;
Begin
UnmapViewOfFile (FSHAREMEM);
CloseHandle (Fmapfile);
inherited;
End

Desktop program Core source code:

Constructor Tpublicvars.create (Anew:boolean);
var
secmem:security_attributes;
Asd:security_descriptor;
Begin
inherited Create;
{Create a Kernel object access that any user can access}
InitializeSecurityDescriptor (@aSD, security_descriptor_revision);
SetSecurityDescriptorDacl (@aSD, True, Nil, False);
Secmem.nlength: = SizeOf (security_attributes);
Secmem.lpsecuritydescriptor: = @aSD;
Secmem.binherithandle: = False;
Fmapfile: = CreateFileMapping ($FFFFFFFF, @SecMem, Page_readwrite, 0, Csharedmemsize, csharedmemname);
Fmapfile: = openfilemapping (file_map_all_access, False, csharedmemname);
if (fmapfile = 0) then
Begin
Raise Exception.create (Syserrormessage (GetLastError));
OutputDebugString (PChar (Syserrormessage (GetLastError)));
End
Else
Begin//Success
Fsharemem: = MapViewOfFile (fmapfile, file_map_all_access, 0, 0, csharedmemsize);
OutputDebugString (PChar (Syserrormessage (GetLastError) + ', handle= ' + inttostr (Handle)));
End;
End;

destructor Tpublicvars.destroy;
Begin
UnmapViewOfFile (FSHAREMEM);
CloseHandle (Fmapfile);
inherited;
End

For detailed source code, see the source code for shared memory in Reporting Services and report COM. It is important to note that the creation of shared memory needs to be initialized in Servicestart, cannot be placed in initialization, or insufficient information will appear because initialization is code that executes before the application is initialized.

Iii. using COM components in services

Calling COM components in a service cannot be created directly in a desktop program, call CoInitialize (nil) before each creation, and call CoUninitialize when released. Example: Invoking the ADO component
Var
Qry:tadoquery;
Begin
CoInitialize (nil);
Qry: = Tadoquery.create (nil);
Try
...
Finally
Qry.free;
CoUninitialize;

First, improve the stability of Delphi program
Software quality is the lifeline of a product, but also the key to the happiness of software developers, a lot of programmers every day, because of software quality and night-long overtime, often encountered the situation is just released by the program to release the patch package. Software quality is like a nightmare, constantly chasing the programmers behind, so that they are busy, and even in the programmer spread a sentence: "Life is endless, bug."
Today we are not going to explore what can be reproduced bug, we have to reproduce the bug is not defined as a bug, only the non-reproducible bugs, you will Chafanbusi, restless. I used to develop server software in a company, the result of the program is unstable, and are some non-reproducible errors, resulting in our need to constantly send people staring at the server to run. Instability is like a demon all day in our hearts, the leadership of the constant urging, customer's incessant complaints, so that our project team all tired, suffer badly. I'm looking at countless non-reproducible bug discoveries, mainly due to the following eight reasons:
1. The variable is not initialized;
2. function return value is not initialized;
3. Errors caused by compilation optimization;
4. function recursion;
5. Message re-entry;
6. Wild hands;
7. Memory leaks;
8. Concurrency;
You will find that there are some small problems, so programmers must develop good habits in their daily development.
Second, the variable is not initialized
Delphi default initialization of the variables are: global variables, class members, other variables in the function body is not initialized, so some of the variables used to judge or loop must remember to initialize, and the enumeration type, the requested memory needs to be initialized, Pchar must add #0 at the end. For example, the following return results are likely to appear garbled.
function temppath:string;
Begin
SetLength (result, GetTempPath (0, PChar (result)));
GetTempPath (Length (Result), PChar (result));
Result: = PChar (Result);
End
The correct wording should
function temppath:string;
Begin
SetLength (result, GetTempPath (0, PChar (result)));
ZeroMemory (PChar (Result), Length (result));
GetTempPath (Length (Result), PChar (result));
Result: = PChar (Result);
End
This program is typical in the application of memory, the Pchar is not initialized, so the end may be random values, but through the zeromemory to the end of the assignment #0.
Third, the function return value is not initialized
The Exit function in Delphi uses the Exit function, and there are many functions that do not initialize the function return value when exiting, so the return value of the function returns a random value, which causes an irreversible error for the program to run. For example: The execution results of the following program will surprise you.
Procedure Notinitresult;
Var
I:integer;
function GetString (Avalue:integer): string;
Begin
If Avalue = 0 Then
Result: = ' True ';
End
Begin
For I: =-1 to 1 do
Begin
ShowMessage (GetString (i));
End
End
The result you see is: ', ' true ', ' true ', and the correct wording should be:
Procedure Notinitresult;
Var
I:integer;
function GetString (Avalue:integer): string;
Begin
If Avalue = 0 Then
Result: = ' True '
Else
Result: = ';
End
Begin
For I: =-1 to 1 do
Begin
ShowMessage (GetString (i));
End
End
Therefore, for the IF or case statement must be assigned the initial value, the above function can also be written as:
function GetString (Avalue:integer): string;
Begin
Result: = ';
If Avalue = 0 Then
Result: = ' True ';
End
function GetString (Avalue:integer): string;
Begin
Case Avalue of
0:result: = ' True ';
else Result: = ';
End
End
Iv. errors caused by compilation optimization
Now the compiler will compile the code to optimize out some of the code can not be executed, for example: Boolean type optimization is the most common one, the following example can be very good to explain the problem.
Procedure Tform1.btn1click (Sender:tobject);
Var
s:string;
Begin
If Gettrue or GetValue1 (s) Then
ShowMessage (' Hello ' + s);
End
Procedure Tform1.btn2click (Sender:tobject);
Var
s:string;
Begin
If Gettrue or GetValue2 (s) Then
ShowMessage (' Hello ' + s);
End
function TForm1.GetTrue:Boolean;
Begin
Result: = True;
End
function Tform1.getvalue1 (var s:string): Variant;
Begin
Result: = True;
S: = ' world ';
End
function Tform1.getvalue2 (var s:string): Boolean;
Begin
Result: = True;
S: = ' world ';
End
You will find that when you click Btn1, the result is "Hello Word", but when you click btn2: "Hello", this is because Gettrue returns True when you click Btn2, so the second sentence is not executed, But BTN1 is bound to execute because of the variant-to-Boolean conversion.

V. Function recursion
If there is a recursive function, you need to pay special attention to whether it will normally exit the function execution, if the execution continues, the program call stack is all eaten, resulting in an abnormal termination of the program, the following example: As long as a little btn1, the program will silently die, and there is no log, such code in service mode requires special attention , because your service is running unattended, and if this happens, your service will exit directly, without any hint, to find the problem.
Procedure Tform1.btn1click (Sender:tobject);
Procedure Recursive;
Begin
Recursive;
End
Begin
Recursive;
End
VI. Message Re-entry
The concept of message re-entry is that one message execution process has not been executed, and the same message goes into the same function processing. Message re-entry is due to the fact that in many software calls to Application.processmessage to update the interface, if an operation takes a long time, you can change to a thread to execute, or do not call the Application.processmessage function. For example, the following function can easily cause a message to be re-entered.
Procedure Tform1.btn1click (Sender:tobject);
Var
I:integer;
Begin
For I: = 0 to 10000000 do
Begin
Application.processmessages;
End
End
If you have to use Application.processmessage to update the interface, you should make sure that the message will not be delivered a second time during the execution of the function, as in this example you can prevent the message from being re-entered by using the BTN1 state, and the correct wording is:
Procedure Tform1.btn1click (Sender:tobject);
Var
I:integer;
Begin
Btn1. Enabled: = False;
For I: = 0 to 10000000 do
Begin
Application.processmessages;
End
Btn1. Enabled: = True;
End
In addition, when sending a message, you also need to pay special attention to the differences between SendMessage and PostMessage, SendMessage is the sending wait for the message processing to return, PostMessage is posted to the message buffer pool queue, immediately return (this message may not be processed) , the message needs to wait until it's time to process it.
Seven, Wild Hands
The wild pointer is not detected at compile time, only appears at runtime, the most common error of wild pointers is access violation error (AV error), the error is that you point to the physical memory is not available. The wild pointer appears mainly due to the following four kinds of causes: 1, the pointer variable is not initialized, 2, the pointer is used again after free or dispose, the pointer operation goes beyond the scope of the variable, 4, takes the address of a string, does not determine whether the string has been allocated memory.
The code determines whether the pointer is a null pointer by judging whether the value of the pointer is between 0x00000000 and 0x0000ffff, and if it is possible to use an if statement between this, the pointer is considered valid if it is not somewhere in between. Therefore, after the application or release of the pointer, the point to the address is a random value, so the IF statement can not be judged. Also in Delphi, you put the pointer to nil, translated into the assembly code is different or a bit, you can open the CPU window to view, such as:
Fm: = nil; The generated assembly is: XOR eax eax, that is, the pointer is set to 0x00000000.

Viii. memory leaks
Memory leak refers to the software in the run process for the requested memory space is not released, resulting in a larger memory footprint, the final program crashes unexpectedly, and at this time will not leave any traces, there is no system log to check. Memory leaks are also divided into two, one is the program together, and then take up the memory, will not grow with the program running, one is with the program running constantly growing, if the first can be spared, to two kinds of must be carefully checked, check the tool recommended with FASTMM, And the Delphi project Properties Compiler->use Debug Dcus and Linker->map file->detailed selected, so FASTMM can apply for memory call stack and MAP address, Great for finding memory leaks. Finding a memory leak can generally be considered in the following ways:
1. When you use Dispose to release memory, you need to add the definition information, if you do not define the information, for some pointers or string can not be released, for the structure of the body of the pointer should first release the internal pointer;
2. Use Freemem or freememory to free up memory, you can not add size information, this is because the Delphi memory manager internally know the pointer size information;
3. The override function must be inherited to release the memory requested by the parent class;
4. The requested memory to ensure the release, you can use the Try ... finally ... end to ensure the release of memory, but should eliminate this code style try ... Request Memory ... finally ... Free memory ... end;
5. System kernel object to be sure to close;
6. The application of the pointer if in some cases allocated space, remember to initialize to nil, when released to determine whether it is empty, because the release of NULL pointers will also lead to memory leaks;
7. In addition, PostMessage can also lead to memory leaks, which is sent through the PostMessage structure, released inside the message processing function, if the frequent call PostMessage, the message processing loop is not busy, will lose some of the message, caused a memory leak, the default Windows message Queue Length is 4000, if the message queue has 4,000, you can then use PostMessage delivery message, will be discarded, resulting in the application of the structure can not be released, causing memory leaks;
IX, concurrency
If the program involves multiple threads, and there is a collaborative relationship between threads, if the thread hangs dead, it is necessary to check thread synchronization, generally this kind of problem is more difficult to check, and need to understand the code execution process, it is more difficult to deal with a class of problems. With the help of some tripartite tools, such as "Procexp.exe" is a very good tool, with which he can see the state of each thread, and if a thread is parked, you can find the problem point through the map address and the call stack. such as the thread State of Excel such as:

X. Some effective recommendations
In view of these problems, we in the daily development, should pay attention to what problems, the following are some of my suggestions:
1. Explore the needs, the deeper the need to understand the quality of the code, the structure of the lighter, readability and maintenance greatly improved;
2. Test-driven development;
3. Good code style, good coding habits for the software quality has greatly improved;
4. Variables (pointers, arrays) should be initialized immediately after they are created;
5. Check the initial value of the variable, the default value error, or insufficient precision;
6. Type conversion, be sure to use as and is;
7. Check the variable overflow or underflow, array out of bounds;
8. Check I/O error, I/O is not always returned to true;
9. The data structure is good enough, do not design all-encompassing, very flexible information structures;
10. Poor code, do not think of changes and available, should be rewritten, because it is very likely to lead to gourd Dipper float;
11. Every alarm that appears in the program compilation is taken seriously, to write code without warning;
12. The use of const for parameters that do not require modification will not only improve efficiency, but also enhance security;

This example is I originally wrote a completion port demo program, did not undergo a rigorous stability check, just as how to write a sample, only for your reference, the following is a brief introduction of the completion port:

The "complete port" model is by far the most complex I/O model, especially for the need to manage a large number of sockets at the same time, using this model, can often achieve the best system performance. But only for Windows NT and Windows 2000 and above operating systems. Because of the complexity of the design, only when your application needs to manage hundreds of or even thousands of sockets at the same time, and it is hoped that the performance of the application can be linearly increased as the number of CPUs installed in the system increases, the "completion port" model is considered.
The overlapping I/O (Overlapped I/O) model enables applications to achieve better system performance. The basic design of the overlapping model is to have the application use an overlapping data structure to post one or more Winsock I/O requests at a time. For which submitted requests are made, the application can provide services to them after they are completed. This model is suitable for various Windows platforms except Windows CE.
The most challenging development completion port is the number of threads and the management of memory, after creating a completion port, you need to create one or more worker threads to service the completion port after I/O requests are posted to the completion port object. But exactly how many threads should be created, which is actually the most complex aspect of completing the port, typically using a single thread for each CPU (with the number of CPUs plus 1 or the number of cpu*2 threads). Memory allocation efficiency is low because the application in the allocation of memory, the system core needs to keep Lock/unlock, and in the case of multi-CPU, will become the entire program performance bottleneck, not with the number of CPUs increased performance, a better way to allocate more than one block of memory at a time.
Below is I write a completion port demo program, test on my computer can reach the link 5,100 customer service side, the server performance is also very good, because I write the customer service side occupies more resources, finally directly restarted, see the code. The main bottleneck of the demo program is to send this piece of message, which should be removed in the actual application.

Code: http://download.csdn.net/source/1737865

Delphi Writing Service Program summary

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.