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
Related
I write this minimal example to test a strange behavior I found in Indy.
I have a TCP server and client, and I send 50 x 2MB buffers from client to server. If I open the Windows WiFi menu (the one you use to connect to a network), my TCP transfer is stopped for a few seconds and then continues. This is not good for my app, because I cannot use read timeouts.
Is something wrong with Indy? Or my code? Or Windows? Or is it something else? Can I fix it somehow?
I have uploaded a YouTube Video to demonstrate this issue in action.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, IdTCPConnection,
IdTCPClient, IdBaseComponent, IdComponent, IdCustomTCPServer, IdTCPServer,
IdContext, IdIOHandlerSocket, IdGlobal;
type
TClientThread = class(TThread)
private
Socket: TIdIOHandlerSocket;
protected
procedure Execute; override;
public
constructor Create(ASocket: TIdIOHandlerSocket);
end;
TForm1 = class(TForm)
Memo1: TMemo;
Server: TIdTCPServer;
Client: TIdTCPClient;
BStart: TButton;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure ServerExecute(AContext: TIdContext);
procedure BStartClick(Sender: TObject);
private
procedure LogEvent(var Msg: TMessage); message WM_USER;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
//---- Client -----------------------------------------------------------------
constructor TClientThread.Create(ASocket: TIdIOHandlerSocket);
begin
inherited Create;
Socket:= ASocket;
end;
procedure TClientThread.Execute;
var I, bSize: Integer;
Buff: TIdBytes;
begin
bSize:= 2000000;
SetLength(Buff, bSize+4);
Move(bSize, Buff[0], 4);
for I:= 1 to 50 do begin
Socket.Write(Buff, bSize+4);
end;
end;
//---- Server -----------------------------------------------------------------
procedure TForm1.ServerExecute(AContext: TIdContext);
var Cmd: Cardinal;
Buff: TIdBytes;
I: Integer;
procedure SendString(const AStr: String);
var Tmp: String;
begin
Tmp:= AStr;
SendMessage(Form1.Handle, WM_USER, WPARAM(#Tmp), 0);
end;
begin
SendString('Server START.');
I:= 0;
try
repeat
Cmd:= AContext.Connection.Socket.ReadUInt32(False);
if Cmd = $FFFFFFFF then SendString('Srv: keep alive')
else if Cmd <> 0 then begin
Inc(I);
SendString('Srv read '+IntToStr(I)+': '+IntToStr(Cmd)+' bytes');
if Length(Buff) < Cmd then SetLength(Buff, Cmd);
AContext.Connection.Socket.ReadBytes(Buff, Cmd, False);
SendString('Srv read done.');
end;
until Cmd = 0;
finally
AContext.Connection.Disconnect;
SendString('Server STOP.');
end;
end;
//---- Form -------------------------------------------------------------------
procedure TForm1.FormCreate(Sender: TObject);
begin
Server.Bindings.Clear;
Server.Bindings.Add.SetBinding(<the host>, <the port>);
Server.Active:= True;
Client.Host:= <a host>;
Client.Port:= <a port>;
Client.Connect;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Client.Disconnect;
Server.Active:= False;
end;
procedure TForm1.BStartClick(Sender: TObject);
begin
TClientThread.Create(Client.Socket);
end;
procedure TForm1.LogEvent(var Msg: TMessage);
begin
Memo1.Lines.Add(PString(Msg.WParam)^.Substring(0));
end;
end.
Edit:
I found that
the problem persist even with the NetCom7 library.
the blocking period it is greatly reduced (about 1s) if I try the app on another machine.
This is not an issue with Indy itself, or the underlying TCP connection, as evident by the fact that the TCP connection is not being cut off completely while the WiFi menu is open.
I would be more concerned over why the video shows your UI running so slowly in general. You have no sleeps in your code, nothing else running in your main UI thread, so your comms should be slamming through those 50 TCP messages in a blink, but it is not. You are sending a synchronous window message to the main UI thread between each TCP message read, and it is clear in the video that there is a delay in processing each of those messages. So, I have to assume that something external to Indy is blocking your main UI thread from handling window messages in a timely manner. Opening the WiFi menu is simply exacerbating that issue for a few seconds.
I imagine that if you were to drop a TTimer (which is a window message based timer) onto your Form, set its Interval to a short value, and have its OnTimer event display a counter on your UI, you would likely see those updates appear delayed, as well.
If you get rid of the SendString() calls inside of the TIdTCPServer's reading loop (or at least replace SendMessage() with PostMessage()) so that the loop is not being blocked by the UI at all, and then have your client send a Cmd=0 message (or disconnect) after sending its 50 Buff messages so that the reading loop terminates, you should see the Server STOP message appear in your UI very quickly. Transmitting ~100 MB of data should not take more than a few seconds on modern networking hardware.
Play a local video file (not in a web browser) and see if it hangs during your maneuver, too. That indicates the system is in general affected, not only the network.
I use DPC Latency Checker (Deferred Procedure Calls) to see if there are spikes of (typically) device drivers that take too long. Just like the manual explains: if you find spikes, try to disable single hardware components and repeat measuring. Sadly even the majority of hardware drivers are sloppy coded, and finding alternatives is a hard task.
I'm only using TidPOP3 component from Indy 10 and my code is below:
type
TForm4 = class(TForm)
Button1: TButton;
Pop3: TIdPOP3;
IdSSLIOHandlerSocketOpenSSL1: TIdSSLIOHandlerSocketOpenSSL;
Mensaje : TIdMessage;
RchTMessage: TRichEdit;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form4: TForm4;
implementation
{$R *.dfm}
procedure TForm4.Button1Click(Sender: TObject);
begin
Pop3 := TIdPOP3.Create;
Pop3.Host := 'pop.gmail.com';
Pop3.Port := 995;
Pop3.Username := '**********#gmail.com';
Pop3.Password := '**********';
try
if not Pop3.Connected then
begin
Pop3.Connect;
Application.ProcessMessages;
end;
except on E: Exception do RchTMessage.Lines.Add(E.Message);
end;
end;
end.
When I press the button I got the Error "Connection Closed Gracefully". I've already configured the gmail account i'm using to be ready to accept Pop3 connections and to accept non-secure connections from other apps. Also, I configured TIdSSLIOHandlerSocketOpenSSL component in the form.
Thanks and Happy New Year for everyone.
Your button OnClick handler is creating a new TIdPOP3 object instead of using the one that you dropped on your Form. You are not configuring that object to interact with Gmail using SSL/TLS, as you are not attaching the TIdSSLIOHandlerSocketOpenSSL to it, or setting its UseTLS property (to utUseImplicitTLS) .
I need to create a delphi application where when it's started the server is started as well and starts sending messages immediately, but I haven't found an example or tutorial and the nearly 5000 page Indy manual doesn't make it clear to me how I can do this...
This example uses a Delphi 2009 VCL application with a main form, which contains only one visual component, a TMemo named “MemoLog”.
Client and server are both started in the FormCreate event. Note that the client code does not handle connection loss, but this can be implemented with a separate re-connect loop within the thread.
procedure TServerPushExampleForm.FormCreate(Sender: TObject);
begin
ExampleServer := TMyPushServer.Create;
ExampleServer.DefaultPort := 8088;
ExampleServer.Active := True;
ExampleClient := TMyPushClientThread.Create('localhost', 8088,
MemoLog.Lines);
end;
Server
The server code uses a TIdTCPCustomServer subclass which waits for a random time and then sends a string to the client.
function TMyPushServer.DoExecute(AContext: TIdContext): Boolean;
begin
Result := inherited;
// simulate hard work
Sleep(Random(3000));
AContext.Connection.IOHandler.WriteLn(
'Completed at ' + TimeToStr(Now), IndyTextEncoding_UTF8);
end;
Client
The client code uses a TThread subclass to run asynchronously without blocking the main VCL thread. It contains a private TIdTCPClient instance, and periodically tries to receive a string from the connection.
...
S := TCPClient.IOHandler.ReadLn(IndyTextEncoding_UTF8);
...
Full Delphi Form Code
Below is the full code for the example main form.
unit Unit1;
interface
uses
IdCustomTCPServer, IdTCPClient, IdContext,
SysUtils, Classes, Forms, StdCtrls, Controls;
type
TMyPushClientThread = class(TThread)
private
TCPClient: TIdTCPClient;
FLog: TStrings;
public
constructor Create(AHost: string; APort: Word; ALog: TStrings);
destructor Destroy; override;
procedure Execute; override;
end;
TMyPushServer = class (TIdCustomTCPServer)
protected
function DoExecute(AContext: TIdContext): Boolean; override;
end;
TServerPushExampleForm = class(TForm)
MemoLog: TMemo;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
ExampleClient: TMyPushClientThread;
ExampleServer: TMyPushServer;
end;
var
ServerPushExampleForm: TServerPushExampleForm;
implementation
uses
IdGlobal;
{$R *.dfm}
procedure TServerPushExampleForm.FormCreate(Sender: TObject);
begin
ExampleServer := TMyPushServer.Create;
ExampleServer.DefaultPort := 8088;
ExampleServer.Active := True;
ExampleClient := TMyPushClientThread.Create('localhost', 8088, MemoLog.Lines);
end;
procedure TServerPushExampleForm.FormDestroy(Sender: TObject);
begin
ExampleServer.Free;
ExampleClient.Terminate;
ExampleClient.WaitFor;
ExampleClient.Free;
end;
{ TMyPushServer }
function TMyPushServer.DoExecute(AContext: TIdContext): Boolean;
begin
Result := inherited;
// simulate hard work
Sleep(Random(3000));
AContext.Connection.IOHandler.WriteLn(
'Completed at ' + TimeToStr(Now), IndyTextEncoding_UTF8);
end;
{ TMyPushClientThread }
constructor TMyPushClientThread.Create(AHost: string; APort: Word; ALog: TStrings);
begin
inherited Create(False);
FLog := ALog;
TCPClient := TIdTCPClient.Create;
TCPClient.Host := AHost;
TCPClient.Port := APort;
TCPClient.ReadTimeout := 500;
end;
destructor TMyPushClientThread.Destroy;
begin
TCPClient.Free;
inherited;
end;
procedure TMyPushClientThread.Execute;
var
S: string;
begin
TCPClient.Connect;
while not Terminated do
begin
S := TCPClient.IOHandler.ReadLn(IndyTextEncoding_UTF8);
if not TCPClient.IOHandler.ReadLnTimedout then
begin
TThread.Queue(nil,
procedure
begin
FLog.Append(S);
end);
end;
end;
TCPClient.Disconnect;
end;
end.
(From https://mikejustin.wordpress.com/2014/04/19/indy-10-tidtcpserver-server-side-message-push-example/)
The way that Indy works is to have the client (TidTCPClient) connect to the server (TidTCPServer) and then exchange data between them, back-and-forth until the connection is terminated either willfully or by premature disconnect.
I am only referring to the actual Indy TCP components here and not to the way you see your applications.
At the application level you might consider an application the server app and another the client app but both can/may contain both TidTCPClient and TidTCPServer components with which they communicate with other apps. This means that the server app can initiate a connection to a client app via the server app's TidTCPClient component and the client app will receive the connection via its TidTCPServer component. This would be a possible solution but keep in mind that generally clients are dynamic and ever changing while servers are usually static and as such it will be a mission to keep track of where clients are. Too many headaches and too much work as well.
So I think it is better to have clients keep track of their rarely changing servers and as such it is better to have a TidTCPServer component for the server app and have it wait for client connections before it starts to send messages.
So to implement; your clients would have to constantly try to connect to the server at regular intervals until it finds the server. The server can then send as many messages as it wants until asked to stop or until premature disconnect in which case the cycle will be restarted. There are ways in Indy to keep track of client connections and you can keep an internal list of the clients through those means. This makes more sense. It is the way that most client-server apps work. Just think of Skype and any Web Server. The clients contacts the server and receives data if needs be.
At the server side:
Create the TidTCPServer object.
Setup the TidTCPServer to listen on one or more of its local IP
Addresses and choose an IP port for them.
Assign code to the TidTCPServer which it will run as soon as a client
connects to it via the OnExecute of the TidTCPServer. In this code you will send the messages to the connected client.
Activate the TidTCPServer so that it is in Listening mode.
At the client side:
Create a TidTCPClient object.
Setup the TidTCPClient to use a specific host and port (The IP
Address/Host Name of the server and the port you chose)
In a repeating loop with intervals try to connect to the server.
As soon as the connection is established the client may send the
server something or immediatelly try to read from the connection
which is what it will receive if the server sends something
There are many examples for this type of operation. You must try first and if you struggle you can always ask questions specific to the problem you are having.
I have a Delphi 6 application that works with the Skype API. I want to know when the Skype client has shut down even though my software did not launch it (so I don't have a process handle for it). This way I can know if the user shut down the Skype client I can get the process ID for the Skype client fairly easily, so is there a Windows API call or other technique that accepts a process ID where I can get a notification when the process (Skype client) has terminated?
If not, is there a WinApi call that I can use to poll Windows to see if the process ID is still valid, or does Windows reuse process IDs so there's a chance that I could end up with a process ID belonging to a recently launched process that is not the Skype client, which would invalidate my polling efforts?
Call OpenProcess to get a process handle. The SYNCHRONIZE access right will probably be enough. Then wait on the handle. Something like:
HANDLE hProcess = OpenProcess(SYNCHRONIZE, FALSE, pid);
WaitForSingleObject(hProcess, INFINITE);
CloseHandle(hProcess);
You can use the __InstanceDeletionEvent WMI intrinsic event to monitor the Win32_Process class and the filter by the ProcessId property, this event run in async mode in your code.
Check this sample code (Written in delphi XE2, but must work in delphi 6 without problems)
Note : You must import the Microsoft WMI Scripting V1.2 Library before to use it.
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, WbemScripting_TLB;
type
TWmiAsyncEvent = class
private
FWQL : string;
FSink : TSWbemSink;
FLocator : ISWbemLocator;
FServices : ISWbemServices;
procedure EventReceived(ASender: TObject; const objWbemObject: ISWbemObject; const objWbemAsyncContext: ISWbemNamedValueSet);
public
procedure Start;
constructor Create(Pid : DWORD);
Destructor Destroy;override;
end;
TFrmDemo = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
AsyncEvent : TWmiAsyncEvent;
public
{ Public declarations }
end;
var
FrmDemo: TFrmDemo;
implementation
{$R *.dfm}
uses
ActiveX;
{ TWmiAsyncEvent }
constructor TWmiAsyncEvent.Create(Pid: DWORD);
begin
inherited Create;
CoInitializeEx(nil, COINIT_MULTITHREADED);
FLocator := CoSWbemLocator.Create;
FServices := FLocator.ConnectServer('.', 'root\CIMV2', '', '', '', '', wbemConnectFlagUseMaxWait, nil);
FSink := TSWbemSink.Create(nil);
FSink.OnObjectReady := EventReceived;
//construct the WQL sentence with the pid to monitor
FWQL:=Format('Select * From __InstanceDeletionEvent Within 1 Where TargetInstance ISA "Win32_Process" And TargetInstance.ProcessId=%d',[Pid]);
end;
destructor TWmiAsyncEvent.Destroy;
begin
if FSink<>nil then
FSink.Cancel;
FLocator :=nil;
FServices :=nil;
FSink :=nil;
CoUninitialize;
inherited;
end;
procedure TWmiAsyncEvent.EventReceived(ASender: TObject;
const objWbemObject: ISWbemObject;
const objWbemAsyncContext: ISWbemNamedValueSet);
var
PropVal: OLEVariant;
begin
PropVal := objWbemObject;
//do something when the event is received.
ShowMessage(Format('The Application %s Pid %d was finished',[String(PropVal.TargetInstance.Name), Integer(PropVal.TargetInstance.ProcessId)]));
end;
procedure TWmiAsyncEvent.Start;
begin
FServices.ExecNotificationQueryAsync(FSink.DefaultInterface,FWQL,'WQL', 0, nil, nil);
end;
procedure TFrmDemo.FormCreate(Sender: TObject);
begin
//here you must pass the pid of the process
AsyncEvent:=TWmiAsyncEvent.Create(1852);
AsyncEvent.Start;
end;
procedure TFrmDemo.FormDestroy(Sender: TObject);
begin
AsyncEvent.Free;
end;
end.
For more info you can check this article Delphi and WMI Events
Windows does reuse process IDs, so do not rely on that by itself.
You can use EnumProcesses() to know which processes are currently running, then grab their filenames and process IDs, etc. See this example on MSDN.
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)