The Environment
I've created a web server in Delphi using Indy component TidHTTPServer. I'm using Delphi XE2 which came with Indy version 10.5.8. The
server is running as a desktop app with a form that displays a log of the connections and their requests. It is running on Windows 7
Professional. Requests are for SQL data from a Firebird database. The response is JSON. All traffic is HTTP.
The Challenge
When I was testing it with a small number of users everything worked great. Now that I have rolled it out to about 400 users there are
communication problems. The server stops responding to requests and the only way I can get it to respond again is to reboot the machine it is running on and then restart it. The need to reboot occurs more frequently during
high volume times.
The Symptoms
Using Windows netstat I have noticed that whenever a TCP connection of type CLOSE_WAIT occurs the server stops responding to requests and I have to reboot again
The Test Procedure
I have been able to simulate this hanging even when there is no traffic on the server. I created a web page that sends multiple requests with
a delay between each request.
The web page let's me specify the number of requests to make, how long to wait between each request, and how long to wait before timing out. Even at one millisecond between requests the server seems to respond without issue.
The Test Results
If I set the time out period of each request to a very small number, like 1 msec, I can make my Delphi HTTP Server hang. At a 1 msec timeout requests to my server fail every time, as I would expect. The time out is so short my server can't possibly respond quickly enough.
What I don't understand is that after I force this timeout at the client side, even a relatively small number of requests (fewer than 50), my Delphi web server no longer responds to any requests. When I run netstat on the server machine there are a number of CLOSE_WAIT socket connections. Even after an hour and after closing my server the CLOSE_WAIT socket connections persist.
The Questions
What is going on? Why does my Delphi Indy idHTTPServer stop responding when there are (even just one) CLOSE_WAIT socket connection? The CLOSE_WAITs don't go away and the server does not start responding again. I have to reboot.
What am I not doing?
Here is the results of netstat command showing CLOSE_WAITs:
C:\Windows\system32>netstat -abn | findstr 62000
TCP 0.0.0.0:62000 0.0.0.0:0 LISTENING
TCP 10.1.1.13:62000 9.49.1.3:57036 TIME_WAIT
TCP 10.1.1.13:62000 9.49.1.3:57162 CLOSE_WAIT
TCP 10.1.1.13:62000 9.49.1.3:57215 CLOSE_WAIT
TCP 10.1.1.13:62000 9.49.1.3:57244 CLOSE_WAIT
TCP 10.1.1.13:62000 9.49.1.3:57263 CLOSE_WAIT
TCP 10.1.1.13:62000 9.49.1.3:57279 ESTABLISHED
TCP 10.1.1.13:62000 104.236.216.73:59051 ESTABLISHED
Here is the essence of my web server:
unit MyWebServer;
interface
Uses
...
Type
TfrmWebServer = class(TForm)
...
IdHTTPServer: TIdHTTPServer;
...
procedure IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
procedure IdHTTPServerDisconnect(AContext: TIdContext);
procedure btnStartClick(Sender: TObject);
...
dbFirebird : TIBDatabase;
txFireird : TIBTransaction;
...
private
function CreateSomeResponseStringData: string;
end;
implementation
procedure TfrmWebServer.btnStartClick(Sender: TObject);
begin
{set the IP's and proit to listen on}
IdHTTPServer.Bindings.Clear;
IdHTTPServer.Bindings.Add.IP := GetSetting(OPTION_TCPIP_ADDRESS);
IdHTTPServer.Bindings.Add.Port := Str2Int(GetSetting(OPTION_TCPIP_PORT));
{start the web server}
IdHTTPServer.Active := TRUE;
...
dbFirebird.Transactrion := txFirebird;
...
end;
procedure TfrmWebServer.IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
qryFirebird : TIBSql;
function CreateSomeResponseStringData: string;
begin
qryFirebird := NIL;
qryFirebird := TIBSql.Create(IdHTTPServer);
qryFirebird.Database := dbFirebird;
dbFirebird.Connected := FALSE;
dbFirebird.Connected := TRUE;
qryFirebird.Active := TRUE;
Result := {...whatever string will be returned}
end;
function CreateAnErrorResponse: string;
begin
Result := {...whatever string will be returned}
end;
begin
try
AResponseInfo.ContentText := CreateSomeResponseStringData;
{Clean up: What do I do here to make sure that the connection that was served is:
- properly closed so that I don't run out of resourses?
- anything that needs to be cleaned up is freed so no memory leaks
- TIME_WAIT, CLOSE_WAIT, any other kind of _WAITs are not accumulating?}
except;
AResponseInfo.ContentText := CreateAnErrorResponse;
end;
qryFirebird.Free;
end;
procedure TfrmWebServer.IdHTTPServerDisconnect(AContext: TIdContext);
begin
{Maybe I do the "Clean Up" here? I tried Disconnect as shown but still lots of
TIME_WAIT tcp/ip connections accumulate. even after the app is closed}
AContext.Connection.Disconnect;
end;
end.
There are at least two major issues with this code that could cause the crashing:
The database and transaction objects are global to all threads created by IdHTTPServer. When you disconnect the database it would disconnect for all threads.
If there is a run time error assigning content text this line AResponseInfo.ContentText := CreateAnErrorResponse; is not in an exception block.
Here is how I would fix this:
...
procedure TfrmWebServer.btnStartClick(Sender: TObject);
begin
{set the IP's and port to listen on}
IdHTTPServer.Bindings.Clear;
IdHTTPServer.Default.Port := Str2Int(GetSetting(OPTION_TCPIP_PORT));
IdHTTPServer.Bindings.Add.IP := GetSetting(OPTION_TCPIP_ADDRESS);
{start the web server}
IdHTTPServer.Active := TRUE;
...
end;
procedure TfrmWebServer.IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
{make these local to each thread}
qryFirebird : TIBSql;
dbFirebird : TIBDatabase;
txFirebird : TIBTransaction;
function CreateSomeResponseStringData: string;
begin
dbFirebird := TIBDatbase.Create(IdHTTPServer);
txFirebird := TIBTransaction.Create(IdHTTPServer);
qryFirebird := TIBSql.Create(IdHTTPServer);
dbFirebird.Transaction := txFirebird;
qryFirebird.Database := dbFirebird;
...Add params that do the log in to database
dbFirebird.Connected := TRUE;
qryFirebird.Active := TRUE;
Result := {...whatever string will be returned}
end;
function CreateAnErrorResponse: string;
begin
Result := {...whatever string will be returned}
end;
begin
try
try
...
AResponseInfo.ContentText := CreateSomeResponseStringData;
...
except;
try
AResponseInfo.ContentText := CreateAnErrorResponse;
except
{give up}
end;
end;
finaly
qryFirebird.Free;
dbFirebird.Free;
txFirebird.Free;
end;
end;
end.
Good morning to all.I am building a Delphi TCP server/client application using Indy 10.0.52,with TIdTCPClient and TIdTCPServer. I have problem with receiving asynchronous responses from server. Here's my part of sample code:
Client:
procedure TfrmMain.ClientSend();
begin
tcpClient.IOHandler.WriteLn('100|HelloServer');
if tcpClient.IOHandler.ReadLn() = '1100' then //Here I get 1100
begin
tcpClient.IOHandler.WriteLn('200|Are_you_ready_for_data?');
if tcpClient.IOHandler.ReadLn() = '1200' then
begin
end;
end;
end;
Server:
procedure TfrmMain.tcpServerExecute(AContext: TIdContext);
var command:Integer;
var received,value:String;
begin
received := AContext.Connection.IOHandler.ReadLn;
command := StrToInt(SomeSplitFunction(received,'first_part')); //Here I get 100
value := SomeSplitFunction(received,'second_part'); //Here I get 'HelloServer'
case command of
100:begin
//Do something with value...
AContext.Connection.IOHandler.WriteLn('1100');
end;
200:begin
//Do something with value...
AContext.Connection.IOHandler.WriteLn('1200');
end;
end;
end;
The problem is that the case 200 on tcpServerExecute is never executed, therefore the second ReadLn on client site is never read.Is multiple asynchronous data sending in single procedure supported?I have came across several examples with simple Indy TCP Server/Client applications, but I'm little stuck here.Just to mention that connection is working and I connect to server without problems.
I am modifying an Indy10 TCP/IP application and I would like your suggestions/opinions/sample code on implementing a client side function that does the following
a) on application startup when the splash screen is displayed, it verifies that the client computer has internet access and the TCP Server is up and running and waiting for communication. If this is not the case, the application should terminate.
b) does (a) above before ANY data exchange between the client and the server
In addition, does the server need to repeatedly broadcast some sort of message to inform potential clients that it is up and running?
Thanks for your assistance.
How to verify if it's possible to connect to a TCP server ?
To your first question; definitely wrap the connection attempt to a separate thread, which you'll run when your splash screen shows. In that thread you can simply try to Connect and catch the exception. If the exception is raised, the connection failed. If not, you were able to connect. For the notification about this state I would use custom messages, which you'll send to a splash screen form like shown in the following pseudocode:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdTCPClient;
const
WM_CONNECTION_NOTIFY = WM_USER + 1;
SC_CONNECTION_FAILURE = 0;
SC_CONNECTION_SUCCESS = 1;
type
TConnThread = class(TThread)
private
FMsgHandler: HWND;
FTCPClient: TIdTCPClient;
protected
procedure Execute; override;
public
constructor Create(const AHost: string; APort: Word; ATimeout: Integer;
AMsgHandler: HWND); reintroduce;
destructor Destroy; override;
end;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FConnThread: TConnThread;
procedure WMConnectionNotify(var AMessage: TMessage); message WM_CONNECTION_NOTIFY;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TConnThread }
constructor TConnThread.Create(const AHost: string; APort: Word;
ATimeout: Integer; AMsgHandler: HWND);
begin
inherited Create(False);
FreeOnTerminate := False;
FMsgHandler := AMsgHandler;
FTCPClient := TIdTCPClient.Create(nil);
FTCPClient.Host := AHost;
FTCPClient.Port := APort;
FTCPClient.ConnectTimeout := ATimeout;
end;
destructor TConnThread.Destroy;
begin
FTCPClient.Free;
inherited;
end;
procedure TConnThread.Execute;
begin
try
FTCPClient.Connect;
PostMessage(FMsgHandler, WM_CONNECTION_NOTIFY, 0, SC_CONNECTION_SUCCESS);
except
PostMessage(FMsgHandler, WM_CONNECTION_NOTIFY, 0, SC_CONNECTION_FAILURE);
end;
end;
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
FConnThread := TConnThread.Create('123.4.5.6', 123, 5000, Handle);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FConnThread.Free;
end;
procedure TForm1.WMConnectionNotify(var AMessage: TMessage);
begin
case AMessage.LParam of
// the connection failed
SC_CONNECTION_FAILURE: ;
// the connection succeeded
SC_CONNECTION_SUCCESS: ;
end;
end;
end.
Does the server need to repeatedly broadcast some sort of message to inform potential clients that is running ?
No, this works in a different direction - client asks server if it's running. It's like that simply because server doesn't know clients, but client knows server.
On "does the server need to repeatedly broadcast some sort of message":
There are systems (servers, services) which advertise their location (IP adress, port number) and even additional information (for example a status) to interested clients actively using IP Multicast.
It is easy to implement both server- and client side with Internet Direct (Indy) UDP components.
Here is a IP multicast example for Delphi for the open source message broker Apache ActiveMQ with full source code:
Discover ActiveMQ brokers with Delphi XE4 and Indy 10.6
For some specific needs i need to create procedure that waits for socket request (or answer) in dll:
TForm1 = class(TForm)
ServerSocket1: TServerSocket;
......
procedure MyWaitProc; stdcall;
begin
Go := false;
while not Go do
begin
// Wating...
// Application.ProcessMessages; // Works with this line
end;
end;
procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
begin
MessageBoxA(0, PAnsiChar('Received: '+Socket.ReceiveText), '', MB_OK);
Go := true;
end;
exports
MyWaitProc;
When I call Application.ProcessMessages everything works fine: application waits for request and then continues. But in my case calling Application.ProcessMessages causes to unlocking main form on host application (not dll's one). When I don't call Application.ProcessMessages application just hangs couse it cannot handle message...
So, how to create such a procedure that's wating for socket answer ?
Maybe there a way to wait for socket answer without using Application.ProcessMessages ?
EDIT
I also tried to use TIdTCPServer, for some reasons, the result is the same.
TForm1 = class(TForm)
IdTCPServer1: TIdTCPServer;
.....
procedure MyWaitProc; stdcall;
begin
Go := false;
while not Go do
begin
// Waiting ...
// Application.ProcessMessages;
end;
end;
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
s: string;
begin
s := AContext.Connection.Socket.ReadString(1);
AllText := AllText + s;
Go := True;
end;
TServerSocket runs in non-blocking mode by default, which depends on processing window messages. To remove that dependancy, you have to switch it to blocking mode instead.
TIdTCPServer runs in blocking mode exclusively, so no window messages. If you are having a problem with it, then you are misusing it. For example, in your TServerSocket code, you do not set Go = True until after a response has been received, but in your TServerSocket code you are setting Go = True before reading a response instead.
As an alternative, have a look at Indy's TIdSimpleServer component. TIdSimpleServer is synchronous and only accepts 1 connection at a time, whereas TIdTCPServer is asynchronous and accepts many connections at a time. For example:
TForm1 = class(TForm)
ServerSocket: TIdSimpleServer;
procedure MyWaitProc; stdcall;
var
s: String;
begin
ServerSocket.Listen;
s := ServerSocket.IOHandler.ReadLn;
ServerSocket.Disconnect;
MessageBox(0, PChar('Received: '+s), '', MB_OK);
end;
exports
MyWaitProc;
Rather than creating a loop that occasionally calls Application.ProcessMessages you can create a descendant of TThread and move the socket request to the TThread.Execute method. Use TThread.OnTerminate to notify your form(or any other class) when the thread has completed its work.
There is sample code which gives more details about how to use TThread.
There are several other 3rd party threading libraries that either provide more flexibility or are easier to use than TThread and I would highly recommend any of them over TThread if you are new to multi-threading.
Note: There are some serious side-effects to using Application.ProcessMessages. You are seeing one of them in your code with the dll unlocking the application's mainform. It breaks the single-threaded UI model the VCL is build upon. ProcessMessages has its place but using threads is more appropriate for the situation you're describing.
var Slowpoke: TMyLongRunningProcessThread;
procedure MyWaitProc(Completed:TNotifyEvent)
begin
Slowpoke := TMyLongRunningProcessThread.Create(True);
Slowpoke.FreeOnTerminate := True;
Slowpoke.OnTerminate := Completed;
Slowpoke.Resume;
end;
MyWaitProc returns immediately after starting the thread so the GUI is free to respond to user actions. When the thread terminates it calls the event handler pointed to by Completed.
Obviously if you need to retrieve data from the thread you'll want to either have the thread write to an accessible memory location before it Frees itself or remove the FreeOnTerminate so the data can be retreived from the thread through a property.
I a have a multithread application (MIDAS) that makes uses of windows messages to communicate with itself.
MAIN FORM
The main form receives windows messages sent by the RDM
LogData(‘DataToLog’)
Because windows messages are used they have the following attributes
Received messages are Indivisible
Received messages are Queued in the order they are sent
QUESTION:
Can you Suggest a better way doing this without using windows messages ?
MAIN FORM CODE
const
UM_LOGDATA = WM_USER+1002;
type
TLogData = Record
Msg : TMsgNum;
Src : Integer;
Data : String;
end;
PLogData = ^TLogData;
TfrmMain = class(TForm)
//
private
procedure LogData(var Message: TMessage); message UM_LOGDATA;
public
//
end;
procedure TfrmMain.LogData(var Message: TMessage);
var LData : PLogData;
begin
LData := PLogData(Message.LParam);
SaveData(LData.Msg,LData.Src,LData.Data);
Dispose(LData);
end;
RDM CODE
procedure TPostBoxRdm.LogData(DataToLog : String);
var
WMsg : TMessage;
LData : PLogData;
Msg : TMsgNum;
begin
Msg := MSG_POSTBOX_RDM;
WMsg.LParamLo := Integer(Msg);
WMsg.LParamHi := Length(DataToLog);
new(LData);
LData.Msg := Msg;
LData.Src := 255;
LData.Data := DataToLog;
WMsg.LParam := Integer(LData);
PostMessage(frmMain.Handle, UM_LOGDATA, Integer(Msg), WMsg.LParam);
end;
EDIT:
Why I want to get rid of the windows messages:
I would like to convert the application into a windows service
When the system is busy – the windows message buffer gets full and things slows down
Use Named Pipes. If you don't know how to use them, then now is the time to learn.
With named pipes, you can send any type of data structure (as long as both the server and the client know what that data structure is). I usually use an array of records to send large collections of info back and forth. Very handy.
I use Russell Libby's free (and open-source) named pipe components. Comes with a TPipeServer and a TPipeClient visual component. They make using named pipes incredibly easy, and named pipes are great for inter-process communication (IPC).
You can get the component here. The description from the source is: // Description : Set of client and server named pipe components for Delphi, as
// well a console pipe redirection component.
Also, Russell helped me out on Experts-Exchange with using an older version of this component to work in a console app to send/receive messages over named pipes. This may help as a guide in getting you up and running with using his components. Please note, that in a VCL app or service, you don't need to write your own message loop as I did in this console app.
program CmdClient;
{$APPTYPE CONSOLE}
uses
Windows, Messages, SysUtils, Pipes;
type
TPipeEventHandler = class(TObject)
public
procedure OnPipeSent(Sender: TObject; Pipe: HPIPE; Size: DWORD);
end;
procedure TPipeEventHandler.OnPipeSent(Sender: TObject; Pipe: HPIPE; Size: DWORD);
begin
WriteLn('On Pipe Sent has executed!');
end;
var
lpMsg: TMsg;
WideChars: Array [0..255] of WideChar;
myString: String;
iLength: Integer;
pcHandler: TPipeClient;
peHandler: TPipeEventHandler;
begin
// Create message queue for application
PeekMessage(lpMsg, 0, WM_USER, WM_USER, PM_NOREMOVE);
// Create client pipe handler
pcHandler:=TPipeClient.CreateUnowned;
// Resource protection
try
// Create event handler
peHandler:=TPipeEventHandler.Create;
// Resource protection
try
// Setup clien pipe
pcHandler.PipeName:='myNamedPipe';
pcHandler.ServerName:='.';
pcHandler.OnPipeSent:=peHandler.OnPipeSent;
// Resource protection
try
// Connect
if pcHandler.Connect(5000) then
begin
// Dispatch messages for pipe client
while PeekMessage(lpMsg, 0, 0, 0, PM_REMOVE) do DispatchMessage(lpMsg);
// Setup for send
myString:='the message I am sending';
iLength:=Length(myString) + 1;
StringToWideChar(myString, wideChars, iLength);
// Send pipe message
if pcHandler.Write(wideChars, iLength * 2) then
begin
// Flush the pipe buffers
pcHandler.FlushPipeBuffers;
// Get the message
if GetMessage(lpMsg, pcHandler.WindowHandle, 0, 0) then DispatchMessage(lpMsg);
end;
end
else
// Failed to connect
WriteLn('Failed to connect to ', pcHandler.PipeName);
finally
// Show complete
Write('Complete...');
// Delay
ReadLn;
end;
finally
// Disconnect event handler
pcHandler.OnPipeSent:=nil;
// Free event handler
peHandler.Free;
end;
finally
// Free pipe client
pcHandler.Free;
end;
end.
Option 1: Custom Message Queue
You can build a custom message queue, and push messages to the queue, sort the queue based on business rules, and pop messages from queue from the main thread for processing. Use a critical section for synchronization.
Option 2: Callbacks
Use callbacks to send data back and forth from the threads. Again, use a critical section for synchronization.
OmniThreadLibrary contains very efficient message queue in OtlComm.pas unit.
Documentation is not very good at the moment (start here) but you can always use the forum.
Yes – Gabr you can use windows messages in a service.
==============================
Before Windows Vista, you could have configured your service to interact with the desktop. That makes the service run on the same desktop as a logged-in user, so a program running as that user could send messages to your service's windows. Windows Vista isolates services, though; they can't interact with any user's desktop anymore.
=============================
A Quote from Rob Kennedy answer to ‘TService won’t process messages’
But I will not able to use 'frmMain.Handle' to post messages from the RDM to the main form in windows Vista.
All I need to do is find a different way of posting & receive the message
Windows Messages CAN still be used in Windows Vista! The issue at hand is that a technology in vista called User Interface Privilege Isolation (UIPI) prevents processes with a lower integrity level (IL) from sending messages to a proccess with a high IL (e.g. a windows service has a high IL and user-mode apps have medium IL).
However, this can be bypassed and medium IL apps can be allowed to send wm's to high IL processes.
Wikipedia says it best:
UIPI is not a security boundary, and does not aim to protect against
all shatter attacks. UI Accessibility
Applications can bypass UIPI by
setting their "uiAccess" value to TRUE
as part of their manifest file. This
requires the application to be in the
Program Files or Windows directory, as
well as to be signed by a valid code
signing authority, but these
requirements will not necessarily stop
malware from respecting them.
Additionally, some messages are still allowed through, such as
WM_KEYDOWN, which allows a lower IL
process to drive input to an elevated
command prompt.
Finally, the function
ChangeWindowMessageFilter allows a
medium IL process (all non-elevated
processes except Internet Explorer
Protected Mode) to change the messages
that a high IL process can receive
from a lower IL process. This
effectively allows bypassing UIPI,
unless running from Internet Explorer
or one of its child processes.
Someone over at Delphi-PRAXIS (link is in German. Use Google to Translate the page) has already tackled this problem and posted their code using ChangeWindowMessageFilter. I believe their issue is that WM_COPYDATA would not work on Vista until they modified their code to bypass UIPI for WM_COPYDATA.
Original Link (German)
unit uMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, StdCtrls, uallHook, uallProcess, uallUtil, uallKernel;
type
TfrmMain = class(TForm)
lbl1: TLabel;
tmrSearchCondor: TTimer;
mmo1: TMemo;
procedure FormCreate(Sender: TObject);
procedure tmrSearchCondorTimer(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private-Deklarationen }
fCondorPID : DWord;
fInjected : Boolean;
fDontWork : Boolean;
procedure SearchCondor;
procedure InjectMyFunctions;
procedure UnloadMyFunctions;
function GetDebugPrivileges : Boolean;
procedure WriteText(s : string);
procedure WMNOTIFYCD(var Msg: TWMCopyData); message WM_COPYDATA;
public
{ Public-Deklarationen }
end;
var
frmMain: TfrmMain;
ChangeWindowMessageFilter: function (msg : Cardinal; dwFlag : Word) : BOOL; stdcall;
implementation
{$R *.dfm}
type Tmydata = packed record
datacount: integer;
ind: boolean;
end;
const cCondorApplication = 'notepad.exe';
cinjComFuntionsDLL = 'injComFunctions.dll';
var myData : TMydata;
procedure TfrmMain.WMNOTIFYCD(var Msg: TWMCopyData);
begin
if Msg.CopyDataStruct^.cbData = sizeof(TMydata) then
begin
CopyMemory(#myData,Msg.CopyDataStruct^.lpData,sizeof(TMyData));
WriteText(IntToStr(mydata.datacount))
end;
end;
procedure TfrmMain.WriteText(s : string);
begin
mmo1.Lines.Add(DateTimeToStr(now) + ':> ' + s);
end;
procedure TfrmMain.InjectMyFunctions;
begin
if not fInjected then begin
if InjectLibrary(fCondorPID, PChar(GetExeDirectory + cinjComFuntionsDLL)) then fInjected := True;
end;
end;
procedure TfrmMain.UnloadMyFunctions;
begin
if fInjected then begin
UnloadLibrary(fCondorPID, PChar(GetExeDirectory + cinjComFuntionsDLL));
fInjected := False;
end;
end;
procedure TfrmMain.SearchCondor;
begin
fCondorPID := FindProcess(cCondorApplication);
if fCondorPID <> 0 then begin
lbl1.Caption := 'Notepad is running!';
InjectMyFunctions;
end else begin
lbl1.Caption := 'Notepad isn''t running!';
end;
end;
procedure TfrmMain.FormDestroy(Sender: TObject);
begin
UnloadMyFunctions;
end;
function TfrmMain.GetDebugPrivileges : Boolean;
begin
Result := False;
if not SetDebugPrivilege(SE_PRIVILEGE_ENABLED) then begin
Application.MessageBox('No Debug rights!', 'Error', MB_OK);
end else begin
Result := True;
end;
end;
procedure TfrmMain.FormCreate(Sender: TObject);
begin
#ChangeWindowMessageFilter := GetProcAddress(LoadLibrary('user32.dll'), 'ChangeWindowMessageFilter');
ChangeWindowMessageFilter(WM_COPYDATA, 1);
fInjected := False;
fDontWork := not GetDebugPrivileges;
tmrSearchCondor.Enabled := not fDontWork;
end;
procedure TfrmMain.tmrSearchCondorTimer(Sender: TObject);
begin
tmrSearchCondor.Enabled := False;
SearchCondor;
tmrSearchCondor.Enabled := True;
end;
end.
The creators of the madExcept library etc provide IPC functionality which can be used instead of Windows messages.
http://help.madshi.net/IPC.htm
I developed a Windows screensaver at one stage, and I wanted to get my screensaver to send some notification to another program, and while the screensaver was active, I was unable to get window messages to work between the two apps.
I replaced it with the IPC functionality mentioned above.
Worked a treat.
I use this library for IPc (uses shared memory + mutex):
http://17slon.com/gp/gp/gpsync.htm
It has TGpMessageQueueReader and TGpMessageQueueWriter. Use "Global\" in front of the name, so you can use it to communicate between a Windows Service and a "Service GUI Helper" when a user logs in. (the Global\ prefix is needed for Vista because of session security rings, but also for Windows XP/2003 between user sessions).
It is very fast, multithreaded, etc. I would use this one instead of WM_COPYDATA (slow & much overhead if you use it a lot, but for small things messages can be OK)