How to catch the start and completion of applications (processes) in Win.
How to measure the time of each application?
You can use the GetProcessTimes function to get timing information for a particular process.
BOOL WINAPI GetProcessTimes(
__in HANDLE hProcess,
__out LPFILETIME lpCreationTime,
__out LPFILETIME lpExitTime,
__out LPFILETIME lpKernelTime,
__out LPFILETIME lpUserTime
);
See this example
program GetProcessTime;
{$APPTYPE CONSOLE}
uses
DateUtils,
Windows,
tlhelp32,
SysUtils;
Procedure GetAllProcessTime;
var
HandleSnapShot : THandle;
EntryParentProc : TProcessEntry32;
DummyCreateFileTime : Windows.FILETIME;
DummyExitFileTime : Windows.FILETIME;
DummyKernelFileTime : Windows.FILETIME;
DummyUserFileTime : Windows.FILETIME;
aFileName : String;
h : THandle;
ActualTime : TDateTime;
Dif : TDateTime;
CreationTime : TDateTime;
function FileTime2DateTime(FileTime: TFileTime): TDateTime; //Convert then FileTime to TDatetime format
var
LocalTime: TFileTime;
DOSTime : Integer;
begin
FileTimeToLocalFileTime(FileTime, LocalTime);
FileTimeToDosDateTime(LocalTime, LongRec(DOSTime).Hi, LongRec(DOSTime).Lo);
Result := FileDateToDateTime(DOSTime);
end;
begin
HandleSnapShot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); //get the list of process
if HandleSnapShot <> INVALID_HANDLE_VALUE then
begin
EntryParentProc.dwSize := SizeOf(EntryParentProc);
if Process32First(HandleSnapShot, EntryParentProc) then //Get the first process in the list
begin
Writeln( Format('%-30s %-20s %-16s',['FileName','Start','Running Time']) );
ActualTime:=Now;
repeat
h:=OpenProcess(PROCESS_QUERY_INFORMATION,false,EntryParentProc.th32ProcessID); //open a particular process
if GetProcessTimes(h, DummyCreateFileTime, DummyExitFileTime, DummyKernelFileTime, DummyUserFileTime) then //get the timing info
begin
aFileName:=ExtractFileName(EntryParentProc.szExeFile);
CreationTime:=FileTime2DateTime(DummyCreateFileTime); //get the initial time of the process
Dif := ActualTime-CreationTime; //calculate the elapsed time
Writeln( Format('%-30s %-20s %-16s',[aFileName,FormatDateTime('DD-MM-YYYY HH:NN:SS',CreationTime),FormatDateTime('HH:NN:SS',Dif)]) );
end;
CloseHandle(h);
until not Process32Next(HandleSnapShot, EntryParentProc);
end;
CloseHandle(HandleSnapShot);
end;
end;
begin
try
GetAllProcessTime();
Readln;
except
on E: Exception do
begin
Writeln(E.ClassName, ': ', E.Message);
Readln;
end;
end;
end.
There are a couple or so Windows API calls with 'hook' in the name that allow you to capture systemwide events. You'll have to build those into a DLL, and then make calls to the 'hooking' DLL from a separate application.
That, VERY briefly, is it. Hope that gets you started!
Windows Performance Analyzer has features specifically for timing applications from the moment they start, even if they're in the boot or log-in sequence.
the WH_CBT hook is most likely the one you are after. It allows you to be notified by the OS whenever a window is created or destroyed. You will want to use this hook to grab the handle and from the handle get the process id using GetWindowThreadProcessId. You can then pass this handle to the function GetProcessTimes (suggested by RRUZ) to get the times. An example (although dated, the concepts are still the same) is available in the GpSysHook source.
Windows Management Instrumentation offers event subscription. The nice thing with WMI is that it works remote too, using DCOM and SOAP.
WMI offers the capability to notify a subscriber for any event it is interested in.
WMI uses the WMI Query Language (WQL)
to submit WQL event queries and
defines the type of events to be
returned. The eventing mechanism, with
all related callbacks, is part of the
WMI COM/DCOM and automation
interfaces.
A free WMI client implementation for Delphi is available online (not sure if it supports event callbacks):
Magenta Systems WMI and SMART Component
Related
There's something I don't understand about waitable timers. I searched online and read specs on MSDN plus whatever I could find on stackoverflow (such as link) but my timers fire almost immediately after the PC is suspended.
To focus on the problem I wrote a small test app using XE5 (64-bit) in Windows 7 and tried to duplicate the project found here: http://www.codeproject.com/Articles/49798/Wake-the-PC-from-standby-or-hibernation
I thought the problem was the way I derive time but I can't seem to find the problem.
The test app looks like this (very simple):
I declare the thread type as follows
type
TWakeupThread = class(TThread)
private
FTime: LARGE_INTEGER;
protected
procedure Execute; override;
public
constructor Create(Time: LARGE_INTEGER);
end;
...
constructor TWakeupThread.Create(Time: LARGE_INTEGER);
begin
inherited Create(False);
FreeOnTerminate:=True;
FTime:=Time;
end;
procedure TWakeupThread.Execute;
var
hTimer: THandle;
begin
// Create a waitable timer.
hTimer:=CreateWaitableTimer(nil, True, 'WakeupThread');
if (hTimer <> 0) then
begin
//CancelWaitableTimer(hTimer);
if (SetWaitableTimer(hTimer, FTime.QuadPart, 0, nil, nil, True)) then
begin
WaitForSingleObject(hTimer, INFINITE);
end;
CloseHandle(hTimer);
end;
end;
Now when the "Set timer" button is clicked I calculate file time this way and create the thread.
procedure TForm1.btnSetTimerClick(Sender: TObject);
var
iUTCTime : LARGE_INTEGER;
SysTime : _SystemTime;
FTime : _FileTime;
hHandle : THandle;
dt : TDateTime;
begin
ReplaceDate(dt,uiDate.DateTime);
ReplaceTime(dt,uiTime.DateTime);
DateTimeToSystemTime(dt, SysTime);
SystemTimeToFileTime(SysTime, FTime);
LocalFileTimeToFileTime(FTime, FTime);
iUTCTime.LowPart := FTime.dwLowDateTime;
iUTCTime.HighPart := FTime.dwHighDateTime;
TWakeupThread.Create(iUTCTime);
end;
This does not work. The timer seems to fire less then 2 minutes after the system enters suspend mode regardless of the amount of time selected. Any pointers as to what I am doing wrong would be appreciated.
EDIT
Found this interesting command line tool that let's us inspect the waitable timers. From command you can "see" the state of your waitable timers by typing:
powercfg -waketimers
I can use this to confirm that my timers are being set properly. I can also use this to confirm that my timers are still running when the PCs prematurely wake-up.
Using the same tool you can get a list of devices that are able to wake from hibernation (mouse, keyboard, network in my case):
powercfg -devicequery wake_armed
On all systems tested, the command "powercfg -lastwake" returns the following which I do not know how to decipher:
Wake History Count - 1
Wake History [0]
Wake Source Count - 0
I enabled both sleep and hibernate in Windows and both will wake up after a few seconds. There's no keyboard / mouse activity and we don't have devices sending WOL (wake-on-lan) requests to these PCs.
I'm wondering if there's something special I need to do when calling SetSuspendState; here's my code:
function SetSuspendState(Hibernate, ForceCritical, DisableWakeEvent: Boolean): Boolean;
//Hibernate = False : system suspends
//Hibernate = True : system hibernates
begin
if not Assigned(_SetSuspendState) then
#_SetSuspendState := LinkAPI('POWRPROF.dll', 'SetSuspendState');
if Assigned(_SetSuspendState) then
Result := _SetSuspendState(Hibernate, ForceCritical, DisableWakeEvent)
else
Result := False;
end;
function LinkAPI(const module, functionname: string): Pointer;
var
hLib: HMODULE;
begin
hLib := GetModuleHandle(PChar(module));
if hLib =0 then
hLib := LoadLibrary(PChar(module));
if hLib <> 0 then
Result := getProcAddress(hLib, PChar(functionname))
else
Result := nil;
end;
procedure TForm1.btnSuspendClick(Sender: TObject);
begin
SetSuspendState(True, False, False);
end;
The problem was not related to delphi or the code in any way. The problem was created by a Windows 7 feature that enables more than magic packets when WOL is enabled. Forcing Windows to only listen for magic packets solved the problem.
MS Link: http://support.microsoft.com/kb/941145
Thank you to everyone who tried to help and especially to David Heffernan who alluded to the possibility that something else was waking up the PC.
I am trying to get the full path of all running processes in the system as a standard user without administrative rights on Windows XP / Server 2003. Getting the list of all running processes and handles to them is not a problem (Toolhelp "Process32First" / Native API "NtQuerySystemInformation" / PsApi "EnumProcesses"). My problem is that these calls do not return proper process handles, but some sort of handle that I first have to pass to "OpenProcess" to get a proper process handle that I then can use to query for the full image path (by calling "GetProcessImageFileName" or some low level function). But, for processes not started by the current user, "OpenProcess" fails if I am not an admin.
Can anybody point me in the rights direction on how to retrieve this information? Process Hacker and Process Explorer are able to do it, so it should be possible. I am aware that Process Hacker's source code is available, but as far as I understand it uses some sort of driver to query running processes.
Correction: As David Heffernan pointed out in his answer Process Explorer and Process Hacker do not display the full image path on Windows XP when started by a non admin user.
Here's the requested code (written in Delphi):
function GetProcessDetails (const th32ProcessID: THandle) : String;
var
szImageFileName : array [0..MAX_PATH] of Char;
hProcess : THandle;
begin
hProcess := OpenProcess (PROCESS_QUERY_INFORMATION or PROCESS_VM_READ,
false, th32ProcessID);
if (hProcess = 0) then
exit;
if (GetProcessImageFileName (hProcess, #szImageFileName [0],
MAX_PATH) > 0) then
Result := szImageFileName;
CloseHandle (hProcess);
end; { GetProcessDetails }
And here's the function that uses "Process32First / Process32Next" to retrieve the process information:
procedure FillProcessListToolHelp;
var
hSnapShot : THandle;
PE : TProcessEntry32;
sImageFileName : String;
begin
hSnapShot := CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0);
PE.dwSize := SizeOf (TProcessEntry32);
if (Process32First (hSnapShot, PE)) then
repeat
if (PE.th32ProcessID <> 0) then
sImageFileName := GetProcessDetails (PE.th32ProcessID);
until (Process32Next (hSnapShot, PE) = false);
CloseHandle (hSnapShot);
end; { FillProcessListToolHelp }
Please be aware that "SeDebugPrivilege" has been assigned before making the call to "FillProcessListToolHelp". Also, right now I am only interested in a solution for 32 bit Windows.
The code that you present works as intended and is the best that you can do. You can add a little more diagnostics to it to see better what is going on:
hProcess := OpenProcess (PROCESS_QUERY_INFORMATION or PROCESS_VM_READ,
false, th32ProcessID);
if (hProcess = 0) then
begin
Writeln(IntToStr(GetLastError));
exit;
end;
What you will then learn is that when OpenProcess is failing, it is because GetLastError is returning 5, aka ERROR_ACCESS_DENIED. And that is because, as you yourself have identified, the processes in question are owned by a different user.
Windows security means that your standard user process is simply unable to open a handle to these processes with the necessary access rights. If you wish to obtain information about these processes you will need to execute your code with sufficient privileges, for example by running as administrator.
I believe that the best you can do without admin privileges is to use the information returned in the szExeFile member of TProcessEntry32.
{$APPTYPE CONSOLE}
uses
System.SysUtils, Winapi.Windows, Winapi.TlHelp32;
function GetProcessImageFileName(hProcess: THandle; lpImageFileName: LPTSTR;
nSize: DWORD): DWORD; stdcall;
external 'PSAPI.dll' name 'GetProcessImageFileNameW';
function ImageFileName(const PE: TProcessEntry32): string;
var
szImageFileName: array [0 .. MAX_PATH] of Char;
hProcess: THandle;
begin
Result := PE.szExeFile; // fallback in case the other API calls fail
hProcess := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false,
PE.th32ProcessID);
if (hProcess = 0) then
exit;
if (GetProcessImageFileName(hProcess, #szImageFileName[0], MAX_PATH) > 0) then
Result := szImageFileName;
CloseHandle(hProcess);
end;
procedure FillProcessListToolHelp;
var
hSnapShot: THandle;
PE: TProcessEntry32;
begin
hSnapShot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PE.dwSize := SizeOf(TProcessEntry32);
if (Process32First(hSnapShot, PE)) then
repeat
if (PE.th32ProcessID <> 0) then
Writeln(ImageFileName(PE));
until (Process32Next(hSnapShot, PE) = false);
CloseHandle(hSnapShot);
end;
begin
FillProcessListToolHelp;
Readln;
end.
Now, in the question you state:
Process Hacker and Process Explorer are able to do it, so it should be possible.
But that is not the case. At least for Process Explorer which is what I use. Here's what Process Explorer, when running as standard user, has to offer for the smss process:
Having just tried out Process Hacker, I can see that it will yield the information you want, even when not running elevated. So if you wish to do this yourself you simply need to read the Process Hacker and do what it does.
I've a program that access multiple serial ports using cport.
To configure, till now I simply listed all available comports in a combobox to make a selection, but the increasing number of drivers with (virtual) serial interfaces makes configuring for end-users troublesome.
The current detection works with createfile(), but that has the problem that you only get exists/nonexists and maybe "busy" as information.
However to improve, I need per COM port a identification string, like the hardware device/driver (device manager) it is connected too. This would make it easier for the user to narrow the comports down (since we deliver a finite number of serial cards)
Probably it is available from WMI, but that's quite a jungle, does sb have more concrete information, or better, code?
(Delphi XE3, Win7+, no solution that requires additional installing or deployment please)
If you want enumerate the COM ports getting a friendly name you can use the SetupAPI and the GUID_DEVINTERFACE_COMPORT device interface class.
Try this sample
{$APPTYPE CONSOLE}
{$R *.res}
uses
Windows,
SysUtils,
JvSetupApi;
const
GUID_DEVINTERFACE_COMPORT:TGUID='{86E0D1E0-8089-11D0-9CE4-08003E301F73}';
procedure EnumerateCOMPorts;
var
cbRequired : DWORD;
hdev : HDEVINFO;
idev : Integer;
did : TSPDeviceInterfaceData;
pdidd : PSPDeviceInterfaceDetailData;
PropertyBuffer : array[0..255] of Char;
DeviceInfoData: TSPDevInfoData;
PropertyRegDataType: DWORD;
RequiredSize: DWORD;
begin
// enumerate the com ports
hdev := SetupDiGetClassDevs(#GUID_DEVINTERFACE_COMPORT, nil, 0, DIGCF_PRESENT OR DIGCF_DEVICEINTERFACE);
if ( INVALID_HANDLE_VALUE <> THandle(hdev) ) then
begin
try
idev:=0;
ZeroMemory(#did, SizeOf(did));
did.cbSize := SizeOf(did);
repeat
if (SetupDiEnumDeviceInterfaces(hdev, nil, GUID_DEVINTERFACE_COMPORT, idev, did)) then
begin
cbRequired := 0;
SetupDiGetDeviceInterfaceDetail(hdev, #did, nil, 0, cbRequired, nil);
if (ERROR_INSUFFICIENT_BUFFER= GetLastError()) then
begin
pdidd:=AllocMem(cbRequired);
try
pdidd.cbSize := SizeOf(TSPDeviceInterfaceDetailData);
DeviceInfoData.cbSize:= SizeOf(DeviceInfoData);
RequiredSize:=0;
if (SetupDiGetDeviceInterfaceDetail(hdev, #did, pdidd, cbRequired, RequiredSize, #DeviceInfoData)) then
begin
PropertyRegDataType:=0;
RequiredSize:=0;
if SetupDiGetDeviceRegistryProperty(hdev, DeviceInfoData, SPDRP_FRIENDLYNAME, PropertyRegDataType, PBYTE(#PropertyBuffer[0]), SizeOf(PropertyBuffer), RequiredSize) then
Writeln(Format('Friendly Name - %s',[PropertyBuffer]));
if SetupDiGetDeviceRegistryProperty(hdev, DeviceInfoData, SPDRP_DEVICEDESC, PropertyRegDataType, PBYTE(#PropertyBuffer[0]), SizeOf(PropertyBuffer), RequiredSize) then
Writeln(Format('Description - %s',[PropertyBuffer]));
if SetupDiGetDeviceRegistryProperty(hdev, DeviceInfoData, SPDRP_LOCATION_INFORMATION, PropertyRegDataType, PBYTE(#PropertyBuffer[0]), SizeOf(PropertyBuffer), RequiredSize) then
Writeln(Format('Location - %s',[PropertyBuffer]));
end
else
RaiseLastOSError;
finally
FreeMem(pdidd);
end;
end;
end
else
Break;
inc(idev);
until false;
finally
SetupDiDestroyDeviceInfoList(hdev);
end;
end;
end;
begin
try
if not LoadsetupAPI then exit;
EnumerateCOMPorts;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
readln;
end.
This will return something like so
Note : The JvSetupApi unit is part of the JVCL library.
You can use HKEY_LOCAL_MACHINE \HARDWARE\DEVICEMAP\SERIALCOMM for COMxx style short names. Keep remember specify read only access to avoid Admin rights / UAC necessity. You can see both usb232 adapters and real comm ports.
You can also chek HKEY_LOCAL_MACHINE \SYSTEM\CurrentControlSet\Enum\Root\PORTS but it seems a bit tricky.
Heres an example of how I'm doing it right now :
var
Client : String;
Handle : Integer;
begin
Client := 'Window Name';
GetWindowThreadProcessId(FindWindow(nil, PAnsiChar(Client)), #ProcessId);
Handle := OpenProcess(PROCESS_ALL_ACCESS, False, ProcessId);
end;
I'd rather grab the Process's handle with its exe name...
Is this possible this?
Since the link provided by vcldeveloper is broken, here's the full function code that works without 3rd party components.
First we will find process id (PID), and then we'll get process handle by opening all access (since the OP mentioned in the comments he will need this for ReadProcessMemory functionality).
If the function for PID returns 0, it means that the process is most likely not running (or just not found in the running process list)
function GetPIDbyProcessName(processName:String):integer;
var
GotProcess: Boolean;
tempHandle: tHandle;
procE: tProcessEntry32;
begin
tempHandle:=CreateToolHelp32SnapShot(TH32CS_SNAPALL, 0);
procE.dwSize:=SizeOf(procE);
GotProcess:=Process32First(tempHandle, procE);
{$B-}
if GotProcess and not SameText(procE.szExeFile, processName) then
repeat GotProcess := Process32Next(tempHandle, procE);
until (not GotProcess) or SameText(procE.szExeFile,processName);
{$B+}
if GotProcess then
result := procE.th32ProcessID
else
result := 0; // process not found in running process list
CloseHandle(tempHandle);
end;
Next, we will get/open Process handle from the PID we got. The whole code/usage is as follows:
var myPID, myProcessHandle: integer;
begin
myPID:=GetPIDbyProcessName('someExeName.exe');
myProcessHandle:=OpenProcess(PROCESS_ALL_ACCESS,False,myPID);
end;
You should store the myProcessHandle in such way that it is accessable for ReadProcessMemory(myProcessHandle...) as first parameter.
Also, add these to your global uses clauses:
Winapi.Windows (for ReadProcessMemory and OpenProcess)
Winapi.tlHelp32(for getting PID tProcessEntry32 variable)
Application.Handle?
Looks like you're trying to program in Delphi by WinAPI. In the vast majority do not need it, VCL provides appropriate object-oriented wrappers.
May be You will find something in this component pack: GLibWMI
You can use ProcessInfo:
var
ProcessInfo : TProcessInfo;
Process : TProcessItem;
PID: Cardinal;
ProcessHandle : THandle;
begin
ProcessInfo := TProcessInfo.Create(nil);
try
Process := ProcessInfo.RunningProcesses.FindByName('Notepad.exe');
if Assigned(Process) then
begin
PID := Process.ProcessID;
ProcessHandle := OpenProcess(PROCESS_ALL_ACCESS,False,PID);
if ProcessHandle > 0 then
try
{Add your code here}
finally
CloseHandle(ProcessHandle);
end;
end;
finally
ProcessInfo.Free;
end;
end;
If you do not like using a third-party component, you can study source code of ProcessInfo to see how it retrieves list of running processes, and their properties. Basically it relays on Windows Tool Help API for most of its features.
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)