Delphi Memory Mapped Files Notice new data? - delphi

althoug my questionn might be noob-ish, let me explain:
I recently started playing with MMF, created 2 processes wich access the same memory Pointer, Process1 writes an integer to MMF, Process2 has a button, which onClick, it displays the first integer in MMF.
What i want to do is, when i "send", write data from Process1 to MMF, Process2 Notices this request ontime, and displays the data on exact time, and so on with new data written.
I'm not sure whether a Thread checking for changes in MMF would be ok, sounds Dirty.
Hope somebody could point me out a solution, because i'm out of ideas :(.
Here's a piece of code:
procedure OpenMap;
var
llInit: Boolean;
lInt: Integer;
begin
if Hmapping<>0 then Exit;
HMapping := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE,
0, MAPFILESIZE, pchar('wowsniff'));
// Check if already exists
llInit := (GetLastError() <> ERROR_ALREADY_EXISTS);
if (hMapping = 0) then
exit;
PMapData := MapViewOfFile(HMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if PMapData = nil then
exit;
if (llInit) then
begin
// Init block to #0 if newly created
FillChar(PMapData^, MAPFILESIZE, 0);
end
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
LockMap;
PDword(PMapData)^:=Strtoint(edit1.Text);
UnlockMap;
end;

Use a named event object for that, either via TEvent or CreateEvent(). Both processes can create the same event name (just like they are creating the same mapping name), then Process 1 can signal the event whenever it writes new data, and Process 2 can wait for the event to be signaled before reading the data (for real-time reading, you should use a thread for the waiting/reading).
You can use a named mutex object, via TMutex or CreateMutex(), to implement your lock/unlock functionality when reading/writing the data.
Try something like this:
Process 1 :
procedure OpenMap;
var
llInit: Boolean;
begin
llInit := False;
if hMapEvent = 0 then
begin
hMapEvent := CreateEvent(nil, True, False, PChar('wowsniffDataReady'));
if hMapEvent = 0 then RaiseLastOSError;
end;
if hMapLock = 0 then
begin
hMapLock := CreateMutex(nil, False, PChar('wowsniffDataLock'));
if hMapLock = 0 then RaiseLastOSError;
end;
if hMapping = 0 then
begin
hMapping := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, MAPFILESIZE, PChar('wowsniff'));
if hMapping = 0 then RaiseLastOSError;
// Check if already exists
llInit := (GetLastError() <> ERROR_ALREADY_EXISTS);
end;
if PMapData = nil then
begin
PMapData := MapViewOfFile(hMapping, FILE_MAP_WRITE, 0, 0, MAPFILESIZE);
if PMapData = nil then RaiseLastOSError;
if llInit then
begin
// Init block to #0 if newly created
ZeroMemory(PMapData, MAPFILESIZE);
end;
end;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
LockMap;
try
PDword(PMapData)^ := StrToInt(Edit1.Text);
SetEvent(hMapEvent);
finally
UnlockMap;
end;
end;
procedure TForm1.LockMap;
var
llRet: DWORD;
begin
llRet := WaitForSingleObject(hMapLock, 5000);
if llRet = WAIT_OBJECT_0 then Exit;
if llRet <> WAIT_FAILED then SetLastError(llRet);
RaiseLastOSError;
end;
procedure TForm1.UnlockMap;
begin
ReleaseMutex(hMapLock);
end;
Process 2:
type
TMyThread = class(TThread)
private
hTerminate: THandle;
hMapLock: THandle;
hMapEvent: THandle;
hMapping: THandle;
PMapData: Pointer;
protected
procedure Execute; override;
procedure DoTerminate; override;
procedure TerminatedSet; override;
public
constructor Create;
destructor Destroy; override;
end;
constructor TMyThread.Create;
begin
inherited Create(False);
hTerminate := CreateEvent(nil, True, False, nil);
if hTerminate = 0 then RaiseLastOSError;
end;
destructor TMyThread.Destroy;
begin
if hTerminate <> 0 then CloseHandle(hTerminate)
end;
procedure TMyThread.TerminatedSet;
begin
SetEvent(hTerminate);
end;
procedure TMyThread.Execute;
var
llInit: Boolean;
llRet, llValue: DWORD;
llHandles: array[0..1] of THandle;
begin
hMapEvent := CreateEvent(nil, True, False, PChar('wowsniffDataReady'));
if hMapEvent = 0 then RaiseLastOSError;
hMapLock := CreateMutex(nil, False, PChar('wowsniffDataLock'));
if hMapLock = 0 then RaiseLastOSError;
hMapping := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, MAPFILESIZE, PChar('wowsniff'));
if hMapping = 0 then RaiseLastOSError;
// Check if already exists
llInit := (GetLastError() <> ERROR_ALREADY_EXISTS);
PMapData := MapViewOfFile(hMapping, FILE_MAP_WRITE, 0, 0, MAPFILESIZE);
if PMapData = nil then RaiseLastOSError;
if llInit then
begin
// Init block to #0 if newly created
FillChar(PMapData^, MAPFILESIZE, 0);
end;
llHandles[0] := hMapEvent;
llHandles[1] := hTerminate;
while not Terminated do
begin
llRet := WaitForMultipleObjects(2, PWOHandleArray(#llHandles), False, INFINITE);
case llRet of
WAIT_OBJECT_0+0:
begin
llRet := WaitForSingleObject(hMapLock, 5000);
if llRet = WAIT_OBJECT_0 then
begin
try
llValue := PDword(PMapData)^;
ResetEvent(hMapEvent);
finally
ReleaseMutex(hMapLock);
end;
// use llValue as needed...
Continue;
end;
end;
WAIT_OBJECT_0+1:
begin
Exit;
end;
end;
if llRet <> WAIT_FAILED then SetLastError(llRet);
RaiseLastOSError;
end;
end;
procedure TMyThread.DoTerminate;
begin
if PMapData <> nil then UnmapViewOfFile(PMapData);
if hMapping <> 0 then CloseHandle(hMapping);
if hMapEvent <> 0 then CloseHandle(hMapEvent);
if hMapLock <> 0 then CloseHandle(hMapLock);
inherited;
end;

Related

Disconnect unknow connections at TIdTcpServer OnConnect

I'm having a problem. I created a TIdTCPServer but I need to prevent false/unknown connections.
I tried this:
procedure Wait(millisecs: Integer);
var
tick: dword;
AnEvent: THandle;
begin
AnEvent := CreateEvent(nil, False, False, nil);
try
tick := GetTickCount + dword(millisecs);
while (millisecs > 0) and (MsgWaitForMultipleObjects(1, AnEvent, False, millisecs, QS_ALLINPUT) <> WAIT_TIMEOUT) do begin
Application.ProcessMessages;
if Application.Terminated then Exit;
millisecs := tick - GetTickcount;
end;
finally
CloseHandle(AnEvent);
end;
end;
procedure CheckCon(Con: Pointer);
begin
Wait(5000);
if TClient(Con).HWID = '' then TClient(Con).Connection.Disconnect;
EndThread(0);
end;
constructor TClient.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil);
var
ThreadId : Cardinal;
begin
inherited Create(AConnection, AYarn, AList);
FCriticalSection := TCriticalSection.Create;
Queue := TIdThreadSafeStringList.Create;
BeginThread(nil, 0, #CheckCon, Self, 0, ThreadId);
end;
OnConnect event code:
procedure TForm1.IdTCPServer1Connect(AContext: TIdContext);
var
Conexao : TClient;
Retorno : TArray<String>;
Query : TFDQuery;
Libera : Boolean;
IPEX : Boolean;
begin
Libera := True;
IPEX := True;
Conexao := TClient(AContext);
Retorno := AContext.Connection.IOHandler.ReadLn.Split(['#']);
if Length(Retorno) = 0 then
begin
AContext.Connection.Disconnect;
Exit;
end;
Conexao.IP := AContext.Connection.Socket.Binding.PeerIP;
Conexao.HWID := Retorno[1];
Conexao.Connected := Now;
Conexao.Ping := Ticks;
ClientStateUpdated(Conexao, RetornaTraducao(40));
TThread.Queue(nil,
procedure
begin
Memo2.Lines.Add(Format(RetornaTraducao(36), [TimeToStr(Now), Conexao.IP, Conexao.HWID]));
end);
end;
If I test creating a low number of unknown clients, it works good, but if I flood it with MANY connections, the application crashes. I need something like this to prevent unknown connections in my TIdTCPServer.
I tried calling
Memo2.Lines.Add(Format('[%s]', [AContext.Connection.IOHandler.ReadLn]));
in IdTCPServer1Connect to determine if the connection was my application, but if the client only connects and doesn't send anything, the line doesn't execute.
Starting a worker thread inside of TClient's constructor is completely unnecessary (the TClient object is already run in a thread created by the server). You can simply set a 5 second timeout on the ReadLn() call itself and be done with it.
Also, TIdTCPServer is a multi-threaded component, its events are fired in the context of worker threads, so access to UI controls like Memo2 MUST by synchronized with the UI thread or else bad things happen.
Try something more like this:
constructor TClient.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil);
begin
inherited Create(AConnection, AYarn, AList);
FCriticalSection := TCriticalSection.Create;
Queue := TIdThreadSafeStringList.Create;
end;
...
// code adapted from my reply to your previous question:
//
// https://stackoverflow.com/a/58479489/65863
//
// tweak as needed...
//
procedure TForm1.ClientStateUpdated(Client: TClient; const Msg: string);
var
IP, HWID: string;
begin
IP := Client.IP;
HWID := Client.HWID;
TThread.Queue(nil,
procedure
begin
Memo2.Lines.Add(Format(RetornaTraducao(36), [TimeToStr(Now), IP, HWID, Msg]));
end
);
end;
procedure TForm1.IdTCPServer1Connect(AContext: TIdContext);
var
Conexao : TClient;
Retorno : TArray<String>;
begin
Conexao := TClient(AContext);
Retorno := AContext.Connection.IOHandler.ReadLn(LF, 5000).Split(['#']);
if (Length(Retorno) < 2) or (Retorno[1] = '') then
begin
AContext.Connection.Disconnect;
Exit;
end;
Conexao.IP := AContext.Binding.PeerIP;
Conexao.HWID := Retorno[1];
Conexao.Connected := Now;
Conexao.Ping := Ticks;
ClientStateUpdated(Conexao, RetornaTraducao(40){'connect'});
end;
procedure TForm1.IdTCPServer1Disconnect(AContext: TIdContext);
var
Conexao : TClient;
begin
Conexao := TClient(AContext);
if Conexao.Connected <> 0 then
ClientStateUpdated(Conexao, RetornaTraducao(...){'disconnect'});
end;

GetFocus() returns null for MS Word

I am writing a Keyboard application that hooks the keyboard and remaps the keys. For this, I have created two projects, one .exe and .dll. In the .dll project, I detect the Handle of the Window in which the user is typing by GetFocus(). However, it works fine in notepad, but not in MS Word since I am not able to get the Window's Handle for the MS Word, using GetFocus()
I understand, that is because it might be running under different thread and hence, I need to get the Parent Window Handle by GetForegroundWindow() and iterate through its child windows and somehow get the right Handle.
While searching on internet I found following code (http://www.codeproject.com/Articles/34752/Control-in-Focus-in-Other-Processes)
activeWindowHandle:= GetForegroundWindow();
activeWindowThread:= GetWindowThreadProcessId(activeWindowHandle, 0);
thisWindowThread:= GetWindowThreadProcessId(lpHookRec^.TheHookHandle, 0);
AttachThreadInput(activeWindowThread, thisWindowThread, true);
lpHookRec^.TheAppWinHandle:= GetFocus();
AttachThreadInput(activeWindowThread, thisWindowThread, false);
However, it is not working for me :(
In my code I have written
lpHookRec^.TheAppWinHandle := GetFocus();
and that gives me the Handle of the NotePad window in lpHookRec^.TheAppWinHandle. However, if I use MS Word instead of NotePad, the above code gives me null(zero). So need to write function that returns the correct Handle, irrespective of thread it is running under, something like
function GetAppliWinHandle: Hwnd;
var
activeWindowHandle,activeWindowThread,thisWindowThread,focusedControlHandle: Hwnd;
begin { GetAppliWinHandle }
focusedControlHandle := GetFocus();
if focusedControlHandle = 0 then
begin
activeWindowHandle := GetForegroundWindow();
activeWindowThread := GetWindowThreadProcessId(activeWindowHandle, 0);
thisWindowThread := GetWindowThreadProcessId(lpHookRec^.TheHookHandle, 0);
AttachThreadInput(activeWindowThread, thisWindowThread, true);
focusedControlHandle := GetFocus();
AttachThreadInput(activeWindowThread, thisWindowThread, false);
end;
Result:=focusedControlHandle
end; { GetAppliWinHandle }
and here is the complete code for the dll
library TheHook;
uses
Windows, Messages, SysUtils;
{Define a record for recording and passing information process wide}
type
PHookRec = ^THookRec;
THookRec = packed record
TheHookHandle: HHOOK;
TheAppWinHandle: HWND;
TheCtrlWinHandle: HWND;
TheKeyCount: DWORD;
end;
var
hObjHandle: THandle; {Variable for the file mapping object}
lpHookRec: PHookRec; {Pointer to our hook record}
procedure MapFileMemory(dwAllocSize: DWORD);
begin
{Create a process wide memory mapped variable}
hObjHandle := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, dwAllocSize,
'HookRecMemBlock');
if (hObjHandle = 0) then
begin
MessageBox(0, 'Hook DLL', 'Could not create file map object', MB_OK);
exit;
end;
{Get a pointer to our process wide memory mapped variable}
lpHookRec := MapViewOfFile(hObjHandle, file_MAP_write, 0, 0, dwAllocSize);
if (lpHookRec = nil) then
begin
CloseHandle(hObjHandle);
MessageBox(0, 'Hook DLL', 'Could not map file', MB_OK);
exit;
end;
end;
procedure UnMapFileMemory;
begin
{Delete our process wide memory mapped variable}
if (lpHookRec <> nil) then
begin
UnMapViewOfFile(lpHookRec);
lpHookRec := nil;
end;
if (hObjHandle > 0) then
begin
CloseHandle(hObjHandle);
hObjHandle := 0;
end;
end;
function GetHookRecPointer: pointer stdcall;
begin
{Return a pointer to our process wide memory mapped variable}
result := lpHookRec;
end;
{The function that actually processes the keystrokes for our hook}
function KeyBoardProc(Code: integer; wParam: integer; lParam: integer): integer;
stdcall;
function GetAppliWinHandle: Hwnd;
var
activeWindowHandle,activeWindowThread,thisWindowThread,focusedControlHandle: Hwnd;
begin { GetAppliWinHandle }
focusedControlHandle := GetFocus();
if focusedControlHandle = 0 then
begin
activeWindowHandle := GetForegroundWindow();
activeWindowThread := GetWindowThreadProcessId(activeWindowHandle, 0);
thisWindowThread := GetWindowThreadProcessId(lpHookRec^.TheHookHandle, 0);
AttachThreadInput(activeWindowThread, thisWindowThread, true);
focusedControlHandle := GetFocus();
AttachThreadInput(activeWindowThread, thisWindowThread, false);
end;
Result:=focusedControlHandle
end; { GetAppliWinHandle }
var
KeyUp: bool;
{Remove comments for additional functionability ... :
IsAltPressed: bool;
IsCtrlPressed: bool;
IsShiftPressed: bool;
}
begin
result := 0;
case Code of
HC_ACTION:
begin
{We trap the keystrokes here}
{is this a key up message?}
KeyUp := ((lParam and (1 shl 31)) <> 0);
{Remove comments for additional functionability ... :
{is the Alt key pressed}
if ((lParam and (1 shl 29)) <> 0) then
begin
IsAltPressed := TRUE;
end
else
begin
IsAltPressed := FALSE;
end;
{is the Control key pressed}
if ((GetKeyState(VK_CONTROL) and (1 shl 15)) <> 0) then
begin
IsCtrlPressed := TRUE;
end
else
begin
IsCtrlPressed := FALSE;
end;
{if the Shift key pressed}
if ((GetKeyState(VK_SHIFT) and (1 shl 15)) <> 0) then
begin
IsShiftPressed := TRUE;
end
else
begin
IsShiftPressed := FALSE;
end;
}
{if KeyUp then increment the key count}
if (KeyUp <> FALSE) then
begin
Inc(lpHookRec^.TheKeyCount);
end;
case wParam of
{Was the enter key pressed?}
VK_RETURN:
begin
{if KeyUp}
if (KeyUp <> FALSE) then
begin
{Post a bogus message to the window control in our app}
PostMessage(lpHookRec^.TheCtrlWinHandle, WM_KEYDOWN, 0, 0);
PostMessage(lpHookRec^.TheCtrlWinHandle, WM_KEYUP, 0, 0);
end;
{if you wanted to swallow the keystroke then return -1, else if you
want
to allow the keystroke then return 0}
result := 0;
exit;
end; {VK_RETURN}
{if the left arrow key is pressed then lets play a joke!}
VK_LEFT:
begin
{Get the Handle of the Application Window in lpHookRec^.TheAppWinHandle}
lpHookRec^.TheAppWinHandle:=GetAppliWinHandle;
{if KeyUp}
if (KeyUp <> FALSE) then
begin
{Create a UpArrow keyboard event}
keybd_event(VK_RIGHT, 0, 0, 0);
keybd_event(VK_RIGHT, 0, KEYEVENTF_KEYUP, 0);
end;
{Swallow the keystroke}
result := -1;
exit;
end; {VK_LEFT}
end; {case wParam}
{Allow the keystroke}
result := 0;
end; {HC_ACTION}
HC_NOREMOVE:
begin
{This is a keystroke message, but the keystroke message has not been removed
from the message queue, since an application has called PeekMessage()
specifying PM_NOREMOVE}
result := 0;
exit;
end;
end; {case code}
if (Code < 0) then
{Call the next hook in the hook chain}
result := CallNextHookEx(lpHookRec^.TheHookHandle, Code, wParam, lParam);
end;
procedure StartKeyBoardHook stdcall;
begin
{if we have a process wide memory variable and the hook has not already been
set...}
if ((lpHookRec <> nil) and (lpHookRec^.TheHookHandle = 0)) then
begin
{set the hook and remember our hook handle}
lpHookRec^.TheHookHandle := SetWindowsHookEx(WH_KEYBOARD, #KeyBoardProc,
hInstance, 0);
end;
end;
procedure StopKeyBoardHook stdcall;
begin
{if we have a process wide memory variable and the hook has already been set...}
if ((lpHookRec <> nil) and (lpHookRec^.TheHookHandle <> 0)) then
begin
{Remove our hook and clear our hook handle}
if (UnHookWindowsHookEx(lpHookRec^.TheHookHandle) <> FALSE) then
begin
lpHookRec^.TheHookHandle := 0;
end;
end;
end;
procedure DllEntryPoint(dwReason: DWORD);
begin
case dwReason of
Dll_Process_Attach:
begin
{if we are getting mapped into a process, then get a pointer to our
process wide memory mapped variable}
hObjHandle := 0;
lpHookRec := nil;
MapFileMemory(sizeof(lpHookRec^));
end;
Dll_Process_Detach:
begin
{if we are getting unmapped from a process then, remove the pointer to
our process wide memory mapped variable}
UnMapFileMemory;
end;
end;
end;
exports
KeyBoardProc name 'KEYBOARDPROC',
GetHookRecPointer name 'GETHOOKRECPOINTER',
StartKeyBoardHook name 'STARTKEYBOARDHOOK',
StopKeyBoardHook name 'STOPKEYBOARDHOOK';
begin
{set our Dll's main entry point}
DLLProc := #DllEntryPoint;
{Call our Dll's main entry point}
DllEntryPoint(Dll_Process_Attach);
end.
Per the GetFocus() documentation:
Retrieves the handle to the window that has the keyboard focus, if the window is attached to the calling thread's message queue.
...
GetFocus returns the window with the keyboard focus for the current thread's message queue. If GetFocus returns NULL, another thread's queue may be attached to a window that has the keyboard focus.
Use the GetForegroundWindow function to retrieve the handle to the window with which the user is currently working. You can associate your thread's message queue with the windows owned by another thread by using the AttachThreadInput function.
You are trying to do this part, but you are not doing it correctly, and you are not checking for errors along the way. You are also mistakenly using the HWND data type for thread IDs, but they are not HWNDs, they are DWORDs instead.
Try something more like this:
function GetAppliWinHandle: HWND;
var
activeWindowHandle: HWND;
activeWindowThread, thisThread: DWORD;
begin
Result := GetFocus();
if Result = 0 then
begin
activeWindowHandle := GetForegroundWindow();
if activeWindowHandle <> 0 then
begin
activeWindowThread := GetWindowThreadProcessId(activeWindowHandle, 0);
thisThread := GetCurrentThreadId();
if AttachThreadInput(activeWindowThread, thisThread, TRUE) then
begin
Result := GetFocus();
AttachThreadInput(activeWindowThread, thisThread, FALSE);
end;
end;
end;
end;
However, the same documentation also says:
To get the window with the keyboard focus on the foreground queue or the queue of another thread, use the GetGUIThreadInfo function.
For example:
function GetAppliWinHandle: HWND;
var
activeWindowHandle: HWND;
activeWindowThread: DWORD;
gui: TGUIThreadinfo;
begin
Result := GetFocus();
if Result = 0 then
begin
activeWindowHandle := GetForegroundWindow();
if activeWindowHandle <> 0 then
begin
activeWindowThread := GetWindowThreadProcessId(activeWindowHandle, 0);
gui.cbSize := sizeof(gui);
if GetGUIThreadInfo(activeWindowThread, gui) then
Result := gui.hwndFocus;
end;
end;
end;
Or simpler:
function GetAppliWinHandle: HWND;
var
gui: TGUIThreadinfo;
begin
gui.cbSize := sizeof(gui);
if GetGUIThreadInfo(0, gui) then
Result := gui.hwndFocus
else
Result := 0;
end;

Delphi and sleep function

I am having some issues regarding the sleep function. I have my application which executes an external command with some options:
str := 'C:\BERN52\MENU\menu.exe C:\BERN52\GPS\PAN\DAILY.INP C:\GPSUSER52\WORK\MENUAUX_DAILY.INP';
WinExec(Pansichar(str), SW_Shownormal);
After that when this process is finished I should kill it and continue with another things. I did the following:
Sleep(60000*StrToInt(Form1.Edit11.Text));
winexec('taskkill /F /IM menu.exe', SW_HIDE);
...
This sleeping time can be 4 minutes but also can be 2 days.
Of cause the main window going to the 'not responding' mode during this time. Could anyone suggest to me how to do this in a proper way?
First off, WinExec() has been deprecated since 32bit Windows was first introduced. Use ShellExecuteEx() or CreateProcess() instead. This also provides you with a process handle that you can use to detect when the spawned process terminates, and you can also use it to kill the process if your timeout elapses.
type
PHandle = ^THandle;
function StartProcess(const CmdLine: string; ProcessHandle: PHandle = nil): boolean;
var
si: TStartupInfo;
pi: TProcessInformation;
str: string;
begin
Result := False;
if ProcessHandle <> nil then ProcessHandle^ := 0;
str := CmdLine;
{$IFDEF UNICODE}
UniqueString(str);
{$ENDIF}
ZeroMemory(#si, sizeof(si));
si.cbSize := sizeof(si);
si.dwFlags := STARTF_USESHOWWINDOW;
si.wShowWindow := SW_SHOWNORMAL;
Result := CreateProcess(nil, PChar(str), nil, nil, False, 0, nil, nil, si, pi);
if Result then
begin
CloseHandle(pi.hThread);
if ProcessHandle <> nil then
ProcessHandle^ := pi.hProcess
else
CloseHandle(pi.hThread);
end;
end;
If you absolutely must block your calling code while waiting, use MsgWaitForMultipleObjects() in a loop so you can still service the message queue:
procedure TForm1.Start;
var
hProcess: THandle;
Timeout, StartTicks, Elapsed, Ret: DWORD;
begin
Timeout := 60000 * StrToInt(Edit11.Text);
if StartProcess('C:\BERN52\MENU\menu.exe C:\BERN52\GPS\PAN\DAILY.INP C:\GPSUSER52\WORK\MENUAUX_DAILY.INP', #hProcess) then
try
repeat
StartTicks := GetTickCount;
Ret := MsgWaitForMultipleObjects(1, hProcess, False, Timeout, QS_ALLINPUT);
if Ret <> (WAIT_OBJECT_0+1) then Break;
Application.ProcessMessages;
Elapsed := GetTickCount - StartTicks;
if Elapsed <= Timeout then
Dec(Timeout, Elapsed)
else
Timeout := 0;
until False;
if Ret <> WAIT_OBJECT_0 then
TerminateProcess(hProcess, 0);
finally
CloseHandle(hProcess);
end;
end;
Otherwise, use a TTimer so the main message loop is not blocked:
var
hProcess: THandle = 0;
procedure TForm1.Start;
begin
Timer1.Active := False;
if StartProcess('C:\BERN52\MENU\menu.exe C:\BERN52\GPS\PAN\DAILY.INP C:\GPSUSER52\WORK\MENUAUX_DAILY.INP', #hProcess) then
begin
Timer1.Tag := StrToInt(Edit11.Text);
Timer1.Interval := 1000;
Timer1.Active := True;
end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
Ret: DWORD;
begin
Ret := WaitForSingleObject(hProcess, 0);
if Ret = WAIT_TIMEOUT then
begin
Timer1.Tag := Timer1.Tag - 1;
if Timer1.Tag > 0 then
Exit;
end;
if Ret <> WAIT_OBJECT_0 then
TerminateProcess(hProcess, 0);
CloseHandle(hProcess);
hProcess := 0;
Timer1.Active := False;
end;
Otherwise, use a worker thread instead of a timer:
type
TStartProcessThread = class(TThread)
private
fCmdLine: string;
fTimeout: DWORD;
fTermEvent: THandle;
protected
procedure Execute; override;
public
constructor Create(const CmdLine; Timeout: DWORD);
destructor Destroy; override;
procedure Stop;
end;
function StartProcess(const CmdLine: string; ProcessHandle: PHandle = nil): boolean;
begin
// as shown above...
end;
constructor TStartProcessThread.Create(const CmdLine; Timeout: DWORD);
begin
inherited Create(True);
fTermEvent := CreateEvent(nil, True, False, nil);
if fTermEvent = 0 then RaiseLastOSError;
fCmdLine := CmdLine;
fTimeout := Timeout;
FreeOnTerminate := True;
end;
destructor TStartProcessThread.Destroy;
begin
if fTermEvent <> 0 then CloseHandle(fTermEvent);
inherited;
end;
procedure TStartProcessThread.Stop;
begin
Terminate;
SetEvent(hTermEvent);
end;
procedure TStartProcessThread.Execute;
var
H: array[0..1] of THandle;
begin
if not StartProcess(fCmdLine, #H[0]) then Exit;
H[1] := fTermEvent;
if WaitForMultipleObjects(2, PWOHandleArray(#H), False, INFINITE) <> WAIT_OBJECT_0 then
TerminateProcess(H[0], 0);
CloseHandle(H[0]);
end;
var
Thread: TStartProcessThread = nil;
procedure TForm1.Start;
begin
Thread := TStartProcessThread.Create('C:\BERN52\MENU\menu.exe C:\BERN52\GPS\PAN\DAILY.INP C:\GPSUSER52\WORK\MENUAUX_DAILY.INP', 60000 * StrToInt(Edit11.Text));
Thread.OnTerminate := ThreadTerminated;
Thread.Start;
end;
procedure TForm1.ThreadTerminated(Sender: TObject);
begin
Thread := nil;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if Thread <> nil then
begin
Thread.OnTerminate := nil;
Thread.Stop;
end;
end;
If you call Sleep in the UI thread, then the UI thread is no longer able to service its message queue. The not responding message is inevitable. The clear conclusion from this is that you must not call Sleep in the UI thread.
You could spin up another thread and put your Sleep call there. When that call to Sleep returns you can then do whatever needs to be done.
Some other comments:
Sleeping for such a long time is usually not the best solution to any problem. Perhaps you want to schedule a task. Or perhaps you'd be better having a periodic pulse in your program that checked whether the timeout had expired.
Winexec has been deprecated since 32 bit Windows was released, over 20 years. Use CreateProcess to start an external process.
If you wish to kill a process, use TerminateProcess.
Termination seems a little drastic. Isn't there any other way for you to persuade this other program to stop?

Delphi - hook\bypass\catch all the OutputDebugString from the system into my application with process id and name

Based on this question I've created a small application which is catching all debug strings into my application. Code of the thread is shown below.
I want to get the process id and its name for each debug string I got. After I've made some research I got this article where it says: "The first 4 bytes (32-bit DWORD) is the process ID of the process that wrote the text using OutputDebugString.". I have tried to get the process by calling the functions below, but the result is null.
TempString := '';
CopyMemory(PChar(TempString), SharedMemory, sizeof(SharedMemory)); // - returns 0....
TempString := String(PAnsiChar(SharedMemory) + SizeOf(DWORD));
I do not know what it is wrong.
Also is it possible to get the name of the process which has sent the debug string?
Thread code:
interface
uses Classes,
windows,
Forms,
StdCtrls,
SysUtils;
type
TDebugStringThread = class(TThread)
private
FMemo : TMemo;
protected
procedure Execute; override;
procedure DoShowData;
procedure DoShowErrors;
public
constructor Create(aMemo : TMemo);
end;
implementation
var SharedMessage: string;
ErrsMess : String;
constructor TDebugStringThread.Create(aMemo: TMemo);
begin
FMemo := aMemo;
FreeOnTerminate := True;
inherited Create(False);
end;
procedure TDebugStringThread.DoShowData;
begin
FMemo.Lines.Add(SharedMessage);
end;
procedure TDebugStringThread.DoShowErrors;
begin
FMemo.Lines.Add('Error ' + ErrsMess);
ErrsMess := '';
end;
procedure TDebugStringThread.Execute;
var SharedMem: Pointer;
SharedFile: THandle;
WaitingResult: DWORD;
DataReadyEvent: THandle;
BufferReadyEvent: THandle;
SecurityAttributes: SECURITY_ATTRIBUTES;
SecurityDescriptor: SECURITY_DESCRIPTOR;
SharedMemory : Pointer;
TempString : String;
begin
ErrsMess := '';
SecurityAttributes.nLength := SizeOf(SECURITY_ATTRIBUTES);
SecurityAttributes.bInheritHandle := True;
SecurityAttributes.lpSecurityDescriptor := #SecurityDescriptor;
if not InitializeSecurityDescriptor(#SecurityDescriptor, SECURITY_DESCRIPTOR_REVISION) then
Exit;
if not SetSecurityDescriptorDacl(#SecurityDescriptor, True, nil, False) then
Exit;
BufferReadyEvent := CreateEvent(#SecurityAttributes, False, True, 'DBWIN_BUFFER_READY');
if BufferReadyEvent = 0 then
Exit;
DataReadyEvent := CreateEvent(#SecurityAttributes, False, False, 'DBWIN_DATA_READY');
if DataReadyEvent = 0 then
Exit;
SharedFile := CreateFileMapping(THandle(-1), #SecurityAttributes, PAGE_READWRITE, 0, 4096, 'Global\DBWIN_BUFFER');
if SharedFile = 0 then
begin
ErrsMess := SysErrorMessage(GetLastError);
Synchronize(DoShowErrors);
Exit;
end;
SharedMem := MapViewOfFile(SharedFile, FILE_MAP_READ, 0, 0, 512);
SharedMemory := MapViewOfFile(SharedFile, SECTION_MAP_READ, 0, 0, 1024);
if not Assigned(SharedMem) then
begin
ErrsMess := SysErrorMessage(GetLastError);
Synchronize(DoShowErrors);
Exit;
end;
if not Assigned(SharedMemory) then
begin
ErrsMess := SysErrorMessage(GetLastError);
Synchronize(DoShowErrors);
end;
while (not Terminated) and (not Application.Terminated) do
begin
SetEvent(BufferReadyEvent);
WaitingResult := WaitForSingleObject(DataReadyEvent, INFINITE);
case WaitingResult of
WAIT_TIMEOUT: Continue;
WAIT_OBJECT_0:
begin
try
TempString := '';
//CopyMemory(PChar(TempString), SharedMemory, sizeof(SharedMemory)); // - returns 0....
TempString := String(PAnsiChar(SharedMemory) + SizeOf(DWORD));
SharedMessage := TempString + ' ' + String(PAnsiChar(SharedMem) + SizeOf(DWORD));
Synchronize(DoShowData);
finally
end;
end;
WAIT_FAILED: Continue;
end;
end;
UnmapViewOfFile(SharedMem);
CloseHandle(SharedFile);
end;
end.
To read the first four bytes as DWORD:
var
ProcessID: DWORD;
...
ProcessID := PDWORD(SharedMemory)^;
See here how to get the file name of the process.
If the first four bytes are really a DWord, then the first thing you need to do is stop trying to copy it into a string. Integers aren't strings, and storing one in the other doesn't transform it.
var
ProcessID: DWord;
ProcessID := PDWord(SharedMemory)^;
If you're getting zero from that, then the first four bytes of the memory contain zero. If you're expecting something else, then either your expectations are wrong, or you're not reading from the right place.

Delphi: shellexecute and sw_hide

I'm trying to run the application is hidden, but the application form is still visible.
ShellExecute(Handle, nil, 'app.exe', nil, nil, SW_HIDE);
How to run a hidden application in Delphi?
I would suggest using CreateProcess instead, because it returns the process ID of the newly launched application and you can use it to get the window's handle. Here's a function I have been using, maybe you can take away unnecessary fragments and adapt it to your needs?
// record to store window information
TWndInfo = record
pid: DWord;
WndHandle: HWND;
width, height: Integer;
end;
PWndInfo = ^TWndInfo;
{$HINTS OFF}
{ .: ExecNewProcess :. }
function ExecNewProcess(const ProgramName: String;
const StartHidden, WaitForInput: Boolean; out WndInfo: TWndInfo): Boolean;
var
StartInfo: TStartupInfo;
ProcInfo: TProcessInformation;
R: TRect;
SL: TStringList;
{$REGION 'EnumProcess'}
function EnumProcess(hHwnd: HWND; lParam: Integer): Boolean; stdcall;
var
WndInfo: PWndInfo;
pid: DWORD;
begin
Result := True;
WndInfo := PWndInfo(lParam);
if (WndInfo = nil) or (hHwnd = 0) then
exit;
GetWindowThreadProcessId(hHwnd, pid);
if (pid = WndInfo.PID) then
begin
if (WndInfo.WndHandle = 0) and (IsWindowVisible(hHwnd)) then
WndInfo.WndHandle := hHwnd;
//Result := False;
end;
end;
{$ENDREGION}
begin
Result := False;
ZeroMemory(#StartInfo, SizeOf(TStartupInfo));
ZeroMemory(#ProcInfo, SizeOf(TProcessInformation));
StartInfo.cb := SizeOf(TStartupInfo);
StartInfo.dwFlags := STARTF_USESTDHANDLES;
if StartHidden then
begin
StartInfo.dwFlags := STARTF_USESHOWWINDOW or StartInfo.dwFlags;
StartInfo.wShowWindow := SW_SHOWMINNOACTIVE;
end;
Result := CreateProcess(PChar(ProgramName), nil, nil, nil, False, 0, nil,
nil, StartInfo, ProcInfo);
try
if Result then
begin
WndInfo.WndHandle := 0;
WndInfo.PID := ProcInfo.dwProcessId;
if WaitForInput then
WaitForInputIdle(ProcInfo.hProcess, INFINITE);
EnumWindows(#EnumProcess, Integer(#WndInfo));
if (WndInfo.WndHandle <> 0) then
begin
if (StartHidden) then
ShowWindow(WndInfo.WndHandle, SW_HIDE);
Windows.GetWindowRect(WndInfo.WndHandle, R);
WndInfo.Width := R.Right - R.Left;
WndInfo.Height := R.Bottom - R.Top;
end;
end;
finally
CloseHandle(ProcInfo.hProcess);
CloseHandle(ProcInfo.hThread);
end;
end;
{$HINTS ON}
As you can read here
http://msdn.microsoft.com/en-us/library/windows/desktop/bb762153%28v=vs.85%29.aspx
it is up to the application to decide how to handle the SW_HIDE. Thus the application has to fetch the message and hide itself, as far as i see...

Resources