Download demo project - 142 Kb
Download demo executable - 320 Kb
Introduction
When this new
website emerged - a wonderful initiative - I decided to endorse it by writing a
'small' sample project which tries to answer a lot of the issues raised
concerning interprocess communication. Later on, more and more functionality
was added until you see something like IPS is today.
The Interprocess Communication
It will show you
- How to
start a child process with a
CreateProcess
call (based on Joseph M.
Newcomer�s code where I removed the resource leak).
- How to
synchronize (= in this case "wait for a process to end") with
the Process Handle.
- How to
�try� to end a process cleanly, (If that does not help, shoot the
perpetrator down).
- How to
create a Frame Window with a threaded framework solution for watching
processes.
Except for some
advanced features concerning process information retrieval that is obtained
from calling UNDOCUMENTED APIs and reading from the NT/W2K system table, most
things are quite simple with regard to interprocess synchronization.
The findings or
'techniques' we demonstrate in this article are based on work done by Sven B.
Schreiber, and was published comprehensively in the Dr. Dobb's Journal #305 of
November 1999 [ (c) 1999 Miller Freeman, Inc., San Francisco (CA) ]. (www.ddj.com).
A few C functions
to retrieve process information in an OS independent (only MS OS-es were
implied here) way are implemented in the "Win32Ext" files. The more interesting
ones are:
DWORD WINAPI GetProcessModuleName(DWORD dwProcessId, PWORD p_UnicodeString, DWORD dwMaxLength);
DWORD WINAPI GetParentProcessId(DWORD dwProcessId, PDWORD p_dwParentPId);
DWORD WINAPI GetChildProcesses (DWORD dwProcessId, PDWORD p_dwChildPIds, DWORD dwMaxLength, PDWORD p_dwNrChildren);
DWORD WINAPI GetNumberOfProcesses(PDWORD p_dwNrProcesses);
DWORD WINAPI GetProcessCommitCharge(DWORD dwProcessId, PDWORD p_dwCommitCharge);
DWORD WINAPI GetProcessUserTime(DWORD dwProcessId, PLARGE_INTEGER p_UserTime);
DWORD WINAPI GetProcessKernelTime(DWORD dwProcessId, PLARGE_INTEGER p_KernelTime);
DWORD WINAPI GetProcessThreadIds(DWORD dwProcessId, PDWORD p_dwThreadIds, DWORD dwMaxLength, PDWORD p_dwNrThreads);
DWORD WINAPI GetProcessCreationTime DWORD dwProcessId, PSYSTEMTIME p_SystemTime);
DWORD WINAPI GetProcessBasePriority(DWORD dwProcessId, PDWORD p_dwBasePriority);
Interprocess
communication through message dispatching (e.g. used to politely request an
application to stop) is done by posting messages to the process windows and was
based on code published by Martin-Pierre Frenette. (Sending a message to the
Main Frame Window of Another Application, given only the Process Handle).
You will also
notice that some programs do NOT FOLLOW the standard Parent - Child Process
rules: If you would start "Explorer.exe" from within
"IPS.exe", you will see that the explorer process is NOT a child of
IPS.
The Process Object and Process Debugger Object
I rewrote the
original IPS code to have somewhat more structure (Let�s face it, it was poor
C++, and even now� ). So I came up with the ProcessObject
class. It provides an
easy way to most things you ever wanted to do with a process. Functions are
available to launch and terminate a process, to change its base priority, to
send it a message (if it has a window), �
Also, � you can
attach to the target process as a debugger by using a ProcessDebugger
Object.
The Process
Debugger is notified of the following events through a C++ call-back mechanism:
�����
void OnException(DebugEvent_Exception* pDE);
void OnProcessStart(DebugEvent_Process* pDE);
void OnProcessExit(DebugEvent_Process* pDE);
void OnThreadStart(DebugEvent_Thread* pDE);
void OnThreadExit(DebugEvent_Thread* pDE);
void OnDllLoad(DebugEvent_DLL* pDE);
void OnDllUnload(DebugEvent_DLL* pDE);
void OnDebugOutput(DebugEvent_Output* pDE);
The respective
pointers to the DebugEvent
objects provide all the necessary information during
the events. Getting the Debug functions to work can be error prone, so if you
want a simplified idea of how the VC Debugger does it, check it out.
The
ProcessDebugger also has a nifty Thread Stack Dumper Object (through
aggregation), which can come in handy if you want to know the exact context of
an exception for example. I found my sample of stack dumping functionality at
Felix Kaska�s Win32 Pages ( http://mvps.org/win32/
). I (almost completely) rewrote the functionality and put it in the respective
StackFrame
and ThreadFunctionStack
classes. It provides functionality to dump
the stack of a thread inside its own process, possibly as a reaction to an
exception (but not only at that time), and it is also possible to dump the stack
of a thread in another process (if you have the necessary DEBUG privileges, of
course). The debug symbols, if available, are only loaded when needed. It�s up
to the software engineer to decide whether he or she wants to put a minimal
amount of debug information into the executable. If a �.map� file was built (in
the link step), the information provided by the stack dump in combination with
the map file should be sufficient to figure out the function context even when
no debug information is included in the executable. (Read MSDN if you are not
sure how to add and choose the type of debug information). If more debug symbol
information (besides registers and instruction pointers) is available in the
executable, it will be used (by IPS through the ImageHlp dll) and logged which
makes it a lot easier to figure out what went wrong.
Tracing
For internal bug
tracking IPS uses the TraceLogger
class, which can log or trace functions (and
function returns), errors and unhandled exceptions to a log file. By using
macros we can put extra information in the log file to make our debugging tasks
in the field easier. If required, the log file can be split up into separate
parts (for file usage rotation purposes, so the maximum file size used by the
logger is limited), which could be encrypted using for example a CryptLib or an
in-house developed stream encryption algorithm. IPS does not encrypt the log
file because the source is there for everyone to see. If an unhandled exception
occurs, naturally IPS will stack dump its own offending thread before
terminating. (If you would be so kind as to send the log file to me if that
happens.)
The Trace Level
can be adjusted at run-time (see the IPS about box).
You could also
remove some of the function tracing by redefining the macros to empty
statements (and recompiling the Application) :
LOGTRACE(l, s)
LOGTRACEFUNC(s)
LOGTRACEERROR(e, s)
LOGTRACEFUNCRETURN(e, s)
LOGTRACEDUMPSTACK()
LOGTRACEOSTREAM(s)
LOGTRACEPROGRESSWITHOSTREAM()
Live Debug Output Window
For Logging the debug
event, I used a slightly modified version of TOutputWnd
made by Ben Ashley. The
original version can be found at CodeGuru ( www.codeguru.com
). The IPS user can dump the log file in text format, which can be used to
point the support department or developer of the crashed program to the
offending code. (Doh!)
Future Enchancements
System wide
Hooking of the CreateProcess
(and CreateThread
) calls to improve performance
instead of using a dumb timer.
Just wondering
whether it is necessary to dump all the threads on a program crash�
Provide real
UNICODE compliant code.
Other (Any
suggestions?)
Note on the Compiler Used
I used an
evaluation version of the Intel 5.0 Compiler, which produces (much bigger)
faster and automatically optimised code for the Intel PI/PII/PIII family. It
integrates seamlessly in the MS VC Development environment and is fully (?)
compatible with the MS VC Compiler.
Gert Boddaert,
Alias GBO.