Interface unit for sending debug messages to SysInternals ProcessMonitor
Posted by tier777 on 2010-04-15
The latest version of SysInternals‘ excellent ProcessMonitor is now able to receive custom debug log messages and display them right in between the I/O logs.
Even though I still don’t quite get the rationale for the need to create a new API for this (in contrast to simply merging in the functionality of DebugView) I went straight ahead and converted the API to Delphi.
This should work with Delphi 6 and later. I successfully tested with Delphi 2007, 2009 and 2010.
(Update: the originally posted version did actually not work with Delphi 2007 or earlier because I had inexplicably overlooked “that Unicode thing”… Thanks to GunSmoker for pointing that out! Using $ifdef UNICODE and WideString is a cheap cop-out, I know – but it works)
Enjoy:
unit ProcMonDebugOutput;
interface
uses
Windows;
type
TProcessMonitorLogger = class
private
const
// FILE_WRITE_ACCESS = $00000002;
// METHOD_BUFFERED = $00000000;
// FILE_DEVICE_PROCMON_LOG = $00009535;
// IOCTL_EXTERNAL_LOG_DEBUGOUT = CTL_CODE(FILE_DEVICE_PROCMON_LOG,
// $81,
// METHOD_BUFFERED,
// FILE_WRITE_ACCESS);
IOCTL_EXTERNAL_LOG_DEBUGOUT = $95358204;
class var
FDevice: THandle; // = INVALID_HANDLE_VALUE
class function Open(): THandle;
class procedure Close();
{$IF CompilerVersion >= 21}
class constructor Create;
class destructor Destroy;
{$IFEND}
public
class function Output(const AOutputString: {$ifdef UNICODE}String{$else}WideString{$endif}): Boolean;
end;
PML = TProcessMonitorLogger;
implementation
{ TProcessMonitorLogger }
//function CTL_CODE(const ADevType, AFunc, AMethod, AAccess: Cardinal): Cardinal; inline;
//begin
// Result := (ADevType shl 16) or (AAccess shl 14) or (AFunc shl 2) or AMethod;
//end;
{$IF CompilerVersion >= 21}
class constructor TProcessMonitorLogger.Create;
begin
FDevice := INVALID_HANDLE_VALUE;
end;
class destructor TProcessMonitorLogger.Destroy;
begin
Close();
end;
{$IFEND}
class procedure TProcessMonitorLogger.Close;
begin
if INVALID_HANDLE_VALUE <> FDevice then
begin
CloseHandle(FDevice);
FDevice := INVALID_HANDLE_VALUE;
end;
end;
class function TProcessMonitorLogger.Open(): THandle;
begin
if INVALID_HANDLE_VALUE = FDevice then
FDevice := CreateFile('\\.\Global\ProcmonDebugLogger',
GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE,
nil,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
Result := FDevice;
end;
class function TProcessMonitorLogger.Output(const AOutputString: {$ifdef UNICODE}String{$else}WideString{$endif}): Boolean;
var
lProcMonHwnd: THandle;
lInputLength: Cardinal;
lOutputLength: Cardinal;
lLastError: Cardinal;
begin
Result := False;
if AOutputString = '' then
SetLastError(ERROR_INVALID_PARAMETER)
else
begin
lProcMonHwnd := Open();
if lProcMonHwnd <> INVALID_HANDLE_VALUE then
begin
lInputLength := Length(AOutputString) * SizeOf(WideChar);
lOutputLength := 0;
Result := DeviceIoControl(lProcMonHwnd,
IOCTL_EXTERNAL_LOG_DEBUGOUT,
PWideChar(AOutputString),
lInputLength,
nil,
0,
lOutputLength,
nil);
if not Result then
begin
lLastError := GetLastError();
if lLastError = ERROR_INVALID_PARAMETER then
SetLastError(ERROR_WRITE_FAULT);
end;
end
else
SetLastError(ERROR_BAD_DRIVER);
end;
end;
{$IF CompilerVersion < 21}
initialization
TProcessMonitorLogger.FDevice := INVALID_HANDLE_VALUE;
finalization
TProcessMonitorLogger.Close();
{$IFEND}
end.
Usage:
uses
ProcMonDebugOutput;
begin
PML.Output('How hard could it be?');
end;
Subscribe by Email
Aleksey Timohin said
Hi!
It will not work in Delphi 6 without modifications. Reasons:
1) no constants allowed in classes declaration. so IOCTL_EXTERNAL_LOG_DEBUGOUT should be moved to outside for Delphi 6 compatibility
2) Delphi 6 does not allow to use class var in class declaration. After I moved FDevice: THandle; from class to implementation, everything worked fine.
Thanks!
tier777 said
Thanks for pointing that out and sorry for the mess-up. Do you know when class const and class var were introduced? I’d wager somewhere around Delphi 2006?
I now realize it’s been a rather long time since I had to write Delphi code with backward-compatibility in mind…
GunSmoker said
See Complete list of defines for Delphi versions.
LDS said
From John Robbins’ Blog:
“Mark and I bounced a couple of emails back and forth discussing how the trace statements should get into Process Monitor. Originally, I thought merging the functionality of Debug View and Process Monitor would be the way to go but Mark thought adding the OutputDebugString capturing to Process Monitor would add lots of extraneous lines of output that would get in most people’s way. We settled on the idea that getting your tracing into Process Monitor should be something a developer specifically opts into by calling an API.”
tier777 said
Luigi: yes, I read that too (and commented on it but the comment is apparently still in moderation) but I don’t really think it makes a lot of sense. ProcessMonitor already has excellent filtering capabilities and IMHO would actually be pretty much unusable without them. So I don’t really buy the “adding lots of extraneous lines of output that would get in most people’s way” argument.
That’s exactly the part of the article that I was talking about when I wrote that I didn’t get the rationale behind the new API.
John Robbins said
Keep in mind it was more than just the OutputDebugString flood we were avoiding. First, the majority of Process Monitor users are *definitely not* software developers, but network administrators. Adding more stuff to the log that they probably don’t care about is not helpful to them. Second it was much easier to add the custom interface than to essentially add all the Debug View code to Process Monitor. Like all development there are trade offs.
Hope that clarifies!
John Robbins
tier777 said
John,
> Hope that clarifies!
it does… a bit at least…
Anyway, as easy as it was to implement the new API I’m not really complaining (anymore).
However, the proprietary API still means that existing installations (which are already using OutputDebugString) can’t benefit from this. I will first have to recompile and deploy a new release of our products to our customers.
GunSmoker said
Are you sure this will work on D6? Delphi 6 is ANSI, not unicode.
tier777 said
Indeed. Sorry for that.
I was only thinking about the
{$IF CompilerVersion >=21}when I wrote that…I’ll post a revised version shortly.
GunSmoker said
BTW, you can use
.Output(const AOutputString: UnicodeString);In order to work, add:
type{$IFNDEF UNICODE}
UnicodeString = WideString;
{$ENDIF}
TProcessMonitorLogger = class
...
That way you’ll get both nice declarations and compatibility with the old Delphi.